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,247 @@
|
|
|
1
|
+
// src/core/compose/component.ts
|
|
2
|
+
/**
|
|
3
|
+
* @module core/compose/component
|
|
4
|
+
* @description Core utilities for component composition and creation with built-in mobile support
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createElement, CreateElementOptions } from '../dom/create';
|
|
8
|
+
import {
|
|
9
|
+
normalizeEvent,
|
|
10
|
+
hasTouchSupport,
|
|
11
|
+
TOUCH_CONFIG,
|
|
12
|
+
PASSIVE_EVENTS
|
|
13
|
+
} from '../utils/mobile';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Touch state interface to track touch interactions
|
|
17
|
+
*/
|
|
18
|
+
export interface TouchState {
|
|
19
|
+
startTime: number;
|
|
20
|
+
startPosition: { x: number; y: number };
|
|
21
|
+
isTouching: boolean;
|
|
22
|
+
activeTarget: EventTarget | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Base component interface with prefix utilities
|
|
27
|
+
*/
|
|
28
|
+
export interface BaseComponent {
|
|
29
|
+
config: Record<string, any>;
|
|
30
|
+
componentName?: string;
|
|
31
|
+
getClass: (name: string) => string;
|
|
32
|
+
getModifierClass: (base: string, modifier: string) => string;
|
|
33
|
+
getElementClass: (base: string, element: string) => string;
|
|
34
|
+
touchState: TouchState;
|
|
35
|
+
updateTouchState: (event: Event, status: 'start' | 'end') => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Element component extends base with element
|
|
40
|
+
*/
|
|
41
|
+
export interface ElementComponent extends BaseComponent {
|
|
42
|
+
element: HTMLElement;
|
|
43
|
+
addClass: (...classes: string[]) => ElementComponent;
|
|
44
|
+
destroy: () => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Options for withElement enhancer
|
|
49
|
+
*/
|
|
50
|
+
export interface WithElementOptions {
|
|
51
|
+
tag?: string;
|
|
52
|
+
componentName?: string;
|
|
53
|
+
attrs?: Record<string, any>;
|
|
54
|
+
className?: string | string[];
|
|
55
|
+
forwardEvents?: Record<string, boolean | ((component: any, event: Event) => boolean)>;
|
|
56
|
+
interactive?: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Creates helper functions for managing CSS class names with a prefix
|
|
61
|
+
* @param {string} prefix - Prefix to apply to class names
|
|
62
|
+
* @returns {Object} Class name utilities
|
|
63
|
+
*/
|
|
64
|
+
const withPrefix = (prefix: string) => ({
|
|
65
|
+
/**
|
|
66
|
+
* Gets a prefixed class name
|
|
67
|
+
* @param {string} name - Base class name
|
|
68
|
+
* @returns {string} Prefixed class name
|
|
69
|
+
*/
|
|
70
|
+
getClass: (name: string): string => `${prefix}-${name}`,
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Gets a prefixed modifier class name
|
|
74
|
+
* @param {string} base - Base class name
|
|
75
|
+
* @param {string} modifier - Modifier name
|
|
76
|
+
* @returns {string} Prefixed modifier class
|
|
77
|
+
*/
|
|
78
|
+
getModifierClass: (base: string, modifier: string): string => `${base}--${modifier}`,
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Gets a prefixed element class name
|
|
82
|
+
* @param {string} base - Base class name
|
|
83
|
+
* @param {string} element - Element name
|
|
84
|
+
* @returns {string} Prefixed element class
|
|
85
|
+
*/
|
|
86
|
+
getElementClass: (base: string, element: string): string => `${base}-${element}`
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Creates a base component with configuration and prefix utilities.
|
|
91
|
+
* This forms the foundation for all components in the system.
|
|
92
|
+
*
|
|
93
|
+
* @param {Object} config - Component configuration
|
|
94
|
+
* @returns {BaseComponent} Base component with prefix utilities
|
|
95
|
+
*/
|
|
96
|
+
export const createBase = (config: Record<string, any> = {}): BaseComponent => ({
|
|
97
|
+
config,
|
|
98
|
+
componentName: config.componentName,
|
|
99
|
+
...withPrefix(config.prefix || 'mtrl'),
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Manages the touch interaction state for the component.
|
|
103
|
+
*/
|
|
104
|
+
touchState: {
|
|
105
|
+
startTime: 0,
|
|
106
|
+
startPosition: { x: 0, y: 0 },
|
|
107
|
+
isTouching: false,
|
|
108
|
+
activeTarget: null
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Updates the component's touch state based on user interactions.
|
|
113
|
+
* Tracks touch position and timing for gesture recognition.
|
|
114
|
+
*/
|
|
115
|
+
updateTouchState(event: Event, status: 'start' | 'end'): void {
|
|
116
|
+
const normalized = normalizeEvent(event);
|
|
117
|
+
|
|
118
|
+
if (status === 'start') {
|
|
119
|
+
this.touchState = {
|
|
120
|
+
startTime: Date.now(),
|
|
121
|
+
startPosition: {
|
|
122
|
+
x: normalized.clientX,
|
|
123
|
+
y: normalized.clientY
|
|
124
|
+
},
|
|
125
|
+
isTouching: true,
|
|
126
|
+
activeTarget: normalized.target
|
|
127
|
+
};
|
|
128
|
+
} else if (status === 'end') {
|
|
129
|
+
this.touchState.isTouching = false;
|
|
130
|
+
this.touchState.activeTarget = null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Higher-order function that adds a DOM element to a component
|
|
137
|
+
* @param {WithElementOptions} options - Element creation options
|
|
138
|
+
* @returns {Function} Component enhancer
|
|
139
|
+
*/
|
|
140
|
+
export const withElement = (options: WithElementOptions = {}) =>
|
|
141
|
+
(base: BaseComponent): ElementComponent => {
|
|
142
|
+
/**
|
|
143
|
+
* Handles the start of a touch interaction.
|
|
144
|
+
*/
|
|
145
|
+
const handleTouchStart = (event: Event): void => {
|
|
146
|
+
base.updateTouchState(event, 'start');
|
|
147
|
+
element.classList.add(`${base.getClass('touch-active')}`);
|
|
148
|
+
|
|
149
|
+
if (options.forwardEvents?.touchstart && base.config?.emit) {
|
|
150
|
+
base.config.emit('touchstart', normalizeEvent(event));
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Handles the end of a touch interaction.
|
|
156
|
+
*/
|
|
157
|
+
const handleTouchEnd = (event: Event): void => {
|
|
158
|
+
if (!base.touchState.isTouching) return;
|
|
159
|
+
|
|
160
|
+
const touchDuration = Date.now() - base.touchState.startTime;
|
|
161
|
+
element.classList.remove(`${base.getClass('touch-active')}`);
|
|
162
|
+
base.updateTouchState(event, 'end');
|
|
163
|
+
|
|
164
|
+
// Emit tap event for short touches
|
|
165
|
+
if (touchDuration < TOUCH_CONFIG.TAP_THRESHOLD && base.config?.emit) {
|
|
166
|
+
base.config.emit('tap', normalizeEvent(event));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (options.forwardEvents?.touchend && base.config?.emit) {
|
|
170
|
+
base.config.emit('touchend', normalizeEvent(event));
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Handles touch movement.
|
|
176
|
+
*/
|
|
177
|
+
const handleTouchMove = (event: Event): void => {
|
|
178
|
+
if (!base.touchState.isTouching) return;
|
|
179
|
+
|
|
180
|
+
const normalized = normalizeEvent(event);
|
|
181
|
+
const deltaX = normalized.clientX - base.touchState.startPosition.x;
|
|
182
|
+
const deltaY = normalized.clientY - base.touchState.startPosition.y;
|
|
183
|
+
|
|
184
|
+
// Detect and emit swipe gestures
|
|
185
|
+
if (Math.abs(deltaX) > TOUCH_CONFIG.SWIPE_THRESHOLD && base.config?.emit) {
|
|
186
|
+
base.config.emit('swipe', {
|
|
187
|
+
direction: deltaX > 0 ? 'right' : 'left',
|
|
188
|
+
deltaX,
|
|
189
|
+
deltaY
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (options.forwardEvents?.touchmove && base.config?.emit) {
|
|
194
|
+
base.config.emit('touchmove', { ...normalized, deltaX, deltaY });
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// Create element options from component options
|
|
199
|
+
const elementOptions: CreateElementOptions = {
|
|
200
|
+
tag: options.tag || 'div',
|
|
201
|
+
className: [
|
|
202
|
+
base.getClass(options.componentName || base.componentName || 'component'),
|
|
203
|
+
hasTouchSupport() && options.interactive ? base.getClass('interactive') : null,
|
|
204
|
+
options.className
|
|
205
|
+
].filter(Boolean),
|
|
206
|
+
attrs: options.attrs || {},
|
|
207
|
+
context: base
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// Create the element with appropriate classes
|
|
211
|
+
const element = createElement(elementOptions);
|
|
212
|
+
|
|
213
|
+
// Add event listeners only if touch is supported and the component is interactive
|
|
214
|
+
if (hasTouchSupport() && options.interactive) {
|
|
215
|
+
element.addEventListener('touchstart', handleTouchStart, PASSIVE_EVENTS);
|
|
216
|
+
element.addEventListener('touchend', handleTouchEnd);
|
|
217
|
+
element.addEventListener('touchmove', handleTouchMove, PASSIVE_EVENTS);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
...base,
|
|
222
|
+
element,
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Adds CSS classes to the element
|
|
226
|
+
* @param {...string} classes - CSS classes to add
|
|
227
|
+
* @returns {ElementComponent} Component instance for chaining
|
|
228
|
+
*/
|
|
229
|
+
addClass(...classes: string[]): ElementComponent {
|
|
230
|
+
element.classList.add(...classes.filter(Boolean));
|
|
231
|
+
return this;
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Removes the element and cleans up event listeners.
|
|
236
|
+
* Ensures proper resource cleanup when the component is destroyed.
|
|
237
|
+
*/
|
|
238
|
+
destroy(): void {
|
|
239
|
+
if (hasTouchSupport() && options.interactive) {
|
|
240
|
+
element.removeEventListener('touchstart', handleTouchStart);
|
|
241
|
+
element.removeEventListener('touchend', handleTouchEnd);
|
|
242
|
+
element.removeEventListener('touchmove', handleTouchMove);
|
|
243
|
+
}
|
|
244
|
+
element.remove();
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// src/core/compose/features/checkable.ts
|
|
2
|
+
|
|
3
|
+
import { BaseComponent } from '../component';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for checkable feature
|
|
7
|
+
*/
|
|
8
|
+
export interface CheckableConfig {
|
|
9
|
+
checked?: boolean;
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Component with input element
|
|
15
|
+
*/
|
|
16
|
+
export interface InputComponent extends BaseComponent {
|
|
17
|
+
input: HTMLInputElement;
|
|
18
|
+
emit?: (event: string, data: any) => InputComponent;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Checkable state manager interface
|
|
23
|
+
*/
|
|
24
|
+
export interface CheckableManager {
|
|
25
|
+
/**
|
|
26
|
+
* Sets the checked state to true
|
|
27
|
+
* Emits change event if state changes
|
|
28
|
+
* @returns CheckableManager instance for chaining
|
|
29
|
+
*/
|
|
30
|
+
check: () => CheckableManager;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Sets the checked state to false
|
|
34
|
+
* Emits change event if state changes
|
|
35
|
+
* @returns CheckableManager instance for chaining
|
|
36
|
+
*/
|
|
37
|
+
uncheck: () => CheckableManager;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Toggles the current checked state
|
|
41
|
+
* Always emits change event
|
|
42
|
+
* @returns CheckableManager instance for chaining
|
|
43
|
+
*/
|
|
44
|
+
toggle: () => CheckableManager;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Gets the current checked state
|
|
48
|
+
* @returns Whether component is checked
|
|
49
|
+
*/
|
|
50
|
+
isChecked: () => boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Component with checkable capabilities
|
|
55
|
+
*/
|
|
56
|
+
export interface CheckableComponent extends BaseComponent {
|
|
57
|
+
checkable: CheckableManager;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Adds checked state management to a component with an input
|
|
62
|
+
* Manages visual state and event emission for checked changes
|
|
63
|
+
*
|
|
64
|
+
* @param config - Checkable configuration
|
|
65
|
+
* @returns Function that enhances a component with checkable functionality
|
|
66
|
+
*/
|
|
67
|
+
export const withCheckable = <T extends CheckableConfig>(config: T = {} as T) =>
|
|
68
|
+
<C extends InputComponent>(component: C): C & CheckableComponent => {
|
|
69
|
+
if (!component.input) return component as C & CheckableComponent;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Updates component classes to reflect checked state
|
|
73
|
+
*/
|
|
74
|
+
const updateStateClasses = (): void => {
|
|
75
|
+
component.element.classList.toggle(
|
|
76
|
+
`${component.getClass('switch')}--checked`,
|
|
77
|
+
component.input.checked
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Set initial state
|
|
82
|
+
if (config.checked) {
|
|
83
|
+
component.input.checked = true;
|
|
84
|
+
updateStateClasses();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Update classes whenever checked state changes
|
|
88
|
+
if (component.emit) {
|
|
89
|
+
component.on?.('change', updateStateClasses);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const checkable: CheckableManager = {
|
|
93
|
+
/**
|
|
94
|
+
* Sets the checked state to true
|
|
95
|
+
* Emits change event if state changes
|
|
96
|
+
* @returns CheckableManager instance for chaining
|
|
97
|
+
*/
|
|
98
|
+
check() {
|
|
99
|
+
if (!component.input.checked) {
|
|
100
|
+
component.input.checked = true;
|
|
101
|
+
updateStateClasses();
|
|
102
|
+
component.emit?.('change', {
|
|
103
|
+
checked: true,
|
|
104
|
+
value: component.input.value
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return this;
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Sets the checked state to false
|
|
112
|
+
* Emits change event if state changes
|
|
113
|
+
* @returns CheckableManager instance for chaining
|
|
114
|
+
*/
|
|
115
|
+
uncheck() {
|
|
116
|
+
if (component.input.checked) {
|
|
117
|
+
component.input.checked = false;
|
|
118
|
+
updateStateClasses();
|
|
119
|
+
component.emit?.('change', {
|
|
120
|
+
checked: false,
|
|
121
|
+
value: component.input.value
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return this;
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Toggles the current checked state
|
|
129
|
+
* Always emits change event
|
|
130
|
+
* @returns CheckableManager instance for chaining
|
|
131
|
+
*/
|
|
132
|
+
toggle() {
|
|
133
|
+
component.input.checked = !component.input.checked;
|
|
134
|
+
updateStateClasses();
|
|
135
|
+
component.emit?.('change', {
|
|
136
|
+
checked: component.input.checked,
|
|
137
|
+
value: component.input.value
|
|
138
|
+
});
|
|
139
|
+
return this;
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Gets the current checked state
|
|
144
|
+
* @returns Whether component is checked
|
|
145
|
+
*/
|
|
146
|
+
isChecked() {
|
|
147
|
+
return component.input.checked;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
...component,
|
|
153
|
+
checkable
|
|
154
|
+
};
|
|
155
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// src/core/compose/features/disabled.ts
|
|
2
|
+
|
|
3
|
+
import { BaseComponent, ElementComponent } from '../component';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for disabled feature
|
|
7
|
+
*/
|
|
8
|
+
export interface DisabledConfig {
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
componentName?: string;
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Disabled state manager interface
|
|
16
|
+
*/
|
|
17
|
+
export interface DisabledManager {
|
|
18
|
+
/**
|
|
19
|
+
* Enables the component
|
|
20
|
+
* @returns DisabledManager instance for chaining
|
|
21
|
+
*/
|
|
22
|
+
enable(): DisabledManager;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Disables the component
|
|
26
|
+
* @returns DisabledManager instance for chaining
|
|
27
|
+
*/
|
|
28
|
+
disable(): DisabledManager;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Toggles the disabled state
|
|
32
|
+
* @returns DisabledManager instance for chaining
|
|
33
|
+
*/
|
|
34
|
+
toggle(): DisabledManager;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Checks if the component is disabled
|
|
38
|
+
* @returns true if disabled
|
|
39
|
+
*/
|
|
40
|
+
isDisabled(): boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Component with disabled state capabilities
|
|
45
|
+
*/
|
|
46
|
+
export interface DisabledComponent extends BaseComponent {
|
|
47
|
+
disabled: DisabledManager;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Adds disabled state management to a component
|
|
52
|
+
*
|
|
53
|
+
* @param config - Configuration object
|
|
54
|
+
* @returns Function that enhances a component with disabled state management
|
|
55
|
+
*/
|
|
56
|
+
export const withDisabled = <T extends DisabledConfig>(config: T) =>
|
|
57
|
+
<C extends ElementComponent>(component: C): C & DisabledComponent => {
|
|
58
|
+
// Get the disabled class based on component name
|
|
59
|
+
const disabledClass = `${component.getClass(config.componentName || component.componentName || 'component')}--disabled`;
|
|
60
|
+
|
|
61
|
+
// Directly implement disabled functionality
|
|
62
|
+
const disabled: DisabledManager = {
|
|
63
|
+
enable() {
|
|
64
|
+
component.element.classList.remove(disabledClass);
|
|
65
|
+
if ('input' in component && component.input instanceof HTMLElement) {
|
|
66
|
+
component.input.disabled = false;
|
|
67
|
+
component.input.removeAttribute('disabled');
|
|
68
|
+
} else {
|
|
69
|
+
(component.element as HTMLButtonElement).disabled = false;
|
|
70
|
+
component.element.removeAttribute('disabled');
|
|
71
|
+
}
|
|
72
|
+
return this;
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
disable() {
|
|
76
|
+
component.element.classList.add(disabledClass);
|
|
77
|
+
if ('input' in component && component.input instanceof HTMLElement) {
|
|
78
|
+
component.input.disabled = true;
|
|
79
|
+
component.input.setAttribute('disabled', 'true');
|
|
80
|
+
} else {
|
|
81
|
+
(component.element as HTMLButtonElement).disabled = true;
|
|
82
|
+
component.element.setAttribute('disabled', 'true');
|
|
83
|
+
}
|
|
84
|
+
return this;
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
toggle() {
|
|
88
|
+
if (this.isDisabled()) {
|
|
89
|
+
this.enable();
|
|
90
|
+
} else {
|
|
91
|
+
this.disable();
|
|
92
|
+
}
|
|
93
|
+
return this;
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
isDisabled() {
|
|
97
|
+
if ('input' in component && component.input instanceof HTMLElement) {
|
|
98
|
+
return component.input.disabled;
|
|
99
|
+
}
|
|
100
|
+
return (component.element as HTMLButtonElement).disabled;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Initialize disabled state if configured
|
|
105
|
+
if (config.disabled) {
|
|
106
|
+
// Use requestAnimationFrame to ensure DOM is ready
|
|
107
|
+
requestAnimationFrame(() => {
|
|
108
|
+
disabled.disable();
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
...component,
|
|
114
|
+
disabled
|
|
115
|
+
};
|
|
116
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// src/core/compose/features/events.ts
|
|
2
|
+
/**
|
|
3
|
+
* @module core/compose/features
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createEmitter, Emitter } from '../../state/emitter';
|
|
7
|
+
import { BaseComponent } from '../component';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Component with event capabilities
|
|
11
|
+
*/
|
|
12
|
+
export interface EventComponent extends BaseComponent {
|
|
13
|
+
/**
|
|
14
|
+
* Subscribe to an event
|
|
15
|
+
* @param event - Event name
|
|
16
|
+
* @param handler - Event handler
|
|
17
|
+
* @returns Component instance for chaining
|
|
18
|
+
*/
|
|
19
|
+
on(event: string, handler: (...args: any[]) => void): EventComponent;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Unsubscribe from an event
|
|
23
|
+
* @param event - Event name
|
|
24
|
+
* @param handler - Event handler
|
|
25
|
+
* @returns Component instance for chaining
|
|
26
|
+
*/
|
|
27
|
+
off(event: string, handler: (...args: any[]) => void): EventComponent;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Emit an event
|
|
31
|
+
* @param event - Event name
|
|
32
|
+
* @param data - Event data
|
|
33
|
+
* @returns Component instance for chaining
|
|
34
|
+
*/
|
|
35
|
+
emit(event: string, data?: any): EventComponent;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Adds event handling capabilities to a component
|
|
40
|
+
* Returns event system ready to use immediately
|
|
41
|
+
*
|
|
42
|
+
* @returns Function that enhances a component with event capabilities
|
|
43
|
+
*/
|
|
44
|
+
export const withEvents = () =>
|
|
45
|
+
<T extends BaseComponent>(component: T): T & EventComponent => {
|
|
46
|
+
const emitter: Emitter = createEmitter();
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
...component,
|
|
50
|
+
on(event: string, handler: (...args: any[]) => void) {
|
|
51
|
+
emitter.on(event, handler);
|
|
52
|
+
return this;
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
off(event: string, handler: (...args: any[]) => void) {
|
|
56
|
+
emitter.off(event, handler);
|
|
57
|
+
return this;
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
emit(event: string, data?: any) {
|
|
61
|
+
emitter.emit(event, data);
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// src/core/compose/features/icon.ts
|
|
2
|
+
|
|
3
|
+
import { createIcon, IconManager } from '../../build/icon';
|
|
4
|
+
import { BaseComponent, ElementComponent } from '../component';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Configuration for icon feature
|
|
8
|
+
*/
|
|
9
|
+
export interface IconConfig {
|
|
10
|
+
icon?: string;
|
|
11
|
+
iconPosition?: 'start' | 'end';
|
|
12
|
+
iconSize?: string;
|
|
13
|
+
prefix?: string;
|
|
14
|
+
componentName?: string;
|
|
15
|
+
text?: string;
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Component with icon capabilities
|
|
21
|
+
*/
|
|
22
|
+
export interface IconComponent extends BaseComponent {
|
|
23
|
+
icon: IconManager;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Updates the component's circular style based on content
|
|
28
|
+
* Adds circular class if there's an icon but no text
|
|
29
|
+
*/
|
|
30
|
+
const updateCircularStyle = (component: ElementComponent, config: IconConfig): void => {
|
|
31
|
+
const hasText = config.text;
|
|
32
|
+
const hasIcon = config.icon;
|
|
33
|
+
|
|
34
|
+
const circularClass = `${component.getClass('button')}--circular`;
|
|
35
|
+
if (!hasText && hasIcon) {
|
|
36
|
+
component.element.classList.add(circularClass);
|
|
37
|
+
} else {
|
|
38
|
+
component.element.classList.remove(circularClass);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Adds icon management to a component
|
|
44
|
+
*
|
|
45
|
+
* @param config - Configuration object containing icon information
|
|
46
|
+
* @returns Function that enhances a component with icon capabilities
|
|
47
|
+
*/
|
|
48
|
+
export const withIcon = <T extends IconConfig>(config: T) =>
|
|
49
|
+
<C extends ElementComponent>(component: C): C & IconComponent => {
|
|
50
|
+
const icon = createIcon(component.element, {
|
|
51
|
+
prefix: config.prefix,
|
|
52
|
+
type: config.componentName || 'component',
|
|
53
|
+
position: config.iconPosition,
|
|
54
|
+
iconSize: config.iconSize
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (config.icon) {
|
|
58
|
+
icon.setIcon(config.icon);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
updateCircularStyle(component, config);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
...component,
|
|
65
|
+
icon
|
|
66
|
+
};
|
|
67
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// src/core/compose/features/index.ts
|
|
2
|
+
|
|
3
|
+
// Core features
|
|
4
|
+
export { withEvents } from './events';
|
|
5
|
+
export { withText } from './text';
|
|
6
|
+
export { withIcon } from './icon';
|
|
7
|
+
export { withVariant } from './variant';
|
|
8
|
+
export { withSize } from './size';
|
|
9
|
+
export { withPosition } from './position';
|
|
10
|
+
export { withRipple } from './ripple';
|
|
11
|
+
export { withInput } from './input';
|
|
12
|
+
export { withCheckable } from './checkable';
|
|
13
|
+
export { withStyle } from './style';
|
|
14
|
+
export { withTextInput } from './textinput';
|
|
15
|
+
export { withTextLabel } from './textlabel';
|
|
16
|
+
export { withTrack } from './track';
|
|
17
|
+
export { withEvents as withEnhancedEvents } from './withEvents';
|
|
18
|
+
|
|
19
|
+
// State management features
|
|
20
|
+
export { withDisabled } from './disabled';
|
|
21
|
+
export { withLifecycle } from './lifecycle';
|
|
22
|
+
|
|
23
|
+
// Re-export interfaces for better developer experience
|
|
24
|
+
export type { EventComponent } from './events';
|
|
25
|
+
export type { TextComponent } from './text';
|
|
26
|
+
export type { IconComponent } from './icon';
|
|
27
|
+
export type { LifecycleComponent, Lifecycle } from './lifecycle';
|
|
28
|
+
export type { DisabledComponent, DisabledManager } from './disabled';
|
|
29
|
+
export type { RippleComponent } from './ripple';
|
|
30
|
+
export type { InputComponent } from './input';
|
|
31
|
+
export type { CheckableComponent, CheckableManager } from './checkable';
|
|
32
|
+
export type { TextInputComponent } from './textinput';
|
|
33
|
+
export type { LabelComponent, LabelManager } from './textlabel';
|
|
34
|
+
export type { TrackComponent } from './track';
|
|
35
|
+
export type { EnhancedEventComponent } from './withEvents';
|