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,48 @@
|
|
|
1
|
+
// src/components/card/content.ts
|
|
2
|
+
import { PREFIX } from '../../core/config';
|
|
3
|
+
import { pipe } from '../../core/compose';
|
|
4
|
+
import { createBase, withElement } from '../../core/compose/component';
|
|
5
|
+
import { CardContentConfig } from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates a card content component
|
|
9
|
+
* @param {CardContentConfig} config - Content configuration
|
|
10
|
+
* @returns {HTMLElement} Card content element
|
|
11
|
+
*/
|
|
12
|
+
export const createCardContent = (config: CardContentConfig = {}): HTMLElement => {
|
|
13
|
+
const baseConfig = {
|
|
14
|
+
...config,
|
|
15
|
+
componentName: 'card-content',
|
|
16
|
+
prefix: PREFIX
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const content = pipe(
|
|
21
|
+
createBase,
|
|
22
|
+
withElement({
|
|
23
|
+
tag: 'div',
|
|
24
|
+
componentName: 'card-content',
|
|
25
|
+
className: [
|
|
26
|
+
config.class,
|
|
27
|
+
config.padding === false ? `${PREFIX}-card-content--no-padding` : null
|
|
28
|
+
],
|
|
29
|
+
html: config.html,
|
|
30
|
+
text: config.text
|
|
31
|
+
})
|
|
32
|
+
)(baseConfig);
|
|
33
|
+
|
|
34
|
+
// Add children if provided
|
|
35
|
+
if (Array.isArray(config.children)) {
|
|
36
|
+
config.children.forEach(child => {
|
|
37
|
+
if (child instanceof HTMLElement) {
|
|
38
|
+
content.element.appendChild(child);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return content.element;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('Card content creation error:', error instanceof Error ? error.message : String(error));
|
|
46
|
+
throw new Error(`Failed to create card content: ${error instanceof Error ? error.message : String(error)}`);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// src/components/card/features.ts
|
|
2
|
+
import { PREFIX } from '../../core/config';
|
|
3
|
+
import { createElement } from '../../core/dom/create';
|
|
4
|
+
import { BaseComponent, CardComponent, LoadingFeature, ExpandableFeature, SwipeableFeature } from './types';
|
|
5
|
+
|
|
6
|
+
interface LoadingConfig {
|
|
7
|
+
initialState?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ExpandableConfig {
|
|
11
|
+
initialExpanded?: boolean;
|
|
12
|
+
expandableContent?: HTMLElement;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface SwipeableConfig {
|
|
16
|
+
onSwipeLeft?: (component: CardComponent) => void;
|
|
17
|
+
onSwipeRight?: (component: CardComponent) => void;
|
|
18
|
+
threshold?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Higher-order function to add loading state to a card
|
|
23
|
+
* @param {LoadingConfig} config - Loading state configuration
|
|
24
|
+
* @returns {Function} Card component enhancer
|
|
25
|
+
*/
|
|
26
|
+
export const withLoading = (config: LoadingConfig = {}) => (component: BaseComponent): BaseComponent & { loading: LoadingFeature } => {
|
|
27
|
+
const initialState = config.initialState || false;
|
|
28
|
+
let loadingElement: HTMLElement | null = null;
|
|
29
|
+
let isLoading = initialState;
|
|
30
|
+
|
|
31
|
+
function setLoading(loading: boolean): void {
|
|
32
|
+
isLoading = loading;
|
|
33
|
+
|
|
34
|
+
if (loading && !loadingElement) {
|
|
35
|
+
// Create and add loading overlay
|
|
36
|
+
loadingElement = createElement({
|
|
37
|
+
tag: 'div',
|
|
38
|
+
className: `${PREFIX}-card-loading-overlay`,
|
|
39
|
+
container: component.element
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Add spinner
|
|
43
|
+
createElement({
|
|
44
|
+
tag: 'div',
|
|
45
|
+
className: `${PREFIX}-card-loading-spinner`,
|
|
46
|
+
container: loadingElement
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
component.element.classList.add(`${PREFIX}-card--state-loading`);
|
|
50
|
+
} else if (!loading && loadingElement) {
|
|
51
|
+
// Remove loading overlay
|
|
52
|
+
loadingElement.remove();
|
|
53
|
+
loadingElement = null;
|
|
54
|
+
component.element.classList.remove(`${PREFIX}-card--state-loading`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (initialState) {
|
|
59
|
+
setLoading(true);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
...component,
|
|
64
|
+
loading: {
|
|
65
|
+
isLoading: () => isLoading,
|
|
66
|
+
setLoading
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Higher-order function to add expandable behavior to a card
|
|
73
|
+
* @param {ExpandableConfig} config - Expandable configuration
|
|
74
|
+
* @returns {Function} Card component enhancer
|
|
75
|
+
*/
|
|
76
|
+
export const withExpandable = (config: ExpandableConfig = {}) => (component: BaseComponent): BaseComponent & { expandable: ExpandableFeature } => {
|
|
77
|
+
const initialExpanded = config.initialExpanded || false;
|
|
78
|
+
let isExpanded = initialExpanded;
|
|
79
|
+
const expandableContent = config.expandableContent;
|
|
80
|
+
let expandButton: HTMLButtonElement;
|
|
81
|
+
|
|
82
|
+
// Create expand/collapse button
|
|
83
|
+
expandButton = createElement({
|
|
84
|
+
tag: 'button',
|
|
85
|
+
className: `${PREFIX}-card-expand-button`,
|
|
86
|
+
attrs: {
|
|
87
|
+
'aria-expanded': isExpanded ? 'true' : 'false',
|
|
88
|
+
'aria-label': isExpanded ? 'Collapse' : 'Expand'
|
|
89
|
+
}
|
|
90
|
+
}) as HTMLButtonElement;
|
|
91
|
+
|
|
92
|
+
// Add to card as action if not already present
|
|
93
|
+
const actionsContainer = component.element.querySelector(`.${PREFIX}-card-actions`);
|
|
94
|
+
if (actionsContainer) {
|
|
95
|
+
actionsContainer.appendChild(expandButton);
|
|
96
|
+
} else {
|
|
97
|
+
// Create actions container if not present
|
|
98
|
+
const newActionsContainer = createElement({
|
|
99
|
+
tag: 'div',
|
|
100
|
+
className: `${PREFIX}-card-actions`,
|
|
101
|
+
container: component.element
|
|
102
|
+
});
|
|
103
|
+
newActionsContainer.appendChild(expandButton);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Set initial state
|
|
107
|
+
if (expandableContent) {
|
|
108
|
+
expandableContent.classList.add(`${PREFIX}-card-expandable-content`);
|
|
109
|
+
if (!initialExpanded) {
|
|
110
|
+
expandableContent.style.display = 'none';
|
|
111
|
+
}
|
|
112
|
+
component.element.appendChild(expandableContent);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Set expanded state
|
|
116
|
+
function setExpanded(expanded: boolean): void {
|
|
117
|
+
isExpanded = expanded;
|
|
118
|
+
|
|
119
|
+
if (expandableContent) {
|
|
120
|
+
expandableContent.style.display = expanded ? 'block' : 'none';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
expandButton.setAttribute('aria-expanded', expanded ? 'true' : 'false');
|
|
124
|
+
expandButton.setAttribute('aria-label', expanded ? 'Collapse' : 'Expand');
|
|
125
|
+
|
|
126
|
+
if (expanded) {
|
|
127
|
+
component.element.classList.add(`${PREFIX}-card--expanded`);
|
|
128
|
+
} else {
|
|
129
|
+
component.element.classList.remove(`${PREFIX}-card--expanded`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
component.emit?.('expandedChanged', { expanded });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Toggle expanded state
|
|
136
|
+
function toggleExpanded(): void {
|
|
137
|
+
setExpanded(!isExpanded);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Add click handler to toggle button
|
|
141
|
+
expandButton.addEventListener('click', (e) => {
|
|
142
|
+
e.stopPropagation();
|
|
143
|
+
toggleExpanded();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
...component,
|
|
148
|
+
expandable: {
|
|
149
|
+
isExpanded: () => isExpanded,
|
|
150
|
+
setExpanded,
|
|
151
|
+
toggleExpanded
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Higher-order function to add swipeable behavior to a card
|
|
158
|
+
* @param {SwipeableConfig} config - Swipeable configuration
|
|
159
|
+
* @returns {Function} Card component enhancer
|
|
160
|
+
*/
|
|
161
|
+
export const withSwipeable = (config: SwipeableConfig = {}) => (component: BaseComponent): BaseComponent & { swipeable: SwipeableFeature } => {
|
|
162
|
+
const threshold = config.threshold || 100;
|
|
163
|
+
let startX = 0;
|
|
164
|
+
let currentX = 0;
|
|
165
|
+
|
|
166
|
+
function handleTouchStart(e: TouchEvent): void {
|
|
167
|
+
startX = e.touches[0].clientX;
|
|
168
|
+
component.element.style.transition = 'none';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function handleTouchMove(e: TouchEvent): void {
|
|
172
|
+
if (!startX) return;
|
|
173
|
+
|
|
174
|
+
currentX = e.touches[0].clientX;
|
|
175
|
+
const diffX = currentX - startX;
|
|
176
|
+
|
|
177
|
+
// Apply transform to move card
|
|
178
|
+
component.element.style.transform = `translateX(${diffX}px)`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function handleTouchEnd(): void {
|
|
182
|
+
if (!startX) return;
|
|
183
|
+
|
|
184
|
+
component.element.style.transition = 'transform 0.3s ease';
|
|
185
|
+
const diffX = currentX - startX;
|
|
186
|
+
|
|
187
|
+
if (Math.abs(diffX) >= threshold) {
|
|
188
|
+
// Swipe threshold reached
|
|
189
|
+
if (diffX > 0 && config.onSwipeRight) {
|
|
190
|
+
// Swipe right
|
|
191
|
+
component.element.style.transform = 'translateX(100%)';
|
|
192
|
+
config.onSwipeRight(component as CardComponent);
|
|
193
|
+
} else if (diffX < 0 && config.onSwipeLeft) {
|
|
194
|
+
// Swipe left
|
|
195
|
+
component.element.style.transform = 'translateX(-100%)';
|
|
196
|
+
config.onSwipeLeft(component as CardComponent);
|
|
197
|
+
} else {
|
|
198
|
+
// Reset if no handler
|
|
199
|
+
component.element.style.transform = 'translateX(0)';
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
// Reset if below threshold
|
|
203
|
+
component.element.style.transform = 'translateX(0)';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
startX = 0;
|
|
207
|
+
currentX = 0;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Add event listeners
|
|
211
|
+
component.element.addEventListener('touchstart', handleTouchStart as EventListener);
|
|
212
|
+
component.element.addEventListener('touchmove', handleTouchMove as EventListener);
|
|
213
|
+
component.element.addEventListener('touchend', handleTouchEnd);
|
|
214
|
+
|
|
215
|
+
// Add swipeable class
|
|
216
|
+
component.element.classList.add(`${PREFIX}-card--swipeable`);
|
|
217
|
+
|
|
218
|
+
// Return enhanced component
|
|
219
|
+
return {
|
|
220
|
+
...component,
|
|
221
|
+
swipeable: {
|
|
222
|
+
reset: () => {
|
|
223
|
+
component.element.style.transition = 'transform 0.3s ease';
|
|
224
|
+
component.element.style.transform = 'translateX(0)';
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// src/components/card/header.ts
|
|
2
|
+
import { PREFIX } from '../../core/config';
|
|
3
|
+
import { pipe } from '../../core/compose';
|
|
4
|
+
import { createBase, withElement } from '../../core/compose/component';
|
|
5
|
+
import { createElement } from '../../core/dom/create';
|
|
6
|
+
import { CardHeaderConfig } from './types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates a card header component
|
|
10
|
+
* @param {CardHeaderConfig} config - Header configuration
|
|
11
|
+
* @returns {HTMLElement} Card header element
|
|
12
|
+
*/
|
|
13
|
+
export const createCardHeader = (config: CardHeaderConfig = {}): HTMLElement => {
|
|
14
|
+
const baseConfig = {
|
|
15
|
+
...config,
|
|
16
|
+
componentName: 'card-header',
|
|
17
|
+
prefix: PREFIX
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const header = pipe(
|
|
22
|
+
createBase,
|
|
23
|
+
withElement({
|
|
24
|
+
tag: 'div',
|
|
25
|
+
componentName: 'card-header',
|
|
26
|
+
className: config.class
|
|
27
|
+
})
|
|
28
|
+
)(baseConfig);
|
|
29
|
+
|
|
30
|
+
// Create text container
|
|
31
|
+
const textContainer = createElement({
|
|
32
|
+
tag: 'div',
|
|
33
|
+
className: `${PREFIX}-card-header-text`,
|
|
34
|
+
container: header.element
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Add title if provided
|
|
38
|
+
if (config.title) {
|
|
39
|
+
createElement({
|
|
40
|
+
tag: 'h3',
|
|
41
|
+
className: `${PREFIX}-card-header-title`,
|
|
42
|
+
text: config.title,
|
|
43
|
+
container: textContainer
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Add subtitle if provided
|
|
48
|
+
if (config.subtitle) {
|
|
49
|
+
createElement({
|
|
50
|
+
tag: 'h4',
|
|
51
|
+
className: `${PREFIX}-card-header-subtitle`,
|
|
52
|
+
text: config.subtitle,
|
|
53
|
+
container: textContainer
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Add avatar if provided
|
|
58
|
+
if (config.avatar) {
|
|
59
|
+
const avatarElement = typeof config.avatar === 'string'
|
|
60
|
+
? createElement({
|
|
61
|
+
tag: 'div',
|
|
62
|
+
className: `${PREFIX}-card-header-avatar`,
|
|
63
|
+
html: config.avatar
|
|
64
|
+
})
|
|
65
|
+
: config.avatar;
|
|
66
|
+
|
|
67
|
+
header.element.insertBefore(avatarElement, header.element.firstChild);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Add action if provided
|
|
71
|
+
if (config.action) {
|
|
72
|
+
const actionElement = typeof config.action === 'string'
|
|
73
|
+
? createElement({
|
|
74
|
+
tag: 'div',
|
|
75
|
+
className: `${PREFIX}-card-header-action`,
|
|
76
|
+
html: config.action
|
|
77
|
+
})
|
|
78
|
+
: config.action;
|
|
79
|
+
|
|
80
|
+
header.element.appendChild(actionElement);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return header.element;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error('Card header creation error:', error instanceof Error ? error.message : String(error));
|
|
86
|
+
throw new Error(`Failed to create card header: ${error instanceof Error ? error.message : String(error)}`);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// src/components/card/index.ts
|
|
2
|
+
export { default } from './card'
|
|
3
|
+
export { createCardContent } from './content'
|
|
4
|
+
export { createCardHeader } from './header'
|
|
5
|
+
export { createCardActions } from './actions'
|
|
6
|
+
export { createCardMedia } from './media'
|
|
7
|
+
export {
|
|
8
|
+
CardVariant,
|
|
9
|
+
CardElevation,
|
|
10
|
+
CardSchema,
|
|
11
|
+
CardHeaderConfig,
|
|
12
|
+
CardContentConfig,
|
|
13
|
+
CardActionsConfig,
|
|
14
|
+
CardMediaConfig,
|
|
15
|
+
CardComponent
|
|
16
|
+
} from './types'
|
|
17
|
+
|
|
18
|
+
// Export constants for backward compatibility
|
|
19
|
+
export { CARD_VARIANTS, CARD_ELEVATIONS, CARD_SCHEMA } from './constants'
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/components/card/media.ts
|
|
2
|
+
import { PREFIX } from '../../core/config';
|
|
3
|
+
import { pipe } from '../../core/compose';
|
|
4
|
+
import { createBase, withElement } from '../../core/compose/component';
|
|
5
|
+
import { CardMediaConfig } from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates a card media component
|
|
9
|
+
* @param {CardMediaConfig} config - Media configuration
|
|
10
|
+
* @returns {HTMLElement} Card media element
|
|
11
|
+
*/
|
|
12
|
+
export const createCardMedia = (config: CardMediaConfig = {}): HTMLElement => {
|
|
13
|
+
const baseConfig = {
|
|
14
|
+
...config,
|
|
15
|
+
componentName: 'card-media',
|
|
16
|
+
prefix: PREFIX
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const media = pipe(
|
|
21
|
+
createBase,
|
|
22
|
+
withElement({
|
|
23
|
+
tag: 'div',
|
|
24
|
+
componentName: 'card-media',
|
|
25
|
+
className: [
|
|
26
|
+
config.class,
|
|
27
|
+
config.aspectRatio ? `${PREFIX}-card-media--${config.aspectRatio.replace(':', '-')}` : null,
|
|
28
|
+
config.contain ? `${PREFIX}-card-media--contain` : null
|
|
29
|
+
]
|
|
30
|
+
})
|
|
31
|
+
)(baseConfig);
|
|
32
|
+
|
|
33
|
+
// If custom element is provided, use it
|
|
34
|
+
if (config.element instanceof HTMLElement) {
|
|
35
|
+
media.element.appendChild(config.element);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Otherwise create an image if src is provided
|
|
39
|
+
else if (config.src) {
|
|
40
|
+
const img = document.createElement('img');
|
|
41
|
+
img.src = config.src;
|
|
42
|
+
if (config.alt) img.alt = config.alt;
|
|
43
|
+
img.className = `${PREFIX}-card-media-img`;
|
|
44
|
+
media.element.appendChild(img);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return media.element;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error('Card media creation error:', error instanceof Error ? error.message : String(error));
|
|
50
|
+
throw new Error(`Failed to create card media: ${error instanceof Error ? error.message : String(error)}`);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// src/components/card/types.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Card variant types following Material Design 3
|
|
5
|
+
*/
|
|
6
|
+
export enum CardVariant {
|
|
7
|
+
ELEVATED = 'elevated',
|
|
8
|
+
FILLED = 'filled',
|
|
9
|
+
OUTLINED = 'outlined'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Card elevation levels
|
|
14
|
+
*/
|
|
15
|
+
export enum CardElevation {
|
|
16
|
+
RESTING = 1,
|
|
17
|
+
HOVERED = 2,
|
|
18
|
+
DRAGGED = 4
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Interface for card configuration
|
|
23
|
+
*/
|
|
24
|
+
export interface CardSchema {
|
|
25
|
+
variant?: CardVariant;
|
|
26
|
+
interactive?: boolean;
|
|
27
|
+
fullWidth?: boolean;
|
|
28
|
+
clickable?: boolean;
|
|
29
|
+
draggable?: boolean;
|
|
30
|
+
class?: string;
|
|
31
|
+
headerConfig?: CardHeaderConfig;
|
|
32
|
+
contentConfig?: CardContentConfig;
|
|
33
|
+
actionsConfig?: CardActionsConfig;
|
|
34
|
+
mediaConfig?: CardMediaConfig;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Interface for card header configuration
|
|
39
|
+
*/
|
|
40
|
+
export interface CardHeaderConfig {
|
|
41
|
+
title?: string;
|
|
42
|
+
subtitle?: string;
|
|
43
|
+
avatar?: HTMLElement | string;
|
|
44
|
+
action?: HTMLElement | string;
|
|
45
|
+
class?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Interface for card content configuration
|
|
50
|
+
*/
|
|
51
|
+
export interface CardContentConfig {
|
|
52
|
+
text?: string;
|
|
53
|
+
html?: string;
|
|
54
|
+
children?: HTMLElement[];
|
|
55
|
+
padding?: boolean;
|
|
56
|
+
class?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Interface for card actions configuration
|
|
61
|
+
*/
|
|
62
|
+
export interface CardActionsConfig {
|
|
63
|
+
actions?: HTMLElement[];
|
|
64
|
+
fullBleed?: boolean;
|
|
65
|
+
vertical?: boolean;
|
|
66
|
+
align?: 'start' | 'center' | 'end' | 'space-between';
|
|
67
|
+
class?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Interface for card media configuration
|
|
72
|
+
*/
|
|
73
|
+
export interface CardMediaConfig {
|
|
74
|
+
src?: string;
|
|
75
|
+
alt?: string;
|
|
76
|
+
element?: HTMLElement;
|
|
77
|
+
aspectRatio?: '16:9' | '4:3' | '1:1' | string;
|
|
78
|
+
contain?: boolean;
|
|
79
|
+
class?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Base component interface
|
|
84
|
+
*/
|
|
85
|
+
export interface BaseComponent {
|
|
86
|
+
element: HTMLElement;
|
|
87
|
+
getClass: (name?: string) => string;
|
|
88
|
+
getModifierClass: (base: string, modifier: string) => string;
|
|
89
|
+
getElementClass: (base: string, element: string) => string;
|
|
90
|
+
addClass: (...classes: string[]) => BaseComponent;
|
|
91
|
+
emit?: (event: string, data?: any) => void;
|
|
92
|
+
config: CardComponentConfig;
|
|
93
|
+
touchState?: TouchState;
|
|
94
|
+
updateTouchState?: (event: TouchEvent | MouseEvent, status: 'start' | 'end') => void;
|
|
95
|
+
lifecycle?: ComponentLifecycle;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Touch state interface
|
|
100
|
+
*/
|
|
101
|
+
export interface TouchState {
|
|
102
|
+
startTime: number;
|
|
103
|
+
startPosition: { x: number; y: number };
|
|
104
|
+
isTouching: boolean;
|
|
105
|
+
activeTarget: EventTarget | null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Component lifecycle interface
|
|
110
|
+
*/
|
|
111
|
+
export interface ComponentLifecycle {
|
|
112
|
+
mount?: () => void;
|
|
113
|
+
update?: () => void;
|
|
114
|
+
destroy: () => void;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Card component configuration
|
|
119
|
+
*/
|
|
120
|
+
export interface CardComponentConfig extends CardSchema {
|
|
121
|
+
componentName: string;
|
|
122
|
+
prefix: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Card component interface
|
|
127
|
+
*/
|
|
128
|
+
export interface CardComponent extends BaseComponent {
|
|
129
|
+
// Card-specific methods
|
|
130
|
+
addContent: (contentElement: HTMLElement) => CardComponent;
|
|
131
|
+
setHeader: (headerElement: HTMLElement) => CardComponent;
|
|
132
|
+
addMedia: (mediaElement: HTMLElement, position?: 'top' | 'bottom') => CardComponent;
|
|
133
|
+
setActions: (actionsElement: HTMLElement) => CardComponent;
|
|
134
|
+
makeDraggable: (dragStartCallback?: (event: DragEvent) => void) => CardComponent;
|
|
135
|
+
destroy: () => void;
|
|
136
|
+
|
|
137
|
+
// Optional feature interfaces
|
|
138
|
+
loading?: LoadingFeature;
|
|
139
|
+
expandable?: ExpandableFeature;
|
|
140
|
+
swipeable?: SwipeableFeature;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Loading feature interface
|
|
145
|
+
*/
|
|
146
|
+
export interface LoadingFeature {
|
|
147
|
+
isLoading: () => boolean;
|
|
148
|
+
setLoading: (loading: boolean) => void;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Expandable feature interface
|
|
153
|
+
*/
|
|
154
|
+
export interface ExpandableFeature {
|
|
155
|
+
isExpanded: () => boolean;
|
|
156
|
+
setExpanded: (expanded: boolean) => void;
|
|
157
|
+
toggleExpanded: () => void;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Swipeable feature interface
|
|
162
|
+
*/
|
|
163
|
+
export interface SwipeableFeature {
|
|
164
|
+
reset: () => void;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* API options interface
|
|
169
|
+
*/
|
|
170
|
+
export interface ApiOptions {
|
|
171
|
+
lifecycle: {
|
|
172
|
+
destroy: () => void;
|
|
173
|
+
};
|
|
174
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// src/components/checkbox/api.ts
|
|
2
|
+
import { BaseComponent, CheckboxComponent, ApiOptions } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Enhances checkbox component with API methods
|
|
6
|
+
* @param {ApiOptions} options - API configuration
|
|
7
|
+
* @returns {Function} Higher-order function that adds API methods to component
|
|
8
|
+
*/
|
|
9
|
+
export const withAPI = ({ disabled, lifecycle, checkable }: ApiOptions) =>
|
|
10
|
+
(component: BaseComponent): CheckboxComponent => ({
|
|
11
|
+
...component as any,
|
|
12
|
+
element: component.element,
|
|
13
|
+
input: component.input as HTMLInputElement,
|
|
14
|
+
|
|
15
|
+
// Value management
|
|
16
|
+
getValue: component.getValue || (() => ''),
|
|
17
|
+
setValue(value: string): CheckboxComponent {
|
|
18
|
+
component.setValue?.(value);
|
|
19
|
+
return this;
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
// State management
|
|
23
|
+
check(): CheckboxComponent {
|
|
24
|
+
checkable.check();
|
|
25
|
+
return this;
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
uncheck(): CheckboxComponent {
|
|
29
|
+
checkable.uncheck();
|
|
30
|
+
return this;
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
toggle(): CheckboxComponent {
|
|
34
|
+
checkable.toggle();
|
|
35
|
+
return this;
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
isChecked(): boolean {
|
|
39
|
+
return checkable.isChecked();
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
setIndeterminate(state: boolean): CheckboxComponent {
|
|
43
|
+
component.setIndeterminate?.(state);
|
|
44
|
+
return this;
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// Label management
|
|
48
|
+
setLabel(text: string): CheckboxComponent {
|
|
49
|
+
component.text?.setText(text);
|
|
50
|
+
return this;
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
getLabel(): string {
|
|
54
|
+
return component.text?.getText() || '';
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
// Event handling
|
|
58
|
+
on(event: string, handler: Function): CheckboxComponent {
|
|
59
|
+
component.on?.(event, handler);
|
|
60
|
+
return this;
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
off(event: string, handler: Function): CheckboxComponent {
|
|
64
|
+
component.off?.(event, handler);
|
|
65
|
+
return this;
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
// State management
|
|
69
|
+
enable(): CheckboxComponent {
|
|
70
|
+
disabled.enable();
|
|
71
|
+
return this;
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
disable(): CheckboxComponent {
|
|
75
|
+
disabled.disable();
|
|
76
|
+
return this;
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
destroy(): void {
|
|
80
|
+
lifecycle.destroy();
|
|
81
|
+
}
|
|
82
|
+
});
|