mtrl 0.2.5 → 0.2.7
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/index.ts +18 -0
- package/package.json +1 -1
- package/src/components/badge/_styles.scss +123 -115
- package/src/components/badge/api.ts +57 -59
- package/src/components/badge/badge.ts +16 -2
- package/src/components/badge/config.ts +65 -11
- package/src/components/badge/constants.ts +22 -12
- package/src/components/badge/features.ts +44 -40
- package/src/components/badge/types.ts +42 -30
- package/src/components/bottom-app-bar/_styles.scss +103 -0
- package/src/components/bottom-app-bar/bottom-app-bar.ts +196 -0
- package/src/components/bottom-app-bar/config.ts +73 -0
- package/src/components/bottom-app-bar/index.ts +11 -0
- package/src/components/bottom-app-bar/types.ts +108 -0
- package/src/components/button/_styles.scss +0 -66
- package/src/components/button/api.ts +5 -0
- package/src/components/button/button.ts +0 -2
- package/src/components/button/config.ts +5 -0
- package/src/components/button/constants.ts +0 -6
- package/src/components/button/index.ts +2 -2
- package/src/components/button/types.ts +7 -7
- package/src/components/card/_styles.scss +67 -25
- package/src/components/card/api.ts +54 -3
- package/src/components/card/card.ts +25 -6
- package/src/components/card/config.ts +189 -22
- package/src/components/card/constants.ts +20 -19
- package/src/components/card/content.ts +299 -2
- package/src/components/card/features.ts +158 -4
- package/src/components/card/index.ts +31 -9
- package/src/components/card/types.ts +166 -15
- package/src/components/checkbox/_styles.scss +0 -2
- 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/datepicker/_styles.scss +358 -0
- package/src/components/datepicker/api.ts +272 -0
- package/src/components/datepicker/config.ts +144 -0
- package/src/components/datepicker/constants.ts +98 -0
- package/src/components/datepicker/datepicker.ts +346 -0
- package/src/components/datepicker/index.ts +9 -0
- package/src/components/datepicker/render.ts +452 -0
- package/src/components/datepicker/types.ts +268 -0
- package/src/components/datepicker/utils.ts +290 -0
- package/src/components/dialog/_styles.scss +174 -128
- package/src/components/dialog/api.ts +48 -13
- package/src/components/dialog/config.ts +9 -5
- package/src/components/dialog/dialog.ts +6 -3
- package/src/components/dialog/features.ts +290 -130
- package/src/components/dialog/types.ts +7 -4
- package/src/components/divider/_styles.scss +57 -0
- package/src/components/divider/config.ts +81 -0
- package/src/components/divider/divider.ts +37 -0
- package/src/components/divider/features.ts +207 -0
- package/src/components/divider/index.ts +5 -0
- package/src/components/divider/types.ts +55 -0
- package/src/components/extended-fab/_styles.scss +267 -0
- package/src/components/extended-fab/api.ts +141 -0
- package/src/components/extended-fab/config.ts +108 -0
- package/src/components/extended-fab/constants.ts +36 -0
- package/src/components/extended-fab/extended-fab.ts +125 -0
- package/src/components/extended-fab/index.ts +4 -0
- package/src/components/extended-fab/types.ts +287 -0
- package/src/components/fab/_styles.scss +225 -0
- package/src/components/fab/api.ts +97 -0
- package/src/components/fab/config.ts +94 -0
- package/src/components/fab/constants.ts +41 -0
- package/src/components/fab/fab.ts +67 -0
- package/src/components/fab/index.ts +4 -0
- package/src/components/fab/types.ts +234 -0
- package/src/components/navigation/_styles.scss +1 -0
- package/src/components/navigation/api.ts +78 -50
- package/src/components/navigation/features/items.ts +280 -0
- package/src/components/navigation/nav-item.ts +72 -23
- package/src/components/navigation/navigation.ts +54 -2
- package/src/components/navigation/types.ts +210 -188
- 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/search/_styles.scss +306 -0
- package/src/components/search/api.ts +203 -0
- package/src/components/search/config.ts +87 -0
- package/src/components/search/constants.ts +21 -0
- package/src/components/search/features/index.ts +4 -0
- package/src/components/search/features/search.ts +718 -0
- package/src/components/search/features/states.ts +165 -0
- package/src/components/search/features/structure.ts +198 -0
- package/src/components/search/index.ts +10 -0
- package/src/components/search/search.ts +52 -0
- package/src/components/search/types.ts +163 -0
- package/src/components/segmented-button/_styles.scss +117 -0
- package/src/components/segmented-button/config.ts +67 -0
- package/src/components/segmented-button/constants.ts +42 -0
- package/src/components/segmented-button/index.ts +4 -0
- package/src/components/segmented-button/segment.ts +155 -0
- package/src/components/segmented-button/segmented-button.ts +250 -0
- package/src/components/segmented-button/types.ts +219 -0
- package/src/components/slider/_styles.scss +221 -168
- package/src/components/slider/accessibility.md +59 -0
- package/src/components/slider/api.ts +41 -120
- package/src/components/slider/config.ts +51 -49
- package/src/components/slider/features/handlers.ts +495 -0
- package/src/components/slider/features/index.ts +1 -2
- package/src/components/slider/features/slider.ts +66 -84
- package/src/components/slider/features/states.ts +195 -0
- package/src/components/slider/features/structure.ts +141 -184
- package/src/components/slider/features/ui.ts +150 -201
- package/src/components/slider/index.ts +2 -11
- package/src/components/slider/slider.ts +9 -12
- package/src/components/slider/types.ts +39 -24
- package/src/components/switch/_styles.scss +0 -2
- package/src/components/tabs/_styles.scss +346 -154
- 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 +403 -0
- package/src/components/tabs/index.ts +60 -3
- package/src/components/tabs/indicator.ts +285 -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 +332 -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/components/timepicker/README.md +277 -0
- package/src/components/timepicker/_styles.scss +451 -0
- package/src/components/timepicker/api.ts +632 -0
- package/src/components/timepicker/clockdial.ts +482 -0
- package/src/components/timepicker/config.ts +130 -0
- package/src/components/timepicker/constants.ts +138 -0
- package/src/components/timepicker/index.ts +8 -0
- package/src/components/timepicker/render.ts +613 -0
- package/src/components/timepicker/timepicker.ts +117 -0
- package/src/components/timepicker/types.ts +336 -0
- package/src/components/timepicker/utils.ts +241 -0
- package/src/components/top-app-bar/_styles.scss +225 -0
- package/src/components/top-app-bar/config.ts +83 -0
- package/src/components/top-app-bar/index.ts +11 -0
- package/src/components/top-app-bar/top-app-bar.ts +316 -0
- package/src/components/top-app-bar/types.ts +140 -0
- package/src/core/build/_ripple.scss +6 -6
- package/src/core/build/ripple.ts +72 -95
- package/src/core/compose/component.ts +1 -1
- package/src/core/compose/features/badge.ts +79 -0
- package/src/core/compose/features/icon.ts +3 -1
- package/src/core/compose/features/index.ts +3 -1
- package/src/core/compose/features/ripple.ts +4 -1
- package/src/core/compose/features/textlabel.ts +26 -2
- package/src/core/dom/create.ts +5 -0
- package/src/index.ts +9 -0
- package/src/styles/abstract/_theme.scss +115 -3
- package/src/styles/themes/_autumn.scss +21 -0
- package/src/styles/themes/_base-theme.scss +61 -0
- package/src/styles/themes/_baseline.scss +58 -0
- package/src/styles/themes/_bluekhaki.scss +125 -0
- package/src/styles/themes/_brownbeige.scss +125 -0
- package/src/styles/themes/_browngreen.scss +125 -0
- package/src/styles/themes/_forest.scss +6 -0
- package/src/styles/themes/_greenbeige.scss +125 -0
- package/src/styles/themes/_material.scss +125 -0
- package/src/styles/themes/_ocean.scss +6 -0
- package/src/styles/themes/_sageivory.scss +125 -0
- package/src/styles/themes/_spring.scss +6 -0
- package/src/styles/themes/_summer.scss +5 -0
- package/src/styles/themes/_sunset.scss +5 -0
- package/src/styles/themes/_tealcaramel.scss +125 -0
- package/src/styles/themes/_winter.scss +6 -0
- package/src/components/card/actions.ts +0 -48
- package/src/components/card/header.ts +0 -88
- package/src/components/card/media.ts +0 -52
- package/src/components/navigation/features/items.js +0 -192
- package/src/components/slider/features/appearance.ts +0 -94
- package/src/components/slider/features/disabled.ts +0 -43
- package/src/components/slider/features/events.ts +0 -164
- package/src/components/slider/features/interactions.ts +0 -261
- package/src/components/slider/features/keyboard.ts +0 -112
- package/src/core/collection/adapters/mongodb.js +0 -232
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// src/components/fab/types.ts
|
|
2
|
+
import { FAB_VARIANTS, FAB_SIZES } from './constants';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Configuration interface for the FAB component
|
|
6
|
+
* @category Components
|
|
7
|
+
*/
|
|
8
|
+
export interface FabConfig {
|
|
9
|
+
/**
|
|
10
|
+
* FAB variant that determines visual styling
|
|
11
|
+
* @default 'primary'
|
|
12
|
+
*/
|
|
13
|
+
variant?: keyof typeof FAB_VARIANTS | string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* FAB size variant
|
|
17
|
+
* @default 'default'
|
|
18
|
+
*/
|
|
19
|
+
size?: keyof typeof FAB_SIZES | string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Whether the FAB is initially disabled
|
|
23
|
+
* @default false
|
|
24
|
+
*/
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* FAB icon HTML content
|
|
29
|
+
* @example '<svg>...</svg>'
|
|
30
|
+
*/
|
|
31
|
+
icon?: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Icon size in pixels or other CSS units
|
|
35
|
+
* @example '24px'
|
|
36
|
+
*/
|
|
37
|
+
iconSize?: string;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Additional CSS classes to add to the FAB
|
|
41
|
+
* @example 'home-fab bottom-right'
|
|
42
|
+
*/
|
|
43
|
+
class?: string;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Button value attribute
|
|
47
|
+
*/
|
|
48
|
+
value?: string;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Position of the FAB on the screen
|
|
52
|
+
* @example 'bottom-right'
|
|
53
|
+
*/
|
|
54
|
+
position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | string;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Button type attribute
|
|
58
|
+
* @default 'button'
|
|
59
|
+
*/
|
|
60
|
+
type?: 'button' | 'submit' | 'reset';
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Accessible label for screen readers
|
|
64
|
+
*/
|
|
65
|
+
ariaLabel?: string;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Whether to enable ripple effect
|
|
69
|
+
* @default true
|
|
70
|
+
*/
|
|
71
|
+
ripple?: boolean;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Component prefix for class names
|
|
75
|
+
* @default 'mtrl'
|
|
76
|
+
*/
|
|
77
|
+
prefix?: string;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Component name used in class generation
|
|
81
|
+
*/
|
|
82
|
+
componentName?: string;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Ripple effect configuration
|
|
86
|
+
*/
|
|
87
|
+
rippleConfig?: {
|
|
88
|
+
/** Duration of the ripple animation in milliseconds */
|
|
89
|
+
duration?: number;
|
|
90
|
+
/** Timing function for the ripple animation */
|
|
91
|
+
timing?: string;
|
|
92
|
+
/** Opacity values for ripple start and end [start, end] */
|
|
93
|
+
opacity?: [string, string];
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Whether to show the FAB with an entrance animation
|
|
98
|
+
* @default false
|
|
99
|
+
*/
|
|
100
|
+
animate?: boolean;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* FAB component interface
|
|
105
|
+
* @category Components
|
|
106
|
+
*/
|
|
107
|
+
export interface FabComponent {
|
|
108
|
+
/** The FAB's DOM element */
|
|
109
|
+
element: HTMLButtonElement;
|
|
110
|
+
|
|
111
|
+
/** API for managing FAB icons */
|
|
112
|
+
icon: {
|
|
113
|
+
/** Sets the icon HTML content */
|
|
114
|
+
setIcon: (html: string) => any;
|
|
115
|
+
/** Gets the current icon HTML content */
|
|
116
|
+
getIcon: () => string;
|
|
117
|
+
/** Gets the icon DOM element */
|
|
118
|
+
getElement: () => HTMLElement | null;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/** API for managing disabled state */
|
|
122
|
+
disabled: {
|
|
123
|
+
/** Enables the FAB */
|
|
124
|
+
enable: () => void;
|
|
125
|
+
/** Disables the FAB */
|
|
126
|
+
disable: () => void;
|
|
127
|
+
/** Checks if the FAB is disabled */
|
|
128
|
+
isDisabled: () => boolean;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/** API for managing component lifecycle */
|
|
132
|
+
lifecycle: {
|
|
133
|
+
/** Destroys the component and cleans up resources */
|
|
134
|
+
destroy: () => void;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Gets a class name with the component's prefix
|
|
139
|
+
* @param name - Base class name
|
|
140
|
+
* @returns Prefixed class name
|
|
141
|
+
*/
|
|
142
|
+
getClass: (name: string) => string;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Gets the FAB's value attribute
|
|
146
|
+
* @returns FAB value
|
|
147
|
+
*/
|
|
148
|
+
getValue: () => string;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Sets the FAB's value attribute
|
|
152
|
+
* @param value - New value
|
|
153
|
+
* @returns The FAB component for chaining
|
|
154
|
+
*/
|
|
155
|
+
setValue: (value: string) => FabComponent;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Enables the FAB (removes disabled attribute)
|
|
159
|
+
* @returns The FAB component for chaining
|
|
160
|
+
*/
|
|
161
|
+
enable: () => FabComponent;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Disables the FAB (adds disabled attribute)
|
|
165
|
+
* @returns The FAB component for chaining
|
|
166
|
+
*/
|
|
167
|
+
disable: () => FabComponent;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Sets the FAB's icon
|
|
171
|
+
* @param icon - Icon HTML content
|
|
172
|
+
* @returns The FAB component for chaining
|
|
173
|
+
*/
|
|
174
|
+
setIcon: (icon: string) => FabComponent;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Gets the FAB's icon HTML content
|
|
178
|
+
* @returns Icon HTML
|
|
179
|
+
*/
|
|
180
|
+
getIcon: () => string;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Sets the FAB's position
|
|
184
|
+
* @param position - Position value ('top-right', 'bottom-left', etc.)
|
|
185
|
+
* @returns The FAB component for chaining
|
|
186
|
+
*/
|
|
187
|
+
setPosition: (position: string) => FabComponent;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Gets the current position of the FAB
|
|
191
|
+
* @returns Current position
|
|
192
|
+
*/
|
|
193
|
+
getPosition: () => string | null;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Lowers the FAB (useful for pressed state)
|
|
197
|
+
* @returns The FAB component for chaining
|
|
198
|
+
*/
|
|
199
|
+
lower: () => FabComponent;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Raises the FAB back to its default elevation
|
|
203
|
+
* @returns The FAB component for chaining
|
|
204
|
+
*/
|
|
205
|
+
raise: () => FabComponent;
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Destroys the FAB component and cleans up resources
|
|
209
|
+
*/
|
|
210
|
+
destroy: () => void;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Adds an event listener to the FAB
|
|
214
|
+
* @param event - Event name ('click', 'focus', etc.)
|
|
215
|
+
* @param handler - Event handler function
|
|
216
|
+
* @returns The FAB component for chaining
|
|
217
|
+
*/
|
|
218
|
+
on: (event: string, handler: Function) => FabComponent;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Removes an event listener from the FAB
|
|
222
|
+
* @param event - Event name
|
|
223
|
+
* @param handler - Event handler function
|
|
224
|
+
* @returns The FAB component for chaining
|
|
225
|
+
*/
|
|
226
|
+
off: (event: string, handler: Function) => FabComponent;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Adds CSS classes to the FAB element
|
|
230
|
+
* @param classes - One or more class names to add
|
|
231
|
+
* @returns The FAB component for chaining
|
|
232
|
+
*/
|
|
233
|
+
addClass: (...classes: string[]) => FabComponent;
|
|
234
|
+
}
|
|
@@ -1,79 +1,107 @@
|
|
|
1
|
-
// src/components/
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
1
|
+
// src/components/button/api.ts
|
|
2
|
+
import { ButtonComponent } from './types';
|
|
3
|
+
|
|
4
|
+
interface ApiOptions {
|
|
5
|
+
disabled: {
|
|
6
|
+
enable: () => void;
|
|
7
|
+
disable: () => void;
|
|
8
|
+
};
|
|
9
|
+
lifecycle: {
|
|
10
|
+
destroy: () => void;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ComponentWithElements {
|
|
15
|
+
element: HTMLElement;
|
|
16
|
+
text: {
|
|
17
|
+
setText: (content: string) => any;
|
|
18
|
+
getText: () => string;
|
|
19
|
+
getElement: () => HTMLElement | null;
|
|
20
|
+
};
|
|
21
|
+
icon: {
|
|
22
|
+
setIcon: (html: string) => any;
|
|
23
|
+
getIcon: () => string;
|
|
24
|
+
getElement: () => HTMLElement | null;
|
|
25
|
+
};
|
|
26
|
+
getClass: (name: string) => string;
|
|
27
|
+
}
|
|
9
28
|
|
|
10
29
|
/**
|
|
11
|
-
* Enhances
|
|
12
|
-
* @param {ApiOptions} options - API configuration
|
|
30
|
+
* Enhances a button component with API methods
|
|
31
|
+
* @param {ApiOptions} options - API configuration options
|
|
13
32
|
* @returns {Function} Higher-order function that adds API methods to component
|
|
33
|
+
* @internal This is an internal utility for the Button component
|
|
14
34
|
*/
|
|
15
35
|
export const withAPI = ({ disabled, lifecycle }: ApiOptions) =>
|
|
16
|
-
(component:
|
|
36
|
+
(component: ComponentWithElements): ButtonComponent => ({
|
|
17
37
|
...component as any,
|
|
18
|
-
element: component.element,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// Item management
|
|
22
|
-
addItem(config: NavItemConfig): NavigationComponent {
|
|
23
|
-
component.addItem?.(config);
|
|
24
|
-
return this;
|
|
25
|
-
},
|
|
38
|
+
element: component.element as HTMLButtonElement,
|
|
39
|
+
|
|
40
|
+
getValue: () => component.element.value,
|
|
26
41
|
|
|
27
|
-
|
|
28
|
-
component.
|
|
42
|
+
setValue(value: string) {
|
|
43
|
+
component.element.value = value;
|
|
29
44
|
return this;
|
|
30
45
|
},
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
enable() {
|
|
48
|
+
disabled.enable();
|
|
49
|
+
return this;
|
|
34
50
|
},
|
|
35
51
|
|
|
36
|
-
|
|
37
|
-
|
|
52
|
+
disable() {
|
|
53
|
+
disabled.disable();
|
|
54
|
+
return this;
|
|
38
55
|
},
|
|
39
56
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
57
|
+
setText(content: string) {
|
|
58
|
+
component.text.setText(content);
|
|
59
|
+
this.updateCircularStyle();
|
|
60
|
+
|
|
61
|
+
// If removing text from a button with an icon, ensure it has an accessible name
|
|
62
|
+
if (!content && component.icon.getElement()) {
|
|
63
|
+
if (!this.element.getAttribute('aria-label')) {
|
|
64
|
+
const className = this.element.className.split(' ')
|
|
65
|
+
.find(cls => !cls.startsWith(`${component.getClass('button')}`));
|
|
66
|
+
|
|
67
|
+
if (className) {
|
|
68
|
+
this.element.setAttribute('aria-label', className);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
47
73
|
return this;
|
|
48
74
|
},
|
|
49
75
|
|
|
50
|
-
|
|
51
|
-
return component.
|
|
52
|
-
},
|
|
53
|
-
|
|
54
|
-
// Event handling
|
|
55
|
-
on(event: string, handler: Function): NavigationComponent {
|
|
56
|
-
component.on?.(event, handler);
|
|
57
|
-
return this;
|
|
76
|
+
getText() {
|
|
77
|
+
return component.text.getText();
|
|
58
78
|
},
|
|
59
79
|
|
|
60
|
-
|
|
61
|
-
component.
|
|
80
|
+
setIcon(icon: string) {
|
|
81
|
+
component.icon.setIcon(icon);
|
|
82
|
+
this.updateCircularStyle();
|
|
62
83
|
return this;
|
|
63
84
|
},
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
disabled.enable();
|
|
68
|
-
return this;
|
|
85
|
+
|
|
86
|
+
getIcon() {
|
|
87
|
+
return component.icon.getIcon();
|
|
69
88
|
},
|
|
70
89
|
|
|
71
|
-
|
|
72
|
-
|
|
90
|
+
setAriaLabel(label: string) {
|
|
91
|
+
component.element.setAttribute('aria-label', label);
|
|
73
92
|
return this;
|
|
74
93
|
},
|
|
75
94
|
|
|
76
|
-
destroy()
|
|
95
|
+
destroy() {
|
|
77
96
|
lifecycle.destroy();
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
updateCircularStyle() {
|
|
100
|
+
const hasText = component.text.getText();
|
|
101
|
+
if (!hasText && component.icon.getElement()) {
|
|
102
|
+
component.element.classList.add(`${component.getClass('button')}--circular`);
|
|
103
|
+
} else {
|
|
104
|
+
component.element.classList.remove(`${component.getClass('button')}--circular`);
|
|
105
|
+
}
|
|
78
106
|
}
|
|
79
107
|
});
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
// src/components/navigation/features/items.ts
|
|
2
|
+
import { createNavItem, getAllNestedItems } from '../nav-item';
|
|
3
|
+
import { NavItemConfig, NavItemData } from '../types';
|
|
4
|
+
|
|
5
|
+
// Type definitions to help with TypeScript conversion
|
|
6
|
+
interface Component {
|
|
7
|
+
element: HTMLElement;
|
|
8
|
+
emit?: (event: string, data: any) => void;
|
|
9
|
+
lifecycle?: {
|
|
10
|
+
destroy: () => void;
|
|
11
|
+
};
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ItemsComponent extends Component {
|
|
16
|
+
items: Map<string, NavItemData>;
|
|
17
|
+
addItem: (config: NavItemConfig) => ItemsComponent;
|
|
18
|
+
removeItem: (id: string) => ItemsComponent;
|
|
19
|
+
getItem: (id: string) => NavItemData | undefined;
|
|
20
|
+
getAllItems: () => NavItemData[];
|
|
21
|
+
getActive: () => NavItemData | null;
|
|
22
|
+
getItemPath: (id: string) => string[];
|
|
23
|
+
setActive: (id: string) => ItemsComponent;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface NavigationConfig {
|
|
27
|
+
prefix?: string;
|
|
28
|
+
items?: NavItemConfig[];
|
|
29
|
+
[key: string]: any;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const withNavItems = (config: NavigationConfig) => (component: Component): ItemsComponent => {
|
|
33
|
+
const items = new Map<string, NavItemData>();
|
|
34
|
+
let activeItem: NavItemData | null = null;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Recursively stores items in the items Map
|
|
38
|
+
* @param {NavItemConfig} itemConfig - Item configuration
|
|
39
|
+
* @param {HTMLElement} item - Created item element
|
|
40
|
+
*/
|
|
41
|
+
const storeItem = (itemConfig: NavItemConfig, item: HTMLElement): void => {
|
|
42
|
+
items.set(itemConfig.id, { element: item, config: itemConfig });
|
|
43
|
+
|
|
44
|
+
if (itemConfig.items?.length) {
|
|
45
|
+
itemConfig.items.forEach(nestedConfig => {
|
|
46
|
+
const container = item.closest(`.${config.prefix}-nav-item-container`);
|
|
47
|
+
if (container) {
|
|
48
|
+
const nestedContainer = container.querySelector(`.${config.prefix}-nav-nested-container`);
|
|
49
|
+
if (nestedContainer) {
|
|
50
|
+
const nestedItem = nestedContainer.querySelector(`[data-id="${nestedConfig.id}"]`) as HTMLElement;
|
|
51
|
+
if (nestedItem) {
|
|
52
|
+
storeItem(nestedConfig, nestedItem);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Updates the active state for an item
|
|
62
|
+
* @param {HTMLElement} item - Item element to activate
|
|
63
|
+
* @param {NavItemData} itemData - Item data
|
|
64
|
+
* @param {boolean} active - Whether to make active or inactive
|
|
65
|
+
*/
|
|
66
|
+
const updateActiveState = (item: HTMLElement, itemData: NavItemData, active: boolean): void => {
|
|
67
|
+
// Determine the correct active attribute based on role
|
|
68
|
+
const role = item.getAttribute('role');
|
|
69
|
+
|
|
70
|
+
if (active) {
|
|
71
|
+
item.classList.add(`${config.prefix}-nav-item--active`);
|
|
72
|
+
|
|
73
|
+
// Set appropriate attribute based on role
|
|
74
|
+
if (role === 'tab') {
|
|
75
|
+
item.setAttribute('aria-selected', 'true');
|
|
76
|
+
item.setAttribute('tabindex', '0');
|
|
77
|
+
} else if (!item.getAttribute('aria-haspopup')) {
|
|
78
|
+
// Use aria-current for navigation items that aren't expandable
|
|
79
|
+
item.setAttribute('aria-current', 'page');
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
item.classList.remove(`${config.prefix}-nav-item--active`);
|
|
83
|
+
|
|
84
|
+
// Remove appropriate attribute based on role
|
|
85
|
+
if (role === 'tab') {
|
|
86
|
+
item.setAttribute('aria-selected', 'false');
|
|
87
|
+
item.setAttribute('tabindex', '-1');
|
|
88
|
+
} else if (item.hasAttribute('aria-current')) {
|
|
89
|
+
item.removeAttribute('aria-current');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Create initial items
|
|
95
|
+
if (config.items) {
|
|
96
|
+
config.items.forEach(itemConfig => {
|
|
97
|
+
const item = createNavItem(itemConfig, component.element, config.prefix || 'mtrl');
|
|
98
|
+
storeItem(itemConfig, item);
|
|
99
|
+
|
|
100
|
+
if (itemConfig.active) {
|
|
101
|
+
activeItem = { element: item, config: itemConfig };
|
|
102
|
+
updateActiveState(item, activeItem, true);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Handle item clicks
|
|
108
|
+
component.element.addEventListener('click', (event: Event) => {
|
|
109
|
+
const item = (event.target as HTMLElement).closest(`.${config.prefix}-nav-item`) as HTMLElement;
|
|
110
|
+
if (!item || (item as any).disabled || item.getAttribute('aria-haspopup') === 'menu') return;
|
|
111
|
+
|
|
112
|
+
const id = item.dataset.id;
|
|
113
|
+
if (!id) return;
|
|
114
|
+
|
|
115
|
+
const itemData = items.get(id);
|
|
116
|
+
if (!itemData) return;
|
|
117
|
+
|
|
118
|
+
// Skip if this is an expandable item
|
|
119
|
+
if (item.getAttribute('aria-expanded') !== null) return;
|
|
120
|
+
|
|
121
|
+
// Store previous item before updating
|
|
122
|
+
const previousItem = activeItem;
|
|
123
|
+
|
|
124
|
+
// Update active state
|
|
125
|
+
if (activeItem) {
|
|
126
|
+
updateActiveState(activeItem.element, activeItem, false);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
updateActiveState(item, itemData, true);
|
|
130
|
+
activeItem = itemData;
|
|
131
|
+
|
|
132
|
+
// Emit change event with item data
|
|
133
|
+
if (component.emit) {
|
|
134
|
+
component.emit('change', {
|
|
135
|
+
id,
|
|
136
|
+
item: itemData,
|
|
137
|
+
previousItem,
|
|
138
|
+
path: getItemPath(id)
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Gets the path to an item (parent IDs)
|
|
145
|
+
* @param {string} id - Item ID to get path for
|
|
146
|
+
* @returns {Array<string>} Array of parent item IDs
|
|
147
|
+
*/
|
|
148
|
+
const getItemPath = (id: string): string[] => {
|
|
149
|
+
const path: string[] = [];
|
|
150
|
+
let currentItem = items.get(id);
|
|
151
|
+
|
|
152
|
+
if (!currentItem) return path;
|
|
153
|
+
|
|
154
|
+
let parentContainer = currentItem.element.closest(`.${config.prefix}-nav-nested-container`);
|
|
155
|
+
while (parentContainer) {
|
|
156
|
+
const parentItemContainer = parentContainer.parentElement;
|
|
157
|
+
if (!parentItemContainer) break;
|
|
158
|
+
|
|
159
|
+
const parentItem = parentItemContainer.querySelector(`.${config.prefix}-nav-item`);
|
|
160
|
+
if (!parentItem) break;
|
|
161
|
+
|
|
162
|
+
const parentId = parentItem.getAttribute('data-id');
|
|
163
|
+
if (!parentId) break;
|
|
164
|
+
|
|
165
|
+
path.unshift(parentId);
|
|
166
|
+
|
|
167
|
+
// Move up to next level
|
|
168
|
+
parentContainer = parentItemContainer.closest(`.${config.prefix}-nav-nested-container`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return path;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// Clean up when component is destroyed
|
|
175
|
+
if (component.lifecycle) {
|
|
176
|
+
const originalDestroy = component.lifecycle.destroy;
|
|
177
|
+
component.lifecycle.destroy = () => {
|
|
178
|
+
items.clear();
|
|
179
|
+
if (originalDestroy) {
|
|
180
|
+
originalDestroy();
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
...component,
|
|
187
|
+
items,
|
|
188
|
+
|
|
189
|
+
addItem(itemConfig: NavItemConfig) {
|
|
190
|
+
if (items.has(itemConfig.id)) return this;
|
|
191
|
+
|
|
192
|
+
const item = createNavItem(itemConfig, component.element, config.prefix || 'mtrl');
|
|
193
|
+
storeItem(itemConfig, item);
|
|
194
|
+
|
|
195
|
+
if (itemConfig.active) {
|
|
196
|
+
this.setActive(itemConfig.id);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (component.emit) {
|
|
200
|
+
component.emit('itemAdded', {
|
|
201
|
+
id: itemConfig.id,
|
|
202
|
+
item: { element: item, config: itemConfig }
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return this;
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
removeItem(id: string) {
|
|
209
|
+
const item = items.get(id);
|
|
210
|
+
if (!item) return this;
|
|
211
|
+
|
|
212
|
+
// Remove all nested items first
|
|
213
|
+
const nestedItems = getAllNestedItems(item.element, config.prefix || 'mtrl');
|
|
214
|
+
nestedItems.forEach(nestedItem => {
|
|
215
|
+
const nestedId = nestedItem.dataset.id;
|
|
216
|
+
if (nestedId) items.delete(nestedId);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
if (activeItem?.config.id === id) {
|
|
220
|
+
activeItem = null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Remove the entire item container
|
|
224
|
+
const container = item.element.closest(`.${config.prefix}-nav-item-container`);
|
|
225
|
+
if (container) {
|
|
226
|
+
container.remove();
|
|
227
|
+
}
|
|
228
|
+
items.delete(id);
|
|
229
|
+
|
|
230
|
+
if (component.emit) {
|
|
231
|
+
component.emit('itemRemoved', { id, item });
|
|
232
|
+
}
|
|
233
|
+
return this;
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
getItem: (id: string) => items.get(id),
|
|
237
|
+
getAllItems: () => Array.from(items.values()),
|
|
238
|
+
getActive: () => activeItem,
|
|
239
|
+
getItemPath: (id: string) => getItemPath(id),
|
|
240
|
+
|
|
241
|
+
setActive(id: string) {
|
|
242
|
+
const item = items.get(id);
|
|
243
|
+
if (!item || item.config.disabled) return this;
|
|
244
|
+
|
|
245
|
+
if (activeItem) {
|
|
246
|
+
updateActiveState(activeItem.element, activeItem, false);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
updateActiveState(item.element, item, true);
|
|
250
|
+
activeItem = item;
|
|
251
|
+
|
|
252
|
+
// Ensure all parent items are expanded
|
|
253
|
+
const path = getItemPath(id);
|
|
254
|
+
path.forEach(parentId => {
|
|
255
|
+
const parentItem = items.get(parentId);
|
|
256
|
+
if (parentItem) {
|
|
257
|
+
const parentButton = parentItem.element;
|
|
258
|
+
const container = parentButton.closest(`.${config.prefix}-nav-item-container`);
|
|
259
|
+
if (container) {
|
|
260
|
+
const nestedContainer = container.querySelector(`.${config.prefix}-nav-nested-container`);
|
|
261
|
+
if (nestedContainer) {
|
|
262
|
+
parentButton.setAttribute('aria-expanded', 'true');
|
|
263
|
+
nestedContainer.hidden = false;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
if (component.emit) {
|
|
270
|
+
component.emit('activeChanged', {
|
|
271
|
+
id,
|
|
272
|
+
item,
|
|
273
|
+
previousItem: activeItem,
|
|
274
|
+
path: getItemPath(id)
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
return this;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
};
|