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.
- package/package.json +1 -1
- package/src/components/button/api.ts +16 -0
- package/src/components/button/types.ts +9 -0
- package/src/components/menu/api.ts +144 -267
- package/src/components/menu/config.ts +84 -40
- package/src/components/menu/features/anchor.ts +243 -0
- package/src/components/menu/features/controller.ts +1167 -0
- package/src/components/menu/features/index.ts +5 -0
- package/src/components/menu/features/position.ts +353 -0
- package/src/components/menu/index.ts +31 -63
- package/src/components/menu/menu.ts +72 -104
- package/src/components/menu/types.ts +264 -447
- package/src/components/select/api.ts +78 -0
- package/src/components/select/config.ts +76 -0
- package/src/components/select/features.ts +317 -0
- package/src/components/select/index.ts +38 -0
- package/src/components/select/select.ts +73 -0
- package/src/components/select/types.ts +355 -0
- package/src/components/textfield/api.ts +78 -6
- package/src/components/textfield/features/index.ts +17 -0
- package/src/components/textfield/features/leading-icon.ts +127 -0
- package/src/components/textfield/features/placement.ts +149 -0
- package/src/components/textfield/features/prefix-text.ts +107 -0
- package/src/components/textfield/features/suffix-text.ts +100 -0
- package/src/components/textfield/features/supporting-text.ts +113 -0
- package/src/components/textfield/features/trailing-icon.ts +108 -0
- package/src/components/textfield/textfield.ts +51 -15
- package/src/components/textfield/types.ts +70 -0
- package/src/core/collection/adapters/base.ts +62 -0
- package/src/core/collection/collection.ts +300 -0
- package/src/core/collection/index.ts +57 -0
- package/src/core/collection/list-manager.ts +333 -0
- package/src/core/dom/classes.ts +81 -9
- package/src/core/dom/create.ts +30 -19
- package/src/core/layout/README.md +531 -166
- package/src/core/layout/array.ts +3 -4
- package/src/core/layout/config.ts +193 -0
- package/src/core/layout/create.ts +1 -2
- package/src/core/layout/index.ts +12 -2
- package/src/core/layout/object.ts +2 -3
- package/src/core/layout/processor.ts +60 -12
- package/src/core/layout/result.ts +1 -2
- package/src/core/layout/types.ts +105 -50
- package/src/core/layout/utils.ts +69 -61
- package/src/index.ts +6 -2
- package/src/styles/abstract/_variables.scss +18 -0
- package/src/styles/components/_button.scss +21 -5
- package/src/styles/components/{_chip.scss → _chips.scss} +118 -4
- package/src/styles/components/_menu.scss +109 -18
- package/src/styles/components/_select.scss +265 -0
- package/src/styles/components/_textfield.scss +233 -42
- package/src/styles/main.scss +24 -23
- package/src/styles/utilities/_layout.scss +665 -0
- package/src/components/menu/features/items-manager.ts +0 -457
- package/src/components/menu/features/keyboard-navigation.ts +0 -133
- package/src/components/menu/features/positioning.ts +0 -127
- package/src/components/menu/features/visibility.ts +0 -230
- package/src/components/menu/menu-item.ts +0 -86
- package/src/components/menu/utils.ts +0 -67
- package/src/components/textfield/features.ts +0 -322
- package/src/core/collection/adapters/base.js +0 -26
- package/src/core/collection/collection.js +0 -259
- package/src/core/collection/list-manager.js +0 -157
- /package/src/core/collection/adapters/{route.js → route.ts} +0 -0
- /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
|
-
*/
|
|
File without changes
|
|
File without changes
|