mtrl 0.3.5 → 0.3.7

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 (65) hide show
  1. package/package.json +1 -1
  2. package/src/components/button/api.ts +16 -0
  3. package/src/components/button/types.ts +9 -0
  4. package/src/components/menu/api.ts +144 -267
  5. package/src/components/menu/config.ts +84 -40
  6. package/src/components/menu/features/anchor.ts +243 -0
  7. package/src/components/menu/features/controller.ts +1167 -0
  8. package/src/components/menu/features/index.ts +5 -0
  9. package/src/components/menu/features/position.ts +353 -0
  10. package/src/components/menu/index.ts +31 -63
  11. package/src/components/menu/menu.ts +72 -104
  12. package/src/components/menu/types.ts +264 -447
  13. package/src/components/select/api.ts +78 -0
  14. package/src/components/select/config.ts +76 -0
  15. package/src/components/select/features.ts +317 -0
  16. package/src/components/select/index.ts +38 -0
  17. package/src/components/select/select.ts +73 -0
  18. package/src/components/select/types.ts +355 -0
  19. package/src/components/textfield/api.ts +78 -6
  20. package/src/components/textfield/features/index.ts +17 -0
  21. package/src/components/textfield/features/leading-icon.ts +127 -0
  22. package/src/components/textfield/features/placement.ts +149 -0
  23. package/src/components/textfield/features/prefix-text.ts +107 -0
  24. package/src/components/textfield/features/suffix-text.ts +100 -0
  25. package/src/components/textfield/features/supporting-text.ts +113 -0
  26. package/src/components/textfield/features/trailing-icon.ts +108 -0
  27. package/src/components/textfield/textfield.ts +51 -15
  28. package/src/components/textfield/types.ts +70 -0
  29. package/src/core/collection/adapters/base.ts +62 -0
  30. package/src/core/collection/collection.ts +300 -0
  31. package/src/core/collection/index.ts +57 -0
  32. package/src/core/collection/list-manager.ts +333 -0
  33. package/src/core/dom/classes.ts +81 -9
  34. package/src/core/dom/create.ts +30 -19
  35. package/src/core/layout/README.md +531 -166
  36. package/src/core/layout/array.ts +3 -4
  37. package/src/core/layout/config.ts +193 -0
  38. package/src/core/layout/create.ts +1 -2
  39. package/src/core/layout/index.ts +12 -2
  40. package/src/core/layout/object.ts +2 -3
  41. package/src/core/layout/processor.ts +60 -12
  42. package/src/core/layout/result.ts +1 -2
  43. package/src/core/layout/types.ts +105 -50
  44. package/src/core/layout/utils.ts +69 -61
  45. package/src/index.ts +6 -2
  46. package/src/styles/abstract/_variables.scss +18 -0
  47. package/src/styles/components/_button.scss +21 -5
  48. package/src/styles/components/{_chip.scss → _chips.scss} +118 -4
  49. package/src/styles/components/_menu.scss +109 -18
  50. package/src/styles/components/_select.scss +265 -0
  51. package/src/styles/components/_textfield.scss +233 -42
  52. package/src/styles/main.scss +24 -23
  53. package/src/styles/utilities/_layout.scss +665 -0
  54. package/src/components/menu/features/items-manager.ts +0 -457
  55. package/src/components/menu/features/keyboard-navigation.ts +0 -133
  56. package/src/components/menu/features/positioning.ts +0 -127
  57. package/src/components/menu/features/visibility.ts +0 -230
  58. package/src/components/menu/menu-item.ts +0 -86
  59. package/src/components/menu/utils.ts +0 -67
  60. package/src/components/textfield/features.ts +0 -322
  61. package/src/core/collection/adapters/base.js +0 -26
  62. package/src/core/collection/collection.js +0 -259
  63. package/src/core/collection/list-manager.js +0 -157
  64. /package/src/core/collection/adapters/{route.js → route.ts} +0 -0
  65. /package/src/{core/build → styles/utilities}/_ripple.scss +0 -0
