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,162 @@
|
|
|
1
|
+
// src/components/snackbar/api.ts
|
|
2
|
+
import { BaseComponent, SnackbarComponent, ApiOptions, QueuedSnackbar } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Enhances snackbar 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 = ({ lifecycle, queue }: ApiOptions) =>
|
|
10
|
+
(component: BaseComponent): SnackbarComponent => {
|
|
11
|
+
if (!queue) {
|
|
12
|
+
throw new Error('Snackbar queue is required');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let isVisible = false;
|
|
16
|
+
|
|
17
|
+
const enhancedComponent: SnackbarComponent = {
|
|
18
|
+
element: component.element,
|
|
19
|
+
actionButton: component.actionButton,
|
|
20
|
+
timer: component.timer,
|
|
21
|
+
position: component.position,
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Shows the snackbar with animation
|
|
25
|
+
* @returns {SnackbarComponent} Component instance for chaining
|
|
26
|
+
*/
|
|
27
|
+
show(): SnackbarComponent {
|
|
28
|
+
if (isVisible) return this;
|
|
29
|
+
isVisible = true;
|
|
30
|
+
|
|
31
|
+
queue.add({
|
|
32
|
+
...this,
|
|
33
|
+
_show: (): SnackbarComponent => {
|
|
34
|
+
document.body.appendChild(component.element);
|
|
35
|
+
|
|
36
|
+
// Force reflow for animation
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
38
|
+
const _ = component.element.offsetHeight;
|
|
39
|
+
|
|
40
|
+
component.element.classList.add(`${component.getClass?.('snackbar')}--visible`);
|
|
41
|
+
|
|
42
|
+
if (component.timer) {
|
|
43
|
+
component.timer.start();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
} as QueuedSnackbar);
|
|
49
|
+
|
|
50
|
+
return this;
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Hides the snackbar with animation and cleanup
|
|
55
|
+
* @returns {SnackbarComponent} Component instance for chaining
|
|
56
|
+
*/
|
|
57
|
+
hide(): SnackbarComponent {
|
|
58
|
+
if (!isVisible) return this;
|
|
59
|
+
isVisible = false;
|
|
60
|
+
|
|
61
|
+
if (component.timer) {
|
|
62
|
+
component.timer.stop();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const handleTransitionEnd = (event: TransitionEvent): void => {
|
|
66
|
+
if (event.propertyName !== 'opacity') return;
|
|
67
|
+
|
|
68
|
+
component.element.removeEventListener('transitionend', handleTransitionEnd);
|
|
69
|
+
if (component.element.parentNode) {
|
|
70
|
+
component.element.remove();
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
component.element.addEventListener('transitionend', handleTransitionEnd);
|
|
75
|
+
component.element.classList.remove(`${component.getClass?.('snackbar')}--visible`);
|
|
76
|
+
|
|
77
|
+
return this;
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Sets the snackbar message
|
|
82
|
+
* @param {string} text - New message text
|
|
83
|
+
* @returns {SnackbarComponent} Component instance for chaining
|
|
84
|
+
*/
|
|
85
|
+
setMessage(text: string): SnackbarComponent {
|
|
86
|
+
component.text?.setText(text);
|
|
87
|
+
return this;
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Gets the snackbar message
|
|
92
|
+
* @returns {string} Current message text
|
|
93
|
+
*/
|
|
94
|
+
getMessage(): string {
|
|
95
|
+
return component.text?.getText() || '';
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Adds event listener
|
|
100
|
+
* @param {string} event - Event name
|
|
101
|
+
* @param {Function} handler - Event handler
|
|
102
|
+
* @returns {SnackbarComponent} Component instance for chaining
|
|
103
|
+
*/
|
|
104
|
+
on(event: string, handler: Function): SnackbarComponent {
|
|
105
|
+
component.on?.(event, handler);
|
|
106
|
+
return this;
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Removes event listener
|
|
111
|
+
* @param {string} event - Event name
|
|
112
|
+
* @param {Function} handler - Event handler
|
|
113
|
+
* @returns {SnackbarComponent} Component instance for chaining
|
|
114
|
+
*/
|
|
115
|
+
off(event: string, handler: Function): SnackbarComponent {
|
|
116
|
+
component.off?.(event, handler);
|
|
117
|
+
return this;
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Destroys the snackbar component and cleans up resources
|
|
122
|
+
*/
|
|
123
|
+
destroy(): void {
|
|
124
|
+
if (isVisible && component.element.parentNode) {
|
|
125
|
+
component.element.remove();
|
|
126
|
+
}
|
|
127
|
+
if (component.timer) {
|
|
128
|
+
component.timer.stop();
|
|
129
|
+
}
|
|
130
|
+
lifecycle.destroy();
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Set up action button handler
|
|
135
|
+
if (component.actionButton) {
|
|
136
|
+
component.actionButton.addEventListener('click', () => {
|
|
137
|
+
component.emit?.('action');
|
|
138
|
+
component.emit?.('dismiss'); // Emit dismiss to handle queue cleanup
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Set up dismiss handler
|
|
143
|
+
if (component.on) {
|
|
144
|
+
// Store the handler reference so it can be properly removed
|
|
145
|
+
const dismissHandler = (): void => {
|
|
146
|
+
if (isVisible) {
|
|
147
|
+
enhancedComponent.hide();
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
component.on('dismiss', dismissHandler);
|
|
152
|
+
|
|
153
|
+
// Add cleanup to lifecycle
|
|
154
|
+
const originalDestroy = lifecycle.destroy;
|
|
155
|
+
lifecycle.destroy = () => {
|
|
156
|
+
component.off?.('dismiss', dismissHandler);
|
|
157
|
+
originalDestroy?.call(lifecycle);
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return enhancedComponent;
|
|
162
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// src/components/snackbar/config.ts
|
|
2
|
+
import {
|
|
3
|
+
createComponentConfig,
|
|
4
|
+
createElementConfig,
|
|
5
|
+
BaseComponentConfig
|
|
6
|
+
} from '../../core/config/component-config';
|
|
7
|
+
import { SnackbarConfig, BaseComponent, ApiOptions } from './types';
|
|
8
|
+
import { SNACKBAR_VARIANTS, SNACKBAR_POSITIONS } from './constants';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default configuration for the Snackbar component
|
|
12
|
+
*/
|
|
13
|
+
export const defaultConfig: SnackbarConfig = {
|
|
14
|
+
variant: SNACKBAR_VARIANTS.BASIC,
|
|
15
|
+
position: SNACKBAR_POSITIONS.CENTER,
|
|
16
|
+
duration: 4000
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates the base configuration for Snackbar component
|
|
21
|
+
* @param {SnackbarConfig} config - User provided configuration
|
|
22
|
+
* @returns {SnackbarConfig} Complete configuration with defaults applied
|
|
23
|
+
*/
|
|
24
|
+
export const createBaseConfig = (config: SnackbarConfig): SnackbarConfig =>
|
|
25
|
+
createComponentConfig(defaultConfig, config, 'snackbar') as SnackbarConfig;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generates element configuration for the Snackbar component
|
|
29
|
+
* @param {SnackbarConfig} config - Snackbar configuration
|
|
30
|
+
* @returns {Object} Element configuration object for withElement
|
|
31
|
+
*/
|
|
32
|
+
export const getElementConfig = (config: SnackbarConfig) =>
|
|
33
|
+
createElementConfig(config, {
|
|
34
|
+
tag: 'div',
|
|
35
|
+
componentName: 'snackbar',
|
|
36
|
+
className: config.class
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates text configuration for the Snackbar component
|
|
41
|
+
* @param {SnackbarConfig} config - Snackbar configuration
|
|
42
|
+
* @returns {Object} Text configuration object
|
|
43
|
+
*/
|
|
44
|
+
export const getTextConfig = (config: SnackbarConfig) => ({
|
|
45
|
+
...config,
|
|
46
|
+
text: config.message
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates API configuration for the Snackbar component
|
|
51
|
+
* @param {BaseComponent} comp - Component with lifecycle feature
|
|
52
|
+
* @param {SnackbarQueue} queue - Snackbar queue manager
|
|
53
|
+
* @returns {ApiOptions} API configuration object
|
|
54
|
+
*/
|
|
55
|
+
export const getApiConfig = (comp: BaseComponent, queue: any): ApiOptions => ({
|
|
56
|
+
lifecycle: {
|
|
57
|
+
destroy: comp.lifecycle?.destroy || (() => {})
|
|
58
|
+
},
|
|
59
|
+
queue
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export default defaultConfig;
|
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
// src/components/snackbar/constants.
|
|
1
|
+
// src/components/snackbar/constants.ts
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Snackbar visual variants
|
|
5
|
+
*/
|
|
3
6
|
export const SNACKBAR_VARIANTS = {
|
|
4
7
|
BASIC: 'basic',
|
|
5
8
|
ACTION: 'action' // With action button
|
|
6
|
-
}
|
|
9
|
+
} as const;
|
|
7
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Snackbar display positions
|
|
13
|
+
*/
|
|
8
14
|
export const SNACKBAR_POSITIONS = {
|
|
9
15
|
CENTER: 'center',
|
|
10
16
|
START: 'start',
|
|
11
17
|
END: 'end'
|
|
12
|
-
}
|
|
18
|
+
} as const;
|
|
13
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Validation schema for snackbar configuration
|
|
22
|
+
*/
|
|
14
23
|
export const SNACKBAR_SCHEMA = {
|
|
15
24
|
variant: {
|
|
16
25
|
type: 'string',
|
|
@@ -38,4 +47,12 @@ export const SNACKBAR_SCHEMA = {
|
|
|
38
47
|
type: 'string',
|
|
39
48
|
required: false
|
|
40
49
|
}
|
|
41
|
-
}
|
|
50
|
+
} as const;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Snackbar state classes
|
|
54
|
+
*/
|
|
55
|
+
export const SNACKBAR_STATES = {
|
|
56
|
+
VISIBLE: 'visible',
|
|
57
|
+
HIDDEN: 'hidden'
|
|
58
|
+
} as const;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/components/snackbar/features.ts
|
|
2
|
+
import { BaseComponent, SnackbarConfig, SnackbarTimer } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Adds action button to snackbar
|
|
6
|
+
* @param {SnackbarConfig} config - Component configuration
|
|
7
|
+
* @returns {Function} Higher-order function that adds action button
|
|
8
|
+
*/
|
|
9
|
+
export const withActionButton = (config: SnackbarConfig) =>
|
|
10
|
+
(component: BaseComponent): BaseComponent => {
|
|
11
|
+
if (!config.action) return component;
|
|
12
|
+
|
|
13
|
+
const button = document.createElement('button');
|
|
14
|
+
button.className = `${config.prefix}-snackbar-action`;
|
|
15
|
+
button.textContent = config.action;
|
|
16
|
+
|
|
17
|
+
component.element.appendChild(button);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
...component,
|
|
21
|
+
actionButton: button
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Adds auto-dismiss timer functionality
|
|
27
|
+
* @param {SnackbarConfig} config - Component configuration
|
|
28
|
+
* @returns {Function} Higher-order function that adds timer features
|
|
29
|
+
*/
|
|
30
|
+
export const withDismissTimer = (config: SnackbarConfig) =>
|
|
31
|
+
(component: BaseComponent): BaseComponent => {
|
|
32
|
+
let timeoutId: number | null = null;
|
|
33
|
+
|
|
34
|
+
const startTimer = (): void => {
|
|
35
|
+
// Clear any existing timer
|
|
36
|
+
if (timeoutId) {
|
|
37
|
+
clearTimeout(timeoutId);
|
|
38
|
+
timeoutId = null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Only start timer if duration is positive and numeric
|
|
42
|
+
if (typeof config.duration === 'number' && config.duration > 0) {
|
|
43
|
+
timeoutId = window.setTimeout(() => {
|
|
44
|
+
if (component.element && component.emit) {
|
|
45
|
+
component.emit('dismiss');
|
|
46
|
+
}
|
|
47
|
+
}, config.duration);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const stopTimer = (): void => {
|
|
52
|
+
if (timeoutId) {
|
|
53
|
+
clearTimeout(timeoutId);
|
|
54
|
+
timeoutId = null;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Clean up on destroy
|
|
59
|
+
const originalDestroy = component.lifecycle?.destroy;
|
|
60
|
+
if (component.lifecycle) {
|
|
61
|
+
component.lifecycle.destroy = () => {
|
|
62
|
+
stopTimer();
|
|
63
|
+
if (originalDestroy) {
|
|
64
|
+
originalDestroy.call(component.lifecycle);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
...component,
|
|
71
|
+
timer: {
|
|
72
|
+
start: startTimer,
|
|
73
|
+
stop: stopTimer
|
|
74
|
+
} as SnackbarTimer
|
|
75
|
+
};
|
|
76
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/components/snackbar/position.ts
|
|
2
|
+
import { SNACKBAR_POSITIONS } from './constants';
|
|
3
|
+
import { BaseComponent } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Position configuration for the withPosition function
|
|
7
|
+
*/
|
|
8
|
+
interface PositionConfig {
|
|
9
|
+
prefix: string;
|
|
10
|
+
position?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Adds position handling to snackbar
|
|
15
|
+
* @param {PositionConfig} config - Position configuration
|
|
16
|
+
* @returns {Function} Higher-order function that adds position features
|
|
17
|
+
*/
|
|
18
|
+
export const withPosition = (config: PositionConfig) =>
|
|
19
|
+
(component: BaseComponent): BaseComponent => {
|
|
20
|
+
const position = config.position || SNACKBAR_POSITIONS.CENTER;
|
|
21
|
+
const positionClass = `${config.prefix}-snackbar--${position}`;
|
|
22
|
+
|
|
23
|
+
// Add position class
|
|
24
|
+
component.element.classList.add(positionClass);
|
|
25
|
+
|
|
26
|
+
// Method to update position
|
|
27
|
+
const setPosition = (newPosition: string): void => {
|
|
28
|
+
// Remove current position class
|
|
29
|
+
component.element.classList.remove(positionClass);
|
|
30
|
+
|
|
31
|
+
// Add new position class
|
|
32
|
+
const newPositionClass = `${config.prefix}-snackbar--${newPosition}`;
|
|
33
|
+
component.element.classList.add(newPositionClass);
|
|
34
|
+
|
|
35
|
+
// Update visible state transform for center position
|
|
36
|
+
if (component.element.classList.contains(`${config.prefix}-snackbar--visible`)) {
|
|
37
|
+
if (newPosition === SNACKBAR_POSITIONS.CENTER) {
|
|
38
|
+
component.element.style.transform = 'translateX(-50%) scale(1)';
|
|
39
|
+
} else {
|
|
40
|
+
component.element.style.transform = 'scale(1)';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
...component,
|
|
47
|
+
position: {
|
|
48
|
+
/**
|
|
49
|
+
* Get current position
|
|
50
|
+
* @returns {string} Current position
|
|
51
|
+
*/
|
|
52
|
+
getPosition: (): string => position,
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Set new position
|
|
56
|
+
* @param {string} newPosition - New position to set
|
|
57
|
+
* @returns {BaseComponent} Component instance
|
|
58
|
+
*/
|
|
59
|
+
setPosition: (newPosition: string): BaseComponent => {
|
|
60
|
+
if (Object.values(SNACKBAR_POSITIONS).includes(newPosition)) {
|
|
61
|
+
setPosition(newPosition);
|
|
62
|
+
return component;
|
|
63
|
+
} else {
|
|
64
|
+
console.warn(`Invalid position: ${newPosition}. Using default: ${SNACKBAR_POSITIONS.CENTER}`);
|
|
65
|
+
setPosition(SNACKBAR_POSITIONS.CENTER);
|
|
66
|
+
return component;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/components/snackbar/queue.ts
|
|
2
|
+
import { QueuedSnackbar, SnackbarQueue } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a queue manager for snackbars
|
|
6
|
+
* Ensures only one snackbar is visible at a time
|
|
7
|
+
* @returns {SnackbarQueue} Queue manager with add/clear methods
|
|
8
|
+
*/
|
|
9
|
+
export const createSnackbarQueue = (): SnackbarQueue => {
|
|
10
|
+
const queue: QueuedSnackbar[] = [];
|
|
11
|
+
let isProcessing = false;
|
|
12
|
+
|
|
13
|
+
const processQueue = (): void => {
|
|
14
|
+
if (queue.length === 0) {
|
|
15
|
+
isProcessing = false;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
isProcessing = true;
|
|
20
|
+
const snackbar = queue[0];
|
|
21
|
+
|
|
22
|
+
const handleDismiss = (): void => {
|
|
23
|
+
// Remove from queue
|
|
24
|
+
queue.shift();
|
|
25
|
+
// Remove listener and cleanup
|
|
26
|
+
snackbar.off?.('dismiss', handleDismiss);
|
|
27
|
+
// Reset processing state if queue is empty
|
|
28
|
+
if (queue.length === 0) {
|
|
29
|
+
isProcessing = false;
|
|
30
|
+
} else {
|
|
31
|
+
// Process next after a small delay
|
|
32
|
+
setTimeout(processQueue, 200);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Handle both normal dismiss and action button dismissal
|
|
37
|
+
snackbar.on?.('dismiss', handleDismiss);
|
|
38
|
+
snackbar._show();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
/**
|
|
43
|
+
* Adds a snackbar to the queue
|
|
44
|
+
* @param {QueuedSnackbar} snackbar - Snackbar instance with _show method
|
|
45
|
+
*/
|
|
46
|
+
add(snackbar: QueuedSnackbar): void {
|
|
47
|
+
if (!snackbar._show) {
|
|
48
|
+
throw new Error('Snackbar must implement _show method');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
queue.push(snackbar);
|
|
52
|
+
|
|
53
|
+
// Only start processing if not already processing
|
|
54
|
+
if (!isProcessing) {
|
|
55
|
+
processQueue();
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Clears all pending snackbars
|
|
61
|
+
*/
|
|
62
|
+
clear(): void {
|
|
63
|
+
// Remove all queued items
|
|
64
|
+
queue.length = 0;
|
|
65
|
+
isProcessing = false;
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Gets current queue length
|
|
70
|
+
* @returns {number} Number of snackbars in queue
|
|
71
|
+
*/
|
|
72
|
+
getLength(): number {
|
|
73
|
+
return queue.length;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// src/components/snackbar/snackbar.ts
|
|
2
|
+
import { pipe } from '../../core/compose';
|
|
3
|
+
import { createBase, withElement } from '../../core/compose/component';
|
|
4
|
+
import { withActionButton, withDismissTimer } from './features';
|
|
5
|
+
import { withPosition } from './position';
|
|
6
|
+
import {
|
|
7
|
+
withEvents,
|
|
8
|
+
withText,
|
|
9
|
+
withVariant,
|
|
10
|
+
withLifecycle
|
|
11
|
+
} from '../../core/compose/features';
|
|
12
|
+
import { withAPI } from './api';
|
|
13
|
+
import { createSnackbarQueue } from './queue';
|
|
14
|
+
import { SnackbarConfig, SnackbarComponent, SnackbarQueue } from './types';
|
|
15
|
+
import {
|
|
16
|
+
createBaseConfig,
|
|
17
|
+
getElementConfig,
|
|
18
|
+
getTextConfig,
|
|
19
|
+
getApiConfig
|
|
20
|
+
} from './config';
|
|
21
|
+
|
|
22
|
+
// Create a single queue instance to be shared across all snackbars
|
|
23
|
+
const queue: SnackbarQueue = createSnackbarQueue();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates a new Snackbar component
|
|
27
|
+
* @param {SnackbarConfig} config - Snackbar configuration
|
|
28
|
+
* @returns {SnackbarComponent} Snackbar component instance
|
|
29
|
+
*/
|
|
30
|
+
const createSnackbar = (config: SnackbarConfig): SnackbarComponent => {
|
|
31
|
+
if (!config.message) {
|
|
32
|
+
throw new Error('Snackbar message is required');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const baseConfig = createBaseConfig(config);
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const snackbar = pipe(
|
|
39
|
+
createBase,
|
|
40
|
+
withEvents(),
|
|
41
|
+
withElement(getElementConfig(baseConfig)),
|
|
42
|
+
withVariant(baseConfig),
|
|
43
|
+
withPosition(baseConfig),
|
|
44
|
+
withText(getTextConfig(baseConfig)),
|
|
45
|
+
withActionButton(baseConfig),
|
|
46
|
+
withLifecycle(),
|
|
47
|
+
// First apply timer
|
|
48
|
+
withDismissTimer(baseConfig),
|
|
49
|
+
// Then apply API which needs timer
|
|
50
|
+
comp => withAPI(getApiConfig(comp, queue))(comp)
|
|
51
|
+
)(baseConfig);
|
|
52
|
+
|
|
53
|
+
return snackbar as SnackbarComponent;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error('Snackbar creation error:', error instanceof Error ? error.message : String(error));
|
|
56
|
+
throw new Error(`Failed to create snackbar: ${error instanceof Error ? error.message : String(error)}`);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default createSnackbar;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/components/snackbar/constants.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Snackbar visual variants
|
|
5
|
+
*/
|
|
6
|
+
export const SNACKBAR_VARIANTS = {
|
|
7
|
+
BASIC: 'basic',
|
|
8
|
+
ACTION: 'action' // With action button
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Snackbar display positions
|
|
13
|
+
*/
|
|
14
|
+
export const SNACKBAR_POSITIONS = {
|
|
15
|
+
CENTER: 'center',
|
|
16
|
+
START: 'start',
|
|
17
|
+
END: 'end'
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Validation schema for snackbar configuration
|
|
22
|
+
*/
|
|
23
|
+
export const SNACKBAR_SCHEMA = {
|
|
24
|
+
variant: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
enum: Object.values(SNACKBAR_VARIANTS),
|
|
27
|
+
required: false
|
|
28
|
+
},
|
|
29
|
+
position: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
enum: Object.values(SNACKBAR_POSITIONS),
|
|
32
|
+
required: false
|
|
33
|
+
},
|
|
34
|
+
message: {
|
|
35
|
+
type: 'string',
|
|
36
|
+
required: true
|
|
37
|
+
},
|
|
38
|
+
action: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
required: false
|
|
41
|
+
},
|
|
42
|
+
duration: {
|
|
43
|
+
type: 'number',
|
|
44
|
+
required: false
|
|
45
|
+
},
|
|
46
|
+
class: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
required: false
|
|
49
|
+
}
|
|
50
|
+
} as const;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Snackbar state classes
|
|
54
|
+
*/
|
|
55
|
+
export const SNACKBAR_STATES = {
|
|
56
|
+
VISIBLE: 'visible',
|
|
57
|
+
HIDDEN: 'hidden'
|
|
58
|
+
} as const;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// src/components/switch/api.ts
|
|
2
|
+
import { BaseComponent, SwitchComponent, ApiOptions } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Enhances switch 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): SwitchComponent => ({
|
|
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): SwitchComponent {
|
|
18
|
+
component.setValue?.(value);
|
|
19
|
+
return this;
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
// State management
|
|
23
|
+
check(): SwitchComponent {
|
|
24
|
+
checkable.check();
|
|
25
|
+
return this;
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
uncheck(): SwitchComponent {
|
|
29
|
+
checkable.uncheck();
|
|
30
|
+
return this;
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
toggle(): SwitchComponent {
|
|
34
|
+
checkable.toggle();
|
|
35
|
+
return this;
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
isChecked(): boolean {
|
|
39
|
+
return checkable.isChecked();
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
// Label management
|
|
43
|
+
setLabel(text: string): SwitchComponent {
|
|
44
|
+
component.text?.setText(text);
|
|
45
|
+
return this;
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
getLabel(): string {
|
|
49
|
+
return component.text?.getText() || '';
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Event handling
|
|
53
|
+
on(event: string, handler: Function): SwitchComponent {
|
|
54
|
+
component.on?.(event, handler);
|
|
55
|
+
return this;
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
off(event: string, handler: Function): SwitchComponent {
|
|
59
|
+
component.off?.(event, handler);
|
|
60
|
+
return this;
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// State management
|
|
64
|
+
enable(): SwitchComponent {
|
|
65
|
+
disabled.enable();
|
|
66
|
+
return this;
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
disable(): SwitchComponent {
|
|
70
|
+
disabled.disable();
|
|
71
|
+
return this;
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
destroy(): void {
|
|
75
|
+
lifecycle.destroy();
|
|
76
|
+
}
|
|
77
|
+
});
|