mtrl 0.2.5 → 0.2.7
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 +18 -0
- package/package.json +1 -1
- package/src/components/badge/_styles.scss +123 -115
- package/src/components/badge/api.ts +57 -59
- package/src/components/badge/badge.ts +16 -2
- package/src/components/badge/config.ts +65 -11
- package/src/components/badge/constants.ts +22 -12
- package/src/components/badge/features.ts +44 -40
- package/src/components/badge/types.ts +42 -30
- package/src/components/bottom-app-bar/_styles.scss +103 -0
- package/src/components/bottom-app-bar/bottom-app-bar.ts +196 -0
- package/src/components/bottom-app-bar/config.ts +73 -0
- package/src/components/bottom-app-bar/index.ts +11 -0
- package/src/components/bottom-app-bar/types.ts +108 -0
- package/src/components/button/_styles.scss +0 -66
- package/src/components/button/api.ts +5 -0
- package/src/components/button/button.ts +0 -2
- package/src/components/button/config.ts +5 -0
- package/src/components/button/constants.ts +0 -6
- package/src/components/button/index.ts +2 -2
- package/src/components/button/types.ts +7 -7
- package/src/components/card/_styles.scss +67 -25
- package/src/components/card/api.ts +54 -3
- package/src/components/card/card.ts +25 -6
- package/src/components/card/config.ts +189 -22
- package/src/components/card/constants.ts +20 -19
- package/src/components/card/content.ts +299 -2
- package/src/components/card/features.ts +158 -4
- package/src/components/card/index.ts +31 -9
- package/src/components/card/types.ts +166 -15
- package/src/components/checkbox/_styles.scss +0 -2
- package/src/components/chip/chip.ts +1 -9
- package/src/components/chip/constants.ts +0 -10
- package/src/components/chip/index.ts +1 -1
- package/src/components/chip/types.ts +1 -4
- package/src/components/datepicker/_styles.scss +358 -0
- package/src/components/datepicker/api.ts +272 -0
- package/src/components/datepicker/config.ts +144 -0
- package/src/components/datepicker/constants.ts +98 -0
- package/src/components/datepicker/datepicker.ts +346 -0
- package/src/components/datepicker/index.ts +9 -0
- package/src/components/datepicker/render.ts +452 -0
- package/src/components/datepicker/types.ts +268 -0
- package/src/components/datepicker/utils.ts +290 -0
- package/src/components/dialog/_styles.scss +174 -128
- package/src/components/dialog/api.ts +48 -13
- package/src/components/dialog/config.ts +9 -5
- package/src/components/dialog/dialog.ts +6 -3
- package/src/components/dialog/features.ts +290 -130
- package/src/components/dialog/types.ts +7 -4
- package/src/components/divider/_styles.scss +57 -0
- package/src/components/divider/config.ts +81 -0
- package/src/components/divider/divider.ts +37 -0
- package/src/components/divider/features.ts +207 -0
- package/src/components/divider/index.ts +5 -0
- package/src/components/divider/types.ts +55 -0
- package/src/components/extended-fab/_styles.scss +267 -0
- package/src/components/extended-fab/api.ts +141 -0
- package/src/components/extended-fab/config.ts +108 -0
- package/src/components/extended-fab/constants.ts +36 -0
- package/src/components/extended-fab/extended-fab.ts +125 -0
- package/src/components/extended-fab/index.ts +4 -0
- package/src/components/extended-fab/types.ts +287 -0
- package/src/components/fab/_styles.scss +225 -0
- package/src/components/fab/api.ts +97 -0
- package/src/components/fab/config.ts +94 -0
- package/src/components/fab/constants.ts +41 -0
- package/src/components/fab/fab.ts +67 -0
- package/src/components/fab/index.ts +4 -0
- package/src/components/fab/types.ts +234 -0
- package/src/components/navigation/_styles.scss +1 -0
- package/src/components/navigation/api.ts +78 -50
- package/src/components/navigation/features/items.ts +280 -0
- package/src/components/navigation/nav-item.ts +72 -23
- package/src/components/navigation/navigation.ts +54 -2
- package/src/components/navigation/types.ts +210 -188
- package/src/components/progress/_styles.scss +0 -65
- package/src/components/progress/config.ts +1 -2
- package/src/components/progress/constants.ts +0 -14
- package/src/components/progress/index.ts +1 -1
- package/src/components/progress/progress.ts +1 -4
- package/src/components/progress/types.ts +1 -4
- package/src/components/radios/_styles.scss +0 -45
- package/src/components/radios/api.ts +85 -60
- package/src/components/radios/config.ts +1 -2
- package/src/components/radios/constants.ts +0 -9
- package/src/components/radios/index.ts +1 -1
- package/src/components/radios/radio.ts +34 -11
- package/src/components/radios/radios.ts +2 -1
- package/src/components/radios/types.ts +1 -7
- package/src/components/search/_styles.scss +306 -0
- package/src/components/search/api.ts +203 -0
- package/src/components/search/config.ts +87 -0
- package/src/components/search/constants.ts +21 -0
- package/src/components/search/features/index.ts +4 -0
- package/src/components/search/features/search.ts +718 -0
- package/src/components/search/features/states.ts +165 -0
- package/src/components/search/features/structure.ts +198 -0
- package/src/components/search/index.ts +10 -0
- package/src/components/search/search.ts +52 -0
- package/src/components/search/types.ts +163 -0
- package/src/components/segmented-button/_styles.scss +117 -0
- package/src/components/segmented-button/config.ts +67 -0
- package/src/components/segmented-button/constants.ts +42 -0
- package/src/components/segmented-button/index.ts +4 -0
- package/src/components/segmented-button/segment.ts +155 -0
- package/src/components/segmented-button/segmented-button.ts +250 -0
- package/src/components/segmented-button/types.ts +219 -0
- package/src/components/slider/_styles.scss +221 -168
- package/src/components/slider/accessibility.md +59 -0
- package/src/components/slider/api.ts +41 -120
- package/src/components/slider/config.ts +51 -49
- package/src/components/slider/features/handlers.ts +495 -0
- package/src/components/slider/features/index.ts +1 -2
- package/src/components/slider/features/slider.ts +66 -84
- package/src/components/slider/features/states.ts +195 -0
- package/src/components/slider/features/structure.ts +141 -184
- package/src/components/slider/features/ui.ts +150 -201
- package/src/components/slider/index.ts +2 -11
- package/src/components/slider/slider.ts +9 -12
- package/src/components/slider/types.ts +39 -24
- package/src/components/switch/_styles.scss +0 -2
- package/src/components/tabs/_styles.scss +346 -154
- package/src/components/tabs/api.ts +178 -400
- package/src/components/tabs/config.ts +46 -52
- package/src/components/tabs/constants.ts +85 -8
- package/src/components/tabs/features.ts +403 -0
- package/src/components/tabs/index.ts +60 -3
- package/src/components/tabs/indicator.ts +285 -0
- package/src/components/tabs/responsive.ts +144 -0
- package/src/components/tabs/scroll-indicators.ts +149 -0
- package/src/components/tabs/state.ts +186 -0
- package/src/components/tabs/tab-api.ts +258 -0
- package/src/components/tabs/tab.ts +255 -0
- package/src/components/tabs/tabs.ts +50 -31
- package/src/components/tabs/types.ts +332 -128
- package/src/components/tabs/utils.ts +107 -0
- package/src/components/textfield/_styles.scss +0 -98
- package/src/components/textfield/config.ts +2 -3
- package/src/components/textfield/constants.ts +0 -14
- package/src/components/textfield/index.ts +2 -2
- package/src/components/textfield/textfield.ts +0 -2
- package/src/components/textfield/types.ts +1 -4
- package/src/components/timepicker/README.md +277 -0
- package/src/components/timepicker/_styles.scss +451 -0
- package/src/components/timepicker/api.ts +632 -0
- package/src/components/timepicker/clockdial.ts +482 -0
- package/src/components/timepicker/config.ts +130 -0
- package/src/components/timepicker/constants.ts +138 -0
- package/src/components/timepicker/index.ts +8 -0
- package/src/components/timepicker/render.ts +613 -0
- package/src/components/timepicker/timepicker.ts +117 -0
- package/src/components/timepicker/types.ts +336 -0
- package/src/components/timepicker/utils.ts +241 -0
- package/src/components/top-app-bar/_styles.scss +225 -0
- package/src/components/top-app-bar/config.ts +83 -0
- package/src/components/top-app-bar/index.ts +11 -0
- package/src/components/top-app-bar/top-app-bar.ts +316 -0
- package/src/components/top-app-bar/types.ts +140 -0
- package/src/core/build/_ripple.scss +6 -6
- package/src/core/build/ripple.ts +72 -95
- package/src/core/compose/component.ts +1 -1
- package/src/core/compose/features/badge.ts +79 -0
- package/src/core/compose/features/icon.ts +3 -1
- package/src/core/compose/features/index.ts +3 -1
- package/src/core/compose/features/ripple.ts +4 -1
- package/src/core/compose/features/textlabel.ts +26 -2
- package/src/core/dom/create.ts +5 -0
- package/src/index.ts +9 -0
- package/src/styles/abstract/_theme.scss +115 -3
- package/src/styles/themes/_autumn.scss +21 -0
- package/src/styles/themes/_base-theme.scss +61 -0
- package/src/styles/themes/_baseline.scss +58 -0
- package/src/styles/themes/_bluekhaki.scss +125 -0
- package/src/styles/themes/_brownbeige.scss +125 -0
- package/src/styles/themes/_browngreen.scss +125 -0
- package/src/styles/themes/_forest.scss +6 -0
- package/src/styles/themes/_greenbeige.scss +125 -0
- package/src/styles/themes/_material.scss +125 -0
- package/src/styles/themes/_ocean.scss +6 -0
- package/src/styles/themes/_sageivory.scss +125 -0
- package/src/styles/themes/_spring.scss +6 -0
- package/src/styles/themes/_summer.scss +5 -0
- package/src/styles/themes/_sunset.scss +5 -0
- package/src/styles/themes/_tealcaramel.scss +125 -0
- package/src/styles/themes/_winter.scss +6 -0
- package/src/components/card/actions.ts +0 -48
- package/src/components/card/header.ts +0 -88
- package/src/components/card/media.ts +0 -52
- package/src/components/navigation/features/items.js +0 -192
- package/src/components/slider/features/appearance.ts +0 -94
- package/src/components/slider/features/disabled.ts +0 -43
- package/src/components/slider/features/events.ts +0 -164
- package/src/components/slider/features/interactions.ts +0 -261
- package/src/components/slider/features/keyboard.ts +0 -112
- package/src/core/collection/adapters/mongodb.js +0 -232
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
// src/components/card/config.ts
|
|
2
|
+
|
|
2
3
|
import {
|
|
3
4
|
createComponentConfig,
|
|
4
|
-
createElementConfig
|
|
5
|
-
BaseComponentConfig
|
|
5
|
+
createElementConfig
|
|
6
6
|
} from '../../core/config/component-config';
|
|
7
|
-
import { BaseComponent, CardSchema } from './types';
|
|
8
7
|
import { CARD_VARIANTS, CARD_ELEVATIONS } from './constants';
|
|
8
|
+
import {
|
|
9
|
+
createCardHeader,
|
|
10
|
+
createCardContent,
|
|
11
|
+
createCardMedia,
|
|
12
|
+
createCardActions
|
|
13
|
+
} from './content';
|
|
14
|
+
import { CardComponent, CardSchema, ButtonConfig, BaseComponent } from './types';
|
|
9
15
|
|
|
10
16
|
/**
|
|
11
17
|
* Default configuration for the Card component
|
|
18
|
+
* @const {CardSchema}
|
|
12
19
|
*/
|
|
13
20
|
export const defaultConfig: CardSchema = {
|
|
14
21
|
variant: CARD_VARIANTS.ELEVATED,
|
|
@@ -18,8 +25,101 @@ export const defaultConfig: CardSchema = {
|
|
|
18
25
|
draggable: false
|
|
19
26
|
};
|
|
20
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Processes inline configuration options into standard config format
|
|
30
|
+
* Maps shorthand properties to their proper config counterparts
|
|
31
|
+
*
|
|
32
|
+
* @param {CardSchema} config - Raw card configuration
|
|
33
|
+
* @returns {CardSchema} Processed configuration
|
|
34
|
+
*/
|
|
35
|
+
export const processInlineConfig = (config: CardSchema): CardSchema => {
|
|
36
|
+
const processedConfig: CardSchema = { ...config };
|
|
37
|
+
|
|
38
|
+
// Map inline properties to their *Config counterparts
|
|
39
|
+
if (config.header) {
|
|
40
|
+
processedConfig.headerConfig = config.header;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (config.content) {
|
|
44
|
+
processedConfig.contentConfig = config.content;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (config.media) {
|
|
48
|
+
processedConfig.mediaConfig = config.media;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (config.actions) {
|
|
52
|
+
processedConfig.actionsConfig = config.actions;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return processedConfig;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Applies inline configuration to a card component
|
|
60
|
+
* Adds configured elements to the card in the correct order
|
|
61
|
+
*
|
|
62
|
+
* @param {CardComponent} card - Card component to configure
|
|
63
|
+
* @param {CardSchema} config - Processed configuration
|
|
64
|
+
*/
|
|
65
|
+
export const applyInlineConfiguration = (card: CardComponent, config: CardSchema): void => {
|
|
66
|
+
// Add media (top position) if configured
|
|
67
|
+
if (config.mediaConfig && (!config.mediaConfig.position || config.mediaConfig.position === 'top')) {
|
|
68
|
+
const { position, ...mediaConfigWithoutPosition } = config.mediaConfig;
|
|
69
|
+
const mediaElement = createCardMedia(mediaConfigWithoutPosition);
|
|
70
|
+
card.addMedia(mediaElement, 'top');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Add header if configured
|
|
74
|
+
if (config.headerConfig) {
|
|
75
|
+
const headerElement = createCardHeader(config.headerConfig);
|
|
76
|
+
card.setHeader(headerElement);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Add content if configured
|
|
80
|
+
if (config.contentConfig) {
|
|
81
|
+
const contentElement = createCardContent(config.contentConfig);
|
|
82
|
+
card.addContent(contentElement);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Add media (bottom position) if configured
|
|
86
|
+
if (config.mediaConfig && config.mediaConfig.position === 'bottom') {
|
|
87
|
+
const { position, ...mediaConfigWithoutPosition } = config.mediaConfig;
|
|
88
|
+
const mediaElement = createCardMedia(mediaConfigWithoutPosition);
|
|
89
|
+
card.addMedia(mediaElement, 'bottom');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Add actions if configured
|
|
93
|
+
if (config.actionsConfig) {
|
|
94
|
+
const actionsElement = createCardActions(config.actionsConfig);
|
|
95
|
+
card.setActions(actionsElement);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Process buttons if provided (asynchronously)
|
|
99
|
+
if (Array.isArray(config.buttons) && config.buttons.length > 0) {
|
|
100
|
+
import('../button').then(({ default: createButton }) => {
|
|
101
|
+
// Create buttons from configuration
|
|
102
|
+
const actionButtons = config.buttons!.map(buttonConfig =>
|
|
103
|
+
createButton(buttonConfig).element
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Create actions container
|
|
107
|
+
const actionsElement = createCardActions({
|
|
108
|
+
actions: actionButtons,
|
|
109
|
+
align: config.actionsConfig?.align || 'end'
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Add the actions to the card
|
|
113
|
+
card.setActions(actionsElement);
|
|
114
|
+
}).catch(error => {
|
|
115
|
+
console.error('Error processing buttons:', error);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
21
120
|
/**
|
|
22
121
|
* Creates the base configuration for Card component
|
|
122
|
+
*
|
|
23
123
|
* @param {CardSchema} config - User provided configuration
|
|
24
124
|
* @returns {CardSchema} Complete configuration with defaults applied
|
|
25
125
|
*/
|
|
@@ -28,31 +128,64 @@ export const createBaseConfig = (config: CardSchema = {}): CardSchema =>
|
|
|
28
128
|
|
|
29
129
|
/**
|
|
30
130
|
* Generates element configuration for the Card component
|
|
131
|
+
*
|
|
31
132
|
* @param {CardSchema} config - Card configuration
|
|
32
133
|
* @returns {Object} Element configuration object for withElement
|
|
33
134
|
*/
|
|
34
|
-
export const getElementConfig = (config: CardSchema) =>
|
|
35
|
-
|
|
135
|
+
export const getElementConfig = (config: CardSchema) => {
|
|
136
|
+
const isInteractive = config.interactive || config.clickable;
|
|
137
|
+
const defaultRole = isInteractive ? 'button' : 'region';
|
|
138
|
+
|
|
139
|
+
// Prepare ARIA attributes
|
|
140
|
+
const ariaAttrs: Record<string, string> = {};
|
|
141
|
+
if (config.aria) {
|
|
142
|
+
// Add all ARIA attributes from config
|
|
143
|
+
Object.entries(config.aria).forEach(([key, value]) => {
|
|
144
|
+
if (value !== undefined) {
|
|
145
|
+
// Convert attribute name to aria-* format if not already
|
|
146
|
+
const attrName = key.startsWith('aria-') ? key : `aria-${key}`;
|
|
147
|
+
ariaAttrs[attrName] = value;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Set default ARIA role if not specified
|
|
153
|
+
if (!ariaAttrs['role'] && !config.aria?.role) {
|
|
154
|
+
ariaAttrs['role'] = defaultRole;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Add tabindex for interactive cards if not specified
|
|
158
|
+
if (isInteractive && !ariaAttrs['tabindex']) {
|
|
159
|
+
ariaAttrs['tabindex'] = '0';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return createElementConfig(config, {
|
|
36
163
|
tag: 'div',
|
|
37
164
|
className: [
|
|
38
165
|
config.class,
|
|
39
166
|
config.fullWidth ? `${config.prefix}-card--full-width` : null,
|
|
40
|
-
|
|
167
|
+
isInteractive ? `${config.prefix}-card--interactive` : null
|
|
41
168
|
],
|
|
169
|
+
attrs: ariaAttrs,
|
|
42
170
|
forwardEvents: {
|
|
43
171
|
click: (component: BaseComponent) => !!config.clickable,
|
|
44
|
-
mouseenter: (component: BaseComponent) => !!
|
|
45
|
-
mouseleave: (component: BaseComponent) => !!
|
|
172
|
+
mouseenter: (component: BaseComponent) => !!isInteractive,
|
|
173
|
+
mouseleave: (component: BaseComponent) => !!isInteractive,
|
|
174
|
+
keydown: (component: BaseComponent) => !!isInteractive,
|
|
175
|
+
focus: (component: BaseComponent) => !!isInteractive,
|
|
176
|
+
blur: (component: BaseComponent) => !!isInteractive
|
|
46
177
|
},
|
|
47
|
-
interactive:
|
|
178
|
+
interactive: isInteractive
|
|
48
179
|
});
|
|
180
|
+
};
|
|
49
181
|
|
|
50
182
|
/**
|
|
51
183
|
* Creates API configuration for the Card component
|
|
184
|
+
*
|
|
52
185
|
* @param {Object} comp - Component with lifecycle feature
|
|
53
186
|
* @returns {Object} API configuration object
|
|
54
187
|
*/
|
|
55
|
-
export const getApiConfig = (comp) => ({
|
|
188
|
+
export const getApiConfig = (comp: any) => ({
|
|
56
189
|
lifecycle: {
|
|
57
190
|
destroy: () => comp.lifecycle?.destroy?.()
|
|
58
191
|
}
|
|
@@ -60,35 +193,69 @@ export const getApiConfig = (comp) => ({
|
|
|
60
193
|
|
|
61
194
|
/**
|
|
62
195
|
* Adds interactive behavior to card component
|
|
196
|
+
* Uses the MTRL elevation system for proper elevation levels
|
|
197
|
+
*
|
|
63
198
|
* @param {BaseComponent} comp - Card component
|
|
64
199
|
* @returns {BaseComponent} Enhanced card component
|
|
65
200
|
*/
|
|
66
201
|
export const withInteractiveBehavior = (comp: BaseComponent): BaseComponent => {
|
|
67
|
-
|
|
68
|
-
|
|
202
|
+
const config = comp.config;
|
|
203
|
+
const isInteractive = config.interactive || config.clickable;
|
|
204
|
+
|
|
205
|
+
// Implement MD3 elevation changes for interactive cards
|
|
206
|
+
if (isInteractive) {
|
|
207
|
+
// Mouse interactions
|
|
69
208
|
comp.element.addEventListener('mouseenter', () => {
|
|
70
|
-
if (
|
|
71
|
-
comp.element.style.setProperty('--card-elevation', String(CARD_ELEVATIONS.
|
|
209
|
+
if (config.variant === CARD_VARIANTS.ELEVATED) {
|
|
210
|
+
comp.element.style.setProperty('--card-elevation', String(CARD_ELEVATIONS.LEVEL2));
|
|
72
211
|
}
|
|
73
212
|
});
|
|
74
213
|
|
|
75
214
|
comp.element.addEventListener('mouseleave', () => {
|
|
76
|
-
if (
|
|
77
|
-
comp.element.style.setProperty('--card-elevation', String(CARD_ELEVATIONS.
|
|
215
|
+
if (config.variant === CARD_VARIANTS.ELEVATED) {
|
|
216
|
+
comp.element.style.setProperty('--card-elevation', String(CARD_ELEVATIONS.LEVEL1));
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Keyboard interactions for accessibility
|
|
221
|
+
comp.element.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
222
|
+
// Activate on Enter or Space
|
|
223
|
+
if ((e.key === 'Enter' || e.key === ' ') && config.clickable) {
|
|
224
|
+
e.preventDefault();
|
|
225
|
+
comp.element.click();
|
|
78
226
|
}
|
|
79
227
|
});
|
|
228
|
+
|
|
229
|
+
// Focus state handling
|
|
230
|
+
comp.element.addEventListener('focus', () => {
|
|
231
|
+
comp.element.classList.add(`${comp.getClass('card')}--focused`);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
comp.element.addEventListener('blur', () => {
|
|
235
|
+
comp.element.classList.remove(`${comp.getClass('card')}--focused`);
|
|
236
|
+
});
|
|
80
237
|
}
|
|
81
238
|
|
|
82
|
-
// Set up draggable
|
|
83
|
-
if (
|
|
239
|
+
// Set up draggable behavior
|
|
240
|
+
if (config.draggable) {
|
|
84
241
|
comp.element.setAttribute('draggable', 'true');
|
|
85
|
-
|
|
86
|
-
|
|
242
|
+
|
|
243
|
+
comp.element.addEventListener('dragstart', (e: DragEvent) => {
|
|
244
|
+
comp.element.style.setProperty('--card-elevation', String(CARD_ELEVATIONS.LEVEL4));
|
|
245
|
+
comp.element.classList.add(`${comp.getClass('card')}--dragging`);
|
|
87
246
|
comp.emit?.('dragstart', { event: e });
|
|
247
|
+
|
|
248
|
+
// Ensure keyboard users can see what's being dragged
|
|
249
|
+
if (e.dataTransfer) {
|
|
250
|
+
// Set drag image and data
|
|
251
|
+
const cardTitle = comp.element.querySelector(`.${comp.getClass('card')}-header-title`)?.textContent || 'Card';
|
|
252
|
+
e.dataTransfer.setData('text/plain', cardTitle);
|
|
253
|
+
}
|
|
88
254
|
});
|
|
89
255
|
|
|
90
|
-
comp.element.addEventListener('dragend', (e) => {
|
|
91
|
-
comp.element.style.setProperty('--card-elevation', String(CARD_ELEVATIONS.
|
|
256
|
+
comp.element.addEventListener('dragend', (e: DragEvent) => {
|
|
257
|
+
comp.element.style.setProperty('--card-elevation', String(CARD_ELEVATIONS.LEVEL1));
|
|
258
|
+
comp.element.classList.remove(`${comp.getClass('card')}--dragging`);
|
|
92
259
|
comp.emit?.('dragend', { event: e });
|
|
93
260
|
});
|
|
94
261
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/components/card/constants.ts
|
|
2
|
+
|
|
2
3
|
import { CardVariant, CardElevation } from './types';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -6,37 +7,33 @@ import { CardVariant, CardElevation } from './types';
|
|
|
6
7
|
* @enum {string}
|
|
7
8
|
*/
|
|
8
9
|
export const CARD_VARIANTS = {
|
|
10
|
+
/** Elevated card with shadow */
|
|
9
11
|
ELEVATED: CardVariant.ELEVATED,
|
|
12
|
+
/** Filled card with higher surface container color */
|
|
10
13
|
FILLED: CardVariant.FILLED,
|
|
14
|
+
/** Outlined card with border */
|
|
11
15
|
OUTLINED: CardVariant.OUTLINED
|
|
12
16
|
};
|
|
13
17
|
|
|
14
18
|
/**
|
|
15
|
-
* Card elevation levels
|
|
19
|
+
* Card elevation levels based on MD3 guidelines
|
|
20
|
+
* Uses the MTRL elevation system values
|
|
16
21
|
* @enum {number}
|
|
17
22
|
*/
|
|
18
23
|
export const CARD_ELEVATIONS = {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
/** No elevation (for filled and outlined variants) */
|
|
25
|
+
LEVEL0: CardElevation.LEVEL0,
|
|
26
|
+
/** Default elevation for elevated cards */
|
|
27
|
+
LEVEL1: CardElevation.LEVEL1,
|
|
28
|
+
/** Elevation for hovered state */
|
|
29
|
+
LEVEL2: CardElevation.LEVEL2,
|
|
30
|
+
/** Elevation for dragged state */
|
|
31
|
+
LEVEL4: CardElevation.LEVEL4
|
|
22
32
|
};
|
|
23
33
|
|
|
24
|
-
// Default width values following MD3 principles
|
|
25
|
-
export const CARD_WIDTHS = {
|
|
26
|
-
// Mobile-optimized default (MD3 recommends 344dp for small screens)
|
|
27
|
-
DEFAULT: '344px',
|
|
28
|
-
// Percentage-based responsive options
|
|
29
|
-
FULL: '100%',
|
|
30
|
-
HALF: '50%',
|
|
31
|
-
// Fixed widths for different breakpoints
|
|
32
|
-
SMALL: '344px',
|
|
33
|
-
MEDIUM: '480px',
|
|
34
|
-
LARGE: '624px'
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
34
|
/**
|
|
39
|
-
*
|
|
35
|
+
* Card validation schema
|
|
36
|
+
* @const {Object}
|
|
40
37
|
*/
|
|
41
38
|
export const CARD_SCHEMA = {
|
|
42
39
|
variant: {
|
|
@@ -79,5 +76,9 @@ export const CARD_SCHEMA = {
|
|
|
79
76
|
mediaConfig: {
|
|
80
77
|
type: 'object',
|
|
81
78
|
required: false
|
|
79
|
+
},
|
|
80
|
+
aria: {
|
|
81
|
+
type: 'object',
|
|
82
|
+
required: false
|
|
82
83
|
}
|
|
83
84
|
};
|
|
@@ -3,11 +3,25 @@ import { PREFIX } from '../../core/config';
|
|
|
3
3
|
import { pipe } from '../../core/compose';
|
|
4
4
|
import { createBase, withElement } from '../../core/compose/component';
|
|
5
5
|
import { CardContentConfig } from './types';
|
|
6
|
+
import { CARD_CONTENT_PADDING } from './constants';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Creates a card content component
|
|
10
|
+
*
|
|
9
11
|
* @param {CardContentConfig} config - Content configuration
|
|
10
12
|
* @returns {HTMLElement} Card content element
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* // Create text content
|
|
17
|
+
* const textContent = createCardContent({ text: 'Simple text content' });
|
|
18
|
+
*
|
|
19
|
+
* // Create HTML content with no padding
|
|
20
|
+
* const htmlContent = createCardContent({
|
|
21
|
+
* html: '<p>Formatted <strong>HTML</strong> content</p>',
|
|
22
|
+
* padding: false
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
11
25
|
*/
|
|
12
26
|
export const createCardContent = (config: CardContentConfig = {}): HTMLElement => {
|
|
13
27
|
const baseConfig = {
|
|
@@ -17,6 +31,8 @@ export const createCardContent = (config: CardContentConfig = {}): HTMLElement =
|
|
|
17
31
|
};
|
|
18
32
|
|
|
19
33
|
try {
|
|
34
|
+
// Create element with innerHTML instead of html/text properties
|
|
35
|
+
// for more reliable content rendering
|
|
20
36
|
const content = pipe(
|
|
21
37
|
createBase,
|
|
22
38
|
withElement({
|
|
@@ -26,11 +42,22 @@ export const createCardContent = (config: CardContentConfig = {}): HTMLElement =
|
|
|
26
42
|
config.class,
|
|
27
43
|
config.padding === false ? `${PREFIX}-card-content--no-padding` : null
|
|
28
44
|
],
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
attrs: {
|
|
46
|
+
'role': 'region',
|
|
47
|
+
// Add explicit style attributes to ensure visibility
|
|
48
|
+
'style': 'display: block; color: inherit;'
|
|
49
|
+
}
|
|
31
50
|
})
|
|
32
51
|
)(baseConfig);
|
|
33
52
|
|
|
53
|
+
// Explicitly set the innerHTML for more reliable rendering
|
|
54
|
+
if (config.html) {
|
|
55
|
+
content.element.innerHTML = config.html;
|
|
56
|
+
} else if (config.text) {
|
|
57
|
+
// Wrap text in paragraph for proper formatting
|
|
58
|
+
content.element.innerHTML = `<p>${config.text}</p>`;
|
|
59
|
+
}
|
|
60
|
+
|
|
34
61
|
// Add children if provided
|
|
35
62
|
if (Array.isArray(config.children)) {
|
|
36
63
|
config.children.forEach(child => {
|
|
@@ -40,9 +67,279 @@ export const createCardContent = (config: CardContentConfig = {}): HTMLElement =
|
|
|
40
67
|
});
|
|
41
68
|
}
|
|
42
69
|
|
|
70
|
+
// Add debug class to make troubleshooting easier
|
|
71
|
+
// Remove this in production
|
|
72
|
+
content.element.classList.add('debug-content');
|
|
73
|
+
|
|
43
74
|
return content.element;
|
|
44
75
|
} catch (error) {
|
|
45
76
|
console.error('Card content creation error:', error instanceof Error ? error.message : String(error));
|
|
46
77
|
throw new Error(`Failed to create card content: ${error instanceof Error ? error.message : String(error)}`);
|
|
47
78
|
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// src/components/card/header.ts
|
|
82
|
+
import { createElement } from '../../core/dom/create';
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Creates a card header component
|
|
86
|
+
*
|
|
87
|
+
* @param {CardHeaderConfig} config - Header configuration
|
|
88
|
+
* @returns {HTMLElement} Card header element
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* // Create a header with title and subtitle
|
|
93
|
+
* const header = createCardHeader({
|
|
94
|
+
* title: 'Card Title',
|
|
95
|
+
* subtitle: 'Supporting text'
|
|
96
|
+
* });
|
|
97
|
+
*
|
|
98
|
+
* // Create a header with an avatar and action
|
|
99
|
+
* const avatarHeader = createCardHeader({
|
|
100
|
+
* title: 'User Profile',
|
|
101
|
+
* avatar: '<img src="user.jpg" alt="User avatar">',
|
|
102
|
+
* action: createIconButton({ icon: 'more_vert' })
|
|
103
|
+
* });
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export const createCardHeader = (config: any = {}): HTMLElement => {
|
|
107
|
+
const baseConfig = {
|
|
108
|
+
...config,
|
|
109
|
+
componentName: 'card-header',
|
|
110
|
+
prefix: PREFIX
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const header = pipe(
|
|
115
|
+
createBase,
|
|
116
|
+
withElement({
|
|
117
|
+
tag: 'div',
|
|
118
|
+
componentName: 'card-header',
|
|
119
|
+
className: config.class,
|
|
120
|
+
attrs: {
|
|
121
|
+
'role': 'heading',
|
|
122
|
+
'aria-level': '3' // Default heading level
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
)(baseConfig);
|
|
126
|
+
|
|
127
|
+
// Create text container for title and subtitle
|
|
128
|
+
const textContainer = createElement({
|
|
129
|
+
tag: 'div',
|
|
130
|
+
className: `${PREFIX}-card-header-text`,
|
|
131
|
+
container: header.element
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Add title if provided
|
|
135
|
+
if (config.title) {
|
|
136
|
+
createElement({
|
|
137
|
+
tag: 'h3',
|
|
138
|
+
className: `${PREFIX}-card-header-title`,
|
|
139
|
+
text: config.title,
|
|
140
|
+
container: textContainer,
|
|
141
|
+
attrs: {
|
|
142
|
+
id: `${header.element.id || 'card-header'}-title`
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Link the title ID to the card for accessibility if parent card exists
|
|
147
|
+
const parentCard = header.element.closest(`.${PREFIX}-card`);
|
|
148
|
+
if (parentCard && !parentCard.hasAttribute('aria-labelledby')) {
|
|
149
|
+
parentCard.setAttribute('aria-labelledby', `${header.element.id || 'card-header'}-title`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Add subtitle if provided
|
|
154
|
+
if (config.subtitle) {
|
|
155
|
+
createElement({
|
|
156
|
+
tag: 'h4',
|
|
157
|
+
className: `${PREFIX}-card-header-subtitle`,
|
|
158
|
+
text: config.subtitle,
|
|
159
|
+
container: textContainer
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Add avatar if provided
|
|
164
|
+
if (config.avatar) {
|
|
165
|
+
const avatarElement = typeof config.avatar === 'string'
|
|
166
|
+
? createElement({
|
|
167
|
+
tag: 'div',
|
|
168
|
+
className: `${PREFIX}-card-header-avatar`,
|
|
169
|
+
html: config.avatar
|
|
170
|
+
})
|
|
171
|
+
: config.avatar;
|
|
172
|
+
|
|
173
|
+
// Ensure avatar has correct ARIA attributes if it's an image
|
|
174
|
+
const avatarImg = avatarElement.querySelector('img');
|
|
175
|
+
if (avatarImg && !avatarImg.hasAttribute('alt')) {
|
|
176
|
+
avatarImg.setAttribute('alt', ''); // Decorative image
|
|
177
|
+
avatarImg.setAttribute('aria-hidden', 'true');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
header.element.insertBefore(avatarElement, header.element.firstChild);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Add action if provided
|
|
184
|
+
if (config.action) {
|
|
185
|
+
const actionElement = typeof config.action === 'string'
|
|
186
|
+
? createElement({
|
|
187
|
+
tag: 'div',
|
|
188
|
+
className: `${PREFIX}-card-header-action`,
|
|
189
|
+
html: config.action
|
|
190
|
+
})
|
|
191
|
+
: config.action;
|
|
192
|
+
|
|
193
|
+
header.element.appendChild(actionElement);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return header.element;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.error('Card header creation error:', error instanceof Error ? error.message : String(error));
|
|
199
|
+
throw new Error(`Failed to create card header: ${error instanceof Error ? error.message : String(error)}`);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// src/components/card/actions.ts
|
|
204
|
+
/**
|
|
205
|
+
* Creates a card actions component
|
|
206
|
+
*
|
|
207
|
+
* @param {CardActionsConfig} config - Actions configuration
|
|
208
|
+
* @returns {HTMLElement} Card actions element
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```typescript
|
|
212
|
+
* // Create simple actions container with buttons
|
|
213
|
+
* const actions = createCardActions({
|
|
214
|
+
* actions: [
|
|
215
|
+
* createButton({ text: 'Cancel' }),
|
|
216
|
+
* createButton({ text: 'OK', variant: 'filled' })
|
|
217
|
+
* ],
|
|
218
|
+
* align: 'end'
|
|
219
|
+
* });
|
|
220
|
+
*
|
|
221
|
+
* // Create full-bleed actions
|
|
222
|
+
* const fullBleedActions = createCardActions({
|
|
223
|
+
* actions: [createButton({ text: 'View Details', fullWidth: true })],
|
|
224
|
+
* fullBleed: true
|
|
225
|
+
* });
|
|
226
|
+
* ```
|
|
227
|
+
*/
|
|
228
|
+
export const createCardActions = (config: any = {}): HTMLElement => {
|
|
229
|
+
const baseConfig = {
|
|
230
|
+
...config,
|
|
231
|
+
componentName: 'card-actions',
|
|
232
|
+
prefix: PREFIX
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
const actions = pipe(
|
|
237
|
+
createBase,
|
|
238
|
+
withElement({
|
|
239
|
+
tag: 'div',
|
|
240
|
+
componentName: 'card-actions',
|
|
241
|
+
className: [
|
|
242
|
+
config.class,
|
|
243
|
+
config.fullBleed ? `${PREFIX}-card-actions--full-bleed` : null,
|
|
244
|
+
config.vertical ? `${PREFIX}-card-actions--vertical` : null,
|
|
245
|
+
config.align ? `${PREFIX}-card-actions--${config.align}` : null
|
|
246
|
+
],
|
|
247
|
+
attrs: {
|
|
248
|
+
'role': 'group' // Semantically group actions together
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
)(baseConfig);
|
|
252
|
+
|
|
253
|
+
// Add action elements if provided
|
|
254
|
+
if (Array.isArray(config.actions)) {
|
|
255
|
+
config.actions.forEach((action, index) => {
|
|
256
|
+
if (action instanceof HTMLElement) {
|
|
257
|
+
// Ensure each action has accessible attributes
|
|
258
|
+
if (!action.hasAttribute('aria-label') &&
|
|
259
|
+
!action.hasAttribute('aria-labelledby') &&
|
|
260
|
+
action.textContent?.trim() === '') {
|
|
261
|
+
action.setAttribute('aria-label', `Action ${index + 1}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
actions.element.appendChild(action);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return actions.element;
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error('Card actions creation error:', error instanceof Error ? error.message : String(error));
|
|
272
|
+
throw new Error(`Failed to create card actions: ${error instanceof Error ? error.message : String(error)}`);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// src/components/card/media.ts
|
|
277
|
+
/**
|
|
278
|
+
* Creates a card media component
|
|
279
|
+
*
|
|
280
|
+
* @param {CardMediaConfig} config - Media configuration
|
|
281
|
+
* @returns {HTMLElement} Card media element
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```typescript
|
|
285
|
+
* // Create a media component with an image
|
|
286
|
+
* const media = createCardMedia({
|
|
287
|
+
* src: 'image.jpg',
|
|
288
|
+
* alt: 'Descriptive alt text',
|
|
289
|
+
* aspectRatio: '16:9'
|
|
290
|
+
* });
|
|
291
|
+
*
|
|
292
|
+
* // Create a media component with a custom element
|
|
293
|
+
* const customMedia = createCardMedia({
|
|
294
|
+
* element: videoElement,
|
|
295
|
+
* aspectRatio: '4:3'
|
|
296
|
+
* });
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
export const createCardMedia = (config: any = {}): HTMLElement => {
|
|
300
|
+
const baseConfig = {
|
|
301
|
+
...config,
|
|
302
|
+
componentName: 'card-media',
|
|
303
|
+
prefix: PREFIX
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
const media = pipe(
|
|
308
|
+
createBase,
|
|
309
|
+
withElement({
|
|
310
|
+
tag: 'div',
|
|
311
|
+
componentName: 'card-media',
|
|
312
|
+
className: [
|
|
313
|
+
config.class,
|
|
314
|
+
config.aspectRatio ? `${PREFIX}-card-media--${config.aspectRatio.replace(':', '-')}` : null,
|
|
315
|
+
config.contain ? `${PREFIX}-card-media--contain` : null
|
|
316
|
+
]
|
|
317
|
+
})
|
|
318
|
+
)(baseConfig);
|
|
319
|
+
|
|
320
|
+
// If custom element is provided, use it
|
|
321
|
+
if (config.element instanceof HTMLElement) {
|
|
322
|
+
media.element.appendChild(config.element);
|
|
323
|
+
}
|
|
324
|
+
// Otherwise create an image if src is provided
|
|
325
|
+
else if (config.src) {
|
|
326
|
+
const img = document.createElement('img');
|
|
327
|
+
img.src = config.src;
|
|
328
|
+
img.className = `${PREFIX}-card-media-img`;
|
|
329
|
+
|
|
330
|
+
// Ensure alt text is always provided for accessibility
|
|
331
|
+
img.alt = config.alt || '';
|
|
332
|
+
if (!config.alt) {
|
|
333
|
+
// If no alt text is provided, mark as decorative
|
|
334
|
+
img.setAttribute('aria-hidden', 'true');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
media.element.appendChild(img);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return media.element;
|
|
341
|
+
} catch (error) {
|
|
342
|
+
console.error('Card media creation error:', error instanceof Error ? error.message : String(error));
|
|
343
|
+
throw new Error(`Failed to create card media: ${error instanceof Error ? error.message : String(error)}`);
|
|
344
|
+
}
|
|
48
345
|
};
|