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.
Files changed (220) hide show
  1. package/README.md +70 -22
  2. package/index.ts +33 -0
  3. package/package.json +14 -5
  4. package/src/components/button/{styles.scss → _styles.scss} +2 -2
  5. package/src/components/button/api.ts +89 -0
  6. package/src/components/button/button.ts +50 -0
  7. package/src/components/button/config.ts +75 -0
  8. package/src/components/button/constants.ts +17 -0
  9. package/src/components/button/index.ts +4 -0
  10. package/src/components/button/types.ts +118 -0
  11. package/src/components/card/_styles.scss +359 -0
  12. package/src/components/card/actions.ts +48 -0
  13. package/src/components/card/api.ts +102 -0
  14. package/src/components/card/card.ts +41 -0
  15. package/src/components/card/config.ts +99 -0
  16. package/src/components/card/constants.ts +69 -0
  17. package/src/components/card/content.ts +48 -0
  18. package/src/components/card/features.ts +228 -0
  19. package/src/components/card/header.ts +88 -0
  20. package/src/components/card/index.ts +19 -0
  21. package/src/components/card/media.ts +52 -0
  22. package/src/components/card/types.ts +174 -0
  23. package/src/components/checkbox/api.ts +82 -0
  24. package/src/components/checkbox/checkbox.ts +75 -0
  25. package/src/components/checkbox/config.ts +90 -0
  26. package/src/components/checkbox/index.ts +4 -0
  27. package/src/components/checkbox/types.ts +146 -0
  28. package/src/components/chip/_styles.scss +372 -0
  29. package/src/components/chip/api.ts +115 -0
  30. package/src/components/chip/chip-set.ts +225 -0
  31. package/src/components/chip/chip.ts +82 -0
  32. package/src/components/chip/config.ts +92 -0
  33. package/src/components/chip/constants.ts +38 -0
  34. package/src/components/chip/index.ts +4 -0
  35. package/src/components/chip/types.ts +172 -0
  36. package/src/components/list/api.ts +72 -0
  37. package/src/components/list/config.ts +43 -0
  38. package/src/components/list/{constants.js → constants.ts} +34 -7
  39. package/src/components/list/features.ts +224 -0
  40. package/src/components/list/index.ts +14 -0
  41. package/src/components/list/list-item.ts +120 -0
  42. package/src/components/list/list.ts +37 -0
  43. package/src/components/list/types.ts +179 -0
  44. package/src/components/list/utils.ts +47 -0
  45. package/src/components/menu/api.ts +119 -0
  46. package/src/components/menu/config.ts +54 -0
  47. package/src/components/menu/constants.ts +154 -0
  48. package/src/components/menu/features/items-manager.ts +457 -0
  49. package/src/components/menu/features/keyboard-navigation.ts +133 -0
  50. package/src/components/menu/features/positioning.ts +127 -0
  51. package/src/components/menu/features/{visibility.js → visibility.ts} +66 -64
  52. package/src/components/menu/index.ts +14 -0
  53. package/src/components/menu/menu-item.ts +43 -0
  54. package/src/components/menu/menu.ts +53 -0
  55. package/src/components/menu/types.ts +178 -0
  56. package/src/components/navigation/api.ts +79 -0
  57. package/src/components/navigation/config.ts +61 -0
  58. package/src/components/navigation/{constants.js → constants.ts} +10 -10
  59. package/src/components/navigation/index.ts +14 -0
  60. package/src/components/navigation/nav-item.ts +148 -0
  61. package/src/components/navigation/navigation.ts +50 -0
  62. package/src/components/navigation/types.ts +212 -0
  63. package/src/components/progress/_styles.scss +204 -0
  64. package/src/components/progress/api.ts +179 -0
  65. package/src/components/progress/config.ts +124 -0
  66. package/src/components/progress/constants.ts +43 -0
  67. package/src/components/progress/index.ts +5 -0
  68. package/src/components/progress/progress.ts +163 -0
  69. package/src/components/progress/types.ts +102 -0
  70. package/src/components/snackbar/api.ts +162 -0
  71. package/src/components/snackbar/config.ts +62 -0
  72. package/src/components/snackbar/{constants.js → constants.ts} +21 -4
  73. package/src/components/snackbar/features.ts +76 -0
  74. package/src/components/snackbar/index.ts +4 -0
  75. package/src/components/snackbar/position.ts +71 -0
  76. package/src/components/snackbar/queue.ts +76 -0
  77. package/src/components/snackbar/snackbar.ts +60 -0
  78. package/src/components/snackbar/types.ts +58 -0
  79. package/src/components/switch/api.ts +77 -0
  80. package/src/components/switch/config.ts +74 -0
  81. package/src/components/switch/index.ts +4 -0
  82. package/src/components/switch/switch.ts +52 -0
  83. package/src/components/switch/types.ts +142 -0
  84. package/src/components/textfield/api.ts +72 -0
  85. package/src/components/textfield/config.ts +54 -0
  86. package/src/components/textfield/{constants.js → constants.ts} +38 -5
  87. package/src/components/textfield/index.ts +4 -0
  88. package/src/components/textfield/textfield.ts +50 -0
  89. package/src/components/textfield/types.ts +139 -0
  90. package/src/core/compose/base.ts +43 -0
  91. package/src/core/compose/component.ts +247 -0
  92. package/src/core/compose/features/checkable.ts +155 -0
  93. package/src/core/compose/features/disabled.ts +116 -0
  94. package/src/core/compose/features/events.ts +65 -0
  95. package/src/core/compose/features/icon.ts +67 -0
  96. package/src/core/compose/features/index.ts +35 -0
  97. package/src/core/compose/features/input.ts +174 -0
  98. package/src/core/compose/features/lifecycle.ts +139 -0
  99. package/src/core/compose/features/position.ts +94 -0
  100. package/src/core/compose/features/ripple.ts +55 -0
  101. package/src/core/compose/features/size.ts +29 -0
  102. package/src/core/compose/features/style.ts +31 -0
  103. package/src/core/compose/features/text.ts +44 -0
  104. package/src/core/compose/features/textinput.ts +225 -0
  105. package/src/core/compose/features/textlabel.ts +92 -0
  106. package/src/core/compose/features/track.ts +84 -0
  107. package/src/core/compose/features/variant.ts +29 -0
  108. package/src/core/compose/features/withEvents.ts +137 -0
  109. package/src/core/compose/index.ts +54 -0
  110. package/src/core/compose/{pipe.js → pipe.ts} +16 -11
  111. package/src/core/config/component-config.ts +136 -0
  112. package/src/core/config.ts +211 -0
  113. package/src/core/dom/{attributes.js → attributes.ts} +11 -11
  114. package/src/core/dom/classes.ts +60 -0
  115. package/src/core/dom/create.ts +188 -0
  116. package/src/core/dom/events.ts +209 -0
  117. package/src/core/dom/index.ts +10 -0
  118. package/src/core/dom/utils.ts +97 -0
  119. package/src/core/index.ts +111 -0
  120. package/src/core/state/disabled.ts +81 -0
  121. package/src/core/state/emitter.ts +94 -0
  122. package/src/core/state/events.ts +88 -0
  123. package/src/core/state/index.ts +16 -0
  124. package/src/core/state/lifecycle.ts +131 -0
  125. package/src/core/state/store.ts +197 -0
  126. package/src/core/utils/index.ts +45 -0
  127. package/src/core/utils/{mobile.js → mobile.ts} +48 -24
  128. package/src/core/utils/object.ts +41 -0
  129. package/src/core/utils/validate.ts +234 -0
  130. package/src/{index.js → index.ts} +4 -2
  131. package/index.js +0 -11
  132. package/src/components/button/api.js +0 -54
  133. package/src/components/button/button.js +0 -81
  134. package/src/components/button/config.js +0 -10
  135. package/src/components/button/constants.js +0 -63
  136. package/src/components/button/index.js +0 -2
  137. package/src/components/checkbox/api.js +0 -45
  138. package/src/components/checkbox/checkbox.js +0 -96
  139. package/src/components/checkbox/index.js +0 -2
  140. package/src/components/container/api.js +0 -42
  141. package/src/components/container/container.js +0 -45
  142. package/src/components/container/index.js +0 -2
  143. package/src/components/container/styles.scss +0 -66
  144. package/src/components/list/index.js +0 -2
  145. package/src/components/list/list-item.js +0 -147
  146. package/src/components/list/list.js +0 -267
  147. package/src/components/menu/api.js +0 -117
  148. package/src/components/menu/constants.js +0 -42
  149. package/src/components/menu/features/items-manager.js +0 -375
  150. package/src/components/menu/features/keyboard-navigation.js +0 -129
  151. package/src/components/menu/features/positioning.js +0 -125
  152. package/src/components/menu/index.js +0 -2
  153. package/src/components/menu/menu-item.js +0 -41
  154. package/src/components/menu/menu.js +0 -54
  155. package/src/components/navigation/api.js +0 -43
  156. package/src/components/navigation/index.js +0 -2
  157. package/src/components/navigation/nav-item.js +0 -137
  158. package/src/components/navigation/navigation.js +0 -55
  159. package/src/components/snackbar/api.js +0 -125
  160. package/src/components/snackbar/features.js +0 -69
  161. package/src/components/snackbar/index.js +0 -2
  162. package/src/components/snackbar/position.js +0 -63
  163. package/src/components/snackbar/queue.js +0 -74
  164. package/src/components/snackbar/snackbar.js +0 -70
  165. package/src/components/switch/api.js +0 -44
  166. package/src/components/switch/index.js +0 -2
  167. package/src/components/switch/switch.js +0 -71
  168. package/src/components/textfield/api.js +0 -49
  169. package/src/components/textfield/index.js +0 -2
  170. package/src/components/textfield/textfield.js +0 -68
  171. package/src/core/build/_ripple.scss +0 -79
  172. package/src/core/build/constants.js +0 -51
  173. package/src/core/build/icon.js +0 -78
  174. package/src/core/build/ripple.js +0 -159
  175. package/src/core/build/text.js +0 -54
  176. package/src/core/compose/base.js +0 -8
  177. package/src/core/compose/component.js +0 -225
  178. package/src/core/compose/features/checkable.js +0 -114
  179. package/src/core/compose/features/disabled.js +0 -64
  180. package/src/core/compose/features/events.js +0 -48
  181. package/src/core/compose/features/icon.js +0 -33
  182. package/src/core/compose/features/index.js +0 -20
  183. package/src/core/compose/features/input.js +0 -100
  184. package/src/core/compose/features/lifecycle.js +0 -69
  185. package/src/core/compose/features/position.js +0 -60
  186. package/src/core/compose/features/ripple.js +0 -32
  187. package/src/core/compose/features/size.js +0 -9
  188. package/src/core/compose/features/style.js +0 -12
  189. package/src/core/compose/features/text.js +0 -17
  190. package/src/core/compose/features/textinput.js +0 -114
  191. package/src/core/compose/features/textlabel.js +0 -28
  192. package/src/core/compose/features/track.js +0 -49
  193. package/src/core/compose/features/variant.js +0 -9
  194. package/src/core/compose/features/withEvents.js +0 -67
  195. package/src/core/compose/index.js +0 -16
  196. package/src/core/config.js +0 -140
  197. package/src/core/dom/classes.js +0 -70
  198. package/src/core/dom/create.js +0 -132
  199. package/src/core/dom/events.js +0 -175
  200. package/src/core/dom/index.js +0 -5
  201. package/src/core/dom/utils.js +0 -22
  202. package/src/core/index.js +0 -23
  203. package/src/core/state/disabled.js +0 -51
  204. package/src/core/state/emitter.js +0 -63
  205. package/src/core/state/events.js +0 -29
  206. package/src/core/state/index.js +0 -6
  207. package/src/core/state/lifecycle.js +0 -64
  208. package/src/core/state/store.js +0 -112
  209. package/src/core/utils/index.js +0 -39
  210. package/src/core/utils/object.js +0 -22
  211. package/src/core/utils/validate.js +0 -37
  212. /package/src/components/checkbox/{styles.scss → _styles.scss} +0 -0
  213. /package/src/components/checkbox/{constants.js → constants.ts} +0 -0
  214. /package/src/components/list/{styles.scss → _styles.scss} +0 -0
  215. /package/src/components/menu/{styles.scss → _styles.scss} +0 -0
  216. /package/src/components/navigation/{styles.scss → _styles.scss} +0 -0
  217. /package/src/components/snackbar/{styles.scss → _styles.scss} +0 -0
  218. /package/src/components/switch/{styles.scss → _styles.scss} +0 -0
  219. /package/src/components/switch/{constants.js → constants.ts} +0 -0
  220. /package/src/components/textfield/{styles.scss → _styles.scss} +0 -0
