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.
Files changed (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +251 -0
  3. package/index.js +10 -0
  4. package/package.json +17 -0
  5. package/src/components/button/api.js +54 -0
  6. package/src/components/button/button.js +81 -0
  7. package/src/components/button/config.js +8 -0
  8. package/src/components/button/constants.js +63 -0
  9. package/src/components/button/index.js +2 -0
  10. package/src/components/button/styles.scss +231 -0
  11. package/src/components/checkbox/api.js +45 -0
  12. package/src/components/checkbox/checkbox.js +95 -0
  13. package/src/components/checkbox/constants.js +88 -0
  14. package/src/components/checkbox/index.js +2 -0
  15. package/src/components/checkbox/styles.scss +183 -0
  16. package/src/components/container/api.js +42 -0
  17. package/src/components/container/container.js +45 -0
  18. package/src/components/container/index.js +2 -0
  19. package/src/components/container/styles.scss +59 -0
  20. package/src/components/list/constants.js +89 -0
  21. package/src/components/list/index.js +2 -0
  22. package/src/components/list/list-item.js +147 -0
  23. package/src/components/list/list.js +267 -0
  24. package/src/components/list/styles/_list-item.scss +142 -0
  25. package/src/components/list/styles/_list.scss +89 -0
  26. package/src/components/list/styles/_variables.scss +13 -0
  27. package/src/components/list/styles.scss +19 -0
  28. package/src/components/navigation/api.js +43 -0
  29. package/src/components/navigation/constants.js +235 -0
  30. package/src/components/navigation/features/items.js +192 -0
  31. package/src/components/navigation/index.js +2 -0
  32. package/src/components/navigation/nav-item.js +137 -0
  33. package/src/components/navigation/navigation.js +55 -0
  34. package/src/components/navigation/styles/_bar.scss +51 -0
  35. package/src/components/navigation/styles/_base.scss +129 -0
  36. package/src/components/navigation/styles/_drawer.scss +169 -0
  37. package/src/components/navigation/styles/_rail.scss +65 -0
  38. package/src/components/navigation/styles.scss +6 -0
  39. package/src/components/snackbar/api.js +125 -0
  40. package/src/components/snackbar/constants.js +41 -0
  41. package/src/components/snackbar/features.js +69 -0
  42. package/src/components/snackbar/index.js +2 -0
  43. package/src/components/snackbar/position.js +63 -0
  44. package/src/components/snackbar/queue.js +74 -0
  45. package/src/components/snackbar/snackbar.js +70 -0
  46. package/src/components/snackbar/styles.scss +182 -0
  47. package/src/components/switch/api.js +44 -0
  48. package/src/components/switch/constants.js +80 -0
  49. package/src/components/switch/index.js +2 -0
  50. package/src/components/switch/styles.scss +172 -0
  51. package/src/components/switch/switch.js +71 -0
  52. package/src/components/textfield/api.js +49 -0
  53. package/src/components/textfield/constants.js +81 -0
  54. package/src/components/textfield/index.js +2 -0
  55. package/src/components/textfield/styles/base.scss +107 -0
  56. package/src/components/textfield/styles/filled.scss +58 -0
  57. package/src/components/textfield/styles/outlined.scss +66 -0
  58. package/src/components/textfield/styles.scss +6 -0
  59. package/src/components/textfield/textfield.js +68 -0
  60. package/src/core/build/constants.js +51 -0
  61. package/src/core/build/icon.js +78 -0
  62. package/src/core/build/ripple.js +92 -0
  63. package/src/core/build/text.js +54 -0
  64. package/src/core/collection/adapters/base.js +26 -0
  65. package/src/core/collection/adapters/mongodb.js +232 -0
  66. package/src/core/collection/adapters/route.js +201 -0
  67. package/src/core/collection/collection.js +259 -0
  68. package/src/core/collection/list-manager.js +157 -0
  69. package/src/core/compose/base.js +8 -0
  70. package/src/core/compose/component.js +225 -0
  71. package/src/core/compose/features/checkable.js +114 -0
  72. package/src/core/compose/features/disabled.js +25 -0
  73. package/src/core/compose/features/events.js +48 -0
  74. package/src/core/compose/features/icon.js +33 -0
  75. package/src/core/compose/features/index.js +20 -0
  76. package/src/core/compose/features/input.js +92 -0
  77. package/src/core/compose/features/lifecycle.js +69 -0
  78. package/src/core/compose/features/position.js +60 -0
  79. package/src/core/compose/features/ripple.js +32 -0
  80. package/src/core/compose/features/size.js +9 -0
  81. package/src/core/compose/features/style.js +12 -0
  82. package/src/core/compose/features/text.js +17 -0
  83. package/src/core/compose/features/textinput.js +118 -0
  84. package/src/core/compose/features/textlabel.js +28 -0
  85. package/src/core/compose/features/track.js +49 -0
  86. package/src/core/compose/features/variant.js +9 -0
  87. package/src/core/compose/features/withEvents.js +67 -0
  88. package/src/core/compose/index.js +16 -0
  89. package/src/core/compose/pipe.js +69 -0
  90. package/src/core/config.js +140 -0
  91. package/src/core/dom/attributes.js +33 -0
  92. package/src/core/dom/classes.js +70 -0
  93. package/src/core/dom/create.js +133 -0
  94. package/src/core/dom/events.js +175 -0
  95. package/src/core/dom/index.js +5 -0
  96. package/src/core/dom/utils.js +22 -0
  97. package/src/core/index.js +23 -0
  98. package/src/core/layout/index.js +93 -0
  99. package/src/core/state/disabled.js +14 -0
  100. package/src/core/state/emitter.js +63 -0
  101. package/src/core/state/events.js +29 -0
  102. package/src/core/state/index.js +6 -0
  103. package/src/core/state/lifecycle.js +64 -0
  104. package/src/core/state/store.js +112 -0
  105. package/src/core/utils/index.js +39 -0
  106. package/src/core/utils/mobile.js +74 -0
  107. package/src/core/utils/object.js +22 -0
  108. package/src/core/utils/validate.js +37 -0
  109. package/src/index.js +11 -0
  110. package/src/styles/abstract/_base.scss +2 -0
  111. package/src/styles/abstract/_config.scss +28 -0
  112. package/src/styles/abstract/_functions.scss +124 -0
  113. package/src/styles/abstract/_mixins.scss +261 -0
  114. package/src/styles/abstract/_variables.scss +158 -0
  115. package/src/styles/main.scss +78 -0
  116. package/src/styles/themes/_base-theme.scss +49 -0
  117. package/src/styles/themes/_baseline.scss +90 -0
  118. package/src/styles/themes/_forest.scss +71 -0
  119. package/src/styles/themes/_index.scss +6 -0
  120. package/src/styles/themes/_ocean.scss +71 -0
  121. 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,5 @@
1
+ // src/core/dom/index.js
2
+ export { createElement } from './create'
3
+ export { setAttributes, removeAttributes } from './attributes'
4
+ export { addClass, removeClass } from './classes'
5
+ // export { attachEvents } from './events'
@@ -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