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,225 @@
|
|
|
1
|
+
// src/core/compose/features/textinput.ts
|
|
2
|
+
|
|
3
|
+
import { BaseComponent, ElementComponent } from '../component';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for text input feature
|
|
7
|
+
*/
|
|
8
|
+
export interface TextInputConfig {
|
|
9
|
+
/**
|
|
10
|
+
* Input type (text, password, etc.)
|
|
11
|
+
*/
|
|
12
|
+
type?: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Whether to use textarea instead of input
|
|
16
|
+
*/
|
|
17
|
+
multiline?: boolean;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Input name attribute
|
|
21
|
+
*/
|
|
22
|
+
name?: string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether input is required
|
|
26
|
+
*/
|
|
27
|
+
required?: boolean;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Whether input is disabled
|
|
31
|
+
*/
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Maximum allowed length
|
|
36
|
+
*/
|
|
37
|
+
maxLength?: number;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Input validation pattern
|
|
41
|
+
*/
|
|
42
|
+
pattern?: string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Autocomplete setting
|
|
46
|
+
*/
|
|
47
|
+
autocomplete?: string;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Initial input value
|
|
51
|
+
*/
|
|
52
|
+
value?: string;
|
|
53
|
+
|
|
54
|
+
[key: string]: any;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Component with text input capabilities
|
|
59
|
+
*/
|
|
60
|
+
export interface TextInputComponent extends ElementComponent {
|
|
61
|
+
/**
|
|
62
|
+
* Input element
|
|
63
|
+
*/
|
|
64
|
+
input: HTMLInputElement | HTMLTextAreaElement;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Sets the input value
|
|
68
|
+
* @param value - Value to set
|
|
69
|
+
* @returns Component instance for chaining
|
|
70
|
+
*/
|
|
71
|
+
setValue: (value: string) => TextInputComponent;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Gets the current input value
|
|
75
|
+
* @returns Current value
|
|
76
|
+
*/
|
|
77
|
+
getValue: () => string;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Sets an attribute on the input
|
|
81
|
+
* @param name - Attribute name
|
|
82
|
+
* @param value - Attribute value
|
|
83
|
+
* @returns Component instance for chaining
|
|
84
|
+
*/
|
|
85
|
+
setAttribute: (name: string, value: string) => TextInputComponent;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Gets an attribute from the input
|
|
89
|
+
* @param name - Attribute name
|
|
90
|
+
* @returns Attribute value
|
|
91
|
+
*/
|
|
92
|
+
getAttribute: (name: string) => string | null;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Removes an attribute from the input
|
|
96
|
+
* @param name - Attribute name
|
|
97
|
+
* @returns Component instance for chaining
|
|
98
|
+
*/
|
|
99
|
+
removeAttribute: (name: string) => TextInputComponent;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Event emission method if available
|
|
103
|
+
*/
|
|
104
|
+
emit?: (event: string, data: any) => TextInputComponent;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Enhances a component with text input functionality
|
|
109
|
+
*
|
|
110
|
+
* @param config - Text input configuration
|
|
111
|
+
* @returns Function that enhances a component with text input capabilities
|
|
112
|
+
*/
|
|
113
|
+
export const withTextInput = <T extends TextInputConfig>(config: T = {} as T) =>
|
|
114
|
+
<C extends ElementComponent>(component: C): C & TextInputComponent => {
|
|
115
|
+
const input = document.createElement(config.multiline ? 'textarea' : 'input') as
|
|
116
|
+
HTMLInputElement | HTMLTextAreaElement;
|
|
117
|
+
|
|
118
|
+
input.className = `${component.getClass('textfield')}-input`;
|
|
119
|
+
|
|
120
|
+
// Set input attributes
|
|
121
|
+
const attributes: Record<string, string | number | boolean | undefined> = {
|
|
122
|
+
type: config.multiline ? undefined : (config.type || 'text'),
|
|
123
|
+
name: config.name,
|
|
124
|
+
required: config.required,
|
|
125
|
+
disabled: config.disabled,
|
|
126
|
+
maxLength: config.maxLength,
|
|
127
|
+
pattern: config.pattern,
|
|
128
|
+
autocomplete: config.autocomplete,
|
|
129
|
+
value: config.value || ''
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
Object.entries(attributes).forEach(([key, value]) => {
|
|
133
|
+
if (value !== null && value !== undefined) {
|
|
134
|
+
if (typeof value === 'boolean') {
|
|
135
|
+
if (value) {
|
|
136
|
+
input.setAttribute(key, '');
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
input.setAttribute(key, String(value));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Handle input state changes
|
|
145
|
+
const updateInputState = (): boolean => {
|
|
146
|
+
const isEmpty = !input.value;
|
|
147
|
+
component.element.classList.toggle(`${component.getClass('textfield')}--empty`, isEmpty);
|
|
148
|
+
return isEmpty;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Detect autofill using input events instead of animation
|
|
152
|
+
// This is more compatible with our testing environment
|
|
153
|
+
const handleAutofill = (): void => {
|
|
154
|
+
// Check for webkit autofill background
|
|
155
|
+
const isAutofilled =
|
|
156
|
+
input.matches(':-webkit-autofill') ||
|
|
157
|
+
// For Firefox and other browsers
|
|
158
|
+
(window.getComputedStyle(input).backgroundColor === 'rgb(250, 255, 189)' ||
|
|
159
|
+
window.getComputedStyle(input).backgroundColor === 'rgb(232, 240, 254)');
|
|
160
|
+
|
|
161
|
+
if (isAutofilled) {
|
|
162
|
+
component.element.classList.remove(`${component.getClass('textfield')}--empty`);
|
|
163
|
+
component.emit?.('input', { value: input.value, isEmpty: false, isAutofilled: true });
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Event listeners
|
|
168
|
+
input.addEventListener('focus', () => {
|
|
169
|
+
component.element.classList.add(`${component.getClass('textfield')}--focused`);
|
|
170
|
+
component.emit?.('focus', { isEmpty: updateInputState() });
|
|
171
|
+
// Also check for autofill on focus
|
|
172
|
+
setTimeout(handleAutofill, 100);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
input.addEventListener('blur', () => {
|
|
176
|
+
component.element.classList.remove(`${component.getClass('textfield')}--focused`);
|
|
177
|
+
component.emit?.('blur', { isEmpty: updateInputState() });
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
input.addEventListener('input', () => {
|
|
181
|
+
component.emit?.('input', {
|
|
182
|
+
value: input.value,
|
|
183
|
+
isEmpty: updateInputState(),
|
|
184
|
+
isAutofilled: false
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Initial state
|
|
189
|
+
updateInputState();
|
|
190
|
+
|
|
191
|
+
component.element.appendChild(input);
|
|
192
|
+
|
|
193
|
+
// Cleanup
|
|
194
|
+
if ('lifecycle' in component && component.lifecycle?.destroy) {
|
|
195
|
+
const originalDestroy = component.lifecycle.destroy;
|
|
196
|
+
component.lifecycle.destroy = () => {
|
|
197
|
+
input.remove();
|
|
198
|
+
originalDestroy.call(component.lifecycle);
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
...component,
|
|
204
|
+
input,
|
|
205
|
+
setValue(value: string) {
|
|
206
|
+
input.value = value || '';
|
|
207
|
+
updateInputState();
|
|
208
|
+
return this;
|
|
209
|
+
},
|
|
210
|
+
getValue() {
|
|
211
|
+
return input.value;
|
|
212
|
+
},
|
|
213
|
+
setAttribute(name: string, value: string) {
|
|
214
|
+
input.setAttribute(name, value);
|
|
215
|
+
return this;
|
|
216
|
+
},
|
|
217
|
+
getAttribute(name: string) {
|
|
218
|
+
return input.getAttribute(name);
|
|
219
|
+
},
|
|
220
|
+
removeAttribute(name: string) {
|
|
221
|
+
input.removeAttribute(name);
|
|
222
|
+
return this;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// src/core/compose/features/textlabel.ts
|
|
2
|
+
|
|
3
|
+
import { BaseComponent, ElementComponent } from '../component';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for text label feature
|
|
7
|
+
*/
|
|
8
|
+
export interface TextLabelConfig {
|
|
9
|
+
/**
|
|
10
|
+
* Label text
|
|
11
|
+
*/
|
|
12
|
+
label?: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* CSS class prefix
|
|
16
|
+
*/
|
|
17
|
+
prefix?: string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Component name for class generation
|
|
21
|
+
*/
|
|
22
|
+
componentName?: string;
|
|
23
|
+
|
|
24
|
+
[key: string]: any;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Label manager interface
|
|
29
|
+
*/
|
|
30
|
+
export interface LabelManager {
|
|
31
|
+
/**
|
|
32
|
+
* Sets the label text
|
|
33
|
+
* @param text - Text to set
|
|
34
|
+
* @returns LabelManager instance for chaining
|
|
35
|
+
*/
|
|
36
|
+
setText: (text: string) => LabelManager;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Gets the current label text
|
|
40
|
+
* @returns Current text
|
|
41
|
+
*/
|
|
42
|
+
getText: () => string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Gets the label element
|
|
46
|
+
* @returns Label element
|
|
47
|
+
*/
|
|
48
|
+
getElement: () => HTMLElement;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Component with label capabilities
|
|
53
|
+
*/
|
|
54
|
+
export interface LabelComponent extends BaseComponent {
|
|
55
|
+
label: LabelManager;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Adds a text label to a component
|
|
60
|
+
*
|
|
61
|
+
* @param config - Configuration object containing label information
|
|
62
|
+
* @returns Function that enhances a component with a label
|
|
63
|
+
*/
|
|
64
|
+
export const withTextLabel = <T extends TextLabelConfig>(config: T = {} as T) =>
|
|
65
|
+
<C extends ElementComponent>(component: C): C & LabelComponent => {
|
|
66
|
+
if (!config.label) return component as C & LabelComponent;
|
|
67
|
+
|
|
68
|
+
const labelElement = document.createElement('label');
|
|
69
|
+
labelElement.className = `${config.prefix}-${config.componentName}-label`;
|
|
70
|
+
labelElement.textContent = config.label;
|
|
71
|
+
|
|
72
|
+
// Insert label after input for proper z-index stacking
|
|
73
|
+
component.element.appendChild(labelElement);
|
|
74
|
+
|
|
75
|
+
const label: LabelManager = {
|
|
76
|
+
setText(text: string) {
|
|
77
|
+
labelElement.textContent = text;
|
|
78
|
+
return this;
|
|
79
|
+
},
|
|
80
|
+
getText() {
|
|
81
|
+
return labelElement.textContent || '';
|
|
82
|
+
},
|
|
83
|
+
getElement() {
|
|
84
|
+
return labelElement;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
...component,
|
|
90
|
+
label
|
|
91
|
+
};
|
|
92
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// src/core/compose/features/track.ts
|
|
2
|
+
/**
|
|
3
|
+
* @module core/compose/features
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { BaseComponent, ElementComponent } from '../component';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Configuration for track feature
|
|
10
|
+
*/
|
|
11
|
+
export interface TrackConfig {
|
|
12
|
+
/**
|
|
13
|
+
* CSS class prefix
|
|
14
|
+
*/
|
|
15
|
+
prefix: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Component name for class generation
|
|
19
|
+
*/
|
|
20
|
+
componentName: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Custom icon HTML or 'none'
|
|
24
|
+
*/
|
|
25
|
+
icon?: string;
|
|
26
|
+
|
|
27
|
+
[key: string]: any;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Component with track and thumb elements
|
|
32
|
+
*/
|
|
33
|
+
export interface TrackComponent extends BaseComponent {
|
|
34
|
+
/**
|
|
35
|
+
* Track element
|
|
36
|
+
*/
|
|
37
|
+
track: HTMLElement;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Thumb element
|
|
41
|
+
*/
|
|
42
|
+
thumb: HTMLElement;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Default checkmark icon SVG
|
|
47
|
+
* @private
|
|
48
|
+
*/
|
|
49
|
+
const DEFAULT_ICON = `
|
|
50
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
51
|
+
<polyline points="20 6 9 17 4 12"></polyline>
|
|
52
|
+
</svg>`;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Adds track and thumb elements to a component
|
|
56
|
+
*
|
|
57
|
+
* @param config - Track configuration
|
|
58
|
+
* @returns Function that enhances a component with track and thumb elements
|
|
59
|
+
*/
|
|
60
|
+
export const withTrack = <T extends TrackConfig>(config: T) =>
|
|
61
|
+
<C extends ElementComponent>(component: C): C & TrackComponent => {
|
|
62
|
+
const track = document.createElement('span');
|
|
63
|
+
track.className = `${config.prefix}-${config.componentName}-track`;
|
|
64
|
+
|
|
65
|
+
const thumb = document.createElement('span');
|
|
66
|
+
thumb.className = `${config.prefix}-${config.componentName}-thumb`;
|
|
67
|
+
track.appendChild(thumb);
|
|
68
|
+
|
|
69
|
+
// Add icon inside thumb if provided or use default
|
|
70
|
+
if (config.icon !== 'none') {
|
|
71
|
+
const icon = document.createElement('span');
|
|
72
|
+
icon.className = `${config.prefix}-${config.componentName}-thumb-icon`;
|
|
73
|
+
icon.innerHTML = config.icon || DEFAULT_ICON;
|
|
74
|
+
thumb.appendChild(icon);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
component.element.appendChild(track);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
...component,
|
|
81
|
+
track,
|
|
82
|
+
thumb
|
|
83
|
+
};
|
|
84
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/core/compose/features/variant.ts
|
|
2
|
+
|
|
3
|
+
import { BaseComponent, ElementComponent } from '../component';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for variant feature
|
|
7
|
+
*/
|
|
8
|
+
export interface VariantConfig {
|
|
9
|
+
variant?: string;
|
|
10
|
+
prefix?: string;
|
|
11
|
+
componentName?: string;
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Adds a variant class to a component
|
|
17
|
+
*
|
|
18
|
+
* @param config - Configuration object containing variant information
|
|
19
|
+
* @returns Function that enhances a component with the variant class
|
|
20
|
+
*/
|
|
21
|
+
export const withVariant = <T extends VariantConfig>(config: T) =>
|
|
22
|
+
<C extends ElementComponent>(component: C): C => {
|
|
23
|
+
if (config.variant && component.element) {
|
|
24
|
+
// Use config.componentName since we know it's there
|
|
25
|
+
const className = `${config.prefix}-${config.componentName}--${config.variant}`;
|
|
26
|
+
component.element.classList.add(className);
|
|
27
|
+
}
|
|
28
|
+
return component;
|
|
29
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// src/core/compose/features/withEvents.ts
|
|
2
|
+
|
|
3
|
+
import { createEventManager } from '../../state/events';
|
|
4
|
+
import { BaseComponent, ElementComponent } from '../component';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Event manager interface
|
|
8
|
+
*/
|
|
9
|
+
export interface EnhancedEventManager {
|
|
10
|
+
/**
|
|
11
|
+
* Add an event listener
|
|
12
|
+
*/
|
|
13
|
+
on: (event: string, handler: Function) => EnhancedEventManager;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Remove an event listener
|
|
17
|
+
*/
|
|
18
|
+
off: (event: string, handler: Function) => EnhancedEventManager;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Add multiple event listeners at once
|
|
22
|
+
*/
|
|
23
|
+
addListeners: (listeners: Record<string, Function>) => EnhancedEventManager;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Remove multiple event listeners at once
|
|
27
|
+
*/
|
|
28
|
+
removeListeners: (listeners: Record<string, Function>) => EnhancedEventManager;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* One-time event handler
|
|
32
|
+
*/
|
|
33
|
+
once: (event: string, handler: Function) => EnhancedEventManager;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Clean up all event listeners
|
|
37
|
+
*/
|
|
38
|
+
destroy: () => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Component with enhanced event capabilities
|
|
43
|
+
*/
|
|
44
|
+
export interface EnhancedEventComponent extends BaseComponent {
|
|
45
|
+
events: EnhancedEventManager;
|
|
46
|
+
on: (event: string, handler: Function) => EnhancedEventComponent;
|
|
47
|
+
off: (event: string, handler: Function) => EnhancedEventComponent;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Adds enhanced event handling capabilities to a component
|
|
52
|
+
*
|
|
53
|
+
* @param target - Optional custom event target
|
|
54
|
+
* @returns Function that enhances a component with event capabilities
|
|
55
|
+
*/
|
|
56
|
+
export const withEvents = (target?: HTMLElement) =>
|
|
57
|
+
<C extends ElementComponent>(component: C): C & EnhancedEventComponent => {
|
|
58
|
+
const events = createEventManager(target || component.element);
|
|
59
|
+
|
|
60
|
+
// Enhanced event methods
|
|
61
|
+
const enhancedEvents: EnhancedEventManager = {
|
|
62
|
+
/**
|
|
63
|
+
* Add multiple event listeners at once
|
|
64
|
+
* @param listeners - Map of event types to handlers
|
|
65
|
+
*/
|
|
66
|
+
addListeners(listeners: Record<string, Function>) {
|
|
67
|
+
Object.entries(listeners).forEach(([event, handler]) => {
|
|
68
|
+
events.on(event, handler as any);
|
|
69
|
+
});
|
|
70
|
+
return this;
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Remove multiple event listeners at once
|
|
75
|
+
* @param listeners - Map of event types to handlers
|
|
76
|
+
*/
|
|
77
|
+
removeListeners(listeners: Record<string, Function>) {
|
|
78
|
+
Object.entries(listeners).forEach(([event, handler]) => {
|
|
79
|
+
events.off(event, handler as any);
|
|
80
|
+
});
|
|
81
|
+
return this;
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* One-time event handler
|
|
86
|
+
* @param event - Event name
|
|
87
|
+
* @param handler - Event handler
|
|
88
|
+
*/
|
|
89
|
+
once(event: string, handler: Function) {
|
|
90
|
+
const wrappedHandler = (e: Event) => {
|
|
91
|
+
handler(e);
|
|
92
|
+
events.off(event, wrappedHandler as any);
|
|
93
|
+
};
|
|
94
|
+
events.on(event, wrappedHandler as any);
|
|
95
|
+
return this;
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Add an event listener
|
|
100
|
+
*/
|
|
101
|
+
on(event: string, handler: Function) {
|
|
102
|
+
events.on(event, handler as any);
|
|
103
|
+
return this;
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Remove an event listener
|
|
108
|
+
*/
|
|
109
|
+
off(event: string, handler: Function) {
|
|
110
|
+
events.off(event, handler as any);
|
|
111
|
+
return this;
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Clean up all event listeners
|
|
116
|
+
*/
|
|
117
|
+
destroy() {
|
|
118
|
+
events.destroy();
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Add lifecycle integration
|
|
123
|
+
if ('lifecycle' in component && component.lifecycle?.destroy) {
|
|
124
|
+
const originalDestroy = component.lifecycle.destroy;
|
|
125
|
+
component.lifecycle.destroy = () => {
|
|
126
|
+
events.destroy();
|
|
127
|
+
originalDestroy.call(component.lifecycle);
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
...component,
|
|
133
|
+
events: enhancedEvents,
|
|
134
|
+
on: enhancedEvents.on.bind(enhancedEvents),
|
|
135
|
+
off: enhancedEvents.off.bind(enhancedEvents)
|
|
136
|
+
};
|
|
137
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// src/core/compose/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* @module core/compose
|
|
4
|
+
* @description Core composition utilities for creating and combining components
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { pipe, compose, transform } from './pipe';
|
|
8
|
+
export { createComponent } from './base';
|
|
9
|
+
export { createBase, withElement } from './component';
|
|
10
|
+
export {
|
|
11
|
+
withEvents,
|
|
12
|
+
withIcon,
|
|
13
|
+
withSize,
|
|
14
|
+
withPosition,
|
|
15
|
+
withText,
|
|
16
|
+
withVariant,
|
|
17
|
+
withDisabled,
|
|
18
|
+
withLifecycle,
|
|
19
|
+
withRipple,
|
|
20
|
+
withInput,
|
|
21
|
+
withCheckable,
|
|
22
|
+
withStyle,
|
|
23
|
+
withTextInput,
|
|
24
|
+
withTextLabel,
|
|
25
|
+
withTrack,
|
|
26
|
+
withEnhancedEvents
|
|
27
|
+
} from './features';
|
|
28
|
+
|
|
29
|
+
// Component feature interfaces
|
|
30
|
+
export type { Component } from './base';
|
|
31
|
+
export type {
|
|
32
|
+
BaseComponent,
|
|
33
|
+
ElementComponent,
|
|
34
|
+
TouchState,
|
|
35
|
+
WithElementOptions
|
|
36
|
+
} from './component';
|
|
37
|
+
|
|
38
|
+
export type {
|
|
39
|
+
EventComponent,
|
|
40
|
+
TextComponent,
|
|
41
|
+
IconComponent,
|
|
42
|
+
LifecycleComponent,
|
|
43
|
+
Lifecycle,
|
|
44
|
+
DisabledComponent,
|
|
45
|
+
DisabledManager,
|
|
46
|
+
RippleComponent,
|
|
47
|
+
InputComponent,
|
|
48
|
+
CheckableComponent,
|
|
49
|
+
CheckableManager,
|
|
50
|
+
TextInputComponent,
|
|
51
|
+
LabelComponent,
|
|
52
|
+
TrackComponent,
|
|
53
|
+
EnhancedEventComponent
|
|
54
|
+
} from './features';
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
// @module core/compose
|
|
3
|
-
|
|
1
|
+
// src/core/compose/pipe.ts
|
|
4
2
|
/**
|
|
5
3
|
* @namespace compose
|
|
6
4
|
* @description Core composition utilities for creating and combining components
|
|
@@ -21,7 +19,8 @@
|
|
|
21
19
|
* const addOneThenDouble = pipe(addOne, double);
|
|
22
20
|
* console.log(addOneThenDouble(3)); // Output: 8
|
|
23
21
|
*/
|
|
24
|
-
export const pipe = (...fns
|
|
22
|
+
export const pipe = <T>(...fns: Array<(arg: any) => any>) =>
|
|
23
|
+
(x: T): any => fns.reduce((v, f) => f(v), x);
|
|
25
24
|
|
|
26
25
|
/**
|
|
27
26
|
* Performs right-to-left function composition.
|
|
@@ -38,7 +37,8 @@ export const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x)
|
|
|
38
37
|
* const doubleTheAddOne = compose(addOne, double);
|
|
39
38
|
* console.log(doubleTheAddOne(3)); // Output: 7
|
|
40
39
|
*/
|
|
41
|
-
export const compose = (...fns
|
|
40
|
+
export const compose = <T>(...fns: Array<(arg: any) => any>) =>
|
|
41
|
+
(x: T): any => fns.reduceRight((v, f) => f(v), x);
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* Creates a function that applies transformations to an object with shared context.
|
|
@@ -57,13 +57,18 @@ export const compose = (...fns) => (x) => fns.reduceRight((v, f) => f(v), x)
|
|
|
57
57
|
* const person = createPerson({}, { name: 'John', age: 30 });
|
|
58
58
|
* // Result: { name: 'John', age: 30 }
|
|
59
59
|
*/
|
|
60
|
-
export const transform =
|
|
61
|
-
transformers
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
export const transform = <T, C = Record<string, any>>(
|
|
61
|
+
...transformers: Array<(obj: T, context: C) => Partial<T>>
|
|
62
|
+
) => (obj: T, context: C = {} as C): T =>
|
|
63
|
+
transformers.reduce(
|
|
64
|
+
(acc, transformer) => ({
|
|
65
|
+
...acc,
|
|
66
|
+
...transformer(acc, context)
|
|
67
|
+
}),
|
|
68
|
+
obj
|
|
69
|
+
);
|
|
65
70
|
|
|
66
71
|
/**
|
|
67
72
|
* @typedef {Object} TransformContext
|
|
68
73
|
* @property {any} [key] - Any contextual data needed by transformers
|
|
69
|
-
*/
|
|
74
|
+
*/
|