@@ -0,0 +1,131 @@
1
+ // src/core/state/lifecycle.ts
2
+
3
+ import { createEmitter, Emitter } from './emitter';
4
+
5
+ /**
6
+ * Component managers that can be managed by the lifecycle
7
+ */
8
+ export interface LifecycleManagers {
9
+ events?: {
10
+ destroy: () => void;
11
+ };
12
+ text?: {
13
+ getElement: () => HTMLElement | null;
14
+ };
15
+ icon?: {
16
+ getElement: () => HTMLElement | null;
17
+ };
18
+ [key: string]: any;
19
+ }
20
+
21
+ /**
22
+ * Lifecycle manager interface
23
+ */
24
+ export interface LifecycleManager {
25
+ /**
26
+ * Registers a handler for mount event
27
+ * @param handler - Function to call when component is mounted
28
+ * @returns Unsubscribe function
29
+ */
30
+ onMount: (handler: () => void) => () => void;
31
+
32
+ /**
33
+ * Registers a handler for unmount event
34
+ * @param handler - Function to call when component is unmounted
35
+ * @returns Unsubscribe function
36
+ */
37
+ onUnmount: (handler: () => void) => () => void;
38
+
39
+ /**
40
+ * Mounts the component
41
+ */
42
+ mount: () => void;
43
+
44
+ /**
45
+ * Unmounts the component
46
+ */
47
+ unmount: () => void;
48
+
49
+ /**
50
+ * Checks if component is mounted
51
+ * @returns true if mounted
52
+ */
53
+ isMounted: () => boolean;
54
+
55
+ /**
56
+ * Destroys the component
57
+ */
58
+ destroy: () => void;
59
+ }
60
+
61
+ /**
62
+ * Creates a lifecycle manager for a component
63
+ *
64
+ * @param element - Component's DOM element
65
+ * @param managers - Optional component managers to integrate with lifecycle
66
+ * @returns Lifecycle manager interface
67
+ */
68
+ export const createLifecycle = (
69
+ element: HTMLElement,
70
+ managers: LifecycleManagers = {}
71
+ ): LifecycleManager => {
72
+ let mounted = false;
73
+ const emitter: Emitter = createEmitter();
74
+
75
+ return {
76
+ // Mount/Unmount state management
77
+ onMount: (handler: () => void) => emitter.on('mount', handler),
78
+ onUnmount: (handler: () => void) => emitter.on('unmount', handler),
79
+
80
+ mount: () => {
81
+ if (!mounted) {
82
+ mounted = true;
83
+ emitter.emit('mount');
84
+ }
85
+ },
86
+
87
+ unmount: () => {
88
+ if (mounted) {
89
+ mounted = false;
90
+ emitter.emit('unmount');
91
+ emitter.clear();
92
+ }
93
+ },
94
+
95
+ isMounted: () => mounted,
96
+
97
+ // Cleanup and destruction
98
+ destroy() {
99
+ // First trigger unmount
100
+ if (mounted) {
101
+ this.unmount();
102
+ }
103
+
104
+ // Clean up all event listeners
105
+ if (managers.events) {
106
+ managers.events.destroy();
107
+ }
108
+
109
+ // Clean up text element
110
+ if (managers.text) {
111
+ const textElement = managers.text.getElement();
112
+ if (textElement) {
113
+ textElement.remove();
114
+ }
115
+ }
116
+
117
+ // Clean up icon element
118
+ if (managers.icon) {
119
+ const iconElement = managers.icon.getElement();
120
+ if (iconElement) {
121
+ iconElement.remove();
122
+ }
123
+ }
124
+
125
+ // Remove the main element
126
+ if (element) {
127
+ element.remove();
128
+ }
129
+ }
130
+ };
131
+ };
@@ -0,0 +1,197 @@
1
+ // src/core/state/store.ts
2
+
3
+ import { createEmitter, Emitter } from './emitter';
4
+
5
+ /**
6
+ * State store options
7
+ */
8
+ export interface StoreOptions<T> {
9
+ /**
10
+ * Middleware functions that process state changes
11
+ */
12
+ middleware?: Array<(newState: T, oldState: T) => T>;
13
+ }
14
+
15
+ /**
16
+ * State selector function type
17
+ */
18
+ export type Selector<T, R> = (state: T) => R;
19
+
20
+ /**
21
+ * State computation function type for derived state
22
+ */
23
+ export type Computation<T, R> = (state: T) => R;
24
+
25
+ /**
26
+ * State updater function type
27
+ */
28
+ export type Updater<T> = (state: T) => T;
29
+
30
+ /**
31
+ * State store interface
32
+ */
33
+ export interface Store<T> {
34
+ /**
35
+ * Gets current state including derived values
36
+ * @returns Current state
37
+ */
38
+ getState: () => T;
39
+
40
+ /**
41
+ * Updates state
42
+ * @param update - State update object or updater function
43
+ */
44
+ setState: (update: Partial<T> | Updater<T>) => void;
45
+
46
+ /**
47
+ * Subscribes to state changes
48
+ * @param listener - Change listener
49
+ * @returns Unsubscribe function
50
+ */
51
+ subscribe: (listener: (state: T, oldState: T) => void) => () => void;
52
+
53
+ /**
54
+ * Creates a derived state value
55
+ * @param key - Derived state key
56
+ * @param computation - Function to compute derived value
57
+ * @returns Function to remove derived state
58
+ */
59
+ derive: <K extends string, R>(key: K, computation: Computation<T, R>) => () => void;
60
+
61
+ /**
62
+ * Selects a specific slice of state
63
+ * @param selector - State selector function
64
+ * @returns Selected state
65
+ */
66
+ select: <R>(selector: Selector<T, R>) => R;
67
+
68
+ /**
69
+ * Resets state to initial values
70
+ */
71
+ reset: () => void;
72
+ }
73
+
74
+ /**
75
+ * Creates a state store with support for derived state and middleware
76
+ *
77
+ * @param initialState - Initial state object
78
+ * @param options - Store options
79
+ * @returns State store interface
80
+ */
81
+ export const createStore = <T extends Record<string, any>>(
82
+ initialState: T = {} as T,
83
+ options: StoreOptions<T> = {}
84
+ ): Store<T> => {
85
+ let state = { ...initialState };
86
+ const emitter: Emitter = createEmitter();
87
+ const derivedStates = new Map<string, Computation<T, any>>();
88
+ const middleware = options.middleware || [];
89
+
90
+ const notifyListeners = (newState: T, oldState: T): void => {
91
+ emitter.emit('change', newState, oldState);
92
+ };
93
+
94
+ const applyMiddleware = (newState: T, oldState: T): T => {
95
+ return middleware.reduce((state, fn) => fn(state, oldState), newState);
96
+ };
97
+
98
+ return {
99
+ /**
100
+ * Gets current state including derived values
101
+ * @returns Current state
102
+ */
103
+ getState: (): T => {
104
+ const derivedValues: Record<string, any> = {};
105
+ derivedStates.forEach((compute, key) => {
106
+ derivedValues[key] = compute(state);
107
+ });
108
+ return { ...state, ...derivedValues };
109
+ },
110
+
111
+ /**
112
+ * Updates state
113
+ * @param update - State update object or updater function
114
+ */
115
+ setState: (update: Partial<T> | Updater<T>): void => {
116
+ const oldState = { ...state };
117
+ const newState = typeof update === 'function'
118
+ ? update(state)
119
+ : { ...state, ...update };
120
+
121
+ state = applyMiddleware(newState as T, oldState);
122
+ notifyListeners(state, oldState);
123
+ },
124
+
125
+ /**
126
+ * Subscribes to state changes
127
+ * @param listener - Change listener
128
+ * @returns Unsubscribe function
129
+ */
130
+ subscribe: (listener: (state: T, oldState: T) => void): (() => void) =>
131
+ emitter.on('change', listener),
132
+
133
+ /**
134
+ * Creates a derived state value
135
+ * @param key - Derived state key
136
+ * @param computation - Function to compute derived value
137
+ * @returns Function to remove derived state
138
+ */
139
+ derive: <K extends string, R>(key: K, computation: Computation<T, R>): (() => void) => {
140
+ derivedStates.set(key, computation);
141
+ return () => {
142
+ derivedStates.delete(key);
143
+ };
144
+ },
145
+
146
+ /**
147
+ * Selects a specific slice of state
148
+ * @param selector - State selector function
149
+ * @returns Selected state
150
+ */
151
+ select: <R>(selector: Selector<T, R>): R => selector(state),
152
+
153
+ /**
154
+ * Resets state to initial values
155
+ */
156
+ reset: (): void => {
157
+ state = { ...initialState };
158
+ notifyListeners(state, {} as T);
159
+ }
160
+ };
161
+ };
162
+
163
+ /**
164
+ * Example middleware that logs state changes
165
+ *
166
+ * @param newState - New state after change
167
+ * @param oldState - Previous state before change
168
+ * @returns Processed state (unchanged in this middleware)
169
+ */
170
+ export const loggingMiddleware = <T extends Record<string, any>>(newState: T, oldState: T): T => {
171
+ console.log('State change:', {
172
+ old: oldState,
173
+ new: newState,
174
+ diff: Object.keys(newState).reduce((acc, key) => {
175
+ if (newState[key] !== oldState[key]) {
176
+ acc[key] = { from: oldState[key], to: newState[key] };
177
+ }
178
+ return acc;
179
+ }, {} as Record<string, { from: any; to: any }>)
180
+ });
181
+ return newState;
182
+ };
183
+
184
+ /**
185
+ * Creates a derived state selector for filtering objects
186
+ *
187
+ * @param predicate - Filter predicate function
188
+ * @returns Computation function for derived state
189
+ */
190
+ export const deriveFiltered = <T>(predicate: (value: any, key: string) => boolean) =>
191
+ (state: Record<string, T>): Record<string, T> =>
192
+ Object.keys(state).reduce((acc, key) => {
193
+ if (predicate(state[key], key)) {
194
+ acc[key] = state[key];
195
+ }
196
+ return acc;
197
+ }, {} as Record<string, T>);
@@ -0,0 +1,45 @@
1
+ // src/core/utils/index.ts
2
+
3
+ export { isObject, byString } from './object';
4
+ export { normalizeEvent, hasTouchSupport, TOUCH_CONFIG, PASSIVE_EVENTS } from './mobile';
5
+
6
+ /**
7
+ * Normalizes class names by handling various input formats
8
+ * @param {...(string|string[])} classes - Classes to normalize
9
+ * @returns {string[]} Array of unique, non-empty class names
10
+ */
11
+ export const normalizeClasses = (...classes: (string | string[])[]): string[] => {
12
+ return [...new Set(
13
+ classes
14
+ .flat()
15
+ .reduce((acc: string[], cls) => {
16
+ if (typeof cls === 'string') {
17
+ // Split space-separated classes and add them individually
18
+ acc.push(...cls.split(/\s+/));
19
+ }
20
+ return acc;
21
+ }, [])
22
+ .filter(Boolean) // Remove empty strings
23
+ )];
24
+ };
25
+
26
+ /**
27
+ * Creates a transformer that only runs if a condition is met
28
+ * @param {Function} predicate - Condition to check
29
+ * @param {Function} transformer - Transformer to run if condition is true
30
+ * @returns {Function} Conditional transformer
31
+ */
32
+ export const when = <T, C>(
33
+ predicate: (obj: T, context: C) => boolean,
34
+ transformer: (obj: T, context: C) => T
35
+ ) =>
36
+ (obj: T, context: C): T =>
37
+ predicate(obj, context) ? transformer(obj, context) : obj;
38
+
39
+ /**
40
+ * Joins class names, filtering out falsy values
41
+ * @param {...(string | undefined | null | false)} classes - Class names to join
42
+ * @returns {string} Joined class names
43
+ */
44
+ export const classNames = (...classes: (string | undefined | null | false)[]): string =>
45
+ classes.filter(Boolean).join(' ');
@@ -1,23 +1,40 @@
1
- // src/core/utils/mobile.js
1
+ // src/core/utils/mobile.ts
2
2
 
