mtrl 0.2.8 → 0.3.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/index.ts +4 -0
- package/package.json +1 -1
- package/src/components/button/button.ts +34 -5
- package/src/components/navigation/api.ts +131 -96
- package/src/components/navigation/features/controller.ts +273 -0
- package/src/components/navigation/features/items.ts +133 -64
- package/src/components/navigation/navigation.ts +17 -2
- package/src/components/navigation/system/core.ts +302 -0
- package/src/components/navigation/system/events.ts +240 -0
- package/src/components/navigation/system/index.ts +184 -0
- package/src/components/navigation/system/mobile.ts +278 -0
- package/src/components/navigation/system/state.ts +77 -0
- package/src/components/navigation/system/types.ts +364 -0
- package/src/components/slider/config.ts +20 -2
- package/src/components/slider/features/controller.ts +737 -0
- package/src/components/slider/features/handlers.ts +18 -16
- package/src/components/slider/features/index.ts +3 -2
- package/src/components/slider/features/range.ts +104 -0
- package/src/components/slider/schema.ts +141 -0
- package/src/components/slider/slider.ts +34 -13
- package/src/components/switch/api.ts +16 -0
- package/src/components/switch/config.ts +1 -18
- package/src/components/switch/features.ts +198 -0
- package/src/components/switch/index.ts +1 -0
- package/src/components/switch/switch.ts +3 -3
- package/src/components/switch/types.ts +14 -2
- package/src/components/textfield/api.ts +53 -0
- package/src/components/textfield/features.ts +322 -0
- package/src/components/textfield/textfield.ts +8 -0
- package/src/components/textfield/types.ts +12 -3
- package/src/components/timepicker/clockdial.ts +1 -4
- package/src/core/compose/features/textinput.ts +15 -2
- package/src/core/composition/features/dom.ts +45 -0
- package/src/core/composition/features/icon.ts +131 -0
- package/src/core/composition/features/index.ts +12 -0
- package/src/core/composition/features/label.ts +155 -0
- package/src/core/composition/features/layout.ts +47 -0
- package/src/core/composition/index.ts +26 -0
- package/src/core/index.ts +1 -1
- package/src/core/layout/README.md +350 -0
- package/src/core/layout/array.ts +181 -0
- package/src/core/layout/create.ts +55 -0
- package/src/core/layout/index.ts +26 -0
- package/src/core/layout/object.ts +124 -0
- package/src/core/layout/processor.ts +58 -0
- package/src/core/layout/result.ts +85 -0
- package/src/core/layout/types.ts +125 -0
- package/src/core/layout/utils.ts +136 -0
- package/src/index.ts +1 -0
- package/src/styles/abstract/_variables.scss +28 -0
- package/src/styles/components/_navigation-mobile.scss +244 -0
- package/src/styles/components/_navigation-system.scss +151 -0
- package/src/styles/components/_switch.scss +133 -69
- package/src/styles/components/_textfield.scss +259 -27
- package/demo/build.ts +0 -349
- package/demo/index.html +0 -110
- package/demo/main.js +0 -448
- package/demo/styles.css +0 -239
- package/server.ts +0 -86
- package/src/components/slider/features/slider.ts +0 -318
- package/src/components/slider/features/structure.ts +0 -181
- package/src/components/slider/features/ui.ts +0 -388
- package/src/components/textfield/constants.ts +0 -100
- package/src/core/layout/index.js +0 -95
|
@@ -11,11 +11,11 @@ import {
|
|
|
11
11
|
withCheckable
|
|
12
12
|
} from '../../core/compose/features';
|
|
13
13
|
import { withAPI } from './api';
|
|
14
|
+
import { withSupportingText } from './features';
|
|
14
15
|
import { SwitchConfig, SwitchComponent, BaseComponent } from './types';
|
|
15
16
|
import {
|
|
16
17
|
createBaseConfig,
|
|
17
18
|
getElementConfig,
|
|
18
|
-
withLabelPosition,
|
|
19
19
|
getApiConfig
|
|
20
20
|
} from './config';
|
|
21
21
|
|
|
@@ -32,10 +32,10 @@ const createSwitch = (config: SwitchConfig = {}): SwitchComponent => {
|
|
|
32
32
|
createBase,
|
|
33
33
|
withEvents(), // Move events first to ensure system is available
|
|
34
34
|
withElement(getElementConfig(baseConfig)),
|
|
35
|
+
withTextLabel(baseConfig),
|
|
35
36
|
withInput(baseConfig),
|
|
36
37
|
withTrack(baseConfig),
|
|
37
|
-
|
|
38
|
-
withLabelPosition(baseConfig),
|
|
38
|
+
withSupportingText(baseConfig),
|
|
39
39
|
withCheckable(baseConfig),
|
|
40
40
|
withDisabled(baseConfig),
|
|
41
41
|
withLifecycle(),
|
|
@@ -24,8 +24,11 @@ export interface SwitchConfig {
|
|
|
24
24
|
/** Label text */
|
|
25
25
|
label?: string;
|
|
26
26
|
|
|
27
|
-
/**
|
|
28
|
-
|
|
27
|
+
/** Supporting text content */
|
|
28
|
+
supportingText?: string;
|
|
29
|
+
|
|
30
|
+
/** Whether supporting text indicates an error */
|
|
31
|
+
error?: boolean;
|
|
29
32
|
|
|
30
33
|
/** Additional CSS classes */
|
|
31
34
|
class?: string;
|
|
@@ -77,6 +80,15 @@ export interface SwitchComponent {
|
|
|
77
80
|
/** Gets the switch's label text */
|
|
78
81
|
getLabel: () => string;
|
|
79
82
|
|
|
83
|
+
/** Supporting text element */
|
|
84
|
+
supportingTextElement: HTMLElement | null;
|
|
85
|
+
|
|
86
|
+
/** Sets supporting text content */
|
|
87
|
+
setSupportingText: (text: string, isError?: boolean) => SwitchComponent;
|
|
88
|
+
|
|
89
|
+
/** Removes supporting text */
|
|
90
|
+
removeSupportingText: () => SwitchComponent;
|
|
91
|
+
|
|
80
92
|
/** Adds event listener */
|
|
81
93
|
on: (event: string, handler: Function) => SwitchComponent;
|
|
82
94
|
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// src/components/textfield/api.ts
|
|
2
2
|
import { BaseComponent, TextfieldComponent, ApiOptions } from './types';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Enhances textfield component with API methods
|
|
6
|
+
* @param {ApiOptions} options - API configuration
|
|
7
|
+
* @returns {Function} Higher-order function that adds API methods to component
|
|
8
|
+
*/
|
|
4
9
|
/**
|
|
5
10
|
* Enhances textfield component with API methods
|
|
6
11
|
* @param {ApiOptions} options - API configuration
|
|
@@ -43,6 +48,54 @@ export const withAPI = ({ disabled, lifecycle }: ApiOptions) =>
|
|
|
43
48
|
getLabel(): string {
|
|
44
49
|
return component.label?.getText() || '';
|
|
45
50
|
},
|
|
51
|
+
|
|
52
|
+
// Leading icon management (if present)
|
|
53
|
+
leadingIcon: component.leadingIcon || null,
|
|
54
|
+
setLeadingIcon(html: string): TextfieldComponent {
|
|
55
|
+
if (component.setLeadingIcon) {
|
|
56
|
+
component.setLeadingIcon(html);
|
|
57
|
+
}
|
|
58
|
+
return this;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
removeLeadingIcon(): TextfieldComponent {
|
|
62
|
+
if (component.removeLeadingIcon) {
|
|
63
|
+
component.removeLeadingIcon();
|
|
64
|
+
}
|
|
65
|
+
return this;
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
// Trailing icon management (if present)
|
|
69
|
+
trailingIcon: component.trailingIcon || null,
|
|
70
|
+
setTrailingIcon(html: string): TextfieldComponent {
|
|
71
|
+
if (component.setTrailingIcon) {
|
|
72
|
+
component.setTrailingIcon(html);
|
|
73
|
+
}
|
|
74
|
+
return this;
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
removeTrailingIcon(): TextfieldComponent {
|
|
78
|
+
if (component.removeTrailingIcon) {
|
|
79
|
+
component.removeTrailingIcon();
|
|
80
|
+
}
|
|
81
|
+
return this;
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
// Supporting text management (if present)
|
|
85
|
+
supportingTextElement: component.supportingTextElement || null,
|
|
86
|
+
setSupportingText(text: string, isError?: boolean): TextfieldComponent {
|
|
87
|
+
if (component.setSupportingText) {
|
|
88
|
+
component.setSupportingText(text, isError);
|
|
89
|
+
}
|
|
90
|
+
return this;
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
removeSupportingText(): TextfieldComponent {
|
|
94
|
+
if (component.removeSupportingText) {
|
|
95
|
+
component.removeSupportingText();
|
|
96
|
+
}
|
|
97
|
+
return this;
|
|
98
|
+
},
|
|
46
99
|
|
|
47
100
|
// Event handling
|
|
48
101
|
on(event: string, handler: Function): TextfieldComponent {
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
// src/components/textfield/features.ts
|
|
2
|
+
import { BaseComponent, ElementComponent } from '../../core/compose/component';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for leading icon feature
|
|
6
|
+
*/
|
|
7
|
+
export interface LeadingIconConfig {
|
|
8
|
+
/**
|
|
9
|
+
* Leading icon HTML content
|
|
10
|
+
*/
|
|
11
|
+
leadingIcon?: string;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* CSS class prefix
|
|
15
|
+
*/
|
|
16
|
+
prefix?: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Component name
|
|
20
|
+
*/
|
|
21
|
+
componentName?: string;
|
|
22
|
+
|
|
23
|
+
[key: string]: any;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Configuration for trailing icon feature
|
|
28
|
+
*/
|
|
29
|
+
export interface TrailingIconConfig {
|
|
30
|
+
/**
|
|
31
|
+
* Trailing icon HTML content
|
|
32
|
+
*/
|
|
33
|
+
trailingIcon?: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* CSS class prefix
|
|
37
|
+
*/
|
|
38
|
+
prefix?: string;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Component name
|
|
42
|
+
*/
|
|
43
|
+
componentName?: string;
|
|
44
|
+
|
|
45
|
+
[key: string]: any;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Configuration for supporting text feature
|
|
50
|
+
*/
|
|
51
|
+
export interface SupportingTextConfig {
|
|
52
|
+
/**
|
|
53
|
+
* Supporting text content
|
|
54
|
+
*/
|
|
55
|
+
supportingText?: string;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Whether supporting text indicates an error
|
|
59
|
+
*/
|
|
60
|
+
error?: boolean;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* CSS class prefix
|
|
64
|
+
*/
|
|
65
|
+
prefix?: string;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Component name
|
|
69
|
+
*/
|
|
70
|
+
componentName?: string;
|
|
71
|
+
|
|
72
|
+
[key: string]: any;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Component with leading icon capabilities
|
|
77
|
+
*/
|
|
78
|
+
export interface LeadingIconComponent extends BaseComponent {
|
|
79
|
+
/**
|
|
80
|
+
* Leading icon element
|
|
81
|
+
*/
|
|
82
|
+
leadingIcon: HTMLElement | null;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Sets leading icon content
|
|
86
|
+
* @param html - HTML content for the icon
|
|
87
|
+
* @returns Component instance for chaining
|
|
88
|
+
*/
|
|
89
|
+
setLeadingIcon: (html: string) => LeadingIconComponent;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Removes leading icon
|
|
93
|
+
* @returns Component instance for chaining
|
|
94
|
+
*/
|
|
95
|
+
removeLeadingIcon: () => LeadingIconComponent;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Component with trailing icon capabilities
|
|
100
|
+
*/
|
|
101
|
+
export interface TrailingIconComponent extends BaseComponent {
|
|
102
|
+
/**
|
|
103
|
+
* Trailing icon element
|
|
104
|
+
*/
|
|
105
|
+
trailingIcon: HTMLElement | null;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Sets trailing icon content
|
|
109
|
+
* @param html - HTML content for the icon
|
|
110
|
+
* @returns Component instance for chaining
|
|
111
|
+
*/
|
|
112
|
+
setTrailingIcon: (html: string) => TrailingIconComponent;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Removes trailing icon
|
|
116
|
+
* @returns Component instance for chaining
|
|
117
|
+
*/
|
|
118
|
+
removeTrailingIcon: () => TrailingIconComponent;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Component with supporting text capabilities
|
|
123
|
+
*/
|
|
124
|
+
export interface SupportingTextComponent extends BaseComponent {
|
|
125
|
+
/**
|
|
126
|
+
* Supporting text element
|
|
127
|
+
*/
|
|
128
|
+
supportingTextElement: HTMLElement | null;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Sets supporting text content
|
|
132
|
+
* @param text - Text content
|
|
133
|
+
* @param isError - Whether text represents an error
|
|
134
|
+
* @returns Component instance for chaining
|
|
135
|
+
*/
|
|
136
|
+
setSupportingText: (text: string, isError?: boolean) => SupportingTextComponent;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Removes supporting text
|
|
140
|
+
* @returns Component instance for chaining
|
|
141
|
+
*/
|
|
142
|
+
removeSupportingText: () => SupportingTextComponent;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Creates and manages a leading icon for a component
|
|
147
|
+
* @param config - Configuration object with leading icon settings
|
|
148
|
+
* @returns Function that enhances a component with leading icon functionality
|
|
149
|
+
*/
|
|
150
|
+
export const withLeadingIcon = <T extends LeadingIconConfig>(config: T) =>
|
|
151
|
+
<C extends ElementComponent>(component: C): C & LeadingIconComponent => {
|
|
152
|
+
if (!config.leadingIcon) {
|
|
153
|
+
return component as C & LeadingIconComponent;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Create icon element
|
|
157
|
+
const PREFIX = config.prefix || 'mtrl';
|
|
158
|
+
const iconElement = document.createElement('span');
|
|
159
|
+
iconElement.className = `${PREFIX}-${config.componentName || 'textfield'}-leading-icon`;
|
|
160
|
+
iconElement.innerHTML = config.leadingIcon;
|
|
161
|
+
|
|
162
|
+
// Add leading icon to the component
|
|
163
|
+
component.element.appendChild(iconElement);
|
|
164
|
+
|
|
165
|
+
// Add leading-icon class to the component
|
|
166
|
+
component.element.classList.add(`${PREFIX}-${config.componentName || 'textfield'}--with-leading-icon`);
|
|
167
|
+
|
|
168
|
+
// When there's a leading icon, adjust input padding
|
|
169
|
+
if (component.input) {
|
|
170
|
+
component.input.classList.add(`${PREFIX}-${config.componentName || 'textfield'}-input--with-leading-icon`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Add lifecycle integration if available
|
|
174
|
+
if ('lifecycle' in component && component.lifecycle?.destroy) {
|
|
175
|
+
const originalDestroy = component.lifecycle.destroy;
|
|
176
|
+
component.lifecycle.destroy = () => {
|
|
177
|
+
iconElement.remove();
|
|
178
|
+
originalDestroy.call(component.lifecycle);
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
...component,
|
|
184
|
+
leadingIcon: iconElement,
|
|
185
|
+
|
|
186
|
+
setLeadingIcon(html: string) {
|
|
187
|
+
iconElement.innerHTML = html;
|
|
188
|
+
return this;
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
removeLeadingIcon() {
|
|
192
|
+
if (iconElement.parentNode) {
|
|
193
|
+
iconElement.remove();
|
|
194
|
+
component.element.classList.remove(`${PREFIX}-${config.componentName || 'textfield'}--with-leading-icon`);
|
|
195
|
+
if (component.input) {
|
|
196
|
+
component.input.classList.remove(`${PREFIX}-${config.componentName || 'textfield'}-input--with-leading-icon`);
|
|
197
|
+
}
|
|
198
|
+
this.leadingIcon = null;
|
|
199
|
+
}
|
|
200
|
+
return this;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Creates and manages a trailing icon for a component
|
|
207
|
+
* @param config - Configuration object with trailing icon settings
|
|
208
|
+
* @returns Function that enhances a component with trailing icon functionality
|
|
209
|
+
*/
|
|
210
|
+
export const withTrailingIcon = <T extends TrailingIconConfig>(config: T) =>
|
|
211
|
+
<C extends ElementComponent>(component: C): C & TrailingIconComponent => {
|
|
212
|
+
if (!config.trailingIcon) {
|
|
213
|
+
return component as C & TrailingIconComponent;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Create icon element
|
|
217
|
+
const PREFIX = config.prefix || 'mtrl';
|
|
218
|
+
const iconElement = document.createElement('span');
|
|
219
|
+
iconElement.className = `${PREFIX}-${config.componentName || 'textfield'}-trailing-icon`;
|
|
220
|
+
iconElement.innerHTML = config.trailingIcon;
|
|
221
|
+
|
|
222
|
+
// Add trailing icon to the component
|
|
223
|
+
component.element.appendChild(iconElement);
|
|
224
|
+
|
|
225
|
+
// Add trailing-icon class to the component
|
|
226
|
+
component.element.classList.add(`${PREFIX}-${config.componentName || 'textfield'}--with-trailing-icon`);
|
|
227
|
+
|
|
228
|
+
// When there's a trailing icon, adjust input padding
|
|
229
|
+
if (component.input) {
|
|
230
|
+
component.input.classList.add(`${PREFIX}-${config.componentName || 'textfield'}-input--with-trailing-icon`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Add lifecycle integration if available
|
|
234
|
+
if ('lifecycle' in component && component.lifecycle?.destroy) {
|
|
235
|
+
const originalDestroy = component.lifecycle.destroy;
|
|
236
|
+
component.lifecycle.destroy = () => {
|
|
237
|
+
iconElement.remove();
|
|
238
|
+
originalDestroy.call(component.lifecycle);
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
...component,
|
|
244
|
+
trailingIcon: iconElement,
|
|
245
|
+
|
|
246
|
+
setTrailingIcon(html: string) {
|
|
247
|
+
iconElement.innerHTML = html;
|
|
248
|
+
return this;
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
removeTrailingIcon() {
|
|
252
|
+
if (iconElement.parentNode) {
|
|
253
|
+
iconElement.remove();
|
|
254
|
+
component.element.classList.remove(`${PREFIX}-${config.componentName || 'textfield'}--with-trailing-icon`);
|
|
255
|
+
if (component.input) {
|
|
256
|
+
component.input.classList.remove(`${PREFIX}-${config.componentName || 'textfield'}-input--with-trailing-icon`);
|
|
257
|
+
}
|
|
258
|
+
this.trailingIcon = null;
|
|
259
|
+
}
|
|
260
|
+
return this;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Creates and manages supporting text for a component
|
|
267
|
+
* @param config - Configuration object with supporting text settings
|
|
268
|
+
* @returns Function that enhances a component with supporting text functionality
|
|
269
|
+
*/
|
|
270
|
+
export const withSupportingText = <T extends SupportingTextConfig>(config: T) =>
|
|
271
|
+
<C extends ElementComponent>(component: C): C & SupportingTextComponent => {
|
|
272
|
+
if (!config.supportingText) {
|
|
273
|
+
return component as C & SupportingTextComponent;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Create supporting text element
|
|
277
|
+
const PREFIX = config.prefix || 'mtrl';
|
|
278
|
+
const supportingElement = document.createElement('div');
|
|
279
|
+
supportingElement.className = `${PREFIX}-${config.componentName || 'textfield'}-helper`;
|
|
280
|
+
supportingElement.textContent = config.supportingText;
|
|
281
|
+
|
|
282
|
+
if (config.error) {
|
|
283
|
+
supportingElement.classList.add(`${PREFIX}-${config.componentName || 'textfield'}-helper--error`);
|
|
284
|
+
component.element.classList.add(`${PREFIX}-${config.componentName || 'textfield'}--error`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Add supporting text to the component
|
|
288
|
+
component.element.appendChild(supportingElement);
|
|
289
|
+
|
|
290
|
+
// Add lifecycle integration if available
|
|
291
|
+
if ('lifecycle' in component && component.lifecycle?.destroy) {
|
|
292
|
+
const originalDestroy = component.lifecycle.destroy;
|
|
293
|
+
component.lifecycle.destroy = () => {
|
|
294
|
+
supportingElement.remove();
|
|
295
|
+
originalDestroy.call(component.lifecycle);
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
...component,
|
|
301
|
+
supportingTextElement: supportingElement,
|
|
302
|
+
|
|
303
|
+
setSupportingText(text: string, isError = false) {
|
|
304
|
+
supportingElement.textContent = text;
|
|
305
|
+
|
|
306
|
+
// Handle error state
|
|
307
|
+
supportingElement.classList.toggle(`${PREFIX}-${config.componentName || 'textfield'}-helper--error`, isError);
|
|
308
|
+
component.element.classList.toggle(`${PREFIX}-${config.componentName || 'textfield'}--error`, isError);
|
|
309
|
+
|
|
310
|
+
return this;
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
removeSupportingText() {
|
|
314
|
+
if (supportingElement.parentNode) {
|
|
315
|
+
supportingElement.remove();
|
|
316
|
+
this.supportingTextElement = null;
|
|
317
|
+
component.element.classList.remove(`${PREFIX}-${config.componentName || 'textfield'}--error`);
|
|
318
|
+
}
|
|
319
|
+
return this;
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
};
|
|
@@ -10,6 +10,11 @@ import {
|
|
|
10
10
|
withTextLabel
|
|
11
11
|
} from '../../core/compose/features';
|
|
12
12
|
import { withAPI } from './api';
|
|
13
|
+
import {
|
|
14
|
+
withLeadingIcon,
|
|
15
|
+
withTrailingIcon,
|
|
16
|
+
withSupportingText
|
|
17
|
+
} from './features';
|
|
13
18
|
import { TextfieldConfig, TextfieldComponent } from './types';
|
|
14
19
|
import {
|
|
15
20
|
createBaseConfig,
|
|
@@ -33,6 +38,9 @@ const createTextfield = (config: TextfieldConfig = {}): TextfieldComponent => {
|
|
|
33
38
|
withVariant(baseConfig),
|
|
34
39
|
withTextInput(baseConfig),
|
|
35
40
|
withTextLabel(baseConfig),
|
|
41
|
+
withLeadingIcon(baseConfig),
|
|
42
|
+
withTrailingIcon(baseConfig),
|
|
43
|
+
withSupportingText(baseConfig),
|
|
36
44
|
withDisabled(baseConfig),
|
|
37
45
|
withLifecycle(),
|
|
38
46
|
comp => withAPI(getApiConfig(comp))(comp)
|
|
@@ -31,9 +31,6 @@ export interface TextfieldConfig {
|
|
|
31
31
|
/** Label text */
|
|
32
32
|
label?: string;
|
|
33
33
|
|
|
34
|
-
/** Placeholder text */
|
|
35
|
-
placeholder?: string;
|
|
36
|
-
|
|
37
34
|
/** Initial value */
|
|
38
35
|
value?: string;
|
|
39
36
|
|
|
@@ -52,6 +49,18 @@ export interface TextfieldConfig {
|
|
|
52
49
|
/** Autocomplete attribute */
|
|
53
50
|
autocomplete?: string;
|
|
54
51
|
|
|
52
|
+
/** Leading icon HTML content */
|
|
53
|
+
leadingIcon?: string;
|
|
54
|
+
|
|
55
|
+
/** Trailing icon HTML content */
|
|
56
|
+
trailingIcon?: string;
|
|
57
|
+
|
|
58
|
+
/** Supporting text content */
|
|
59
|
+
supportingText?: string;
|
|
60
|
+
|
|
61
|
+
/** Whether supporting text indicates an error */
|
|
62
|
+
error?: boolean;
|
|
63
|
+
|
|
55
64
|
/** Additional CSS classes */
|
|
56
65
|
class?: string;
|
|
57
66
|
|
|
@@ -48,12 +48,9 @@ const CLOCK_CONSTANTS = {
|
|
|
48
48
|
function getThemeColors(prefix: string): ThemeColors {
|
|
49
49
|
const root = document.documentElement;
|
|
50
50
|
const styles = getComputedStyle(root);
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
// Extract primary color
|
|
53
53
|
const primaryColor = styles.getPropertyValue(`--${prefix}-sys-color-primary`).trim() || '#6750A4';
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
console.log('primaryColor', primaryColor)
|
|
57
54
|
|
|
58
55
|
// Extract on-primary color
|
|
59
56
|
const onPrimaryColor = styles.getPropertyValue(`--${prefix}-sys-color-on-primary`).trim() || '#FFFFFF';
|
|
@@ -112,14 +112,14 @@ export interface TextInputComponent extends ElementComponent {
|
|
|
112
112
|
*/
|
|
113
113
|
export const withTextInput = <T extends TextInputConfig>(config: T = {} as T) =>
|
|
114
114
|
<C extends ElementComponent>(component: C): C & TextInputComponent => {
|
|
115
|
-
const
|
|
115
|
+
const isMultiline = config.multiline || config.type === 'multiline';
|
|
116
|
+
const input = document.createElement(isMultiline ? 'textarea' : 'input') as
|
|
116
117
|
HTMLInputElement | HTMLTextAreaElement;
|
|
117
118
|
|
|
118
119
|
input.className = `${component.getClass('textfield')}-input`;
|
|
119
120
|
|
|
120
121
|
// Set input attributes
|
|
121
122
|
const attributes: Record<string, string | number | boolean | undefined> = {
|
|
122
|
-
type: config.multiline ? undefined : (config.type || 'text'),
|
|
123
123
|
name: config.name,
|
|
124
124
|
required: config.required,
|
|
125
125
|
disabled: config.disabled,
|
|
@@ -129,6 +129,14 @@ export const withTextInput = <T extends TextInputConfig>(config: T = {} as T) =>
|
|
|
129
129
|
value: config.value || ''
|
|
130
130
|
};
|
|
131
131
|
|
|
132
|
+
// Only set type attribute for input elements, not for textarea
|
|
133
|
+
if (!isMultiline) {
|
|
134
|
+
attributes.type = config.type || 'text';
|
|
135
|
+
} else {
|
|
136
|
+
// For textarea, add a data attribute to identify it as multiline
|
|
137
|
+
attributes['data-type'] = 'multiline';
|
|
138
|
+
}
|
|
139
|
+
|
|
132
140
|
Object.entries(attributes).forEach(([key, value]) => {
|
|
133
141
|
if (value !== null && value !== undefined) {
|
|
134
142
|
if (typeof value === 'boolean') {
|
|
@@ -188,6 +196,11 @@ export const withTextInput = <T extends TextInputConfig>(config: T = {} as T) =>
|
|
|
188
196
|
// Initial state
|
|
189
197
|
updateInputState();
|
|
190
198
|
|
|
199
|
+
// Add multiline class to the component if it's a textarea
|
|
200
|
+
if (isMultiline) {
|
|
201
|
+
component.element.classList.add(`${component.getClass('textfield')}--multiline`);
|
|
202
|
+
}
|
|
203
|
+
|
|
191
204
|
component.element.appendChild(input);
|
|
192
205
|
|
|
193
206
|
// Cleanup
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// src/core/composition/features/dom.ts
|
|
2
|
+
import createLayout from '../../layout';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates DOM elements from component schema using the core createLayout utility
|
|
6
|
+
* This is a key feature that bridges the gap between declarative schema and actual DOM
|
|
7
|
+
*
|
|
8
|
+
* @returns Component enhancer that creates DOM structure from schema
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* // Materialize component schema into DOM
|
|
13
|
+
* const component = pipe(
|
|
14
|
+
* createBase,
|
|
15
|
+
* withLayout(config),
|
|
16
|
+
* withIcon(config),
|
|
17
|
+
* withLabel(config),
|
|
18
|
+
* withDom()
|
|
19
|
+
* )(config);
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export const withDom = () => component => {
|
|
23
|
+
// Return unmodified component if no schema
|
|
24
|
+
if (!component.schema) {
|
|
25
|
+
return component;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// Use the createLayout function to build the DOM from schema
|
|
30
|
+
const layout = createLayout(component.schema);
|
|
31
|
+
|
|
32
|
+
// Use the layout's utility functions to get components
|
|
33
|
+
const components = layout.getAll();
|
|
34
|
+
|
|
35
|
+
// Return enhanced component with DOM layout
|
|
36
|
+
return {
|
|
37
|
+
...component,
|
|
38
|
+
element: layout.element, // Root element
|
|
39
|
+
components // All component elements
|
|
40
|
+
};
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error('Failed to create DOM structure:', error);
|
|
43
|
+
throw new Error(`Failed to create component DOM: ${error.message}`);
|
|
44
|
+
}
|
|
45
|
+
};
|