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,63 @@
|
|
|
1
|
+
// src/core/state/emitter.js
|
|
2
|
+
/**
|
|
3
|
+
* @module core/state
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates an event emitter with subscription management
|
|
8
|
+
* @memberof module:core/state
|
|
9
|
+
* @function createEmitter
|
|
10
|
+
* @returns {Object} Event emitter interface
|
|
11
|
+
* @property {Function} on - Subscribe to an event
|
|
12
|
+
* @property {Function} off - Unsubscribe from an event
|
|
13
|
+
* @property {Function} emit - Emit an event
|
|
14
|
+
* @property {Function} clear - Clear all subscriptions
|
|
15
|
+
* @example
|
|
16
|
+
* const emitter = createEmitter()
|
|
17
|
+
* const unsubscribe = emitter.on('change', (data) => console.log(data))
|
|
18
|
+
* emitter.emit('change', { value: 42 })
|
|
19
|
+
* unsubscribe()
|
|
20
|
+
*/
|
|
21
|
+
export const createEmitter = () => {
|
|
22
|
+
const events = new Map()
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
/**
|
|
26
|
+
* Subscribe to an event
|
|
27
|
+
* @param {string} event - Event name
|
|
28
|
+
* @param {Function} callback - Event handler
|
|
29
|
+
* @returns {Function} Unsubscribe function
|
|
30
|
+
*/
|
|
31
|
+
on: (event, callback) => {
|
|
32
|
+
const callbacks = events.get(event) || []
|
|
33
|
+
events.set(event, [...callbacks, callback])
|
|
34
|
+
|
|
35
|
+
return () => {
|
|
36
|
+
const callbacks = events.get(event) || []
|
|
37
|
+
events.set(event, callbacks.filter(cb => cb !== callback))
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
off (event, callback) {
|
|
42
|
+
const callbacks = events.get(event) || []
|
|
43
|
+
events.set(event, callbacks.filter(cb => cb !== callback))
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Emit an event
|
|
48
|
+
* @param {string} event - Event name
|
|
49
|
+
* @param {...any} args - Event arguments
|
|
50
|
+
*/
|
|
51
|
+
emit: (event, ...args) => {
|
|
52
|
+
const callbacks = events.get(event) || []
|
|
53
|
+
callbacks.forEach(cb => cb(...args))
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Clear all event listeners
|
|
58
|
+
*/
|
|
59
|
+
clear: () => {
|
|
60
|
+
events.clear()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/core/state/events.js
|
|
2
|
+
export const createEventManager = (element) => {
|
|
3
|
+
const handlers = new Map()
|
|
4
|
+
|
|
5
|
+
return {
|
|
6
|
+
on (event, handler) {
|
|
7
|
+
element.addEventListener(event, handler)
|
|
8
|
+
handlers.set(handler, event)
|
|
9
|
+
return this
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
off (event, handler) {
|
|
13
|
+
element.removeEventListener(event, handler)
|
|
14
|
+
handlers.delete(handler)
|
|
15
|
+
return this
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
destroy () {
|
|
19
|
+
handlers.forEach((event, handler) => {
|
|
20
|
+
element.removeEventListener(event, handler)
|
|
21
|
+
})
|
|
22
|
+
handlers.clear()
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
getHandlers () {
|
|
26
|
+
return handlers
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// src/core/state/lifecycle.js
|
|
2
|
+
import { createEmitter } from './emitter'
|
|
3
|
+
|
|
4
|
+
export const createLifecycle = (element, managers = {}) => {
|
|
5
|
+
let mounted = false
|
|
6
|
+
const emitter = createEmitter()
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
// Mount/Unmount state management
|
|
10
|
+
onMount: (handler) => emitter.on('mount', handler),
|
|
11
|
+
onUnmount: (handler) => emitter.on('unmount', handler),
|
|
12
|
+
|
|
13
|
+
mount: () => {
|
|
14
|
+
if (!mounted) {
|
|
15
|
+
mounted = true
|
|
16
|
+
emitter.emit('mount')
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
unmount: () => {
|
|
21
|
+
if (mounted) {
|
|
22
|
+
mounted = false
|
|
23
|
+
emitter.emit('unmount')
|
|
24
|
+
emitter.clear()
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
isMounted: () => mounted,
|
|
29
|
+
|
|
30
|
+
// Cleanup and destruction
|
|
31
|
+
destroy() {
|
|
32
|
+
// First trigger unmount
|
|
33
|
+
if (mounted) {
|
|
34
|
+
this.unmount()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Clean up all event listeners
|
|
38
|
+
if (managers.events) {
|
|
39
|
+
managers.events.destroy()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Clean up text element
|
|
43
|
+
if (managers.text) {
|
|
44
|
+
const textElement = managers.text.getElement()
|
|
45
|
+
if (textElement) {
|
|
46
|
+
textElement.remove()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Clean up icon element
|
|
51
|
+
if (managers.icon) {
|
|
52
|
+
const iconElement = managers.icon.getElement()
|
|
53
|
+
if (iconElement) {
|
|
54
|
+
iconElement.remove()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Remove the main element
|
|
59
|
+
if (element) {
|
|
60
|
+
element.remove()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// src/core/state/store.js
|
|
2
|
+
import { createEmitter } from './emitter'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a state store with support for derived state and middleware
|
|
6
|
+
* @template T
|
|
7
|
+
* @param {T} initialState - Initial state object
|
|
8
|
+
* @param {Object} [options] - Store options
|
|
9
|
+
* @param {Function[]} [options.middleware] - Middleware functions
|
|
10
|
+
* @returns {Object} State store interface
|
|
11
|
+
*/
|
|
12
|
+
export const createStore = (initialState = {}, options = {}) => {
|
|
13
|
+
let state = { ...initialState }
|
|
14
|
+
const emitter = createEmitter()
|
|
15
|
+
const derivedStates = new Map()
|
|
16
|
+
const middleware = options.middleware || []
|
|
17
|
+
|
|
18
|
+
const notifyListeners = (newState, oldState) => {
|
|
19
|
+
emitter.emit('change', newState, oldState)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const applyMiddleware = (newState, oldState) => {
|
|
23
|
+
return middleware.reduce((state, fn) => fn(state, oldState), newState)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
/**
|
|
28
|
+
* Get current state including derived values
|
|
29
|
+
* @returns {T} Current state
|
|
30
|
+
*/
|
|
31
|
+
getState: () => {
|
|
32
|
+
const derivedValues = {}
|
|
33
|
+
derivedStates.forEach((compute, key) => {
|
|
34
|
+
derivedValues[key] = compute(state)
|
|
35
|
+
})
|
|
36
|
+
return { ...state, ...derivedValues }
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Update state
|
|
41
|
+
* @param {Object|Function} update - State update or updater function
|
|
42
|
+
*/
|
|
43
|
+
setState: (update) => {
|
|
44
|
+
const oldState = { ...state }
|
|
45
|
+
const newState = typeof update === 'function'
|
|
46
|
+
? update(state)
|
|
47
|
+
: { ...state, ...update }
|
|
48
|
+
|
|
49
|
+
state = applyMiddleware(newState, oldState)
|
|
50
|
+
notifyListeners(state, oldState)
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Subscribe to state changes
|
|
55
|
+
* @param {Function} listener - Change listener
|
|
56
|
+
* @returns {Function} Unsubscribe function
|
|
57
|
+
*/
|
|
58
|
+
subscribe: (listener) => emitter.on('change', listener),
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Create a derived state value
|
|
62
|
+
* @param {string} key - Derived state key
|
|
63
|
+
* @param {Function} computation - Function to compute derived value
|
|
64
|
+
* @returns {Function} Function to remove derived state
|
|
65
|
+
*/
|
|
66
|
+
derive: (key, computation) => {
|
|
67
|
+
derivedStates.set(key, computation)
|
|
68
|
+
return () => {
|
|
69
|
+
derivedStates.delete(key)
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Select a specific slice of state
|
|
75
|
+
* @param {Function} selector - State selector function
|
|
76
|
+
* @returns {any} Selected state
|
|
77
|
+
*/
|
|
78
|
+
select: (selector) => selector(state),
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Reset state to initial values
|
|
82
|
+
*/
|
|
83
|
+
reset: () => {
|
|
84
|
+
state = { ...initialState }
|
|
85
|
+
notifyListeners(state, null)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Example middleware
|
|
91
|
+
export const loggingMiddleware = (newState, oldState) => {
|
|
92
|
+
console.log('State change:', {
|
|
93
|
+
old: oldState,
|
|
94
|
+
new: newState,
|
|
95
|
+
diff: Object.keys(newState).reduce((acc, key) => {
|
|
96
|
+
if (newState[key] !== oldState[key]) {
|
|
97
|
+
acc[key] = { from: oldState[key], to: newState[key] }
|
|
98
|
+
}
|
|
99
|
+
return acc
|
|
100
|
+
}, {})
|
|
101
|
+
})
|
|
102
|
+
return newState
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Example derived state
|
|
106
|
+
export const deriveFiltered = (predicate) => (state) =>
|
|
107
|
+
Object.keys(state).reduce((acc, key) => {
|
|
108
|
+
if (predicate(state[key], key)) {
|
|
109
|
+
acc[key] = state[key]
|
|
110
|
+
}
|
|
111
|
+
return acc
|
|
112
|
+
}, {})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/core/utils/index.js
|
|
2
|
+
|
|
3
|
+
export { isObject, byString } from './object'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Normalizes class names by handling various input formats
|
|
7
|
+
* @param {...(string|string[])} classes - Classes to normalize
|
|
8
|
+
* @returns {string[]} Array of unique, non-empty class names
|
|
9
|
+
*/
|
|
10
|
+
export const normalizeClasses = (...classes) => {
|
|
11
|
+
return [...new Set(
|
|
12
|
+
classes
|
|
13
|
+
.flat()
|
|
14
|
+
.reduce((acc, cls) => {
|
|
15
|
+
if (typeof cls === 'string') {
|
|
16
|
+
// Split space-separated classes and add them individually
|
|
17
|
+
acc.push(...cls.split(/\s+/))
|
|
18
|
+
}
|
|
19
|
+
return acc
|
|
20
|
+
}, [])
|
|
21
|
+
.filter(Boolean) // Remove empty strings
|
|
22
|
+
)]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates a transformer that only runs if a condition is met
|
|
27
|
+
* @param {Function} predicate - Condition to check
|
|
28
|
+
* @param {Function} transformer - Transformer to run if condition is true
|
|
29
|
+
* @returns {Function} Conditional transformer
|
|
30
|
+
*/
|
|
31
|
+
export const when = (predicate, transformer) => (obj, context) =>
|
|
32
|
+
predicate(obj, context) ? transformer(obj, context) : obj
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Joins class names, filtering out falsy values
|
|
36
|
+
* @param {...string} classes - Class names to join
|
|
37
|
+
* @returns {string} Joined class names
|
|
38
|
+
*/
|
|
39
|
+
export const classNames = (...classes) => classes.filter(Boolean).join(' ')
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// src/core/utils/mobile.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mobile device detection and capability checks
|
|
5
|
+
* This provides a centralized way to handle mobile-specific features and behaviors
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Detects if the current device is likely a mobile device
|
|
10
|
+
* Uses a combination of user agent and screen size checks for reliability
|
|
11
|
+
*/
|
|
12
|
+
export const isMobileDevice = () => {
|
|
13
|
+
const userAgent = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
|
14
|
+
navigator.userAgent
|
|
15
|
+
)
|
|
16
|
+
const screenSize = window.innerWidth <= 768
|
|
17
|
+
return userAgent || screenSize
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const PASSIVE_EVENTS = { passive: true }
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Configuration constants for mobile interactions
|
|
24
|
+
* Following WCAG guidelines and touch interaction best practices
|
|
25
|
+
*/
|
|
26
|
+
export const TOUCH_CONFIG = {
|
|
27
|
+
TARGET_SIZE: 44, // Minimum touch target size in pixels (WCAG standard)
|
|
28
|
+
FEEDBACK_DURATION: 200, // Duration of touch feedback animation in ms
|
|
29
|
+
TAP_THRESHOLD: 250, // Maximum duration for a touch to be considered a tap
|
|
30
|
+
SWIPE_THRESHOLD: 50 // Minimum distance for a touch to be considered a swipe
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Accessibility-minded touch target sizes
|
|
35
|
+
* Based on WCAG guidelines for touch targets
|
|
36
|
+
*/
|
|
37
|
+
export const TOUCH_TARGETS = {
|
|
38
|
+
MINIMUM: 44, // Minimum recommended size in pixels
|
|
39
|
+
COMFORTABLE: 48, // Comfortable touch target size
|
|
40
|
+
LARGE: 56 // Large touch target for primary actions
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Default passive event configuration for touch events
|
|
45
|
+
* Improves scroll performance on mobile devices
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Detects if the current device supports touch events
|
|
50
|
+
*/
|
|
51
|
+
export const hasTouchSupport = () => {
|
|
52
|
+
return 'ontouchstart' in window || navigator.maxTouchPoints > 0
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Normalizes both mouse and touch events into a consistent format
|
|
57
|
+
* This allows components to handle interactions uniformly
|
|
58
|
+
*/
|
|
59
|
+
export const normalizeEvent = (event) => {
|
|
60
|
+
if (event.touches) {
|
|
61
|
+
const touch = event.touches[0]
|
|
62
|
+
return {
|
|
63
|
+
clientX: touch.clientX,
|
|
64
|
+
clientY: touch.clientY,
|
|
65
|
+
pageX: touch.pageX,
|
|
66
|
+
pageY: touch.pageY,
|
|
67
|
+
target: event.target,
|
|
68
|
+
preventDefault: () => event.preventDefault(),
|
|
69
|
+
stopPropagation: () => event.stopPropagation(),
|
|
70
|
+
type: event.type
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return event
|
|
74
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const isObject = (object) => {
|
|
2
|
+
return object &&
|
|
3
|
+
typeof object === 'object' &&
|
|
4
|
+
Object.getPrototypeOf(object) === Object.getPrototypeOf({})
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const byString = (o, s) => {
|
|
8
|
+
s = s.replace(/\[(\w+)\]/g, '.$1') // convert indexes to properties
|
|
9
|
+
s = s.replace(/^\./, '') // strip a leading dot
|
|
10
|
+
const a = s.split('.')
|
|
11
|
+
for (let i = 0, n = a.length; i < n; ++i) {
|
|
12
|
+
const k = a[i]
|
|
13
|
+
if (k in o) {
|
|
14
|
+
o = o[k]
|
|
15
|
+
} else {
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return o
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { isObject, byString }
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// src/core/utils/validate.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validates configuration object against schema
|
|
5
|
+
* @param {Object} config - Configuration to validate
|
|
6
|
+
* @param {Object} schema - Validation schema
|
|
7
|
+
* @throws {Error} If validation fails
|
|
8
|
+
*/
|
|
9
|
+
export const validateConfig = (config, schema) => {
|
|
10
|
+
const errors = []
|
|
11
|
+
|
|
12
|
+
Object.entries(schema).forEach(([key, rule]) => {
|
|
13
|
+
// Check required fields
|
|
14
|
+
if (rule.required && !config[key]) {
|
|
15
|
+
errors.push(`Missing required field: ${key}`)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Check type if value exists
|
|
19
|
+
if (config[key] !== undefined && rule.type) {
|
|
20
|
+
const type = typeof config[key]
|
|
21
|
+
if (type !== rule.type) {
|
|
22
|
+
errors.push(`Invalid type for ${key}: expected ${rule.type}, got ${type}`)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Check allowed values if specified
|
|
27
|
+
if (config[key] !== undefined && rule.enum) {
|
|
28
|
+
if (!rule.enum.includes(config[key])) {
|
|
29
|
+
errors.push(`Invalid value for ${key}. Must be one of: ${rule.enum.join(', ')}`)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
if (errors.length > 0) {
|
|
35
|
+
throw new Error(`Configuration validation failed:\n${errors.join('\n')}`)
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/index.js
|
|
2
|
+
export { createElement } from './core/dom/create'
|
|
3
|
+
export { default as createLayout } from './core/layout'
|
|
4
|
+
export { default as createButton } from './components/button'
|
|
5
|
+
export { default as createCheckbox } from './components/checkbox'
|
|
6
|
+
export { default as createTextfield } from './components/textfield'
|
|
7
|
+
export { default as createSwitch } from './components/switch'
|
|
8
|
+
export { default as createContainer } from './components/container'
|
|
9
|
+
export { default as createSnackbar } from './components/snackbar'
|
|
10
|
+
export { default as createNavigation } from './components/navigation'
|
|
11
|
+
export { default as createList } from './components/list'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// src/styles/abstract/_config.scss
|
|
2
|
+
@use 'variables' as *;
|
|
3
|
+
@forward 'variables';
|
|
4
|
+
@forward 'functions';
|
|
5
|
+
@forward 'mixins' show
|
|
6
|
+
typography,
|
|
7
|
+
state-layer,
|
|
8
|
+
elevation,
|
|
9
|
+
motion-transition,
|
|
10
|
+
motion-exit,
|
|
11
|
+
motion-enter,
|
|
12
|
+
focus-ring,
|
|
13
|
+
interactive-states,
|
|
14
|
+
flex-center,
|
|
15
|
+
center-content,
|
|
16
|
+
container,
|
|
17
|
+
truncate,
|
|
18
|
+
shape,
|
|
19
|
+
breakpoint-up,
|
|
20
|
+
breakpoint-down,
|
|
21
|
+
breakpoint-between,
|
|
22
|
+
visually-hidden,
|
|
23
|
+
reduced-motion,
|
|
24
|
+
high-contrast,
|
|
25
|
+
rtl,
|
|
26
|
+
touch-target,
|
|
27
|
+
custom-scrollbar,
|
|
28
|
+
print;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// src/styles/abstract/_functions.scss
|
|
2
|
+
@use 'sass:map';
|
|
3
|
+
@use 'sass:math';
|
|
4
|
+
@use 'sass:color';
|
|
5
|
+
@use 'variables' as v;
|
|
6
|
+
|
|
7
|
+
// Get nested map value
|
|
8
|
+
@function map-deep-get($map, $keys...) {
|
|
9
|
+
@each $key in $keys {
|
|
10
|
+
$map: map.get($map, $key);
|
|
11
|
+
}
|
|
12
|
+
@return $map;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Get value from design tokens
|
|
16
|
+
@function get-token($category, $token) {
|
|
17
|
+
$token-map: null;
|
|
18
|
+
|
|
19
|
+
@if $category == 'state' {
|
|
20
|
+
$token-map: v.$state;
|
|
21
|
+
} @else if $category == 'motion' {
|
|
22
|
+
$token-map: v.$motion;
|
|
23
|
+
} @else if $category == 'elevation' {
|
|
24
|
+
$token-map: v.$elevation;
|
|
25
|
+
} @else if $category == 'typescale' {
|
|
26
|
+
$token-map: v.$typescale;
|
|
27
|
+
} @else if $category == 'shape' {
|
|
28
|
+
$token-map: v.$shape;
|
|
29
|
+
} @else if $category == 'z-index' {
|
|
30
|
+
$token-map: v.$z-index;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@if not $token-map {
|
|
34
|
+
@error 'Unknown token category: #{$category}';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
$value: map.get($token-map, $token);
|
|
38
|
+
|
|
39
|
+
@if not $value {
|
|
40
|
+
@error 'Unknown token: #{$token} in category #{$category}';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@return $value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// State Layer
|
|
47
|
+
@function get-state-opacity($state) {
|
|
48
|
+
@return get-token('state', '#{$state}-state-layer-opacity');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Motion
|
|
52
|
+
@function get-motion-duration($duration) {
|
|
53
|
+
@return get-token('motion', 'duration-#{$duration}');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@function get-motion-easing($easing) {
|
|
57
|
+
@return get-token('motion', 'easing-#{$easing}');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Elevation
|
|
61
|
+
@function get-elevation($level) {
|
|
62
|
+
@return get-token('elevation', 'level-#{$level}');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Typography
|
|
66
|
+
@function get-typography($scale) {
|
|
67
|
+
@return get-token('typescale', $scale);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Shape
|
|
71
|
+
@function get-shape($size) {
|
|
72
|
+
@return get-token('shape', $size);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Z-index
|
|
76
|
+
@function get-z-index($layer) {
|
|
77
|
+
@return get-token('z-index', $layer);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Breakpoints
|
|
81
|
+
@function get-breakpoint($size) {
|
|
82
|
+
@return map.get(v.$breakpoints, $size);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Color utilities
|
|
86
|
+
@function get-luminance($color) {
|
|
87
|
+
$red: color.red($color);
|
|
88
|
+
$green: color.green($color);
|
|
89
|
+
$blue: color.blue($color);
|
|
90
|
+
|
|
91
|
+
$luminance: ($red * 0.299 + $green * 0.587 + $blue * 0.114) / 255;
|
|
92
|
+
@return $luminance;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@function is-light-color($color) {
|
|
96
|
+
@return get-luminance($color) > 0.6;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@function get-contrast-color($color, $light: #fff, $dark: #000) {
|
|
100
|
+
@return if(is-light-color($color), $dark, $light);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Unit conversion utilities
|
|
104
|
+
@function strip-unit($number) {
|
|
105
|
+
@if type-of($number) == 'number' and not unitless($number) {
|
|
106
|
+
@return math.div($number, ($number * 0 + 1));
|
|
107
|
+
}
|
|
108
|
+
@return $number;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@function to-rem($size, $base-size: 16px) {
|
|
112
|
+
$rem-size: math.div(strip-unit($size), strip-unit($base-size));
|
|
113
|
+
@return #{$rem-size}rem;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@function to-em($size, $base-size: 16px) {
|
|
117
|
+
$em-size: math.div(strip-unit($size), strip-unit($base-size));
|
|
118
|
+
@return #{$em-size}em;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Value constraint utility
|
|
122
|
+
@function constrain($min, $value, $max) {
|
|
123
|
+
@return max($min, min($value, $max));
|
|
124
|
+
}
|