3
3
  /**
4
4
  * Mobile device detection and capability checks
5
5
  * This provides a centralized way to handle mobile-specific features and behaviors
6
6
  */
7
7
 
8
+ /**
9
+ * Interface for normalized event properties
10
+ */
11
+ export interface NormalizedEvent {
12
+ clientX: number;
13
+ clientY: number;
14
+ pageX: number;
15
+ pageY: number;
16
+ target: EventTarget;
17
+ preventDefault: () => void;
18
+ stopPropagation: () => void;
19
+ type: string;
20
+ }
21
+
8
22
  /**
9
23
  * Detects if the current device is likely a mobile device
10
24
  * Uses a combination of user agent and screen size checks for reliability
11
25
  */
12
- export const isMobileDevice = () => {
26
+ export const isMobileDevice = (): boolean => {
13
27
  const userAgent = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
14
28
  navigator.userAgent
15
- )
16
- const screenSize = window.innerWidth <= 768
17
- return userAgent || screenSize
18
- }
29
+ );
30
+ const screenSize = window.innerWidth <= 768;
31
+ return userAgent || screenSize;
32
+ };
19
33
 
20
- export const PASSIVE_EVENTS = { passive: true }
34
+ /**
35
+ * Options for passive event listeners
36
+ */
37
+ export const PASSIVE_EVENTS: AddEventListenerOptions = { passive: true };
21
38
 