@@ -1,26 +0,0 @@
1
- // src/core/collection/adapters/base.js
2
-
3
- export const OPERATORS = {
4
- EQ: 'eq',
5
- NE: 'ne',
6
- GT: 'gt',
7
- GTE: 'gte',
8
- LT: 'lt',
9
- LTE: 'lte',
10
- IN: 'in',
11
- NIN: 'nin',
12
- CONTAINS: 'contains',
13
- STARTS_WITH: 'startsWith',
14
- ENDS_WITH: 'endsWith'
15
- }
16
-
17
- export const createBaseAdapter = ({ onError } = {}) => {
18
- const handleError = (error, context) => {
19
- onError?.(error, context)
20
- throw error
21
- }
22
-
23
- return {
24
- handleError
25
- }
26
- }
@@ -1,259 +0,0 @@
1
- // src/core/collection/collection.js
2
-
3
- /**
4
- * Event types for collection changes
5
- */
6
- export const COLLECTION_EVENTS = {
7
- CHANGE: 'change',
8
- ADD: 'add',
9
- UPDATE: 'update',
10
- REMOVE: 'remove',
11
- ERROR: 'error',
12
- LOADING: 'loading'
13
- }
14
-
15
- /**
16
- * Query operators for filtering
17
- */
18
- export const OPERATORS = {
19
- EQ: 'eq',
20
- NE: 'ne',
21
- GT: 'gt',
22
- GTE: 'gte',
23
- LT: 'lt',
24
- LTE: 'lte',
25
- IN: 'in',
26
- NIN: 'nin',
27
- CONTAINS: 'contains',
28
- STARTS_WITH: 'startsWith',
29
- ENDS_WITH: 'endsWith'
30
- }
31
-
32
- /**
33
- * Base Collection class providing data management interface
34
- * @template T - Type of items in collection
35
- */
36
- export class Collection {
37
- #items = new Map()
38
- #observers = new Set()
39
- #query = null
40
- #sort = null
41
- #loading = false
42
- #error = null
43
-
44
- /**
45
- * Creates a new collection instance
46
- * @param {Object} config - Collection configuration
47
- * @param {Function} config.transform - Transform function for items
48
- * @param {Function} config.validate - Validation function for items
49
- */
50
- constructor (config = {}) {
51
- this.transform = config.transform || (item => item)
52
- this.validate = config.validate || (() => true)
53
- }
54
-
55
- /**
56
- * Subscribe to collection changes
57
- * @param {Function} observer - Observer callback
58
- * @returns {Function} Unsubscribe function
59
- */
60
- subscribe (observer) {
61
- this.#observers.add(observer)
62
- return () => this.#observers.delete(observer)
63
- }
64
-
65
- /**
66
- * Notify observers of collection changes
67
- * @param {string} event - Event type
68
- * @param {*} data - Event data
69
- */
70
- #notify (event, data) {
71
- this.#observers.forEach(observer => observer({ event, data }))
72
- }
73
-
74
- /**
75
- * Set loading state
76
- * @param {boolean} loading - Loading state
77
- */
78
- #setLoading (loading) {
79
- this.#loading = loading
80
- this.#notify(COLLECTION_EVENTS.LOADING, loading)
81
- }
82
-
83
- /**
84
- * Set error state
85
- * @param {Error} error - Error object
86
- */
87
- #setError (error) {
88
- this.#error = error
89
- this.#notify(COLLECTION_EVENTS.ERROR, error)
90
- }
91
-
92
- /**
93
- * Get collection items based on current query and sort
94
- * @returns {Array<T>} Collection items
95
- */
96
- get items () {
97
- let result = Array.from(this.#items.values())
98
-
99
- if (this.#query) {
100
- result = result.filter(this.#query)
101
- }
102
-
103
- if (this.#sort) {
104
- result.sort(this.#sort)
105
- }
106
-
107
- return result
108
- }
109
-
110
- /**
111
- * Get collection size
112
- * @returns {number} Number of items
113
- */
114
- get size () {
115
- return this.#items.size
116
- }
117
-
118
- /**
119
- * Get loading state
120
- * @returns {boolean} Loading state
121
- */
122
- get loading () {
123
- return this.#loading
124
- }
125
-
126
- /**
127
- * Get error state
128
- * @returns {Error|null} Error object
129
- */
130
- get error () {
131
- return this.#error
132
- }
133
-
134
- /**
135
- * Set query filter
136
- * @param {Function} queryFn - Query function
137
- */
138
- query (queryFn) {
139
- this.#query = queryFn
140
- this.#notify(COLLECTION_EVENTS.CHANGE, this.items)
141
- }
142
-
143
- /**
144
- * Set sort function
145
- * @param {Function} sortFn - Sort function
146
- */
147
- sort (sortFn) {
148
- this.#sort = sortFn
149
- this.#notify(COLLECTION_EVENTS.CHANGE, this.items)
150
- }
151
-
152
- /**
153
- * Add items to collection
154
- * @param {T|Array<T>} items - Items to add
155
- * @returns {Promise<Array<T>>} Added items
156
- */
157
- async add (items) {
158
- try {
159
- this.#setLoading(true)
160
- const toAdd = Array.isArray(items) ? items : [items]
161
-
162
- const validated = toAdd.filter(this.validate)
163
- const transformed = validated.map(this.transform)
164
-
165
- transformed.forEach(item => {
166
- if (!item.id) {
167
- throw new Error('Items must have an id property')
168
- }
169
- this.#items.set(item.id, item)
170
- })
171
-
172
- this.#notify(COLLECTION_EVENTS.ADD, transformed)
173
- this.#notify(COLLECTION_EVENTS.CHANGE, this.items)
174
-
175
- return transformed
176
- } catch (error) {
177
- this.#setError(error)
178
- throw error
179
- } finally {
180
- this.#setLoading(false)
181
- }
182
- }
183
-
184
- /**
185
- * Update items in collection
186
- * @param {T|Array<T>} items - Items to update
187
- * @returns {Promise<Array<T>>} Updated items
188
- */
189
- async update (items) {
190
- try {
191
- this.#setLoading(true)
192
- const toUpdate = Array.isArray(items) ? items : [items]
193
-
194
- const updated = toUpdate.map(item => {
195
- if (!this.#items.has(item.id)) {
196
- throw new Error(`Item with id ${item.id} not found`)
197
- }
198
-
199
- const validated = this.validate(item)
200
- if (!validated) {
201
- throw new Error(`Invalid item: ${item.id}`)
202
- }
203
-
204
- const transformed = this.transform(item)
205
- this.#items.set(item.id, transformed)
206
- return transformed
207
- })
208
-
209
- this.#notify(COLLECTION_EVENTS.UPDATE, updated)
210
- this.#notify(COLLECTION_EVENTS.CHANGE, this.items)
211
-
212
- return updated
213
- } catch (error) {
214
- this.#setError(error)
215
- throw error
216
- } finally {
217
- this.#setLoading(false)
218
- }
219
- }
220
-
221
- /**
222
- * Remove items from collection
223
- * @param {string|Array<string>} ids - Item IDs to remove
224
- * @returns {Promise<Array<string>>} Removed item IDs
225
- */
226
- async remove (ids) {
227
- try {
228
- this.#setLoading(true)
229
- const toRemove = Array.isArray(ids) ? ids : [ids]
230
-
231
- toRemove.forEach(id => {
232
- if (!this.#items.has(id)) {
233
- throw new Error(`Item with id ${id} not found`)
234
- }
235
- this.#items.delete(id)
236
- })
237
-
238
- this.#notify(COLLECTION_EVENTS.REMOVE, toRemove)
239
- this.#notify(COLLECTION_EVENTS.CHANGE, this.items)
240
-
241
- return toRemove
242
- } catch (error) {
243
- this.#setError(error)
244
- throw error
245
- } finally {
246
- this.#setLoading(false)
247
- }
248
- }
249
-
250
- /**
251
- * Clear all items from collection
252
- */
253
- clear () {
254
- this.#items.clear()
255
- this.#query = null
256
- this.#sort = null
257
- this.#notify(COLLECTION_EVENTS.CHANGE, this.items)
258
- }
259
- }
@@ -1,157 +0,0 @@
1
- // src/core/collection/list-manager.js
2
-
3
- import { createRouteAdapter } from './adapters/route'
4
-
5
- /**
6
- * Creates a list manager for a specific collection
7
- * @param {string} collection - Collection name
8
- * @param {Object} config - Configuration options
9
- * @param {Function} config.transform - Transform function for items
10
- * @param {string} config.baseUrl - Base API URL
11
- * @returns {Object} List manager methods
12
- */
13
- export const createListManager = (collection, config = {}) => {
14
- const {
15
- transform = (item) => item,
16
- baseUrl = 'http://localhost:4000/api'
17
- } = config
18
-
19
- // Initialize route adapter
20
- const adapter = createRouteAdapter({
21
- base: baseUrl,
22
- endpoints: {
23
- list: `/${collection}`
24
- },
25
- headers: {
26
- 'Content-Type': 'application/json'
27
- }
28
- })
29
-
30
- // Load items with cursor pagination
31
- const loadItems = async (params = {}) => {
32
- try {
33
- const response = await adapter.read(params)
34
-
35
- return {
36
- items: response.items.map(transform),
37
- meta: response.meta
38
- }
39
- } catch (error) {
40
- console.error(`Error loading ${collection}:`, error)
41
- return {
42
- items: [],
43
- meta: {
44
- cursor: null,
45
- hasNext: false
46
- }
47
- }
48
- }
49
- }
50
-
51
- // Utility to create a cursor-based page loader
52
- const createPageLoader = (list, { onLoad, pageSize = 20 } = {}) => {
53
- let currentCursor = null
54
- let loading = false
55
- const pageHistory = []
56
-
57
- const load = async (cursor = null, addToHistory = true) => {
58
- if (loading) return
59
-
60
- loading = true
61
- onLoad?.({ loading: true })
62
-
63
- const { items, meta } = await loadItems({
64
- limit: pageSize,
65
- cursor
66
- })
67
-
68
- if (addToHistory && cursor) {
69
- pageHistory.push(currentCursor)
70
- }
71
- currentCursor = meta.cursor
72
-
73
- list.setItems(items)
74
- loading = false
75
-
76
- onLoad?.({
77
- loading: false,
78
- hasNext: meta.hasNext,
79
- hasPrev: pageHistory.length > 0,
80
- items
81
- })
82
-
83
- return {
84
- hasNext: meta.hasNext,
85
- hasPrev: pageHistory.length > 0
86
- }
87
- }
88
-
89
- const loadNext = () => load(currentCursor)
90
-
91
- const loadPrev = () => {
92
- const previousCursor = pageHistory.pop()
93
- return load(previousCursor, false)
94
- }
95
-
96
- return {
97
- load,
98
- loadNext,
99
- loadPrev,
100
- get loading () { return loading },
101
- get cursor () { return currentCursor }
102
- }
103
- }
104
-
105
- return {
106
- loadItems,
107
- createPageLoader
108
- }
109
- }
110
-
111
- /**
112
- * Transform functions for common collections
113
- */
114
- export const transforms = {
115
- track: (track) => ({
116
- id: track._id,
117
- headline: track.title || 'Untitled',
118
- supportingText: track.artist || 'Unknown Artist',
119
- meta: track.year?.toString() || ''
120
- }),
121
-
122
- playlist: (playlist) => ({
123
- id: playlist._id,
124
- headline: playlist.name || 'Untitled Playlist',
125
- supportingText: `${playlist.tracks?.length || 0} tracks`,
126
- meta: playlist.creator || ''
127
- }),
128
-
129
- country: (country) => ({
130
- id: country._id,
131
- headline: country.name || country.code,
132
- supportingText: country.continent || '',
133
- meta: country.code || ''
134
- })
135
- }
136
-
137
- /**
138
- * Usage example:
139
- *
140
- * const trackManager = createListManager('track', {
141
- * transform: transforms.track
142
- * })
143
- *
144
- * const loader = trackManager.createPageLoader(list, {
145
- * onLoad: ({ loading, hasNext, items }) => {
146
- * updateNavigation({ loading, hasNext })
147
- * logEvent(`Loaded ${items.length} tracks`)
148
- * }
149
- * })
150
- *
151
- * // Initial load
152
- * await loader.load()
153
- *
154
- * // Navigation
155
- * nextButton.onclick = () => loader.loadNext()
156
- * prevButton.onclick = () => loader.loadPrev()
157
- */