mtrl 0.2.8 → 0.3.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/index.ts +4 -0
- package/package.json +1 -1
- package/src/components/button/button.ts +34 -5
- package/src/components/navigation/api.ts +131 -96
- package/src/components/navigation/features/controller.ts +273 -0
- package/src/components/navigation/features/items.ts +133 -64
- package/src/components/navigation/navigation.ts +17 -2
- package/src/components/navigation/system/core.ts +302 -0
- package/src/components/navigation/system/events.ts +240 -0
- package/src/components/navigation/system/index.ts +184 -0
- package/src/components/navigation/system/mobile.ts +278 -0
- package/src/components/navigation/system/state.ts +77 -0
- package/src/components/navigation/system/types.ts +364 -0
- package/src/components/slider/config.ts +20 -2
- package/src/components/slider/features/controller.ts +737 -0
- package/src/components/slider/features/handlers.ts +18 -16
- package/src/components/slider/features/index.ts +3 -2
- package/src/components/slider/features/range.ts +104 -0
- package/src/components/slider/schema.ts +141 -0
- package/src/components/slider/slider.ts +34 -13
- package/src/components/switch/api.ts +16 -0
- package/src/components/switch/config.ts +1 -18
- package/src/components/switch/features.ts +198 -0
- package/src/components/switch/index.ts +1 -0
- package/src/components/switch/switch.ts +3 -3
- package/src/components/switch/types.ts +14 -2
- package/src/components/textfield/api.ts +53 -0
- package/src/components/textfield/features.ts +322 -0
- package/src/components/textfield/textfield.ts +8 -0
- package/src/components/textfield/types.ts +12 -3
- package/src/components/timepicker/clockdial.ts +1 -4
- package/src/core/compose/features/textinput.ts +15 -2
- package/src/core/composition/features/dom.ts +45 -0
- package/src/core/composition/features/icon.ts +131 -0
- package/src/core/composition/features/index.ts +12 -0
- package/src/core/composition/features/label.ts +155 -0
- package/src/core/composition/features/layout.ts +47 -0
- package/src/core/composition/index.ts +26 -0
- package/src/core/index.ts +1 -1
- package/src/core/layout/README.md +350 -0
- package/src/core/layout/array.ts +181 -0
- package/src/core/layout/create.ts +55 -0
- package/src/core/layout/index.ts +26 -0
- package/src/core/layout/object.ts +124 -0
- package/src/core/layout/processor.ts +58 -0
- package/src/core/layout/result.ts +85 -0
- package/src/core/layout/types.ts +125 -0
- package/src/core/layout/utils.ts +136 -0
- package/src/index.ts +1 -0
- package/src/styles/abstract/_variables.scss +28 -0
- package/src/styles/components/_navigation-mobile.scss +244 -0
- package/src/styles/components/_navigation-system.scss +151 -0
- package/src/styles/components/_switch.scss +133 -69
- package/src/styles/components/_textfield.scss +259 -27
- package/demo/build.ts +0 -349
- package/demo/index.html +0 -110
- package/demo/main.js +0 -448
- package/demo/styles.css +0 -239
- package/server.ts +0 -86
- package/src/components/slider/features/slider.ts +0 -318
- package/src/components/slider/features/structure.ts +0 -181
- package/src/components/slider/features/ui.ts +0 -388
- package/src/components/textfield/constants.ts +0 -100
- package/src/core/layout/index.js +0 -95
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
# Layout Module
|
|
2
|
+
|
|
3
|
+
A lightweight, flexible system for creating and managing visual arrangements and component hierarchies.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Layout Module provides a declarative approach to building UI layouts using either arrays or objects. It efficiently handles DOM operations, component instantiation, and visual arrangement in a bundle-optimized way.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Multiple Schema Formats** - Support for array-based, object-based, and HTML string schemas
|
|
12
|
+
- **Efficient DOM Operations** - Batched DOM manipulations with DocumentFragment
|
|
13
|
+
- **Component Management** - Easy access to component instances via consistent API
|
|
14
|
+
- **Customizable Creation** - Control class prefixing and specify default creators
|
|
15
|
+
- **Optimized for Bundle Size** - Minimal footprint with maximum functionality
|
|
16
|
+
- **TypeScript Support** - Full type definitions for developer experience
|
|
17
|
+
|
|
18
|
+
## Basic Usage
|
|
19
|
+
|
|
20
|
+
### Array-based Layout
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
import { createLayout, createButton, createDialog, createList, createListItem } from 'mtrl';
|
|
24
|
+
|
|
25
|
+
const layout = createLayout([
|
|
26
|
+
// Root level contains primary components
|
|
27
|
+
createButton, 'submitButton', { text: 'Submit', variant: 'primary' },
|
|
28
|
+
|
|
29
|
+
// Dialog is a root component, not nested inside other elements
|
|
30
|
+
createDialog, 'confirmDialog', {
|
|
31
|
+
title: 'Confirm Action',
|
|
32
|
+
closeOnBackdrop: true,
|
|
33
|
+
width: '350px'
|
|
34
|
+
}
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
// Add content to the dialog separately
|
|
38
|
+
const dialogContent = createLayout([
|
|
39
|
+
createList, 'actionsList', {},
|
|
40
|
+
[
|
|
41
|
+
createListItem, 'confirmAction', { text: 'Confirm', leading: 'check' },
|
|
42
|
+
createListItem, 'cancelAction', { text: 'Cancel', leading: 'close' }
|
|
43
|
+
]
|
|
44
|
+
], layout.get('confirmDialog').contentElement);
|
|
45
|
+
|
|
46
|
+
// Access components
|
|
47
|
+
const submitButton = layout.get('submitButton');
|
|
48
|
+
const confirmDialog = layout.get('confirmDialog');
|
|
49
|
+
|
|
50
|
+
// Handle events
|
|
51
|
+
submitButton.on('click', () => confirmDialog.open());
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Object-based Layout
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
import { createLayout, createTopAppBar, createNavigation, createList, createListItem, createTextfield, createButton } from 'mtrl';
|
|
58
|
+
|
|
59
|
+
const layout = createLayout({
|
|
60
|
+
element: {
|
|
61
|
+
creator: createTopAppBar,
|
|
62
|
+
options: {
|
|
63
|
+
title: 'Profile Settings',
|
|
64
|
+
variant: 'small'
|
|
65
|
+
},
|
|
66
|
+
children: {
|
|
67
|
+
navigation: {
|
|
68
|
+
creator: createNavigation,
|
|
69
|
+
options: { variant: 'drawer', persistent: true },
|
|
70
|
+
children: {
|
|
71
|
+
navList: {
|
|
72
|
+
creator: createList,
|
|
73
|
+
options: { interactive: true },
|
|
74
|
+
children: {
|
|
75
|
+
profileLink: {
|
|
76
|
+
creator: createListItem,
|
|
77
|
+
options: { text: 'Profile', leading: 'person' }
|
|
78
|
+
},
|
|
79
|
+
settingsLink: {
|
|
80
|
+
creator: createListItem,
|
|
81
|
+
options: { text: 'Settings', leading: 'settings' }
|
|
82
|
+
},
|
|
83
|
+
logoutLink: {
|
|
84
|
+
creator: createListItem,
|
|
85
|
+
options: { text: 'Logout', leading: 'logout' }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
form: {
|
|
92
|
+
creator: createElement,
|
|
93
|
+
options: { tag: 'form', className: 'profile-form' },
|
|
94
|
+
children: {
|
|
95
|
+
nameField: {
|
|
96
|
+
creator: createTextfield,
|
|
97
|
+
options: { label: 'Full Name', required: true }
|
|
98
|
+
},
|
|
99
|
+
emailField: {
|
|
100
|
+
creator: createTextfield,
|
|
101
|
+
options: { label: 'Email', type: 'email', required: true }
|
|
102
|
+
},
|
|
103
|
+
saveButton: {
|
|
104
|
+
creator: createButton,
|
|
105
|
+
options: { text: 'Save Changes', variant: 'filled' }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Access components
|
|
114
|
+
const topAppBar = layout.element;
|
|
115
|
+
const nameField = layout.get('nameField');
|
|
116
|
+
const saveButton = layout.get('saveButton');
|
|
117
|
+
|
|
118
|
+
// Use the components
|
|
119
|
+
nameField.setValue('John Doe');
|
|
120
|
+
saveButton.on('click', () => console.log('Profile updated'));
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### HTML String Layout
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
import createLayout from 'core/layout';
|
|
127
|
+
|
|
128
|
+
const layout = createLayout(`
|
|
129
|
+
<div class="notification">
|
|
130
|
+
<h3>Welcome!</h3>
|
|
131
|
+
<p>Thank you for joining our platform.</p>
|
|
132
|
+
</div>
|
|
133
|
+
`);
|
|
134
|
+
|
|
135
|
+
// Access the root element
|
|
136
|
+
const notification = layout.element;
|
|
137
|
+
document.body.appendChild(notification);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Using Options Parameter
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
import { createLayout, createButton, createTextfield, createCard, createChip, createTopAppBar, createBottomAppBar } from 'mtrl';
|
|
144
|
+
|
|
145
|
+
// With default creator and disabled prefix
|
|
146
|
+
const formLayout = createLayout(
|
|
147
|
+
[
|
|
148
|
+
// Using string keys relies on the default creator
|
|
149
|
+
'nameField', { label: 'Name', required: true },
|
|
150
|
+
'emailField', { label: 'Email', type: 'email', required: true },
|
|
151
|
+
'phoneField', { label: 'Phone', type: 'tel' },
|
|
152
|
+
|
|
153
|
+
// Explicitly override the default creator
|
|
154
|
+
createButton, 'submitButton', { text: 'Submit', variant: 'filled' }
|
|
155
|
+
],
|
|
156
|
+
document.getElementById('form-container'),
|
|
157
|
+
{
|
|
158
|
+
creator: createTextfield, // Default creator for all elements without a specific constructor
|
|
159
|
+
prefix: false // Disable automatic class prefixing
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// With theme options for a complete dashboard layout
|
|
164
|
+
const dashboardLayout = createLayout(
|
|
165
|
+
[
|
|
166
|
+
createTopAppBar, 'header', {
|
|
167
|
+
title: 'Dashboard',
|
|
168
|
+
actions: ['notifications', 'account']
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
createCard, 'statsCard', {
|
|
172
|
+
title: 'Performance Metrics',
|
|
173
|
+
outlined: true
|
|
174
|
+
},
|
|
175
|
+
[
|
|
176
|
+
createChip, 'visitsChip', { text: 'Visits: 1.2K', leadingIcon: 'visibility' },
|
|
177
|
+
createChip, 'conversionChip', { text: 'Conversion: 5.4%', leadingIcon: 'trending_up' }
|
|
178
|
+
],
|
|
179
|
+
|
|
180
|
+
createCard, 'activityCard', {
|
|
181
|
+
title: 'Recent Activity',
|
|
182
|
+
outlined: true
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
createBottomAppBar, 'footer', {
|
|
186
|
+
actions: [
|
|
187
|
+
{ icon: 'home', label: 'Home' },
|
|
188
|
+
{ icon: 'search', label: 'Search' },
|
|
189
|
+
{ icon: 'settings', label: 'Settings' }
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
],
|
|
193
|
+
document.getElementById('app'),
|
|
194
|
+
{
|
|
195
|
+
theme: 'dark', // Custom theme option
|
|
196
|
+
density: 'comfortable', // Custom density option
|
|
197
|
+
animations: true // Custom animation option
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Access and use the layout
|
|
202
|
+
const nameField = formLayout.get('nameField');
|
|
203
|
+
nameField.setValue('John Doe');
|
|
204
|
+
|
|
205
|
+
const statsCard = dashboardLayout.get('statsCard');
|
|
206
|
+
statsCard.setTitle('Updated Metrics - ' + new Date().toLocaleDateString());
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## API Reference
|
|
210
|
+
|
|
211
|
+
### Core Functions
|
|
212
|
+
|
|
213
|
+
#### `createLayout(schema, parentElement?, options?)`
|
|
214
|
+
|
|
215
|
+
Creates a layout from a schema definition.
|
|
216
|
+
|
|
217
|
+
- **Parameters**:
|
|
218
|
+
- `schema`: Array, object, HTML string, or function returning one of these
|
|
219
|
+
- `parentElement` (optional): Parent element to attach the layout to
|
|
220
|
+
- `options` (optional): Configuration options for layout creation:
|
|
221
|
+
- `creator`: Default creator function to use when not specified in schema
|
|
222
|
+
- `prefix`: Boolean to control whether CSS class prefixing is applied (default: true)
|
|
223
|
+
- Custom options can be added and accessed in component creators
|
|
224
|
+
- **Returns**: Layout result object with components and utility methods
|
|
225
|
+
|
|
226
|
+
#### `processSchema(schema, parentElement?, level?, options?)`
|
|
227
|
+
|
|
228
|
+
Low-level function for processing schemas directly.
|
|
229
|
+
|
|
230
|
+
- **Parameters**:
|
|
231
|
+
- `schema`: Array or object schema
|
|
232
|
+
- `parentElement` (optional): Parent element to attach to
|
|
233
|
+
- `level` (optional): Current recursion level
|
|
234
|
+
- `options` (optional): Layout creation options
|
|
235
|
+
- **Returns**: Layout result object
|
|
236
|
+
|
|
237
|
+
#### `createComponentInstance(Component, options?, layoutOptions?)`
|
|
238
|
+
|
|
239
|
+
Creates a component instance from a constructor or factory function.
|
|
240
|
+
|
|
241
|
+
- **Parameters**:
|
|
242
|
+
- `Component`: Constructor or factory function
|
|
243
|
+
- `options` (optional): Options to pass to the component
|
|
244
|
+
- `layoutOptions` (optional): Global layout options
|
|
245
|
+
- **Returns**: Component instance
|
|
246
|
+
|
|
247
|
+
### Utility Functions
|
|
248
|
+
|
|
249
|
+
#### `isComponent(value)`
|
|
250
|
+
|
|
251
|
+
Checks if a value is a component-like object.
|
|
252
|
+
|
|
253
|
+
- **Parameters**:
|
|
254
|
+
- `value`: Value to check
|
|
255
|
+
- **Returns**: Boolean indicating if the value is a component
|
|
256
|
+
|
|
257
|
+
#### `processClassNames(options, skipPrefix?)`
|
|
258
|
+
|
|
259
|
+
Processes class names in options to add prefixes.
|
|
260
|
+
|
|
261
|
+
- **Parameters**:
|
|
262
|
+
- `options`: Element options containing className
|
|
263
|
+
- `skipPrefix` (optional): Whether to skip adding prefixes
|
|
264
|
+
- **Returns**: Updated options with prefixed classNames
|
|
265
|
+
|
|
266
|
+
#### `flattenLayout(layout)`
|
|
267
|
+
|
|
268
|
+
Flattens a nested layout for easier component access.
|
|
269
|
+
|
|
270
|
+
- **Parameters**:
|
|
271
|
+
- `layout`: Layout object to flatten
|
|
272
|
+
- **Returns**: Flattened layout with components
|
|
273
|
+
|
|
274
|
+
### Result Object
|
|
275
|
+
|
|
276
|
+
The layout result object contains:
|
|
277
|
+
|
|
278
|
+
- `layout`: Raw layout object with all components
|
|
279
|
+
- `element`: Reference to the root element
|
|
280
|
+
- `component`: Flattened component map for easy access
|
|
281
|
+
- `get(name)`: Function to get a component by name
|
|
282
|
+
- `getAll()`: Function to get all components
|
|
283
|
+
- `destroy()`: Function to clean up the layout
|
|
284
|
+
|
|
285
|
+
## Integrating with Layout Manager
|
|
286
|
+
|
|
287
|
+
This module works well with the Layout Manager for advanced application layouts:
|
|
288
|
+
|
|
289
|
+
```javascript
|
|
290
|
+
import createLayout from 'core/layout';
|
|
291
|
+
import createLayoutManager from 'client/core/layout/layout-manager';
|
|
292
|
+
|
|
293
|
+
// Create application layout
|
|
294
|
+
const appLayout = createLayout([
|
|
295
|
+
// Application components...
|
|
296
|
+
]);
|
|
297
|
+
|
|
298
|
+
// Create layout manager with the layout
|
|
299
|
+
const layoutManager = createLayoutManager({
|
|
300
|
+
layout: appLayout.layout,
|
|
301
|
+
layoutAPI: appLayout
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Use layout manager API
|
|
305
|
+
layoutManager.setContent('<h1>Welcome to the app</h1>');
|
|
306
|
+
layoutManager.setPageTitle('Dashboard');
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Performance Considerations
|
|
310
|
+
|
|
311
|
+
### Schema Format Performance
|
|
312
|
+
|
|
313
|
+
**Array-based schemas** generally outperform object-based schemas:
|
|
314
|
+
|
|
315
|
+
- **Faster processing**: 15-30% faster for large layouts
|
|
316
|
+
- **Lower memory usage**: Requires less memory without property names
|
|
317
|
+
- **Better bundle size**: More compact representation in code
|
|
318
|
+
- **Efficient iteration**: Arrays are optimized for sequential access
|
|
319
|
+
|
|
320
|
+
**Object-based schemas** excel in:
|
|
321
|
+
|
|
322
|
+
- **Readability**: More explicit structure with named properties
|
|
323
|
+
- **Maintainability**: Easier to understand complex nested structures
|
|
324
|
+
- **Self-documentation**: Property names describe the layout's purpose
|
|
325
|
+
|
|
326
|
+
**Recommendations**:
|
|
327
|
+
- For **performance-critical** applications, prefer array-based schemas
|
|
328
|
+
- For **complex, deeply nested** structures where maintainability is key, consider object-based schemas
|
|
329
|
+
- For the **best balance**, use array-based schemas for large structures and object-based for complex configurations
|
|
330
|
+
|
|
331
|
+
### Options Performance Considerations
|
|
332
|
+
|
|
333
|
+
- Setting `prefix: false` can improve performance slightly by avoiding class name processing
|
|
334
|
+
- Providing a `creator` function in options is more efficient than having many duplicate creator references in the schema
|
|
335
|
+
- Consider memoizing layout creation for frequently used UI patterns with the same options
|
|
336
|
+
|
|
337
|
+
### General Optimization Tips
|
|
338
|
+
|
|
339
|
+
- Use DocumentFragment for batch DOM operations
|
|
340
|
+
- Create components only when needed
|
|
341
|
+
- Consider memoizing frequently created layouts
|
|
342
|
+
- For large applications, lazy-load secondary layouts
|
|
343
|
+
|
|
344
|
+
## Browser Compatibility
|
|
345
|
+
|
|
346
|
+
The Layout Module is compatible with all modern browsers (Chrome, Firefox, Safari, Edge).
|
|
347
|
+
|
|
348
|
+
## License
|
|
349
|
+
|
|
350
|
+
MIT
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// src/core/layout/array.ts
|
|
2
|
+
/**
|
|
3
|
+
* @module core/layout
|
|
4
|
+
* @description Processor for array-based layout schemas
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createElement } from '../dom/create';
|
|
8
|
+
import { LayoutResult, LayoutOptions } from './types';
|
|
9
|
+
import { isComponent, createFragment, processClassNames } from './utils';
|
|
10
|
+
import { createLayoutResult } from './result';
|
|
11
|
+
import { createComponentInstance } from './processor';
|
|
12
|
+
import { isObject } from '../utils';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Processes an array-based layout definition
|
|
16
|
+
*
|
|
17
|
+
* @param schema - Array-based layout definition
|
|
18
|
+
* @param parentElement - Optional parent element to attach layout to
|
|
19
|
+
* @param level - Current recursion level
|
|
20
|
+
* @param options - Layout creation options
|
|
21
|
+
* @returns Layout result object
|
|
22
|
+
*/
|
|
23
|
+
export function processArraySchema(
|
|
24
|
+
schema: any[],
|
|
25
|
+
parentElement: HTMLElement | null = null,
|
|
26
|
+
level: number = 0,
|
|
27
|
+
options: LayoutOptions = {}
|
|
28
|
+
): LayoutResult {
|
|
29
|
+
level++;
|
|
30
|
+
const layout = {};
|
|
31
|
+
const components = [];
|
|
32
|
+
const fragment = createFragment();
|
|
33
|
+
let component = null;
|
|
34
|
+
|
|
35
|
+
if (!Array.isArray(schema)) {
|
|
36
|
+
console.error('Schema is not an array!', parentElement, level, schema);
|
|
37
|
+
return createLayoutResult(layout);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Get default creator function from options or use createElement
|
|
41
|
+
const defaultCreator = options.creator || createElement;
|
|
42
|
+
|
|
43
|
+
for (let i = 0; i < schema.length; i++) {
|
|
44
|
+
const item = schema[i];
|
|
45
|
+
if (!item) continue;
|
|
46
|
+
|
|
47
|
+
// Handle nested arrays recursively
|
|
48
|
+
if (Array.isArray(item)) {
|
|
49
|
+
const container = component || parentElement;
|
|
50
|
+
// Use this function for recursion, not the external processSchema
|
|
51
|
+
const result = processArraySchema(item, container, level, options);
|
|
52
|
+
Object.assign(layout, result.layout);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Handle different item types
|
|
57
|
+
let creator, name, itemOptions;
|
|
58
|
+
|
|
59
|
+
// Case 1: Item is a function (component creator)
|
|
60
|
+
if (typeof item === 'function') {
|
|
61
|
+
creator = item;
|
|
62
|
+
|
|
63
|
+
// Check next items for name (string) and options (object)
|
|
64
|
+
const nextItem = schema[i+1];
|
|
65
|
+
const afterNextItem = schema[i+2];
|
|
66
|
+
|
|
67
|
+
if (typeof nextItem === 'string') {
|
|
68
|
+
name = nextItem;
|
|
69
|
+
i++; // Skip the name on next iteration
|
|
70
|
+
|
|
71
|
+
// Check if options are provided after the name
|
|
72
|
+
if (isObject(afterNextItem)) {
|
|
73
|
+
itemOptions = afterNextItem;
|
|
74
|
+
i++; // Skip the options on next iteration
|
|
75
|
+
} else {
|
|
76
|
+
itemOptions = {};
|
|
77
|
+
}
|
|
78
|
+
} else if (isObject(nextItem)) {
|
|
79
|
+
// No name provided, just options
|
|
80
|
+
itemOptions = nextItem;
|
|
81
|
+
i++; // Skip the options on next iteration
|
|
82
|
+
} else {
|
|
83
|
+
// No name or options
|
|
84
|
+
itemOptions = {};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Case 2: Item is a string (component name using default creator)
|
|
88
|
+
else if (typeof item === 'string') {
|
|
89
|
+
creator = defaultCreator;
|
|
90
|
+
name = item;
|
|
91
|
+
|
|
92
|
+
// Check next item for options
|
|
93
|
+
const nextItem = schema[i+1];
|
|
94
|
+
if (isObject(nextItem)) {
|
|
95
|
+
itemOptions = nextItem;
|
|
96
|
+
i++; // Skip the options on next iteration
|
|
97
|
+
} else {
|
|
98
|
+
itemOptions = {};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// We should NOT automatically set the tag to match the name
|
|
102
|
+
// If tag is not provided in options, a default like 'div' should be used
|
|
103
|
+
if (creator === createElement && !('tag' in itemOptions)) {
|
|
104
|
+
itemOptions.tag = 'div'; // Default to div if no tag is specified
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Case 3: Item is an object (options for default creator with no name)
|
|
108
|
+
else if (isObject(item)) {
|
|
109
|
+
creator = defaultCreator;
|
|
110
|
+
itemOptions = item;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
// Skip unsupported item types
|
|
114
|
+
console.warn('Skipping unsupported item type:', item);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check for additional options object after main options
|
|
119
|
+
const maybeAdditionalOptions = schema[i+1];
|
|
120
|
+
if (isObject(maybeAdditionalOptions) && !Array.isArray(maybeAdditionalOptions) &&
|
|
121
|
+
typeof maybeAdditionalOptions.prefix !== 'undefined') {
|
|
122
|
+
// Merge the additional options into main options
|
|
123
|
+
Object.assign(itemOptions, maybeAdditionalOptions);
|
|
124
|
+
i++; // Skip on next iteration
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Process options based on prefix setting
|
|
128
|
+
const shouldApplyPrefix =
|
|
129
|
+
// Use item-specific prefix setting if available
|
|
130
|
+
'prefix' in itemOptions ?
|
|
131
|
+
itemOptions.prefix :
|
|
132
|
+
// Otherwise use global options prefix setting (default to true)
|
|
133
|
+
options.prefix !== false;
|
|
134
|
+
|
|
135
|
+
const processedOptions = shouldApplyPrefix ?
|
|
136
|
+
processClassNames(itemOptions) :
|
|
137
|
+
{ ...itemOptions };
|
|
138
|
+
|
|
139
|
+
// Add name to options if needed and not a DOM element tag
|
|
140
|
+
if (name && !('name' in processedOptions) &&
|
|
141
|
+
!(creator === createElement || creator.isElement)) {
|
|
142
|
+
processedOptions.name = name;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Create and store component
|
|
146
|
+
component = createComponentInstance(creator, processedOptions, options);
|
|
147
|
+
const element = isComponent(component) ? component.element : component;
|
|
148
|
+
|
|
149
|
+
if (level === 1) layout.element = element;
|
|
150
|
+
if (name) {
|
|
151
|
+
layout[name] = component;
|
|
152
|
+
components.push([name, component]);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Append to DOM
|
|
156
|
+
if (component) {
|
|
157
|
+
if ('insert' in component && typeof component.insert === 'function') {
|
|
158
|
+
component.insert(fragment);
|
|
159
|
+
} else {
|
|
160
|
+
fragment.appendChild(element);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (parentElement) {
|
|
164
|
+
component._container = parentElement;
|
|
165
|
+
|
|
166
|
+
if ('onInserted' in component && typeof component.onInserted === 'function') {
|
|
167
|
+
component.onInserted(parentElement);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Append fragment to parent in a single operation
|
|
174
|
+
if (parentElement && fragment.hasChildNodes()) {
|
|
175
|
+
const wrapper = isComponent(parentElement) ? parentElement.element : parentElement;
|
|
176
|
+
wrapper.appendChild(fragment);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
layout.components = components;
|
|
180
|
+
return createLayoutResult(layout);
|
|
181
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// src/core/layout/create.ts
|
|
2
|
+
/**
|
|
3
|
+
* @module core/layout
|
|
4
|
+
* @description Main layout creation functionality with optimized DOM operations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Schema, LayoutResult, LayoutOptions } from './types';
|
|
8
|
+
import { processSchema } from './processor';
|
|
9
|
+
import { createLayoutResult } from './result';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates a DOM or component layout based on a layout definition
|
|
13
|
+
* Uses batched DOM operations for better performance
|
|
14
|
+
*
|
|
15
|
+
* @param schema - Layout definition (array-based, object-based, or HTML string)
|
|
16
|
+
* @param parentElement - Optional parent element to attach layout to
|
|
17
|
+
* @param options - Additional options for layout creation
|
|
18
|
+
* @returns Object containing the layout and utility functions
|
|
19
|
+
*/
|
|
20
|
+
export function createLayout(
|
|
21
|
+
schema: Schema | any[] | string | Function,
|
|
22
|
+
parentElement: HTMLElement | null = null,
|
|
23
|
+
options: LayoutOptions = {}
|
|
24
|
+
): LayoutResult {
|
|
25
|
+
// If schema is a function, execute it to get the actual schema
|
|
26
|
+
if (typeof schema === 'function') {
|
|
27
|
+
schema = schema();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Parse HTML string into a layout if needed
|
|
31
|
+
if (typeof schema === 'string') {
|
|
32
|
+
// Create a temporary element to parse HTML
|
|
33
|
+
const template = document.createElement('template');
|
|
34
|
+
template.innerHTML = schema.trim();
|
|
35
|
+
|
|
36
|
+
// Use the parsed DOM structure directly
|
|
37
|
+
const fragment = template.content;
|
|
38
|
+
|
|
39
|
+
if (parentElement && fragment.hasChildNodes()) {
|
|
40
|
+
// Batch DOM operation - append all nodes at once
|
|
41
|
+
parentElement.appendChild(fragment);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Create a basic layout for HTML string
|
|
45
|
+
const layout = {
|
|
46
|
+
element: fragment.firstElementChild as HTMLElement
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Create layout result with HTML content
|
|
50
|
+
return createLayoutResult(layout);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Process the schema using our unified processor with options
|
|
54
|
+
return processSchema(schema, parentElement, 0, options);
|
|
55
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// src/core/layout/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* @module core/layout
|
|
4
|
+
* @description Optimized layout creation system with simplified API
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Export essential types
|
|
8
|
+
export type {
|
|
9
|
+
ComponentLike,
|
|
10
|
+
ElementDefinition,
|
|
11
|
+
Schema,
|
|
12
|
+
LayoutResult,
|
|
13
|
+
LayoutOptions
|
|
14
|
+
} from './types';
|
|
15
|
+
|
|
16
|
+
// Export utility functions
|
|
17
|
+
export { isComponent, processClassNames, flattenLayout } from './utils';
|
|
18
|
+
|
|
19
|
+
// Export core functionality
|
|
20
|
+
export { createLayout } from './create';
|
|
21
|
+
export { createLayoutResult } from './result';
|
|
22
|
+
export { processSchema, createComponentInstance } from './processor';
|
|
23
|
+
|
|
24
|
+
// Default export for backward compatibility and simpler usage
|
|
25
|
+
import { createLayout } from './create';
|
|
26
|
+
export default createLayout;
|