22
39
  /**
23
40
  * Configuration constants for mobile interactions
@@ -28,7 +45,7 @@ export const TOUCH_CONFIG = {
28
45
  FEEDBACK_DURATION: 200, // Duration of touch feedback animation in ms
29
46
  TAP_THRESHOLD: 250, // Maximum duration for a touch to be considered a tap
30
47
  SWIPE_THRESHOLD: 50 // Minimum distance for a touch to be considered a swipe
31
- }
48
+ };
32
49
 
33
50
  /**
34
51
  * Accessibility-minded touch target sizes
@@ -38,37 +55,44 @@ export const TOUCH_TARGETS = {
38
55
  MINIMUM: 44, // Minimum recommended size in pixels
39
56
  COMFORTABLE: 48, // Comfortable touch target size
40
57
  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
- */
58
+ };
47
59
 
48
60
  /**
49
61
  * Detects if the current device supports touch events
50
62
  */
51
- export const hasTouchSupport = () => {
52
- return 'ontouchstart' in window || navigator.maxTouchPoints > 0
53
- }
63
+ export const hasTouchSupport = (): boolean => {
64
+ return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
65
+ };
54
66
 
55
67
  /**
56
68
  * Normalizes both mouse and touch events into a consistent format
57
69
  * This allows components to handle interactions uniformly
58
70
  */
