mtrl 0.0.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/LICENSE +21 -0
- package/README.md +251 -0
- package/index.js +10 -0
- package/package.json +17 -0
- package/src/components/button/api.js +54 -0
- package/src/components/button/button.js +81 -0
- package/src/components/button/config.js +8 -0
- package/src/components/button/constants.js +63 -0
- package/src/components/button/index.js +2 -0
- package/src/components/button/styles.scss +231 -0
- package/src/components/checkbox/api.js +45 -0
- package/src/components/checkbox/checkbox.js +95 -0
- package/src/components/checkbox/constants.js +88 -0
- package/src/components/checkbox/index.js +2 -0
- package/src/components/checkbox/styles.scss +183 -0
- package/src/components/container/api.js +42 -0
- package/src/components/container/container.js +45 -0
- package/src/components/container/index.js +2 -0
- package/src/components/container/styles.scss +59 -0
- package/src/components/list/constants.js +89 -0
- package/src/components/list/index.js +2 -0
- package/src/components/list/list-item.js +147 -0
- package/src/components/list/list.js +267 -0
- package/src/components/list/styles/_list-item.scss +142 -0
- package/src/components/list/styles/_list.scss +89 -0
- package/src/components/list/styles/_variables.scss +13 -0
- package/src/components/list/styles.scss +19 -0
- package/src/components/navigation/api.js +43 -0
- package/src/components/navigation/constants.js +235 -0
- package/src/components/navigation/features/items.js +192 -0
- package/src/components/navigation/index.js +2 -0
- package/src/components/navigation/nav-item.js +137 -0
- package/src/components/navigation/navigation.js +55 -0
- package/src/components/navigation/styles/_bar.scss +51 -0
- package/src/components/navigation/styles/_base.scss +129 -0
- package/src/components/navigation/styles/_drawer.scss +169 -0
- package/src/components/navigation/styles/_rail.scss +65 -0
- package/src/components/navigation/styles.scss +6 -0
- package/src/components/snackbar/api.js +125 -0
- package/src/components/snackbar/constants.js +41 -0
- package/src/components/snackbar/features.js +69 -0
- package/src/components/snackbar/index.js +2 -0
- package/src/components/snackbar/position.js +63 -0
- package/src/components/snackbar/queue.js +74 -0
- package/src/components/snackbar/snackbar.js +70 -0
- package/src/components/snackbar/styles.scss +182 -0
- package/src/components/switch/api.js +44 -0
- package/src/components/switch/constants.js +80 -0
- package/src/components/switch/index.js +2 -0
- package/src/components/switch/styles.scss +172 -0
- package/src/components/switch/switch.js +71 -0
- package/src/components/textfield/api.js +49 -0
- package/src/components/textfield/constants.js +81 -0
- package/src/components/textfield/index.js +2 -0
- package/src/components/textfield/styles/base.scss +107 -0
- package/src/components/textfield/styles/filled.scss +58 -0
- package/src/components/textfield/styles/outlined.scss +66 -0
- package/src/components/textfield/styles.scss +6 -0
- package/src/components/textfield/textfield.js +68 -0
- package/src/core/build/constants.js +51 -0
- package/src/core/build/icon.js +78 -0
- package/src/core/build/ripple.js +92 -0
- package/src/core/build/text.js +54 -0
- package/src/core/collection/adapters/base.js +26 -0
- package/src/core/collection/adapters/mongodb.js +232 -0
- package/src/core/collection/adapters/route.js +201 -0
- package/src/core/collection/collection.js +259 -0
- package/src/core/collection/list-manager.js +157 -0
- package/src/core/compose/base.js +8 -0
- package/src/core/compose/component.js +225 -0
- package/src/core/compose/features/checkable.js +114 -0
- package/src/core/compose/features/disabled.js +25 -0
- package/src/core/compose/features/events.js +48 -0
- package/src/core/compose/features/icon.js +33 -0
- package/src/core/compose/features/index.js +20 -0
- package/src/core/compose/features/input.js +92 -0
- package/src/core/compose/features/lifecycle.js +69 -0
- package/src/core/compose/features/position.js +60 -0
- package/src/core/compose/features/ripple.js +32 -0
- package/src/core/compose/features/size.js +9 -0
- package/src/core/compose/features/style.js +12 -0
- package/src/core/compose/features/text.js +17 -0
- package/src/core/compose/features/textinput.js +118 -0
- package/src/core/compose/features/textlabel.js +28 -0
- package/src/core/compose/features/track.js +49 -0
- package/src/core/compose/features/variant.js +9 -0
- package/src/core/compose/features/withEvents.js +67 -0
- package/src/core/compose/index.js +16 -0
- package/src/core/compose/pipe.js +69 -0
- package/src/core/config.js +140 -0
- package/src/core/dom/attributes.js +33 -0
- package/src/core/dom/classes.js +70 -0
- package/src/core/dom/create.js +133 -0
- package/src/core/dom/events.js +175 -0
- package/src/core/dom/index.js +5 -0
- package/src/core/dom/utils.js +22 -0
- package/src/core/index.js +23 -0
- package/src/core/layout/index.js +93 -0
- package/src/core/state/disabled.js +14 -0
- package/src/core/state/emitter.js +63 -0
- package/src/core/state/events.js +29 -0
- package/src/core/state/index.js +6 -0
- package/src/core/state/lifecycle.js +64 -0
- package/src/core/state/store.js +112 -0
- package/src/core/utils/index.js +39 -0
- package/src/core/utils/mobile.js +74 -0
- package/src/core/utils/object.js +22 -0
- package/src/core/utils/validate.js +37 -0
- package/src/index.js +11 -0
- package/src/styles/abstract/_base.scss +2 -0
- package/src/styles/abstract/_config.scss +28 -0
- package/src/styles/abstract/_functions.scss +124 -0
- package/src/styles/abstract/_mixins.scss +261 -0
- package/src/styles/abstract/_variables.scss +158 -0
- package/src/styles/main.scss +78 -0
- package/src/styles/themes/_base-theme.scss +49 -0
- package/src/styles/themes/_baseline.scss +90 -0
- package/src/styles/themes/_forest.scss +71 -0
- package/src/styles/themes/_index.scss +6 -0
- package/src/styles/themes/_ocean.scss +71 -0
- package/src/styles/themes/_sunset.scss +55 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// src/core/config.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Library prefix used for all components
|
|
5
|
+
*/
|
|
6
|
+
export const PREFIX = 'mtrl'
|
|
7
|
+
|
|
8
|
+
export const getComponentClass = (type) => `${PREFIX}-${type}`
|
|
9
|
+
export const getModifierClass = (baseClass, modifier) => `${baseClass}--${modifier}`
|
|
10
|
+
export const getElementClass = (baseClass, element) => `${baseClass}-${element}`
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Component type identifiers
|
|
14
|
+
* @enum {string}
|
|
15
|
+
*/
|
|
16
|
+
export const COMPONENTS = {
|
|
17
|
+
BUTTON: 'button',
|
|
18
|
+
TEXTFIELD: 'textfield',
|
|
19
|
+
CONTAINER: 'container',
|
|
20
|
+
SNACKBAR: 'snackbar',
|
|
21
|
+
SWITCH: 'switch'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Theme configuration
|
|
26
|
+
* @typedef {Object} ThemeConfig
|
|
27
|
+
* @property {string} name - Theme name
|
|
28
|
+
* @property {Object} variables - Theme CSS variables
|
|
29
|
+
* @property {Object} variants - Theme variants
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Creates a component configuration object
|
|
34
|
+
* @param {string} type - Component type from COMPONENT_TYPES
|
|
35
|
+
* @returns {Object} Component configuration interface
|
|
36
|
+
*/
|
|
37
|
+
export const createComponentConfig = (type) => {
|
|
38
|
+
const baseClass = `${PREFIX}-${type}`
|
|
39
|
+
|
|
40
|
+
// Create the base config object
|
|
41
|
+
const config = {
|
|
42
|
+
prefix: PREFIX,
|
|
43
|
+
type,
|
|
44
|
+
baseClass,
|
|
45
|
+
|
|
46
|
+
// Class name generators
|
|
47
|
+
getClass: () => baseClass,
|
|
48
|
+
getModifierClass: (modifier) => `${baseClass}--${modifier}`,
|
|
49
|
+
getElementClass: (element) => `${baseClass}-${element}`,
|
|
50
|
+
|
|
51
|
+
// Theme support
|
|
52
|
+
withTheme: (theme) => ({
|
|
53
|
+
...config,
|
|
54
|
+
theme,
|
|
55
|
+
getThemeClass: (variant) => `${baseClass}--theme-${theme}-${variant}`
|
|
56
|
+
}),
|
|
57
|
+
|
|
58
|
+
// Variant support
|
|
59
|
+
withVariants: (...variants) => ({
|
|
60
|
+
...config,
|
|
61
|
+
variants,
|
|
62
|
+
hasVariant: (variant) => variants.includes(variant),
|
|
63
|
+
getVariantClass: (variant) =>
|
|
64
|
+
variants.includes(variant) ? `${baseClass}--${variant}` : null
|
|
65
|
+
}),
|
|
66
|
+
|
|
67
|
+
// State support
|
|
68
|
+
withStates: (...states) => ({
|
|
69
|
+
...config,
|
|
70
|
+
states,
|
|
71
|
+
getStateClass: (state) =>
|
|
72
|
+
states.includes(state) ? `${baseClass}--state-${state}` : null
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return config
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Common component states
|
|
81
|
+
* @enum {string}
|
|
82
|
+
*/
|
|
83
|
+
export const STATES = {
|
|
84
|
+
DISABLED: 'disabled',
|
|
85
|
+
FOCUSED: 'focused',
|
|
86
|
+
ACTIVE: 'active',
|
|
87
|
+
LOADING: 'loading',
|
|
88
|
+
ERROR: 'error'
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* CSS class generation utilities
|
|
93
|
+
*/
|
|
94
|
+
export const classNames = {
|
|
95
|
+
/**
|
|
96
|
+
* Creates a BEM-style class name
|
|
97
|
+
* @param {string} block - Block name
|
|
98
|
+
* @param {string} [element] - Element name
|
|
99
|
+
* @param {string} [modifier] - Modifier name
|
|
100
|
+
* @returns {string} BEM class name
|
|
101
|
+
*/
|
|
102
|
+
bem: (block, element, modifier) => {
|
|
103
|
+
let className = block
|
|
104
|
+
if (element) className += `-${element}`
|
|
105
|
+
if (modifier) className += `--${modifier}`
|
|
106
|
+
return className
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Joins class names, filtering out falsy values
|
|
111
|
+
* @param {...string} classes - Class names to join
|
|
112
|
+
* @returns {string} Joined class names
|
|
113
|
+
*/
|
|
114
|
+
join: (...classes) => classes.filter(Boolean).join(' ')
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Creates a themed component configuration
|
|
119
|
+
* @param {string} type - Component type
|
|
120
|
+
* @param {ThemeConfig} theme - Theme configuration
|
|
121
|
+
*/
|
|
122
|
+
export const createThemedComponent = (type, theme) => {
|
|
123
|
+
const config = createComponentConfig(type)
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
...config,
|
|
127
|
+
theme,
|
|
128
|
+
|
|
129
|
+
// Theme-specific class generators
|
|
130
|
+
getThemeClass: (variant) =>
|
|
131
|
+
`${config.getClass()}--theme-${theme.name}${variant ? `-${variant}` : ''}`,
|
|
132
|
+
|
|
133
|
+
// Theme CSS variables
|
|
134
|
+
getCssVariables: () =>
|
|
135
|
+
Object.entries(theme.variables).reduce((acc, [key, value]) => ({
|
|
136
|
+
...acc,
|
|
137
|
+
[`--${PREFIX}-${type}-${key}`]: value
|
|
138
|
+
}), {})
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// src/core/dom/attributes.js
|
|
2
|
+
/**
|
|
3
|
+
* @module core/dom
|
|
4
|
+
* @description DOM manipulation utilities
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Sets multiple attributes on an element
|
|
9
|
+
* @memberof module:core/dom
|
|
10
|
+
* @function setAttributes
|
|
11
|
+
* @param {HTMLElement} element - Target element
|
|
12
|
+
* @param {Object} attrs - Attributes to set
|
|
13
|
+
* @returns {HTMLElement} Modified element
|
|
14
|
+
*/
|
|
15
|
+
export const setAttributes = (element, attrs = {}) => {
|
|
16
|
+
Object.entries(attrs).forEach(([key, value]) => {
|
|
17
|
+
if (value != null) element.setAttribute(key, value)
|
|
18
|
+
})
|
|
19
|
+
return element
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Removes multiple attributes from an element
|
|
24
|
+
* @memberof module:core/dom
|
|
25
|
+
* @function removeAttributes
|
|
26
|
+
* @param {HTMLElement} element - Target element
|
|
27
|
+
* @param {string[]} attrs - Attributes to remove
|
|
28
|
+
* @returns {HTMLElement} Modified element
|
|
29
|
+
*/
|
|
30
|
+
export const removeAttributes = (element, attrs = []) => {
|
|
31
|
+
attrs.forEach(attr => element.removeAttribute(attr))
|
|
32
|
+
return element
|
|
33
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/core/dom/classes.js
|
|
2
|
+
/**
|
|
3
|
+
* @module core/dom
|
|
4
|
+
* @description DOM manipulation utilities
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { normalizeClasses } from '../utils'
|
|
8
|
+
/**
|
|
9
|
+
* Adds multiple classes to an element
|
|
10
|
+
* @memberof module:core/dom
|
|
11
|
+
* @function addClass
|
|
12
|
+
* @param {HTMLElement} element - Target element
|
|
13
|
+
* @param {...string} classes - Classes to add
|
|
14
|
+
* @returns {HTMLElement} Modified element
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Adds multiple classes to an element
|
|
19
|
+
* @param {HTMLElement} element - Target element
|
|
20
|
+
* @param {...string} classes - Classes to add
|
|
21
|
+
* @returns {HTMLElement} Modified element
|
|
22
|
+
*/
|
|
23
|
+
export const addClass = (element, ...classes) => {
|
|
24
|
+
const normalizedClasses = normalizeClasses(...classes)
|
|
25
|
+
if (normalizedClasses.length) {
|
|
26
|
+
element.classList.add(...normalizedClasses)
|
|
27
|
+
}
|
|
28
|
+
return element
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Removes multiple classes from an element
|
|
33
|
+
* @memberof module:core/dom
|
|
34
|
+
* @function removeClass
|
|
35
|
+
* @param {HTMLElement} element - Target element
|
|
36
|
+
* @param {...string} classes - Classes to remove
|
|
37
|
+
* @returns {HTMLElement} Modified element
|
|
38
|
+
*/
|
|
39
|
+
export const removeClass = (element, ...classes) => {
|
|
40
|
+
const normalizedClasses = normalizeClasses(...classes)
|
|
41
|
+
if (normalizedClasses.length) {
|
|
42
|
+
element.classList.remove(...normalizedClasses)
|
|
43
|
+
}
|
|
44
|
+
return element
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Toggles multiple classes on an element
|
|
49
|
+
* @param {HTMLElement} element - Target element
|
|
50
|
+
* @param {...string} classes - Classes to toggle
|
|
51
|
+
* @returns {HTMLElement} Modified element
|
|
52
|
+
*/
|
|
53
|
+
export const toggleClass = (element, ...classes) => {
|
|
54
|
+
const normalizedClasses = normalizeClasses(...classes)
|
|
55
|
+
normalizedClasses.forEach(cls => {
|
|
56
|
+
element.classList.toggle(cls)
|
|
57
|
+
})
|
|
58
|
+
return element
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Checks if an element has all specified classes
|
|
63
|
+
* @param {HTMLElement} element - Target element
|
|
64
|
+
* @param {...string} classes - Classes to check
|
|
65
|
+
* @returns {boolean} True if element has all specified classes
|
|
66
|
+
*/
|
|
67
|
+
export const hasClass = (element, ...classes) => {
|
|
68
|
+
const normalizedClasses = normalizeClasses(...classes)
|
|
69
|
+
return normalizedClasses.every(cls => element.classList.contains(cls))
|
|
70
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// src/core/dom/create.js
|
|
2
|
+
/**
|
|
3
|
+
* @module core/dom
|
|
4
|
+
* @description DOM manipulation utilities
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { setAttributes } from './attributes'
|
|
8
|
+
import { normalizeClasses } from '../utils'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates a DOM element with the specified options
|
|
12
|
+
* @memberof module:core/dom
|
|
13
|
+
* @param {Object} options - Element creation options
|
|
14
|
+
* @param {string} [options.tag='div'] - HTML tag name
|
|
15
|
+
* @param {HTMLElement} [options.container] - Container to append element to
|
|
16
|
+
* @param {string} [options.html] - Inner HTML content
|
|
17
|
+
* @param {string} [options.text] - Text content
|
|
18
|
+
* @param {string} [options.id] - Element ID
|
|
19
|
+
* @param {Object} [options.data] - Dataset attributes
|
|
20
|
+
* @param {string|string[]} [options.className] - CSS classes
|
|
21
|
+
* @param {Object} [options.attrs] - HTML attributes
|
|
22
|
+
* @param {Object} [options.forwardEvents] - Events to forward when component has emit method
|
|
23
|
+
* @param {Function} [options.onCreate] - Callback after element creation
|
|
24
|
+
* @returns {HTMLElement} Created element
|
|
25
|
+
*/
|
|
26
|
+
export const createElement = (options = {}) => {
|
|
27
|
+
const {
|
|
28
|
+
tag = 'div',
|
|
29
|
+
container = null,
|
|
30
|
+
html = '',
|
|
31
|
+
text = '',
|
|
32
|
+
id = '',
|
|
33
|
+
data = {},
|
|
34
|
+
className,
|
|
35
|
+
attrs = {},
|
|
36
|
+
forwardEvents = {},
|
|
37
|
+
onCreate,
|
|
38
|
+
context,
|
|
39
|
+
...rest
|
|
40
|
+
} = options
|
|
41
|
+
|
|
42
|
+
const element = document.createElement(tag)
|
|
43
|
+
|
|
44
|
+
// Handle content
|
|
45
|
+
if (html) element.innerHTML = html
|
|
46
|
+
if (text) element.textContent = text
|
|
47
|
+
if (id) element.id = id
|
|
48
|
+
|
|
49
|
+
// Handle classes
|
|
50
|
+
if (className) {
|
|
51
|
+
const normalizedClasses = normalizeClasses(className)
|
|
52
|
+
if (normalizedClasses.length) {
|
|
53
|
+
element.classList.add(...normalizedClasses)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Handle data attributes
|
|
58
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
59
|
+
element.dataset[key] = value
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// Handle all other attributes
|
|
63
|
+
const allAttrs = { ...attrs, ...rest }
|
|
64
|
+
Object.entries(allAttrs).forEach(([key, value]) => {
|
|
65
|
+
if (value != null) element.setAttribute(key, value)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
// Handle event forwarding if context has emit method
|
|
69
|
+
if (context?.emit && forwardEvents) {
|
|
70
|
+
Object.entries(forwardEvents).forEach(([nativeEvent, eventConfig]) => {
|
|
71
|
+
const shouldForward = typeof eventConfig === 'function'
|
|
72
|
+
? eventConfig
|
|
73
|
+
: () => true
|
|
74
|
+
|
|
75
|
+
element.addEventListener(nativeEvent, (event) => {
|
|
76
|
+
if (shouldForward({ ...context, element }, event)) {
|
|
77
|
+
context.emit(nativeEvent, { event })
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Append to container if provided
|
|
84
|
+
if (container) {
|
|
85
|
+
container.appendChild(element)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (typeof onCreate === 'function') {
|
|
89
|
+
log.info('onCreate', element, context)
|
|
90
|
+
onCreate(element, context)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return element
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Higher-order function to add attributes to an element
|
|
98
|
+
* @param {Object} attrs - Attributes to add
|
|
99
|
+
* @returns {Function} Element transformer
|
|
100
|
+
*/
|
|
101
|
+
export const withAttributes = (attrs) => (element) => {
|
|
102
|
+
setAttributes(element, attrs)
|
|
103
|
+
return element
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Higher-order function to add classes to an element
|
|
108
|
+
* @memberof module:core/dom
|
|
109
|
+
* @param {...string} classes - Classes to add
|
|
110
|
+
* @returns {Function} Element transformer
|
|
111
|
+
*/
|
|
112
|
+
export const withClasses = (...classes) => (element) => {
|
|
113
|
+
const normalizedClasses = normalizeClasses(...classes)
|
|
114
|
+
if (normalizedClasses.length) {
|
|
115
|
+
element.classList.add(...normalizedClasses)
|
|
116
|
+
}
|
|
117
|
+
return element
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Higher-order function to add content to an element
|
|
122
|
+
* @memberof module:core/dom
|
|
123
|
+
* @param {Node|string} content - Content to add
|
|
124
|
+
* @returns {Function} Element transformer
|
|
125
|
+
*/
|
|
126
|
+
export const withContent = (content) => (element) => {
|
|
127
|
+
if (content instanceof Node) {
|
|
128
|
+
element.appendChild(content)
|
|
129
|
+
} else {
|
|
130
|
+
element.textContent = content
|
|
131
|
+
}
|
|
132
|
+
return element
|
|
133
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// src/core/state/events.js
|
|
2
|
+
/**
|
|
3
|
+
* @module core/dom
|
|
4
|
+
* @description DOM manipulation utilities
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates an event manager to handle DOM events with enhanced functionality.
|
|
9
|
+
* Provides a robust interface for managing event listeners with error handling,
|
|
10
|
+
* cleanup, and lifecycle management.
|
|
11
|
+
*
|
|
12
|
+
* @param {HTMLElement} element - DOM element to attach events to
|
|
13
|
+
* @returns {Object} Event manager interface with the following methods:
|
|
14
|
+
*
|
|
15
|
+
* @property {Function} on - Adds an event listener with options
|
|
16
|
+
* @property {Function} off - Removes an event listener
|
|
17
|
+
* @property {Function} pause - Temporarily disables all event listeners
|
|
18
|
+
* @property {Function} resume - Re-enables all event listeners
|
|
19
|
+
* @property {Function} destroy - Removes all event listeners and cleans up
|
|
20
|
+
* @property {Function} getHandlers - Gets all active handlers
|
|
21
|
+
* @property {Function} hasHandler - Checks if a specific handler exists
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const manager = createEventManager(myElement);
|
|
25
|
+
*
|
|
26
|
+
* // Add a listener
|
|
27
|
+
* manager.on('click', (e) => console.log('clicked'), { capture: true });
|
|
28
|
+
*
|
|
29
|
+
* // Remove a listener
|
|
30
|
+
* manager.off('click', myHandler);
|
|
31
|
+
*
|
|
32
|
+
* // Pause all events
|
|
33
|
+
* manager.pause();
|
|
34
|
+
*
|
|
35
|
+
* // Resume all events
|
|
36
|
+
* manager.resume();
|
|
37
|
+
*
|
|
38
|
+
* // Cleanup
|
|
39
|
+
* manager.destroy();
|
|
40
|
+
*/
|
|
41
|
+
export const createEventManager = (element) => {
|
|
42
|
+
// Store handlers with their metadata
|
|
43
|
+
const handlers = new Map()
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Private helper to create a unique handler identifier
|
|
47
|
+
* @memberof module:core/dom
|
|
48
|
+
* @private
|
|
49
|
+
* @param {string} event - Event name
|
|
50
|
+
* @param {Function} handler - Event handler
|
|
51
|
+
* @returns {string} Unique identifier
|
|
52
|
+
*/
|
|
53
|
+
const createHandlerId = (event, handler) =>
|
|
54
|
+
`${event}_${handler.toString()}`
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Wraps an event handler with error boundary and logging
|
|
58
|
+
* @memberof module:core/dom
|
|
59
|
+
* @private
|
|
60
|
+
* @param {Function} handler - Original event handler
|
|
61
|
+
* @param {string} event - Event name for error context
|
|
62
|
+
* @returns {Function} Enhanced handler with error boundary
|
|
63
|
+
*/
|
|
64
|
+
const enhanceHandler = (handler, event) => (e) => {
|
|
65
|
+
try {
|
|
66
|
+
handler(e)
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error(`Error in ${event} handler:`, error)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Safely removes event listener
|
|
74
|
+
* @memberof module:core/dom
|
|
75
|
+
* @param {string} event - Event name
|
|
76
|
+
* @param {Function} handler - Event handler
|
|
77
|
+
*/
|
|
78
|
+
const safeRemoveListener = (event, handler) => {
|
|
79
|
+
try {
|
|
80
|
+
element.removeEventListener(event, handler)
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.warn(`Failed to remove ${event} listener:`, error)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
/**
|
|
88
|
+
* Adds an event listener with options
|
|
89
|
+
* @memberof module:core/dom
|
|
90
|
+
* @param {string} event - Event name
|
|
91
|
+
* @param {Function} handler - Event handler
|
|
92
|
+
* @param {Object} [options] - addEventListener options
|
|
93
|
+
*/
|
|
94
|
+
on (event, handler, options = {}) {
|
|
95
|
+
const enhanced = enhanceHandler(handler, event)
|
|
96
|
+
const id = createHandlerId(event, handler)
|
|
97
|
+
|
|
98
|
+
handlers.set(id, {
|
|
99
|
+
original: handler,
|
|
100
|
+
enhanced,
|
|
101
|
+
event,
|
|
102
|
+
options
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
element.addEventListener(event, enhanced, options)
|
|
106
|
+
return this
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Removes an event listener
|
|
111
|
+
* @memberof module:core/dom
|
|
112
|
+
* @param {string} event - Event name
|
|
113
|
+
* @param {Function} handler - Event handler
|
|
114
|
+
*/
|
|
115
|
+
off (event, handler) {
|
|
116
|
+
const id = createHandlerId(event, handler)
|
|
117
|
+
const stored = handlers.get(id)
|
|
118
|
+
|
|
119
|
+
if (stored) {
|
|
120
|
+
safeRemoveListener(event, stored.enhanced)
|
|
121
|
+
handlers.delete(id)
|
|
122
|
+
}
|
|
123
|
+
return this
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Temporarily disables all event listeners
|
|
128
|
+
*/
|
|
129
|
+
pause () {
|
|
130
|
+
handlers.forEach(({ enhanced, event, options }) => {
|
|
131
|
+
safeRemoveListener(event, enhanced)
|
|
132
|
+
})
|
|
133
|
+
return this
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Re-enables all event listeners
|
|
138
|
+
*/
|
|
139
|
+
resume () {
|
|
140
|
+
handlers.forEach(({ enhanced, event, options }) => {
|
|
141
|
+
element.addEventListener(event, enhanced, options)
|
|
142
|
+
})
|
|
143
|
+
return this
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Removes all event listeners and cleans up
|
|
148
|
+
*/
|
|
149
|
+
destroy () {
|
|
150
|
+
handlers.forEach(({ enhanced, event }) => {
|
|
151
|
+
safeRemoveListener(event, enhanced)
|
|
152
|
+
})
|
|
153
|
+
handlers.clear()
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Gets all active handlers
|
|
158
|
+
* @returns {Map} Map of active handlers
|
|
159
|
+
*/
|
|
160
|
+
getHandlers () {
|
|
161
|
+
return new Map(handlers)
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Checks if a specific handler exists
|
|
166
|
+
* @param {string} event - Event name
|
|
167
|
+
* @param {Function} handler - Event handler
|
|
168
|
+
* @returns {boolean} Whether handler exists
|
|
169
|
+
*/
|
|
170
|
+
hasHandler (event, handler) {
|
|
171
|
+
const id = createHandlerId(event, handler)
|
|
172
|
+
return handlers.has(id)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes class names input by handling various formats:
|
|
3
|
+
* - String with space-separated classes
|
|
4
|
+
* - Array of strings
|
|
5
|
+
* - Mixed array of strings and space-separated classes
|
|
6
|
+
* @param {...(string|string[])} classes - Classes to normalize
|
|
7
|
+
* @returns {string[]} Array of unique, non-empty class names
|
|
8
|
+
*/
|
|
9
|
+
export const normalizeClasses = (...classes) => {
|
|
10
|
+
return [...new Set(
|
|
11
|
+
classes
|
|
12
|
+
.flat()
|
|
13
|
+
.reduce((acc, cls) => {
|
|
14
|
+
if (typeof cls === 'string') {
|
|
15
|
+
// Split space-separated classes and add them individually
|
|
16
|
+
acc.push(...cls.split(/\s+/))
|
|
17
|
+
}
|
|
18
|
+
return acc
|
|
19
|
+
}, [])
|
|
20
|
+
.filter(Boolean) // Remove empty strings
|
|
21
|
+
)]
|
|
22
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/core/index.js
|
|
2
|
+
/**
|
|
3
|
+
* @module core
|
|
4
|
+
* @description Core utilities and building blocks for the component system
|
|
5
|
+
*/
|
|
6
|
+
// Build
|
|
7
|
+
export { createText } from './build/text'
|
|
8
|
+
export { createIcon } from './build/icon'
|
|
9
|
+
|
|
10
|
+
// Classes management
|
|
11
|
+
export { addClass, removeClass } from './dom/classes'
|
|
12
|
+
|
|
13
|
+
// State Management
|
|
14
|
+
export { createDisabled } from './state/disabled'
|
|
15
|
+
export { createEventManager } from './state/events'
|
|
16
|
+
export { createLifecycle } from './state/lifecycle'
|
|
17
|
+
export { createEmitter } from './state/emitter'
|
|
18
|
+
|
|
19
|
+
// Composition Utilities
|
|
20
|
+
export { pipe, compose, transform } from './compose'
|
|
21
|
+
|
|
22
|
+
// General Utilities
|
|
23
|
+
export { classNames } from './utils'
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { isObject } from '../utils'
|
|
2
|
+
|
|
3
|
+
const createComponent = (Component, options = {}) => {
|
|
4
|
+
// Check if Component is a class (has prototype) or function
|
|
5
|
+
const isClass = Component.prototype && Component.prototype.constructor === Component
|
|
6
|
+
|
|
7
|
+
if (isClass) {
|
|
8
|
+
return new Component(options)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return Component(options)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Recursively creates components based on a provided schema.
|
|
16
|
+
*
|
|
17
|
+
* @param {Array} schema - An array of components or sub-schemas.
|
|
18
|
+
* @param {HTMLElement} container - The container for the current level of components.
|
|
19
|
+
* @param {Object} [structure={}] - An accumulator object for components, keyed by name.
|
|
20
|
+
* @param {number} [level=0] - The current level of recursion.
|
|
21
|
+
* @returns {Object} The structure containing all created components.
|
|
22
|
+
*/
|
|
23
|
+
const createLayout = (schema, container, structure = {}, level = 0, components = []) => {
|
|
24
|
+
level++
|
|
25
|
+
let component
|
|
26
|
+
const object = {}
|
|
27
|
+
const fragment = document.createDocumentFragment()
|
|
28
|
+
|
|
29
|
+
if (!Array.isArray(schema)) {
|
|
30
|
+
console.error('Schema is not an array!', container, level, schema)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (let i = 0; i < schema.length; i++) {
|
|
34
|
+
let name
|
|
35
|
+
let options = {}
|
|
36
|
+
|
|
37
|
+
if (schema[i] instanceof Object && typeof schema[i] === 'function') {
|
|
38
|
+
if (isObject(schema[i + 1])) {
|
|
39
|
+
options = schema[i + 1]
|
|
40
|
+
} else if (isObject(schema[i + 2])) {
|
|
41
|
+
options = schema[i + 2]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof schema[i + 1] === 'string') {
|
|
45
|
+
name = schema[i + 1]
|
|
46
|
+
if (!schema[i].isElement && !schema[i].isComponent) {
|
|
47
|
+
options.name = name
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
component = createComponent(schema[i], options)
|
|
52
|
+
|
|
53
|
+
const element = component.element || component
|
|
54
|
+
if (level === 1) structure.element = element
|
|
55
|
+
|
|
56
|
+
if (name) {
|
|
57
|
+
structure[name] = component
|
|
58
|
+
components.push([name, component])
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (component) {
|
|
62
|
+
if (component.insert) component.insert(fragment)
|
|
63
|
+
else fragment.appendChild(element)
|
|
64
|
+
|
|
65
|
+
if (container) {
|
|
66
|
+
// console.log('container', container)
|
|
67
|
+
component._container = container
|
|
68
|
+
if (component.onInserted) component.onInserted(container)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} else if (Array.isArray(schema[i])) {
|
|
72
|
+
if (!component) component = container
|
|
73
|
+
createLayout(schema[i], component, structure, level, components)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (container && fragment.hasChildNodes()) {
|
|
78
|
+
const wrapper = container.element || container
|
|
79
|
+
wrapper.appendChild(fragment)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function get (name) {
|
|
83
|
+
return structure[name] || null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
object.component = structure
|
|
87
|
+
object.components = components
|
|
88
|
+
object.get = get
|
|
89
|
+
|
|
90
|
+
return object
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default createLayout
|