neo.mjs 6.9.1 → 6.9.2
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/apps/ServiceWorker.mjs +2 -2
- package/apps/route/view/MainViewController.mjs +2 -3
- package/examples/ServiceWorker.mjs +2 -2
- package/examples/toolbar/paging/view/MainContainer.mjs +4 -3
- package/examples/treeAccordion/MainContainer.mjs +23 -4
- package/package.json +2 -2
- package/src/DefaultConfig.mjs +2 -2
- package/src/collection/Filter.mjs +40 -31
- package/src/container/Base.mjs +6 -3
- package/src/controller/Base.mjs +47 -12
- package/src/form/field/FileUpload.mjs +2 -2
- package/src/form/field/Select.mjs +4 -4
- package/src/main/DomEvents.mjs +1 -3
- package/src/main/mixin/DeltaUpdates.mjs +3 -1
- package/src/table/View.mjs +1 -1
- package/test/components/siesta.js +23 -3
package/apps/ServiceWorker.mjs
CHANGED
@@ -20,7 +20,7 @@ class MainContainerController extends Component {
|
|
20
20
|
'/contact': 'handleContactRoute',
|
21
21
|
'/users/{userId}': { handler: 'handleUserRoute', preHandler: 'doPrehandling' },
|
22
22
|
// '/users/{userId}' : {handler: 'handleUserRoute', preHandler: 'doPrehandling'}, //example
|
23
|
-
//
|
23
|
+
//'/users/{userId}/posts/{postId}' : {handler:'handleUserPostsRoute', preHandler: 'doPrehandlingFalse'}, //example
|
24
24
|
// default: 'doDefaultHandling' //optional - example
|
25
25
|
},
|
26
26
|
|
@@ -46,7 +46,7 @@ class MainContainerController extends Component {
|
|
46
46
|
|
47
47
|
|
48
48
|
doPrehandling(value, oldValue, params = null) {
|
49
|
-
const userId = parseInt(params);
|
49
|
+
const userId = parseInt(params.userId);
|
50
50
|
if (userId > 0 && userId === this.data.activeUser) {
|
51
51
|
return true;
|
52
52
|
}
|
@@ -132,7 +132,6 @@ class MainContainerController extends Component {
|
|
132
132
|
* @param {Object} data
|
133
133
|
*/
|
134
134
|
onSwitchButtonMetaReset(data) {
|
135
|
-
const currentUser = this.data.activeUser;
|
136
135
|
this.data.activeUser = 0;
|
137
136
|
this.#setUsername();
|
138
137
|
this.#removeMetaButtonSelection();
|
@@ -69,13 +69,14 @@ class MainContainer extends Viewport {
|
|
69
69
|
weight: -10000,
|
70
70
|
|
71
71
|
// Embed a tooltip request into the DOM
|
72
|
-
vdom
|
73
|
-
data
|
74
|
-
|
72
|
+
vdom: {
|
73
|
+
data: {
|
74
|
+
neoTooltip: 'The Label'
|
75
75
|
}
|
76
76
|
}
|
77
77
|
},
|
78
78
|
rowsPerPage: {
|
79
|
+
style : {margin: '0 .5em'},
|
79
80
|
tooltip: 'Set the number of rows to display in a page',
|
80
81
|
weight : -999
|
81
82
|
}
|
@@ -23,10 +23,6 @@ class MainContainer extends ConfigurationViewport {
|
|
23
23
|
cls : ['examples-container-accordion']
|
24
24
|
}
|
25
25
|
|
26
|
-
onConfigChange(config, opts) {
|
27
|
-
this.exampleComponent.items[0][config] = opts.value;
|
28
|
-
}
|
29
|
-
|
30
26
|
createConfigurationComponents() {
|
31
27
|
let me = this,
|
32
28
|
treeList = me.exampleComponent.items[0];
|
@@ -74,6 +70,12 @@ class MainContainer extends ConfigurationViewport {
|
|
74
70
|
stepSize : 3,
|
75
71
|
style : {marginTop: '10px'},
|
76
72
|
value : 400
|
73
|
+
}, {
|
74
|
+
ntype : 'button',
|
75
|
+
handler: me.onRemoveDomButtonClick.bind(me),
|
76
|
+
style : {marginTop: '20px'},
|
77
|
+
text : 'Remove DOM',
|
78
|
+
width : 100
|
77
79
|
}];
|
78
80
|
}
|
79
81
|
|
@@ -169,6 +171,23 @@ class MainContainer extends ConfigurationViewport {
|
|
169
171
|
}]
|
170
172
|
});
|
171
173
|
}
|
174
|
+
|
175
|
+
/**
|
176
|
+
* @param {String} config
|
177
|
+
* @param {Object} opts
|
178
|
+
*/
|
179
|
+
onConfigChange(config, opts) {
|
180
|
+
this.exampleComponent.items[0][config] = opts.value;
|
181
|
+
}
|
182
|
+
|
183
|
+
/**
|
184
|
+
*
|
185
|
+
* @param data
|
186
|
+
*/
|
187
|
+
onRemoveDomButtonClick(data) {
|
188
|
+
let accordion = this.exampleComponent.items[0];
|
189
|
+
accordion.hidden = !accordion.hidden
|
190
|
+
}
|
172
191
|
}
|
173
192
|
|
174
193
|
Neo.applyClassConfig(MainContainer);
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "neo.mjs",
|
3
|
-
"version": "6.9.
|
3
|
+
"version": "6.9.2",
|
4
4
|
"description": "The webworkers driven UI framework",
|
5
5
|
"type": "module",
|
6
6
|
"repository": {
|
@@ -56,7 +56,7 @@
|
|
56
56
|
"neo-jsdoc": "1.0.1",
|
57
57
|
"neo-jsdoc-x": "1.0.5",
|
58
58
|
"postcss": "^8.4.31",
|
59
|
-
"sass": "^1.69.
|
59
|
+
"sass": "^1.69.4",
|
60
60
|
"showdown": "^2.1.0",
|
61
61
|
"webpack": "^5.89.0",
|
62
62
|
"webpack-cli": "^5.1.4",
|
package/src/DefaultConfig.mjs
CHANGED
@@ -236,12 +236,12 @@ const DefaultConfig = {
|
|
236
236
|
useVdomWorker: true,
|
237
237
|
/**
|
238
238
|
* buildScripts/injectPackageVersion.mjs will update this value
|
239
|
-
* @default '6.9.
|
239
|
+
* @default '6.9.2'
|
240
240
|
* @memberOf! module:Neo
|
241
241
|
* @name config.version
|
242
242
|
* @type String
|
243
243
|
*/
|
244
|
-
version: '6.9.
|
244
|
+
version: '6.9.2'
|
245
245
|
};
|
246
246
|
|
247
247
|
Object.assign(DefaultConfig, {
|
@@ -98,7 +98,7 @@ class Filter extends Base {
|
|
98
98
|
}
|
99
99
|
|
100
100
|
afterSetDisabled(...args) {
|
101
|
-
this.fireChangeEvent(...args)
|
101
|
+
this.fireChangeEvent(...args)
|
102
102
|
}
|
103
103
|
|
104
104
|
afterSetFilterBy(value, oldValue) {
|
@@ -106,28 +106,28 @@ class Filter extends Base {
|
|
106
106
|
}
|
107
107
|
|
108
108
|
afterSetIsUpdating(value, oldValue) {
|
109
|
-
value === false && this.fireChangeEvent(value, oldValue)
|
109
|
+
value === false && this.fireChangeEvent(value, oldValue)
|
110
110
|
}
|
111
111
|
|
112
112
|
afterSetOperator(...args) {
|
113
|
-
this.fireChangeEvent(...args)
|
113
|
+
this.fireChangeEvent(...args)
|
114
114
|
}
|
115
115
|
|
116
116
|
afterSetProperty(...args) {
|
117
|
-
this.fireChangeEvent(...args)
|
117
|
+
this.fireChangeEvent(...args)
|
118
118
|
}
|
119
119
|
|
120
120
|
afterSetValue(...args) {
|
121
|
-
this.fireChangeEvent(...args)
|
121
|
+
this.fireChangeEvent(...args)
|
122
122
|
}
|
123
123
|
|
124
124
|
beforeSetFilterBy(value, oldValue) {
|
125
125
|
if (value && typeof value !== 'function') {
|
126
126
|
Neo.logError('filterBy has to be a function', this);
|
127
|
-
return oldValue
|
127
|
+
return oldValue
|
128
128
|
}
|
129
129
|
|
130
|
-
return value
|
130
|
+
return value
|
131
131
|
}
|
132
132
|
|
133
133
|
/**
|
@@ -138,7 +138,7 @@ class Filter extends Base {
|
|
138
138
|
* @protected
|
139
139
|
*/
|
140
140
|
beforeSetOperator(value, oldValue) {
|
141
|
-
return this.beforeSetEnumValue(value, oldValue, 'operator')
|
141
|
+
return this.beforeSetEnumValue(value, oldValue, 'operator')
|
142
142
|
}
|
143
143
|
|
144
144
|
/**
|
@@ -152,10 +152,10 @@ class Filter extends Base {
|
|
152
152
|
value = me.value;
|
153
153
|
|
154
154
|
if (!me.filterBy) {
|
155
|
-
return {operator, property, value}
|
155
|
+
return {operator, property, value}
|
156
156
|
}
|
157
157
|
|
158
|
-
return null
|
158
|
+
return null
|
159
159
|
}
|
160
160
|
|
161
161
|
/**
|
@@ -170,7 +170,7 @@ class Filter extends Base {
|
|
170
170
|
operator: me.operator,
|
171
171
|
property: me.property,
|
172
172
|
value : me.value
|
173
|
-
})
|
173
|
+
})
|
174
174
|
}
|
175
175
|
}
|
176
176
|
|
@@ -187,7 +187,7 @@ class Filter extends Base {
|
|
187
187
|
filterValue, recordValue;
|
188
188
|
|
189
189
|
if (me._disabled) {
|
190
|
-
return false
|
190
|
+
return false
|
191
191
|
}
|
192
192
|
|
193
193
|
if (me._filterBy) {
|
@@ -196,11 +196,11 @@ class Filter extends Base {
|
|
196
196
|
filteredItems,
|
197
197
|
item,
|
198
198
|
value: me._value
|
199
|
-
})
|
199
|
+
})
|
200
200
|
}
|
201
201
|
|
202
202
|
if (me.includeEmptyValues && (me._value === null || Neo.isEmpty(me._value))) {
|
203
|
-
return false
|
203
|
+
return false
|
204
204
|
}
|
205
205
|
|
206
206
|
filterValue = me._value;
|
@@ -208,47 +208,56 @@ class Filter extends Base {
|
|
208
208
|
|
209
209
|
if (filterValue instanceof Date && recordValue instanceof Date) {
|
210
210
|
filterValue = filterValue.valueOf();
|
211
|
-
recordValue = recordValue.valueOf()
|
211
|
+
recordValue = recordValue.valueOf()
|
212
212
|
}
|
213
213
|
|
214
|
-
return !Filter[me._operator](recordValue, filterValue)
|
214
|
+
return !Filter[me._operator](recordValue, filterValue)
|
215
215
|
}
|
216
216
|
|
217
|
-
static ['=='] (a, b) {return a == b
|
218
|
-
static ['==='](a, b) {return a === b
|
219
|
-
static ['!='] (a, b) {return a != b
|
220
|
-
static ['!=='](a, b) {return a !== b
|
221
|
-
static ['<'] (a, b) {return a < b
|
222
|
-
static ['<='] (a, b) {return a <= b
|
223
|
-
static ['>'] (a, b) {return a > b
|
224
|
-
static ['>='] (a, b) {return a >= b
|
217
|
+
static ['=='] (a, b) {return a == b}
|
218
|
+
static ['==='](a, b) {return a === b}
|
219
|
+
static ['!='] (a, b) {return a != b}
|
220
|
+
static ['!=='](a, b) {return a !== b}
|
221
|
+
static ['<'] (a, b) {return a < b}
|
222
|
+
static ['<='] (a, b) {return a <= b}
|
223
|
+
static ['>'] (a, b) {return a > b}
|
224
|
+
static ['>='] (a, b) {return a >= b}
|
225
225
|
|
226
226
|
static ['endsWith'](a, b) {
|
227
|
-
|
227
|
+
if (!Neo.isString(a)) {a = String(a)}
|
228
|
+
if (!Neo.isString(b)) {b = String(b)}
|
229
|
+
|
230
|
+
return a?.toLowerCase().endsWith(b?.toLowerCase()) || false
|
228
231
|
}
|
229
232
|
|
230
233
|
static ['excluded'](a, b) {
|
231
|
-
return b.indexOf(a) < 0
|
234
|
+
return b.indexOf(a) < 0
|
232
235
|
}
|
233
236
|
|
234
237
|
static ['included'](a, b) {
|
235
|
-
return b.indexOf(a) > -1
|
238
|
+
return b.indexOf(a) > -1
|
236
239
|
}
|
237
240
|
|
238
241
|
static ['isDefined'](a, b) {
|
239
|
-
return a !== undefined
|
242
|
+
return a !== undefined
|
240
243
|
}
|
241
244
|
|
242
245
|
static ['isUndefined'](a, b) {
|
243
|
-
return a === undefined
|
246
|
+
return a === undefined
|
244
247
|
}
|
245
248
|
|
246
249
|
static ['like'](a, b) {
|
247
|
-
|
250
|
+
if (!Neo.isString(a)) {a = String(a)}
|
251
|
+
if (!Neo.isString(b)) {b = String(b)}
|
252
|
+
|
253
|
+
return a?.toLowerCase().includes(b?.toLowerCase()) || false
|
248
254
|
}
|
249
255
|
|
250
256
|
static ['startsWith'](a, b) {
|
251
|
-
|
257
|
+
if (!Neo.isString(a)) {a = String(a)}
|
258
|
+
if (!Neo.isString(b)) {b = String(b)}
|
259
|
+
|
260
|
+
return a?.toLowerCase().startsWith(b?.toLowerCase()) || false
|
252
261
|
}
|
253
262
|
}
|
254
263
|
|
package/src/container/Base.mjs
CHANGED
@@ -401,7 +401,7 @@ class Base extends Component {
|
|
401
401
|
* @param {Boolean} [silent=false] true to update the vdom silently (useful for destroying multiple child items in a row)
|
402
402
|
*/
|
403
403
|
destroy(updateParentVdom=false, silent=false) {
|
404
|
-
this.items
|
404
|
+
this.items?.forEach(item => {
|
405
405
|
item.destroy?.(false, true)
|
406
406
|
});
|
407
407
|
|
@@ -502,8 +502,11 @@ class Base extends Component {
|
|
502
502
|
|
503
503
|
if (config.items) {
|
504
504
|
// If we are passed an object, merge the class's own items object into it
|
505
|
-
|
506
|
-
Neo.merge(Neo.clone(me.constructor.config.items), config.items)
|
505
|
+
if (Neo.typeOf(config.items) === 'Object') {
|
506
|
+
me.items = Neo.merge(Neo.clone(me.constructor.config.items), config.items)
|
507
|
+
} else {
|
508
|
+
me._items = Neo.clone(config.items, true, true)
|
509
|
+
}
|
507
510
|
delete config.items
|
508
511
|
}
|
509
512
|
|
package/src/controller/Base.mjs
CHANGED
@@ -21,7 +21,7 @@ class Base extends CoreBase {
|
|
21
21
|
/**
|
22
22
|
* @member {Object} routes={}
|
23
23
|
*/
|
24
|
-
|
24
|
+
routes_: {},
|
25
25
|
|
26
26
|
/**
|
27
27
|
* @member {Object} handleRoutes={}
|
@@ -42,20 +42,42 @@ class Base extends CoreBase {
|
|
42
42
|
super.construct(config);
|
43
43
|
|
44
44
|
const me = this;
|
45
|
+
HashHistory.on('change', me.onHashChange, me);
|
46
|
+
}
|
47
|
+
|
48
|
+
/**
|
49
|
+
* Triggered after the badgePosition config got changed
|
50
|
+
* @param {String} value
|
51
|
+
* @param {String} oldValue
|
52
|
+
* @protected
|
53
|
+
*/
|
54
|
+
afterSetRoutes(value, oldValue){
|
55
|
+
const me = this;
|
56
|
+
|
57
|
+
const functionSort = (a,b) => {
|
58
|
+
const usedRegex = new RegExp("/", "g");
|
59
|
+
return a.match(usedRegex).length - b.match(usedRegex).length;
|
60
|
+
}
|
61
|
+
me.routes = Object.keys(value).sort(functionSort).reduce(
|
62
|
+
(obj, key) => {
|
63
|
+
obj[key] = value[key];
|
64
|
+
return obj;
|
65
|
+
},
|
66
|
+
{}
|
67
|
+
);
|
68
|
+
|
45
69
|
|
46
70
|
me.handleRoutes = {};
|
47
71
|
if (Object.keys(me.routes).length > 0) {
|
48
72
|
Object.keys(me.routes).forEach(key => {
|
49
73
|
if (key.toLowerCase() === 'default'){
|
50
|
-
me.defaultRoute =
|
74
|
+
me.defaultRoute = value[key];
|
51
75
|
} else {
|
52
76
|
me.handleRoutes[key] = new RegExp(key.replace(/{[^\s/]+}/g, '([\\w-]+)')+'$');
|
53
77
|
}
|
54
78
|
|
55
79
|
});
|
56
80
|
}
|
57
|
-
|
58
|
-
HashHistory.on('change', me.onHashChange, me);
|
59
81
|
}
|
60
82
|
|
61
83
|
/**
|
@@ -85,13 +107,26 @@ class Base extends CoreBase {
|
|
85
107
|
|
86
108
|
const me = this;
|
87
109
|
let hasRouteBeenFound = false;
|
88
|
-
Object.keys(me.handleRoutes).
|
110
|
+
Object.keys(me.handleRoutes).forEach( key => {
|
89
111
|
let preHandler = undefined;
|
90
112
|
let executeHandler = undefined;
|
91
113
|
let responsePreHandler = undefined;
|
92
114
|
|
93
115
|
const result = value.hashString.match(me.handleRoutes[key]);
|
94
116
|
if (result){
|
117
|
+
|
118
|
+
const paramsRegex = /{[^\s/]+}/g;
|
119
|
+
const arrayParamIds = key.match(paramsRegex);
|
120
|
+
const arrayParamValues = result.splice(1,result.length - 1);
|
121
|
+
if (arrayParamIds && arrayParamIds.length !== arrayParamValues.length){
|
122
|
+
throw "Number of IDs and number of Values do not match";
|
123
|
+
}
|
124
|
+
|
125
|
+
const paramObject = {};
|
126
|
+
for(let i=0; arrayParamIds && i< arrayParamIds.length; i++){
|
127
|
+
paramObject[ arrayParamIds[i].substring(1,arrayParamIds[i].length -1)] = arrayParamValues[i];
|
128
|
+
}
|
129
|
+
|
95
130
|
const target = me.routes[key];
|
96
131
|
if (Neo.isString(target)){
|
97
132
|
executeHandler = this.routes[key];
|
@@ -100,20 +135,20 @@ class Base extends CoreBase {
|
|
100
135
|
if (Neo.isObject(target)){
|
101
136
|
executeHandler = this.routes[key].handler;
|
102
137
|
preHandler = this.routes[key].preHandler;
|
103
|
-
|
138
|
+
if (preHandler) {
|
139
|
+
responsePreHandler = me[preHandler]?.call(this, value, oldValue, paramObject);
|
140
|
+
} else {
|
141
|
+
responsePreHandler = true;
|
142
|
+
console.warn('No preHandler defined for routes -> todo it better');
|
143
|
+
}
|
104
144
|
}
|
105
145
|
|
106
146
|
hasRouteBeenFound = true;
|
107
147
|
|
108
148
|
if (responsePreHandler) {
|
109
|
-
this[executeHandler]?.call(this, value, oldValue,
|
110
|
-
} else {
|
111
|
-
console.warn('No preHandler defined for routes -> todo it better');
|
149
|
+
this[executeHandler]?.call(this, value, oldValue, paramObject);
|
112
150
|
}
|
113
|
-
return false;
|
114
|
-
|
115
151
|
}
|
116
|
-
return true;
|
117
152
|
});
|
118
153
|
|
119
154
|
if (Object.keys(me.handleRoutes).length > 0 && !hasRouteBeenFound) {
|
@@ -285,7 +285,7 @@ class FileUpload extends Base {
|
|
285
285
|
documentDeleteMethod: 'DELETE',
|
286
286
|
|
287
287
|
/**
|
288
|
-
* @member {String} state_=
|
288
|
+
* @member {String} state_=ready
|
289
289
|
*/
|
290
290
|
state_: 'ready',
|
291
291
|
|
@@ -729,7 +729,7 @@ class FileUpload extends Base {
|
|
729
729
|
isChangeEventNeeded = true;
|
730
730
|
}
|
731
731
|
|
732
|
-
if (isChangeEventNeeded) {
|
732
|
+
if (isChangeEventNeeded && oldValue !== undefined) {
|
733
733
|
me.fireChangeEvent(me.file)
|
734
734
|
}
|
735
735
|
me.validate();
|
@@ -39,9 +39,9 @@ class Select extends Picker {
|
|
39
39
|
*/
|
40
40
|
displayField: 'name',
|
41
41
|
/**
|
42
|
-
* @member {String}
|
42
|
+
* @member {String} filterOperator_='like'
|
43
43
|
*/
|
44
|
-
|
44
|
+
filterOperator_: 'like',
|
45
45
|
/**
|
46
46
|
* True will only fire a change event, in case the TextField input value matches a record.
|
47
47
|
* onFocusLeave() will try to select a hint record, if needed and possible.
|
@@ -109,9 +109,9 @@ class Select extends Picker {
|
|
109
109
|
typeAhead_: true,
|
110
110
|
/**
|
111
111
|
* Set this config to false, in case typing into the input field should not filter list items
|
112
|
-
* @member {Boolean}
|
112
|
+
* @member {Boolean} useFilter_=true
|
113
113
|
*/
|
114
|
-
|
114
|
+
useFilter_: true,
|
115
115
|
/**
|
116
116
|
* This config should point to the store keyProperty or a different model field,
|
117
117
|
* which you want to submit instead
|
package/src/main/DomEvents.mjs
CHANGED
@@ -420,13 +420,11 @@ class DomEvents extends Base {
|
|
420
420
|
onChange(event) {
|
421
421
|
let me = this,
|
422
422
|
target = event.target,
|
423
|
-
tagName = target.tagName,
|
424
|
-
value = target.value,
|
425
423
|
|
426
424
|
data = {
|
427
425
|
...me.getEventData(event),
|
428
426
|
valid: target.checkValidity(),
|
429
|
-
value:
|
427
|
+
value: target.value
|
430
428
|
};
|
431
429
|
|
432
430
|
// input and change events can pass a FileList for input type file
|
@@ -196,7 +196,9 @@ class DeltaUpdates extends Base {
|
|
196
196
|
node = me.getElementOrBody(delta.id);
|
197
197
|
|
198
198
|
if (!node) {
|
199
|
-
|
199
|
+
if (Neo.config.environment === 'development') {
|
200
|
+
console.warn('du_updateNode: node not found for id', delta.id)
|
201
|
+
}
|
200
202
|
} else {
|
201
203
|
Object.entries(delta).forEach(([prop, value]) => {
|
202
204
|
switch(prop) {
|
package/src/table/View.mjs
CHANGED
@@ -237,7 +237,7 @@ class View extends Component {
|
|
237
237
|
me.promiseUpdate().then(() => {
|
238
238
|
if (selectedRows?.length > 0) {
|
239
239
|
// this logic only works for selection.table.RowModel
|
240
|
-
Neo.main.DomAccess.scrollToTableRow({id: selectedRows[0]})
|
240
|
+
Neo.main.DomAccess.scrollToTableRow({appName: me.appName, id: selectedRows[0]})
|
241
241
|
}
|
242
242
|
})
|
243
243
|
}
|
@@ -6,9 +6,29 @@ project.configure({
|
|
6
6
|
});
|
7
7
|
|
8
8
|
project.plan(
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
{
|
10
|
+
group: 'button',
|
11
|
+
items: [
|
12
|
+
'files/button/Base.mjs'
|
13
|
+
]
|
14
|
+
},
|
15
|
+
{
|
16
|
+
group: 'component',
|
17
|
+
items: [
|
18
|
+
'files/component/DateSelector.mjs'
|
19
|
+
]
|
20
|
+
},
|
21
|
+
{
|
22
|
+
group: 'form',
|
23
|
+
items: [
|
24
|
+
{
|
25
|
+
group: 'field',
|
26
|
+
items: [
|
27
|
+
'files/form/field/Select.mjs'
|
28
|
+
]
|
29
|
+
}
|
30
|
+
]
|
31
|
+
}
|
12
32
|
);
|
13
33
|
|
14
34
|
project.start();
|