mtrl 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -22
- package/index.ts +33 -0
- package/package.json +14 -5
- package/src/components/button/{styles.scss → _styles.scss} +2 -2
- package/src/components/button/api.ts +89 -0
- package/src/components/button/button.ts +50 -0
- package/src/components/button/config.ts +75 -0
- package/src/components/button/constants.ts +17 -0
- package/src/components/button/index.ts +4 -0
- package/src/components/button/types.ts +118 -0
- package/src/components/card/_styles.scss +359 -0
- package/src/components/card/actions.ts +48 -0
- package/src/components/card/api.ts +102 -0
- package/src/components/card/card.ts +41 -0
- package/src/components/card/config.ts +99 -0
- package/src/components/card/constants.ts +69 -0
- package/src/components/card/content.ts +48 -0
- package/src/components/card/features.ts +228 -0
- package/src/components/card/header.ts +88 -0
- package/src/components/card/index.ts +19 -0
- package/src/components/card/media.ts +52 -0
- package/src/components/card/types.ts +174 -0
- package/src/components/checkbox/api.ts +82 -0
- package/src/components/checkbox/checkbox.ts +75 -0
- package/src/components/checkbox/config.ts +90 -0
- package/src/components/checkbox/index.ts +4 -0
- package/src/components/checkbox/types.ts +146 -0
- package/src/components/chip/_styles.scss +372 -0
- package/src/components/chip/api.ts +115 -0
- package/src/components/chip/chip-set.ts +225 -0
- package/src/components/chip/chip.ts +82 -0
- package/src/components/chip/config.ts +92 -0
- package/src/components/chip/constants.ts +38 -0
- package/src/components/chip/index.ts +4 -0
- package/src/components/chip/types.ts +172 -0
- package/src/components/list/api.ts +72 -0
- package/src/components/list/config.ts +43 -0
- package/src/components/list/{constants.js → constants.ts} +34 -7
- package/src/components/list/features.ts +224 -0
- package/src/components/list/index.ts +14 -0
- package/src/components/list/list-item.ts +120 -0
- package/src/components/list/list.ts +37 -0
- package/src/components/list/types.ts +179 -0
- package/src/components/list/utils.ts +47 -0
- package/src/components/menu/api.ts +119 -0
- package/src/components/menu/config.ts +54 -0
- package/src/components/menu/constants.ts +154 -0
- package/src/components/menu/features/items-manager.ts +457 -0
- package/src/components/menu/features/keyboard-navigation.ts +133 -0
- package/src/components/menu/features/positioning.ts +127 -0
- package/src/components/menu/features/{visibility.js → visibility.ts} +66 -64
- package/src/components/menu/index.ts +14 -0
- package/src/components/menu/menu-item.ts +43 -0
- package/src/components/menu/menu.ts +53 -0
- package/src/components/menu/types.ts +178 -0
- package/src/components/navigation/api.ts +79 -0
- package/src/components/navigation/config.ts +61 -0
- package/src/components/navigation/{constants.js → constants.ts} +10 -10
- package/src/components/navigation/index.ts +14 -0
- package/src/components/navigation/nav-item.ts +148 -0
- package/src/components/navigation/navigation.ts +50 -0
- package/src/components/navigation/types.ts +212 -0
- package/src/components/progress/_styles.scss +204 -0
- package/src/components/progress/api.ts +179 -0
- package/src/components/progress/config.ts +124 -0
- package/src/components/progress/constants.ts +43 -0
- package/src/components/progress/index.ts +5 -0
- package/src/components/progress/progress.ts +163 -0
- package/src/components/progress/types.ts +102 -0
- package/src/components/snackbar/api.ts +162 -0
- package/src/components/snackbar/config.ts +62 -0
- package/src/components/snackbar/{constants.js → constants.ts} +21 -4
- package/src/components/snackbar/features.ts +76 -0
- package/src/components/snackbar/index.ts +4 -0
- package/src/components/snackbar/position.ts +71 -0
- package/src/components/snackbar/queue.ts +76 -0
- package/src/components/snackbar/snackbar.ts +60 -0
- package/src/components/snackbar/types.ts +58 -0
- package/src/components/switch/api.ts +77 -0
- package/src/components/switch/config.ts +74 -0
- package/src/components/switch/index.ts +4 -0
- package/src/components/switch/switch.ts +52 -0
- package/src/components/switch/types.ts +142 -0
- package/src/components/textfield/api.ts +72 -0
- package/src/components/textfield/config.ts +54 -0
- package/src/components/textfield/{constants.js → constants.ts} +38 -5
- package/src/components/textfield/index.ts +4 -0
- package/src/components/textfield/textfield.ts +50 -0
- package/src/components/textfield/types.ts +139 -0
- package/src/core/compose/base.ts +43 -0
- package/src/core/compose/component.ts +247 -0
- package/src/core/compose/features/checkable.ts +155 -0
- package/src/core/compose/features/disabled.ts +116 -0
- package/src/core/compose/features/events.ts +65 -0
- package/src/core/compose/features/icon.ts +67 -0
- package/src/core/compose/features/index.ts +35 -0
- package/src/core/compose/features/input.ts +174 -0
- package/src/core/compose/features/lifecycle.ts +139 -0
- package/src/core/compose/features/position.ts +94 -0
- package/src/core/compose/features/ripple.ts +55 -0
- package/src/core/compose/features/size.ts +29 -0
- package/src/core/compose/features/style.ts +31 -0
- package/src/core/compose/features/text.ts +44 -0
- package/src/core/compose/features/textinput.ts +225 -0
- package/src/core/compose/features/textlabel.ts +92 -0
- package/src/core/compose/features/track.ts +84 -0
- package/src/core/compose/features/variant.ts +29 -0
- package/src/core/compose/features/withEvents.ts +137 -0
- package/src/core/compose/index.ts +54 -0
- package/src/core/compose/{pipe.js → pipe.ts} +16 -11
- package/src/core/config/component-config.ts +136 -0
- package/src/core/config.ts +211 -0
- package/src/core/dom/{attributes.js → attributes.ts} +11 -11
- package/src/core/dom/classes.ts +60 -0
- package/src/core/dom/create.ts +188 -0
- package/src/core/dom/events.ts +209 -0
- package/src/core/dom/index.ts +10 -0
- package/src/core/dom/utils.ts +97 -0
- package/src/core/index.ts +111 -0
- package/src/core/state/disabled.ts +81 -0
- package/src/core/state/emitter.ts +94 -0
- package/src/core/state/events.ts +88 -0
- package/src/core/state/index.ts +16 -0
- package/src/core/state/lifecycle.ts +131 -0
- package/src/core/state/store.ts +197 -0
- package/src/core/utils/index.ts +45 -0
- package/src/core/utils/{mobile.js → mobile.ts} +48 -24
- package/src/core/utils/object.ts +41 -0
- package/src/core/utils/validate.ts +234 -0
- package/src/{index.js → index.ts} +4 -2
- package/index.js +0 -11
- package/src/components/button/api.js +0 -54
- package/src/components/button/button.js +0 -81
- package/src/components/button/config.js +0 -10
- package/src/components/button/constants.js +0 -63
- package/src/components/button/index.js +0 -2
- package/src/components/checkbox/api.js +0 -45
- package/src/components/checkbox/checkbox.js +0 -96
- package/src/components/checkbox/index.js +0 -2
- package/src/components/container/api.js +0 -42
- package/src/components/container/container.js +0 -45
- package/src/components/container/index.js +0 -2
- package/src/components/container/styles.scss +0 -66
- package/src/components/list/index.js +0 -2
- package/src/components/list/list-item.js +0 -147
- package/src/components/list/list.js +0 -267
- package/src/components/menu/api.js +0 -117
- package/src/components/menu/constants.js +0 -42
- package/src/components/menu/features/items-manager.js +0 -375
- package/src/components/menu/features/keyboard-navigation.js +0 -129
- package/src/components/menu/features/positioning.js +0 -125
- package/src/components/menu/index.js +0 -2
- package/src/components/menu/menu-item.js +0 -41
- package/src/components/menu/menu.js +0 -54
- package/src/components/navigation/api.js +0 -43
- package/src/components/navigation/index.js +0 -2
- package/src/components/navigation/nav-item.js +0 -137
- package/src/components/navigation/navigation.js +0 -55
- package/src/components/snackbar/api.js +0 -125
- package/src/components/snackbar/features.js +0 -69
- package/src/components/snackbar/index.js +0 -2
- package/src/components/snackbar/position.js +0 -63
- package/src/components/snackbar/queue.js +0 -74
- package/src/components/snackbar/snackbar.js +0 -70
- package/src/components/switch/api.js +0 -44
- package/src/components/switch/index.js +0 -2
- package/src/components/switch/switch.js +0 -71
- package/src/components/textfield/api.js +0 -49
- package/src/components/textfield/index.js +0 -2
- package/src/components/textfield/textfield.js +0 -68
- package/src/core/build/_ripple.scss +0 -79
- package/src/core/build/constants.js +0 -51
- package/src/core/build/icon.js +0 -78
- package/src/core/build/ripple.js +0 -159
- package/src/core/build/text.js +0 -54
- package/src/core/compose/base.js +0 -8
- package/src/core/compose/component.js +0 -225
- package/src/core/compose/features/checkable.js +0 -114
- package/src/core/compose/features/disabled.js +0 -64
- package/src/core/compose/features/events.js +0 -48
- package/src/core/compose/features/icon.js +0 -33
- package/src/core/compose/features/index.js +0 -20
- package/src/core/compose/features/input.js +0 -100
- package/src/core/compose/features/lifecycle.js +0 -69
- package/src/core/compose/features/position.js +0 -60
- package/src/core/compose/features/ripple.js +0 -32
- package/src/core/compose/features/size.js +0 -9
- package/src/core/compose/features/style.js +0 -12
- package/src/core/compose/features/text.js +0 -17
- package/src/core/compose/features/textinput.js +0 -114
- package/src/core/compose/features/textlabel.js +0 -28
- package/src/core/compose/features/track.js +0 -49
- package/src/core/compose/features/variant.js +0 -9
- package/src/core/compose/features/withEvents.js +0 -67
- package/src/core/compose/index.js +0 -16
- package/src/core/config.js +0 -140
- package/src/core/dom/classes.js +0 -70
- package/src/core/dom/create.js +0 -132
- package/src/core/dom/events.js +0 -175
- package/src/core/dom/index.js +0 -5
- package/src/core/dom/utils.js +0 -22
- package/src/core/index.js +0 -23
- package/src/core/state/disabled.js +0 -51
- package/src/core/state/emitter.js +0 -63
- package/src/core/state/events.js +0 -29
- package/src/core/state/index.js +0 -6
- package/src/core/state/lifecycle.js +0 -64
- package/src/core/state/store.js +0 -112
- package/src/core/utils/index.js +0 -39
- package/src/core/utils/object.js +0 -22
- package/src/core/utils/validate.js +0 -37
- /package/src/components/checkbox/{styles.scss → _styles.scss} +0 -0
- /package/src/components/checkbox/{constants.js → constants.ts} +0 -0
- /package/src/components/list/{styles.scss → _styles.scss} +0 -0
- /package/src/components/menu/{styles.scss → _styles.scss} +0 -0
- /package/src/components/navigation/{styles.scss → _styles.scss} +0 -0
- /package/src/components/snackbar/{styles.scss → _styles.scss} +0 -0
- /package/src/components/switch/{styles.scss → _styles.scss} +0 -0
- /package/src/components/switch/{constants.js → constants.ts} +0 -0
- /package/src/components/textfield/{styles.scss → _styles.scss} +0 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// src/components/chip/types.ts
|
|
2
|
+
import { CHIP_VARIANTS, CHIP_SIZES } from './constants';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Configuration interface for the Chip component
|
|
6
|
+
*/
|
|
7
|
+
export interface ChipConfig {
|
|
8
|
+
/** Chip variant (filled, outlined, elevated, assist, filter, input, suggestion) */
|
|
9
|
+
variant?: keyof typeof CHIP_VARIANTS | string;
|
|
10
|
+
|
|
11
|
+
/** Chip size (small, medium, large) */
|
|
12
|
+
size?: keyof typeof CHIP_SIZES | string;
|
|
13
|
+
|
|
14
|
+
/** Whether the chip is initially disabled */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
|
|
17
|
+
/** Whether the chip is initially selected */
|
|
18
|
+
selected?: boolean;
|
|
19
|
+
|
|
20
|
+
/** Initial chip text content */
|
|
21
|
+
text?: string;
|
|
22
|
+
|
|
23
|
+
/** Initial chip icon HTML content */
|
|
24
|
+
icon?: string;
|
|
25
|
+
|
|
26
|
+
/** Optional leading icon HTML content */
|
|
27
|
+
leadingIcon?: string;
|
|
28
|
+
|
|
29
|
+
/** Optional trailing icon HTML content */
|
|
30
|
+
trailingIcon?: string;
|
|
31
|
+
|
|
32
|
+
/** Additional CSS classes */
|
|
33
|
+
class?: string;
|
|
34
|
+
|
|
35
|
+
/** Chip value attribute */
|
|
36
|
+
value?: string;
|
|
37
|
+
|
|
38
|
+
/** Whether to enable ripple effect */
|
|
39
|
+
ripple?: boolean;
|
|
40
|
+
|
|
41
|
+
/** Ripple effect configuration */
|
|
42
|
+
rippleConfig?: {
|
|
43
|
+
duration?: number;
|
|
44
|
+
timing?: string;
|
|
45
|
+
opacity?: [string, string];
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** Function called when the trailing icon is clicked */
|
|
49
|
+
onTrailingIconClick?: (chip: ChipComponent) => void;
|
|
50
|
+
|
|
51
|
+
/** Function called when the chip is selected */
|
|
52
|
+
onSelect?: (chip: ChipComponent) => void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Icon API interface
|
|
57
|
+
*/
|
|
58
|
+
export interface IconAPI {
|
|
59
|
+
setIcon: (html: string) => IconAPI;
|
|
60
|
+
getIcon: () => string;
|
|
61
|
+
getElement: () => HTMLElement | null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Text API interface
|
|
66
|
+
*/
|
|
67
|
+
export interface TextAPI {
|
|
68
|
+
setText: (content: string) => TextAPI;
|
|
69
|
+
getText: () => string;
|
|
70
|
+
getElement: () => HTMLElement | null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Chip component interface
|
|
75
|
+
*/
|
|
76
|
+
export interface ChipComponent {
|
|
77
|
+
element: HTMLElement;
|
|
78
|
+
text: TextAPI;
|
|
79
|
+
icon: IconAPI;
|
|
80
|
+
disabled: {
|
|
81
|
+
enable: () => void;
|
|
82
|
+
disable: () => void;
|
|
83
|
+
isDisabled: () => boolean;
|
|
84
|
+
};
|
|
85
|
+
lifecycle: {
|
|
86
|
+
destroy: () => void;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/** Gets the class with the specified name */
|
|
90
|
+
getClass: (name: string) => string;
|
|
91
|
+
|
|
92
|
+
/** Gets the chip's value */
|
|
93
|
+
getValue: () => string | null;
|
|
94
|
+
|
|
95
|
+
/** Sets the chip's value */
|
|
96
|
+
setValue: (value: string) => ChipComponent;
|
|
97
|
+
|
|
98
|
+
/** Enables the chip */
|
|
99
|
+
enable: () => ChipComponent;
|
|
100
|
+
|
|
101
|
+
/** Disables the chip */
|
|
102
|
+
disable: () => ChipComponent;
|
|
103
|
+
|
|
104
|
+
/** Sets the chip's text content */
|
|
105
|
+
setText: (content: string) => ChipComponent;
|
|
106
|
+
|
|
107
|
+
/** Gets the chip's text content */
|
|
108
|
+
getText: () => string;
|
|
109
|
+
|
|
110
|
+
/** Sets the chip's icon */
|
|
111
|
+
setIcon: (icon: string) => ChipComponent;
|
|
112
|
+
|
|
113
|
+
/** Gets the chip's icon */
|
|
114
|
+
getIcon: () => string;
|
|
115
|
+
|
|
116
|
+
/** Sets the chip's trailing icon */
|
|
117
|
+
setTrailingIcon: (icon: string) => ChipComponent;
|
|
118
|
+
|
|
119
|
+
/** Checks if the chip is selected */
|
|
120
|
+
isSelected: () => boolean;
|
|
121
|
+
|
|
122
|
+
/** Sets the chip's selected state */
|
|
123
|
+
setSelected: (selected: boolean) => ChipComponent;
|
|
124
|
+
|
|
125
|
+
/** Toggles the chip's selected state */
|
|
126
|
+
toggleSelected: () => ChipComponent;
|
|
127
|
+
|
|
128
|
+
/** Destroys the chip component and cleans up resources */
|
|
129
|
+
destroy: () => void;
|
|
130
|
+
|
|
131
|
+
/** Adds event listener */
|
|
132
|
+
on: (event: string, handler: Function) => ChipComponent;
|
|
133
|
+
|
|
134
|
+
/** Removes event listener */
|
|
135
|
+
off: (event: string, handler: Function) => ChipComponent;
|
|
136
|
+
|
|
137
|
+
/** Add CSS classes */
|
|
138
|
+
addClass: (...classes: string[]) => ChipComponent;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* API options interface
|
|
143
|
+
*/
|
|
144
|
+
export interface ApiOptions {
|
|
145
|
+
disabled: {
|
|
146
|
+
enable: () => void;
|
|
147
|
+
disable: () => void;
|
|
148
|
+
};
|
|
149
|
+
lifecycle: {
|
|
150
|
+
destroy: () => void;
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Base component interface
|
|
156
|
+
*/
|
|
157
|
+
export interface BaseComponent {
|
|
158
|
+
element: HTMLElement;
|
|
159
|
+
getClass: (name?: string) => string;
|
|
160
|
+
config: any;
|
|
161
|
+
text?: TextAPI;
|
|
162
|
+
icon?: IconAPI;
|
|
163
|
+
disabled?: {
|
|
164
|
+
enable: () => void;
|
|
165
|
+
disable: () => void;
|
|
166
|
+
isDisabled: () => boolean;
|
|
167
|
+
};
|
|
168
|
+
lifecycle?: {
|
|
169
|
+
destroy: () => void;
|
|
170
|
+
};
|
|
171
|
+
[key: string]: any;
|
|
172
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// src/components/list/api.ts
|
|
2
|
+
import { BaseComponent, ListComponent, ListItemConfig } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* API options interface
|
|
6
|
+
*/
|
|
7
|
+
interface ApiOptions {
|
|
8
|
+
disabled: {
|
|
9
|
+
enable: () => any;
|
|
10
|
+
disable: () => any;
|
|
11
|
+
};
|
|
12
|
+
lifecycle: {
|
|
13
|
+
destroy: () => void;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Enhances list component with API methods
|
|
19
|
+
* @param {ApiOptions} options - API configuration
|
|
20
|
+
* @returns {Function} Higher-order function that adds API methods to component
|
|
21
|
+
*/
|
|
22
|
+
export const withAPI = ({ disabled, lifecycle }: ApiOptions) =>
|
|
23
|
+
(component: BaseComponent): ListComponent => ({
|
|
24
|
+
...component as any,
|
|
25
|
+
element: component.element,
|
|
26
|
+
items: component.items as Map<string, any>,
|
|
27
|
+
selectedItems: component.selectedItems as Set<string>,
|
|
28
|
+
|
|
29
|
+
// Selection management
|
|
30
|
+
getSelected(): string[] {
|
|
31
|
+
return component.getSelected?.() || [];
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
setSelected(ids: string[]): void {
|
|
35
|
+
component.setSelected?.(ids);
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
// Item management
|
|
39
|
+
addItem(itemConfig: ListItemConfig): void {
|
|
40
|
+
component.addItem?.(itemConfig);
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
removeItem(id: string): void {
|
|
44
|
+
component.removeItem?.(id);
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// Event handling
|
|
48
|
+
on(event: string, handler: Function): ListComponent {
|
|
49
|
+
component.on?.(event, handler);
|
|
50
|
+
return this;
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
off(event: string, handler: Function): ListComponent {
|
|
54
|
+
component.off?.(event, handler);
|
|
55
|
+
return this;
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// State management
|
|
59
|
+
enable(): ListComponent {
|
|
60
|
+
disabled.enable();
|
|
61
|
+
return this;
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
disable(): ListComponent {
|
|
65
|
+
disabled.disable();
|
|
66
|
+
return this;
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
destroy(): void {
|
|
70
|
+
lifecycle.destroy();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// src/components/list/config.ts
|
|
2
|
+
import {
|
|
3
|
+
createComponentConfig,
|
|
4
|
+
createElementConfig,
|
|
5
|
+
BaseComponentConfig
|
|
6
|
+
} from '../../core/config/component-config';
|
|
7
|
+
import { ListConfig, BaseComponent } from './types';
|
|
8
|
+
import { LIST_TYPES, LIST_LAYOUTS } from './constants';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default configuration for the List component
|
|
12
|
+
*/
|
|
13
|
+
export const defaultConfig: ListConfig = {
|
|
14
|
+
type: LIST_TYPES.DEFAULT,
|
|
15
|
+
layout: LIST_LAYOUTS.HORIZONTAL,
|
|
16
|
+
items: []
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates the base configuration for List component
|
|
21
|
+
* @param {ListConfig} config - User provided configuration
|
|
22
|
+
* @returns {ListConfig} Complete configuration with defaults applied
|
|
23
|
+
*/
|
|
24
|
+
export const createBaseConfig = (config: ListConfig = {}): ListConfig =>
|
|
25
|
+
createComponentConfig(defaultConfig, config, 'list') as ListConfig;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generates element configuration for the List component
|
|
29
|
+
* @param {ListConfig} config - List configuration
|
|
30
|
+
* @returns {Object} Element configuration object for withElement
|
|
31
|
+
*/
|
|
32
|
+
export const getElementConfig = (config: ListConfig) =>
|
|
33
|
+
createElementConfig(config, {
|
|
34
|
+
tag: 'div',
|
|
35
|
+
role: config.type === LIST_TYPES.DEFAULT ? 'list' : 'listbox',
|
|
36
|
+
attrs: {
|
|
37
|
+
'aria-multiselectable': config.type === LIST_TYPES.MULTI_SELECT ? 'true' : undefined
|
|
38
|
+
},
|
|
39
|
+
componentName: 'list',
|
|
40
|
+
className: config.class
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export default defaultConfig;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/components/list/constants.
|
|
1
|
+
// src/components/list/constants.ts
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* List types/variants
|
|
@@ -8,7 +8,7 @@ export const LIST_TYPES = {
|
|
|
8
8
|
SINGLE_SELECT: 'single', // Single selection list
|
|
9
9
|
MULTI_SELECT: 'multi', // Multiple selection list
|
|
10
10
|
RADIO: 'radio' // Radio button list
|
|
11
|
-
}
|
|
11
|
+
} as const;
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* List layout variants
|
|
@@ -16,7 +16,15 @@ export const LIST_TYPES = {
|
|
|
16
16
|
export const LIST_LAYOUTS = {
|
|
17
17
|
HORIZONTAL: 'horizontal', // Default horizontal layout
|
|
18
18
|
VERTICAL: 'vertical' // Items with more content stacked vertically
|
|
19
|
-
}
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* List item layouts
|
|
23
|
+
*/
|
|
24
|
+
export const LIST_ITEM_LAYOUTS = {
|
|
25
|
+
HORIZONTAL: 'horizontal', // Default horizontal layout
|
|
26
|
+
VERTICAL: 'vertical' // Stacked layout with vertical alignment
|
|
27
|
+
} as const;
|
|
20
28
|
|
|
21
29
|
/**
|
|
22
30
|
* List element class names
|
|
@@ -27,11 +35,20 @@ export const LIST_CLASSES = {
|
|
|
27
35
|
GROUP_TITLE: 'list-group-title',
|
|
28
36
|
DIVIDER: 'list-divider',
|
|
29
37
|
SECTION: 'list-section',
|
|
30
|
-
SECTION_TITLE: 'list-section-title'
|
|
31
|
-
|
|
38
|
+
SECTION_TITLE: 'list-section-title',
|
|
39
|
+
ITEM: 'list-item',
|
|
40
|
+
ITEM_CONTENT: 'list-item-content',
|
|
41
|
+
ITEM_LEADING: 'list-item-leading',
|
|
42
|
+
ITEM_TEXT: 'list-item-text',
|
|
43
|
+
ITEM_OVERLINE: 'list-item-overline',
|
|
44
|
+
ITEM_HEADLINE: 'list-item-headline',
|
|
45
|
+
ITEM_SUPPORTING: 'list-item-supporting',
|
|
46
|
+
ITEM_META: 'list-item-meta',
|
|
47
|
+
ITEM_TRAILING: 'list-item-trailing'
|
|
48
|
+
} as const;
|
|
32
49
|
|
|
33
50
|
/**
|
|
34
|
-
* List
|
|
51
|
+
* List validation schema
|
|
35
52
|
*/
|
|
36
53
|
export const LIST_SCHEMA = {
|
|
37
54
|
type: 'object',
|
|
@@ -86,4 +103,14 @@ export const LIST_SCHEMA = {
|
|
|
86
103
|
optional: true
|
|
87
104
|
}
|
|
88
105
|
}
|
|
89
|
-
}
|
|
106
|
+
} as const;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* List item states
|
|
110
|
+
*/
|
|
111
|
+
export const LIST_ITEM_STATES = {
|
|
112
|
+
SELECTED: 'selected',
|
|
113
|
+
DISABLED: 'disabled',
|
|
114
|
+
FOCUSED: 'focused',
|
|
115
|
+
HOVERED: 'hovered'
|
|
116
|
+
} as const;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// src/components/list/features.ts
|
|
2
|
+
import {
|
|
3
|
+
ListConfig,
|
|
4
|
+
ListItemConfig,
|
|
5
|
+
ListItemData,
|
|
6
|
+
BaseComponent,
|
|
7
|
+
SelectionChangeEvent
|
|
8
|
+
} from './types';
|
|
9
|
+
import { LIST_TYPES, LIST_LAYOUTS } from './constants';
|
|
10
|
+
import { createDivider, createSectionTitle } from './utils';
|
|
11
|
+
import createListItem from './list-item';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Enhances component with list content and functionality
|
|
15
|
+
* @param {ListConfig} config - List configuration
|
|
16
|
+
* @returns {Function} Higher-order function that adds list features to component
|
|
17
|
+
*/
|
|
18
|
+
export const withListContent = (config: ListConfig) =>
|
|
19
|
+
(component: BaseComponent): BaseComponent => {
|
|
20
|
+
const { element } = component;
|
|
21
|
+
const prefix = component.prefix || config.prefix || '';
|
|
22
|
+
const items = new Map<string, ListItemData>();
|
|
23
|
+
const selectedItems = new Set<string>();
|
|
24
|
+
|
|
25
|
+
// Set list type
|
|
26
|
+
element.setAttribute('data-type', config.type || LIST_TYPES.DEFAULT);
|
|
27
|
+
|
|
28
|
+
// Handle keyboard navigation
|
|
29
|
+
const handleKeyDown = (event: KeyboardEvent): void => {
|
|
30
|
+
const focusedItem = document.activeElement as HTMLElement;
|
|
31
|
+
if (!focusedItem?.classList.contains(`${prefix}-list-item`)) return;
|
|
32
|
+
|
|
33
|
+
const listItems = Array.from(element.querySelectorAll(`.${prefix}-list-item`)) as HTMLElement[];
|
|
34
|
+
const currentIndex = listItems.indexOf(focusedItem);
|
|
35
|
+
|
|
36
|
+
switch (event.key) {
|
|
37
|
+
case 'ArrowDown':
|
|
38
|
+
case 'ArrowRight':
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
const nextItem = listItems[currentIndex + 1];
|
|
41
|
+
if (nextItem) nextItem.focus();
|
|
42
|
+
break;
|
|
43
|
+
case 'ArrowUp':
|
|
44
|
+
case 'ArrowLeft':
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
const prevItem = listItems[currentIndex - 1];
|
|
47
|
+
if (prevItem) prevItem.focus();
|
|
48
|
+
break;
|
|
49
|
+
case 'Home':
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
listItems[0]?.focus();
|
|
52
|
+
break;
|
|
53
|
+
case 'End':
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
listItems[listItems.length - 1]?.focus();
|
|
56
|
+
break;
|
|
57
|
+
case ' ':
|
|
58
|
+
case 'Enter':
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
handleItemClick(focusedItem);
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Handle item selection
|
|
66
|
+
const handleItemClick = (itemElement: HTMLElement): void => {
|
|
67
|
+
const id = itemElement.dataset.id;
|
|
68
|
+
if (!id) return;
|
|
69
|
+
|
|
70
|
+
const itemData = items.get(id);
|
|
71
|
+
if (!itemData || itemData.disabled) return;
|
|
72
|
+
|
|
73
|
+
switch (config.type) {
|
|
74
|
+
case LIST_TYPES.SINGLE_SELECT:
|
|
75
|
+
// Deselect previously selected item
|
|
76
|
+
selectedItems.forEach(selectedId => {
|
|
77
|
+
const selected = items.get(selectedId);
|
|
78
|
+
if (selected) {
|
|
79
|
+
selected.element.classList.remove(`${prefix}-list-item--selected`);
|
|
80
|
+
selected.element.setAttribute('aria-selected', 'false');
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
selectedItems.clear();
|
|
84
|
+
|
|
85
|
+
// Select new item
|
|
86
|
+
itemElement.classList.add(`${prefix}-list-item--selected`);
|
|
87
|
+
itemElement.setAttribute('aria-selected', 'true');
|
|
88
|
+
selectedItems.add(id);
|
|
89
|
+
break;
|
|
90
|
+
|
|
91
|
+
case LIST_TYPES.MULTI_SELECT:
|
|
92
|
+
const isSelected = selectedItems.has(id);
|
|
93
|
+
if (isSelected) {
|
|
94
|
+
itemElement.classList.remove(`${prefix}-list-item--selected`);
|
|
95
|
+
itemElement.setAttribute('aria-selected', 'false');
|
|
96
|
+
selectedItems.delete(id);
|
|
97
|
+
} else {
|
|
98
|
+
itemElement.classList.add(`${prefix}-list-item--selected`);
|
|
99
|
+
itemElement.setAttribute('aria-selected', 'true');
|
|
100
|
+
selectedItems.add(id);
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
component.emit?.('selectionChange', {
|
|
106
|
+
selected: Array.from(selectedItems),
|
|
107
|
+
item: itemData,
|
|
108
|
+
type: config.type
|
|
109
|
+
} as SelectionChangeEvent);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Create items from configuration
|
|
113
|
+
const createItems = (itemsConfig: ListItemConfig[] = [], container: HTMLElement = element): void => {
|
|
114
|
+
itemsConfig.forEach((itemConfig, index) => {
|
|
115
|
+
if (itemConfig.divider) {
|
|
116
|
+
container.appendChild(createDivider(prefix));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const item = createListItem({
|
|
121
|
+
...itemConfig,
|
|
122
|
+
layout: config.layout || LIST_LAYOUTS.HORIZONTAL,
|
|
123
|
+
role: config.type === LIST_TYPES.RADIO ? 'radio' : 'option'
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
item.element.dataset.id = itemConfig.id;
|
|
127
|
+
item.element.tabIndex = index === 0 ? 0 : -1;
|
|
128
|
+
items.set(itemConfig.id, item);
|
|
129
|
+
|
|
130
|
+
if (itemConfig.selected) {
|
|
131
|
+
selectedItems.add(itemConfig.id);
|
|
132
|
+
item.element.classList.add(`${prefix}-list-item--selected`);
|
|
133
|
+
item.element.setAttribute('aria-selected', 'true');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
container.appendChild(item.element);
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Create sections if configured
|
|
141
|
+
if (config.sections?.length) {
|
|
142
|
+
config.sections.forEach(section => {
|
|
143
|
+
const sectionEl = document.createElement('div');
|
|
144
|
+
sectionEl.className = `${prefix}-list-section`;
|
|
145
|
+
sectionEl.setAttribute('role', 'group');
|
|
146
|
+
if (section.title) {
|
|
147
|
+
sectionEl.appendChild(createSectionTitle(section.title, prefix));
|
|
148
|
+
}
|
|
149
|
+
createItems(section.items, sectionEl);
|
|
150
|
+
element.appendChild(sectionEl);
|
|
151
|
+
});
|
|
152
|
+
} else if (config.items) {
|
|
153
|
+
createItems(config.items);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Add event listeners
|
|
157
|
+
element.addEventListener('click', (event: MouseEvent) => {
|
|
158
|
+
const target = event.target as HTMLElement;
|
|
159
|
+
const item = target.closest(`.${prefix}-list-item`) as HTMLElement;
|
|
160
|
+
if (item) handleItemClick(item);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
element.addEventListener('keydown', handleKeyDown);
|
|
164
|
+
|
|
165
|
+
// Clean up
|
|
166
|
+
if (component.lifecycle) {
|
|
167
|
+
const originalDestroy = component.lifecycle.destroy;
|
|
168
|
+
component.lifecycle.destroy = () => {
|
|
169
|
+
items.clear();
|
|
170
|
+
selectedItems.clear();
|
|
171
|
+
element.removeEventListener('keydown', handleKeyDown);
|
|
172
|
+
originalDestroy?.();
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
...component,
|
|
178
|
+
items,
|
|
179
|
+
selectedItems,
|
|
180
|
+
|
|
181
|
+
// Public methods
|
|
182
|
+
getSelected: () => Array.from(selectedItems),
|
|
183
|
+
|
|
184
|
+
setSelected: (ids: string[]) => {
|
|
185
|
+
selectedItems.clear();
|
|
186
|
+
items.forEach((item, id) => {
|
|
187
|
+
const isSelected = ids.includes(id);
|
|
188
|
+
item.element.classList.toggle(`${prefix}-list-item--selected`, isSelected);
|
|
189
|
+
item.element.setAttribute('aria-selected', isSelected.toString());
|
|
190
|
+
if (isSelected) selectedItems.add(id);
|
|
191
|
+
});
|
|
192
|
+
component.emit?.('selectionChange', {
|
|
193
|
+
selected: Array.from(selectedItems),
|
|
194
|
+
type: config.type
|
|
195
|
+
} as SelectionChangeEvent);
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
addItem: (itemConfig: ListItemConfig) => {
|
|
199
|
+
if (items.has(itemConfig.id)) return;
|
|
200
|
+
|
|
201
|
+
const item = createListItem({
|
|
202
|
+
...itemConfig,
|
|
203
|
+
layout: config.layout || LIST_LAYOUTS.HORIZONTAL
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
item.element.dataset.id = itemConfig.id;
|
|
207
|
+
items.set(itemConfig.id, item);
|
|
208
|
+
element.appendChild(item.element);
|
|
209
|
+
|
|
210
|
+
component.emit?.('itemAdded', { id: itemConfig.id, item });
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
removeItem: (id: string) => {
|
|
214
|
+
const item = items.get(id);
|
|
215
|
+
if (!item) return;
|
|
216
|
+
|
|
217
|
+
item.element.remove();
|
|
218
|
+
items.delete(id);
|
|
219
|
+
selectedItems.delete(id);
|
|
220
|
+
|
|
221
|
+
component.emit?.('itemRemoved', { id, item });
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/components/list/index.ts
|
|
2
|
+
export { default } from './list'
|
|
3
|
+
export { default as createListItem } from './list-item'
|
|
4
|
+
export {
|
|
5
|
+
LIST_TYPES,
|
|
6
|
+
LIST_LAYOUTS,
|
|
7
|
+
LIST_ITEM_LAYOUTS
|
|
8
|
+
} from './constants'
|
|
9
|
+
export {
|
|
10
|
+
ListConfig,
|
|
11
|
+
ListComponent,
|
|
12
|
+
ListItemConfig,
|
|
13
|
+
ListSectionConfig
|
|
14
|
+
} from './types'
|