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
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
// src/core/collection/list-manager.ts
|
|
2
|
+
|
|
3
|
+
import { createRouteAdapter, RouteAdapter, QueryDefinition } from './adapters/route';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* List item interface
|
|
7
|
+
*/
|
|
8
|
+
export interface ListItem {
|
|
9
|
+
id: string;
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* List with items setter
|
|
15
|
+
*/
|
|
16
|
+
export interface List {
|
|
17
|
+
setItems: (items: any[]) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Metadata for pagination
|
|
22
|
+
*/
|
|
23
|
+
export interface PaginationMeta {
|
|
24
|
+
cursor: string | null;
|
|
25
|
+
hasNext: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Response format from loadItems
|
|
30
|
+
*/
|
|
31
|
+
export interface LoadItemsResponse<T = any> {
|
|
32
|
+
items: T[];
|
|
33
|
+
meta: PaginationMeta;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Load status callback data
|
|
38
|
+
*/
|
|
39
|
+
export interface LoadStatus<T = any> {
|
|
40
|
+
loading: boolean;
|
|
41
|
+
hasNext?: boolean;
|
|
42
|
+
hasPrev?: boolean;
|
|
43
|
+
items?: T[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Page loader configuration
|
|
48
|
+
*/
|
|
49
|
+
export interface PageLoaderConfig<T = any> {
|
|
50
|
+
onLoad?: (status: LoadStatus<T>) => void;
|
|
51
|
+
pageSize?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Page loader interface
|
|
56
|
+
*/
|
|
57
|
+
export interface PageLoader<T = any> {
|
|
58
|
+
/**
|
|
59
|
+
* Load a specific page by cursor
|
|
60
|
+
* @param cursor - Cursor pointing to the page
|
|
61
|
+
* @param addToHistory - Whether to add current cursor to history
|
|
62
|
+
* @returns Page status
|
|
63
|
+
*/
|
|
64
|
+
load: (cursor?: string | null, addToHistory?: boolean) => Promise<{
|
|
65
|
+
hasNext: boolean;
|
|
66
|
+
hasPrev: boolean;
|
|
67
|
+
} | undefined>;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Load next page
|
|
71
|
+
* @returns Page status
|
|
72
|
+
*/
|
|
73
|
+
loadNext: () => Promise<{
|
|
74
|
+
hasNext: boolean;
|
|
75
|
+
hasPrev: boolean;
|
|
76
|
+
} | undefined>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Load previous page
|
|
80
|
+
* @returns Page status
|
|
81
|
+
*/
|
|
82
|
+
loadPrev: () => Promise<{
|
|
83
|
+
hasNext: boolean;
|
|
84
|
+
hasPrev: boolean;
|
|
85
|
+
} | undefined>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Current loading state
|
|
89
|
+
*/
|
|
90
|
+
readonly loading: boolean;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Current cursor
|
|
94
|
+
*/
|
|
95
|
+
readonly cursor: string | null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* List manager config
|
|
100
|
+
*/
|
|
101
|
+
export interface ListManagerConfig<T = any> {
|
|
102
|
+
/**
|
|
103
|
+
* Transform function to convert API items to app format
|
|
104
|
+
*/
|
|
105
|
+
transform?: (item: any) => T;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Base URL for API requests
|
|
109
|
+
*/
|
|
110
|
+
baseUrl?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* List manager interface
|
|
115
|
+
*/
|
|
116
|
+
export interface ListManager<T = any> {
|
|
117
|
+
/**
|
|
118
|
+
* Load items with optional parameters
|
|
119
|
+
* @param params - Query parameters
|
|
120
|
+
* @returns Loaded items with pagination metadata
|
|
121
|
+
*/
|
|
122
|
+
loadItems: (params?: Record<string, any>) => Promise<LoadItemsResponse<T>>;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Create a page loader for the specified list
|
|
126
|
+
* @param list - List to manage
|
|
127
|
+
* @param config - Page loader configuration
|
|
128
|
+
* @returns Page loader
|
|
129
|
+
*/
|
|
130
|
+
createPageLoader: (list: List, config?: PageLoaderConfig<T>) => PageLoader<T>;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Creates a list manager for a specific collection
|
|
135
|
+
* @param collection - Collection name
|
|
136
|
+
* @param config - Configuration options
|
|
137
|
+
* @returns List manager methods
|
|
138
|
+
*/
|
|
139
|
+
export const createListManager = <T = any>(
|
|
140
|
+
collection: string,
|
|
141
|
+
config: ListManagerConfig<T> = {}
|
|
142
|
+
): ListManager<T> => {
|
|
143
|
+
const {
|
|
144
|
+
transform = (item: any) => item as T,
|
|
145
|
+
baseUrl = 'http://localhost:4000/api'
|
|
146
|
+
} = config;
|
|
147
|
+
|
|
148
|
+
// Initialize route adapter
|
|
149
|
+
const adapter: RouteAdapter = createRouteAdapter({
|
|
150
|
+
base: baseUrl,
|
|
151
|
+
endpoints: {
|
|
152
|
+
list: `/${collection}`
|
|
153
|
+
},
|
|
154
|
+
headers: {
|
|
155
|
+
'Content-Type': 'application/json'
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Load items with cursor pagination
|
|
161
|
+
* @param params - Query parameters
|
|
162
|
+
* @returns Loaded items with pagination metadata
|
|
163
|
+
*/
|
|
164
|
+
const loadItems = async (params: Record<string, any> = {}): Promise<LoadItemsResponse<T>> => {
|
|
165
|
+
try {
|
|
166
|
+
const response = await adapter.read(params as QueryDefinition);
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
items: response.items.map(transform),
|
|
170
|
+
meta: response.meta as PaginationMeta
|
|
171
|
+
};
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error(`Error loading ${collection}:`, error);
|
|
174
|
+
return {
|
|
175
|
+
items: [],
|
|
176
|
+
meta: {
|
|
177
|
+
cursor: null,
|
|
178
|
+
hasNext: false
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Create a page loader for the specified list
|
|
186
|
+
* @param list - List to manage
|
|
187
|
+
* @param config - Page loader configuration
|
|
188
|
+
* @returns Page loader
|
|
189
|
+
*/
|
|
190
|
+
const createPageLoader = (
|
|
191
|
+
list: List,
|
|
192
|
+
{ onLoad, pageSize = 20 }: PageLoaderConfig<T> = {}
|
|
193
|
+
): PageLoader<T> => {
|
|
194
|
+
let currentCursor: string | null = null;
|
|
195
|
+
let loading = false;
|
|
196
|
+
const pageHistory: Array<string | null> = [];
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Load a specific page by cursor
|
|
200
|
+
* @param cursor - Cursor pointing to the page
|
|
201
|
+
* @param addToHistory - Whether to add current cursor to history
|
|
202
|
+
* @returns Page status
|
|
203
|
+
*/
|
|
204
|
+
const load = async (
|
|
205
|
+
cursor: string | null = null,
|
|
206
|
+
addToHistory = true
|
|
207
|
+
): Promise<{ hasNext: boolean; hasPrev: boolean } | undefined> => {
|
|
208
|
+
if (loading) return;
|
|
209
|
+
|
|
210
|
+
loading = true;
|
|
211
|
+
onLoad?.({ loading: true });
|
|
212
|
+
|
|
213
|
+
const { items, meta } = await loadItems({
|
|
214
|
+
limit: pageSize,
|
|
215
|
+
cursor
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
if (addToHistory && cursor) {
|
|
219
|
+
pageHistory.push(currentCursor);
|
|
220
|
+
}
|
|
221
|
+
currentCursor = meta.cursor;
|
|
222
|
+
|
|
223
|
+
list.setItems(items);
|
|
224
|
+
loading = false;
|
|
225
|
+
|
|
226
|
+
const status = {
|
|
227
|
+
loading: false,
|
|
228
|
+
hasNext: meta.hasNext,
|
|
229
|
+
hasPrev: pageHistory.length > 0,
|
|
230
|
+
items
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
onLoad?.(status);
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
hasNext: meta.hasNext,
|
|
237
|
+
hasPrev: pageHistory.length > 0
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Load next page
|
|
243
|
+
* @returns Page status
|
|
244
|
+
*/
|
|
245
|
+
const loadNext = (): Promise<{ hasNext: boolean; hasPrev: boolean } | undefined> =>
|
|
246
|
+
load(currentCursor);
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Load previous page
|
|
250
|
+
* @returns Page status
|
|
251
|
+
*/
|
|
252
|
+
const loadPrev = (): Promise<{ hasNext: boolean; hasPrev: boolean } | undefined> => {
|
|
253
|
+
const previousCursor = pageHistory.pop();
|
|
254
|
+
return load(previousCursor, false);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
load,
|
|
259
|
+
loadNext,
|
|
260
|
+
loadPrev,
|
|
261
|
+
get loading(): boolean { return loading; },
|
|
262
|
+
get cursor(): string | null { return currentCursor; }
|
|
263
|
+
};
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
loadItems,
|
|
268
|
+
createPageLoader
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Transform functions for common collections
|
|
274
|
+
*/
|
|
275
|
+
export const transforms = {
|
|
276
|
+
/**
|
|
277
|
+
* Transform track data
|
|
278
|
+
* @param track - Raw track data
|
|
279
|
+
* @returns Formatted track data
|
|
280
|
+
*/
|
|
281
|
+
track: (track: any): ListItem => ({
|
|
282
|
+
id: track._id,
|
|
283
|
+
headline: track.title || 'Untitled',
|
|
284
|
+
supportingText: track.artist || 'Unknown Artist',
|
|
285
|
+
meta: track.year?.toString() || ''
|
|
286
|
+
}),
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Transform playlist data
|
|
290
|
+
* @param playlist - Raw playlist data
|
|
291
|
+
* @returns Formatted playlist data
|
|
292
|
+
*/
|
|
293
|
+
playlist: (playlist: any): ListItem => ({
|
|
294
|
+
id: playlist._id,
|
|
295
|
+
headline: playlist.name || 'Untitled Playlist',
|
|
296
|
+
supportingText: `${playlist.tracks?.length || 0} tracks`,
|
|
297
|
+
meta: playlist.creator || ''
|
|
298
|
+
}),
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Transform country data
|
|
302
|
+
* @param country - Raw country data
|
|
303
|
+
* @returns Formatted country data
|
|
304
|
+
*/
|
|
305
|
+
country: (country: any): ListItem => ({
|
|
306
|
+
id: country._id,
|
|
307
|
+
headline: country.name || country.code,
|
|
308
|
+
supportingText: country.continent || '',
|
|
309
|
+
meta: country.code || ''
|
|
310
|
+
})
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Usage example:
|
|
315
|
+
*
|
|
316
|
+
* const trackManager = createListManager<TrackItem>('track', {
|
|
317
|
+
* transform: transforms.track
|
|
318
|
+
* });
|
|
319
|
+
*
|
|
320
|
+
* const loader = trackManager.createPageLoader(list, {
|
|
321
|
+
* onLoad: ({ loading, hasNext, items }) => {
|
|
322
|
+
* updateNavigation({ loading, hasNext });
|
|
323
|
+
* logEvent(`Loaded ${items.length} tracks`);
|
|
324
|
+
* }
|
|
325
|
+
* });
|
|
326
|
+
*
|
|
327
|
+
* // Initial load
|
|
328
|
+
* await loader.load();
|
|
329
|
+
*
|
|
330
|
+
* // Navigation
|
|
331
|
+
* nextButton.onclick = () => loader.loadNext();
|
|
332
|
+
* prevButton.onclick = () => loader.loadPrev();
|
|
333
|
+
*/
|
package/src/core/dom/classes.ts
CHANGED
|
@@ -1,60 +1,132 @@
|
|
|
1
1
|
// src/core/dom/classes.ts
|
|
2
2
|
/**
|
|
3
3
|
* @module core/dom
|
|
4
|
-
* @description DOM manipulation utilities
|
|
4
|
+
* @description DOM manipulation utilities optimized for performance
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { normalizeClasses } from '../utils';
|
|
8
|
+
import { PREFIX } from '../config';
|
|
9
|
+
|
|
10
|
+
// Constant for prefix with dash for better performance
|
|
11
|
+
const PREFIX_WITH_DASH = `${PREFIX}-`;
|
|
8
12
|
|
|
9
13
|
/**
|
|
10
14
|
* Adds multiple classes to an element
|
|
15
|
+
* Automatically adds prefix to classes that don't already have it
|
|
16
|
+
* Optimized for minimal array operations and DOM interactions
|
|
17
|
+
*
|
|
11
18
|
* @param {HTMLElement} element - Target element
|
|
12
19
|
* @param {...(string | string[])} classes - Classes to add
|
|
13
20
|
* @returns {HTMLElement} Modified element
|
|
14
21
|
*/
|
|
15
22
|
export const addClass = (element: HTMLElement, ...classes: (string | string[])[]): HTMLElement => {
|
|
16
23
|
const normalizedClasses = normalizeClasses(...classes);
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
|
|
25
|
+
// Early return for empty classes
|
|
26
|
+
if (!normalizedClasses.length) return element;
|
|
27
|
+
|
|
28
|
+
// Using DOMTokenList's add() with multiple arguments is faster than multiple add() calls
|
|
29
|
+
// But we need to handle prefixing first
|
|
30
|
+
const prefixedClasses: string[] = [];
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < normalizedClasses.length; i++) {
|
|
33
|
+
const cls = normalizedClasses[i];
|
|
34
|
+
if (!cls) continue;
|
|
35
|
+
|
|
36
|
+
prefixedClasses.push(
|
|
37
|
+
cls.startsWith(PREFIX_WITH_DASH) ? cls : PREFIX_WITH_DASH + cls
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Add all classes in a single operation if possible
|
|
42
|
+
if (prefixedClasses.length) {
|
|
43
|
+
element.classList.add(...prefixedClasses);
|
|
19
44
|
}
|
|
45
|
+
|
|
20
46
|
return element;
|
|
21
47
|
};
|
|
22
48
|
|
|
23
49
|
/**
|
|
24
50
|
* Removes multiple classes from an element
|
|
51
|
+
* Handles only exact class names as specified (no automatic prefixing)
|
|
52
|
+
* For better performance, clients should know exactly which classes to remove
|
|
53
|
+
*
|
|
25
54
|
* @param {HTMLElement} element - Target element
|
|
26
55
|
* @param {...(string | string[])} classes - Classes to remove
|
|
27
56
|
* @returns {HTMLElement} Modified element
|
|
28
57
|
*/
|
|
29
58
|
export const removeClass = (element: HTMLElement, ...classes: (string | string[])[]): HTMLElement => {
|
|
30
59
|
const normalizedClasses = normalizeClasses(...classes);
|
|
31
|
-
|
|
32
|
-
|
|
60
|
+
|
|
61
|
+
// Early return for empty classes
|
|
62
|
+
if (!normalizedClasses.length) return element;
|
|
63
|
+
|
|
64
|
+
// Prepare prefixed class names
|
|
65
|
+
const prefixedClasses: string[] = [];
|
|
66
|
+
|
|
67
|
+
for (let i = 0; i < normalizedClasses.length; i++) {
|
|
68
|
+
const cls = normalizedClasses[i];
|
|
69
|
+
if (!cls) continue;
|
|
70
|
+
|
|
71
|
+
// Only add the prefixed version
|
|
72
|
+
prefixedClasses.push(
|
|
73
|
+
cls.startsWith(PREFIX_WITH_DASH) ? cls : PREFIX_WITH_DASH + cls
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Remove all classes in a single operation if possible
|
|
78
|
+
if (prefixedClasses.length) {
|
|
79
|
+
element.classList.remove(...prefixedClasses);
|
|
33
80
|
}
|
|
81
|
+
|
|
34
82
|
return element;
|
|
35
83
|
};
|
|
36
84
|
|
|
37
85
|
/**
|
|
38
86
|
* Toggles multiple classes on an element
|
|
87
|
+
* Automatically adds prefix to classes that don't already have it
|
|
88
|
+
*
|
|
39
89
|
* @param {HTMLElement} element - Target element
|
|
40
90
|
* @param {...(string | string[])} classes - Classes to toggle
|
|
41
91
|
* @returns {HTMLElement} Modified element
|
|
42
92
|
*/
|
|
43
93
|
export const toggleClass = (element: HTMLElement, ...classes: (string | string[])[]): HTMLElement => {
|
|
44
94
|
const normalizedClasses = normalizeClasses(...classes);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < normalizedClasses.length; i++) {
|
|
97
|
+
const cls = normalizedClasses[i];
|
|
98
|
+
if (!cls) continue;
|
|
99
|
+
|
|
100
|
+
const prefixedClass = cls.startsWith(PREFIX_WITH_DASH) ? cls : PREFIX_WITH_DASH + cls;
|
|
101
|
+
element.classList.toggle(prefixedClass);
|
|
102
|
+
}
|
|
103
|
+
|
|
48
104
|
return element;
|
|
49
105
|
};
|
|
50
106
|
|
|
51
107
|
/**
|
|
52
108
|
* Checks if an element has all specified classes
|
|
109
|
+
* Automatically adds prefix to classes that don't already have it
|
|
110
|
+
*
|
|
53
111
|
* @param {HTMLElement} element - Target element
|
|
54
112
|
* @param {...(string | string[])} classes - Classes to check
|
|
55
113
|
* @returns {boolean} True if element has all specified classes
|
|
56
114
|
*/
|
|
57
115
|
export const hasClass = (element: HTMLElement, ...classes: (string | string[])[]): boolean => {
|
|
58
116
|
const normalizedClasses = normalizeClasses(...classes);
|
|
59
|
-
|
|
117
|
+
|
|
118
|
+
// Early return for empty classes (technically all are present)
|
|
119
|
+
if (!normalizedClasses.length) return true;
|
|
120
|
+
|
|
121
|
+
for (let i = 0; i < normalizedClasses.length; i++) {
|
|
122
|
+
const cls = normalizedClasses[i];
|
|
123
|
+
if (!cls) continue;
|
|
124
|
+
|
|
125
|
+
const prefixedClass = cls.startsWith(PREFIX_WITH_DASH) ? cls : PREFIX_WITH_DASH + cls;
|
|
126
|
+
if (!element.classList.contains(prefixedClass)) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return true;
|
|
60
132
|
};
|
package/src/core/dom/create.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { setAttributes } from './attributes';
|
|
8
8
|
import { normalizeClasses } from '../utils';
|
|
9
9
|
import { PREFIX } from '../config';
|
|
10
|
+
import { addClass } from './classes'; // Import addClass
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Event handler function type
|
|
@@ -58,9 +59,22 @@ export interface CreateElementOptions {
|
|
|
58
59
|
data?: Record<string, string>;
|
|
59
60
|
|
|
60
61
|
/**
|
|
61
|
-
* CSS classes
|
|
62
|
+
* CSS classes (will be automatically prefixed with 'mtrl-')
|
|
63
|
+
* Alias for 'className'
|
|
62
64
|
*/
|
|
63
|
-
|
|
65
|
+
class?: string | string[];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* CSS classes (will be automatically prefixed with 'mtrl-')
|
|
69
|
+
* Alias for 'class'
|
|
70
|
+
*/
|
|
71
|
+
className?: string | string[];
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* CSS classes that will NOT be prefixed
|
|
75
|
+
* Added as-is to the element
|
|
76
|
+
*/
|
|
77
|
+
rawClass?: string | string[];
|
|
64
78
|
|
|
65
79
|
/**
|
|
66
80
|
* HTML attributes
|
|
@@ -95,9 +109,6 @@ export interface EventHandlerStorage {
|
|
|
95
109
|
[eventName: string]: EventHandler;
|
|
96
110
|
}
|
|
97
111
|
|
|
98
|
-
// Constant for prefix with dash
|
|
99
|
-
const PREFIX_WITH_DASH = `${PREFIX}-`;
|
|
100
|
-
|
|
101
112
|
/**
|
|
102
113
|
* Creates a DOM element with the specified options
|
|
103
114
|
*
|
|
@@ -112,7 +123,9 @@ export const createElement = (options: CreateElementOptions = {}): HTMLElement =
|
|
|
112
123
|
text = '',
|
|
113
124
|
id = '',
|
|
114
125
|
data = {},
|
|
126
|
+
class: classOption,
|
|
115
127
|
className,
|
|
128
|
+
rawClass,
|
|
116
129
|
attrs = {},
|
|
117
130
|
forwardEvents = {},
|
|
118
131
|
onCreate,
|
|
@@ -127,14 +140,17 @@ export const createElement = (options: CreateElementOptions = {}): HTMLElement =
|
|
|
127
140
|
if (text) element.textContent = text;
|
|
128
141
|
if (id) element.id = id;
|
|
129
142
|
|
|
130
|
-
// Handle classes
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
143
|
+
// 1. Handle prefixed classes using addClass
|
|
144
|
+
const prefixedClassSource = classOption || className;
|
|
145
|
+
if (prefixedClassSource) {
|
|
146
|
+
addClass(element, prefixedClassSource);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 2. Handle raw classes (no prefix)
|
|
150
|
+
if (rawClass) {
|
|
151
|
+
const rawClasses = normalizeClasses(rawClass);
|
|
152
|
+
if (rawClasses.length) {
|
|
153
|
+
element.classList.add(...rawClasses);
|
|
138
154
|
}
|
|
139
155
|
}
|
|
140
156
|
|
|
@@ -230,12 +246,7 @@ export const withAttributes = (attrs: Record<string, any>) =>
|
|
|
230
246
|
*/
|
|
231
247
|
export const withClasses = (...classes: (string | string[])[]) =>
|
|
232
248
|
(element: HTMLElement): HTMLElement => {
|
|
233
|
-
|
|
234
|
-
if (normalizedClasses.length) {
|
|
235
|
-
element.classList.add(...normalizedClasses.map(cls =>
|
|
236
|
-
cls && !cls.startsWith(PREFIX_WITH_DASH) ? PREFIX_WITH_DASH + cls : cls
|
|
237
|
-
).filter(Boolean));
|
|
238
|
-
}
|
|
249
|
+
addClass(element, ...classes);
|
|
239
250
|
return element;
|
|
240
251
|
};
|
|
241
252
|
|