59
- export const normalizeEvent = (event) => {
60
- if (event.touches) {
61
- const touch = event.touches[0]
71
+ export const normalizeEvent = (event: Event): NormalizedEvent => {
72
+ if ('touches' in event && event.touches.length > 0) {
73
+ const touch = event.touches[0];
62
74
  return {
63
75
  clientX: touch.clientX,
64
76
  clientY: touch.clientY,
65
77
  pageX: touch.pageX,
66
78
  pageY: touch.pageY,
67
- target: event.target,
79
+ target: event.target as EventTarget,
68
80
  preventDefault: () => event.preventDefault(),
69
81
  stopPropagation: () => event.stopPropagation(),
70
82
  type: event.type
71
- }
83
+ };
72
84
  }
73
- return event
74
- }
85
+
86
+ // For mouse events
87
+ const mouseEvent = event as MouseEvent;
88
+ return {
89
+ clientX: mouseEvent.clientX,
90
+ clientY: mouseEvent.clientY,
91
+ pageX: mouseEvent.pageX,
92
+ pageY: mouseEvent.pageY,
93
+ target: mouseEvent.target as EventTarget,
94
+ preventDefault: () => mouseEvent.preventDefault(),
95
+ stopPropagation: () => mouseEvent.stopPropagation(),
96
+ type: mouseEvent.type
97
+ };
98
+ };
@@ -0,0 +1,41 @@
1
+ // src/core/utils/object.ts
2
+
3
+ /**
4
+ * Checks if a value is a plain object
5
+ * @param value - The value to check
6
+ * @returns true if the value is a plain object
7
+ */
8
+ export const isObject = (value: unknown): value is Record<string, any> => {
9
+ return Boolean(
10
+ value &&
11
+ typeof value === 'object' &&
12
+ Object.getPrototypeOf(value) === Object.getPrototypeOf({})
13
+ );
14
+ };
15
+
16
+ /**
17
+ * Accesses a nested property of an object using a string path
18
+ * @param obj - The object to traverse
19
+ * @param path - The property path (e.g. 'user.address.street')
20
+ * @returns The value at the specified path or undefined if not found
21
+ */
22
+ export const byString = <T extends Record<string, any>, R = any>(obj: T, path: string): R | undefined => {
23
+ // Convert indexes to properties
24
+ const normalizedPath = path.replace(/\[(\w+)\]/g, '.$1');
25
+ // Strip a leading dot
26
+ const cleanPath = normalizedPath.replace(/^\./, '');
27
+ const keys = cleanPath.split('.');
28
+
29
+ let result: any = obj;
30
+
31
+ for (let i = 0, n = keys.length; i < n; ++i) {
32
+ const key = keys[i];
33
+ if (key in result) {
34
+ result = result[key];
35
+ } else {
36
+ return undefined;
37
+ }
38
+ }
39
+
40
+ return result as R;
41
+ };