neo.mjs 6.9.0 → 6.9.1

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.
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='6.9.0'
23
+ * @member {String} version='6.9.1'
24
24
  */
25
- version: '6.9.0'
25
+ version: '6.9.1'
26
26
  }
27
27
 
28
28
  /**
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='6.9.0'
23
+ * @member {String} version='6.9.1'
24
24
  */
25
- version: '6.9.0'
25
+ version: '6.9.1'
26
26
  }
27
27
 
28
28
  /**
@@ -66,7 +66,14 @@ class MainContainer extends Viewport {
66
66
  // These two have been moved to the start of the Toolbar by their weights
67
67
  label: {
68
68
  style : {marginLeft: 0},
69
- weight: -10000
69
+ weight: -10000,
70
+
71
+ // Embed a tooltip request into the DOM
72
+ vdom : {
73
+ data : {
74
+ 'neo-tooltip' : 'The Label'
75
+ }
76
+ }
70
77
  },
71
78
  rowsPerPage: {
72
79
  tooltip: 'Set the number of rows to display in a page',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "6.9.0",
3
+ "version": "6.9.1",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -47,7 +47,7 @@
47
47
  "autoprefixer": "^10.4.16",
48
48
  "chalk": "^5.3.0",
49
49
  "clean-webpack-plugin": "^4.0.0",
50
- "commander": "^11.0.0",
50
+ "commander": "^11.1.0",
51
51
  "cssnano": "^6.0.1",
52
52
  "envinfo": "^7.10.0",
53
53
  "fs-extra": "^11.1.1",
@@ -56,9 +56,9 @@
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.1",
59
+ "sass": "^1.69.3",
60
60
  "showdown": "^2.1.0",
61
- "webpack": "^5.88.2",
61
+ "webpack": "^5.89.0",
62
62
  "webpack-cli": "^5.1.4",
63
63
  "webpack-dev-server": "4.15.1",
64
64
  "webpack-hook-plugin": "^1.0.7",
@@ -1,12 +1,10 @@
1
1
  .neo-toast {
2
- z-index : 20; // ensure to be on top of table headers
3
2
  align-items : center;
4
3
  background-color: var(--toast-background-color);
5
4
  border-radius : 0.5rem;
6
5
  box-shadow : 0 0.25rem 0.5rem var(--toast-shadow-color);
7
6
  color : var(--toast-color);
8
7
  display : flex;
9
- position : fixed;
10
8
  transition : bottom .3s ease-out, top .3s ease-out;
11
9
 
12
10
  .neo-toast-inner {
@@ -90,6 +88,7 @@
90
88
  /* Toast positions */
91
89
  .neo-toast-tr {
92
90
  right: 1rem;
91
+ left : auto;
93
92
  top : 1rem;
94
93
  }
95
94
 
@@ -107,6 +106,7 @@
107
106
  .neo-toast-br {
108
107
  bottom: 1rem;
109
108
  right : 1rem;
109
+ left : auto;
110
110
  }
111
111
 
112
112
  .neo-toast-bc {
@@ -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.0'
239
+ * @default '6.9.1'
240
240
  * @memberOf! module:Neo
241
241
  * @name config.version
242
242
  * @type String
243
243
  */
244
- version: '6.9.0'
244
+ version: '6.9.1'
245
245
  };
246
246
 
247
247
  Object.assign(DefaultConfig, {
@@ -14,12 +14,15 @@ class Filter extends Base {
14
14
  static observable = true
15
15
  /**
16
16
  * Valid values for the operator config:<br>
17
- * ['==', '===', '!=', '!==', '<', '<=', '>', '>=', 'excluded', 'included', 'isDefined', 'isUndefined', 'like']
17
+ * ['==','===','!=','!==','<','<=','>','>=','endsWith','excluded','included','isDefined','isUndefined','like','startsWith']
18
18
  * @member {String[]} operators
19
19
  * @protected
20
20
  * @static
21
21
  */
22
- static operators = ['==', '===', '!=', '!==', '<', '<=', '>', '>=', 'excluded', 'included', 'isDefined', 'isUndefined', 'like']
22
+ static operators = [
23
+ '==', '===', '!=', '!==', '<', '<=', '>', '>=', 'endsWith', 'excluded', 'included',
24
+ 'isDefined', 'isUndefined', 'like', 'startsWith'
25
+ ]
23
26
 
24
27
  static config = {
25
28
  /**
@@ -220,6 +223,10 @@ class Filter extends Base {
220
223
  static ['>'] (a, b) {return a > b;}
221
224
  static ['>='] (a, b) {return a >= b;}
222
225
 
226
+ static ['endsWith'](a, b) {
227
+ return a?.toLowerCase().endsWith(b?.toLowerCase()) || false;
228
+ }
229
+
223
230
  static ['excluded'](a, b) {
224
231
  return b.indexOf(a) < 0;
225
232
  }
@@ -239,6 +246,10 @@ class Filter extends Base {
239
246
  static ['like'](a, b) {
240
247
  return a?.toLowerCase().includes(b?.toLowerCase()) || false;
241
248
  }
249
+
250
+ static ['startsWith'](a, b) {
251
+ return a?.toLowerCase().startsWith(b?.toLowerCase()) || false;
252
+ }
242
253
  }
243
254
 
244
255
  Neo.applyClassConfig(Filter);
@@ -107,7 +107,8 @@ class Toast extends Base {
107
107
  ]},
108
108
  {cls: ['neo-toast-close', 'fa', 'fa-close'], removeDom: true}
109
109
  ]
110
- }]}
110
+ }]},
111
+ floating : true
111
112
  }
112
113
 
113
114
  /**
@@ -38,6 +38,10 @@ class Select extends Picker {
38
38
  * @member {String} displayField='name'
39
39
  */
40
40
  displayField: 'name',
41
+ /**
42
+ * @member {String} filterOperator='like'
43
+ */
44
+ filterOperator: 'like',
41
45
  /**
42
46
  * True will only fire a change event, in case the TextField input value matches a record.
43
47
  * onFocusLeave() will try to select a hint record, if needed and possible.
@@ -103,6 +107,11 @@ class Select extends Picker {
103
107
  * @member {Boolean} typeAhead_=true
104
108
  */
105
109
  typeAhead_: true,
110
+ /**
111
+ * Set this config to false, in case typing into the input field should not filter list items
112
+ * @member {Boolean} useFilter=true
113
+ */
114
+ useFilter: true,
106
115
  /**
107
116
  * This config should point to the store keyProperty or a different model field,
108
117
  * which you want to submit instead
@@ -111,16 +120,6 @@ class Select extends Picker {
111
120
  valueField: 'id'
112
121
  }
113
122
 
114
- /**
115
- * @member {String} filterOperator='like'
116
- */
117
- filterOperator = 'like'
118
- /**
119
- * Set this config to false, in case typing into the input field should not filter list items
120
- * @member {Boolean} useFilter=true
121
- */
122
- useFilter = true
123
-
124
123
  /**
125
124
  * @param {Object} config
126
125
  */
@@ -367,8 +367,9 @@ class Text extends Base {
367
367
  { cls } = me;
368
368
 
369
369
  NeoArray.toggle(cls, 'neo-not-editable', !value);
370
- me.cls = cls
371
- me.changeInputElKey('readonly', value ? false : true);
370
+ me.cls = cls;
371
+
372
+ me.updateReadOnlyState()
372
373
  }
373
374
 
374
375
  /**
@@ -675,7 +676,7 @@ class Text extends Base {
675
676
  NeoArray[value ? 'add' : 'remove'](cls, 'neo-readonly');
676
677
  me.cls = cls;
677
678
 
678
- me.changeInputElKey('readonly', value ? value : null);
679
+ me.updateReadOnlyState();
679
680
 
680
681
  me.triggers?.forEach(trigger => {
681
682
  trigger.hidden = value ? true : trigger.getHiddenState?.() || false
@@ -1440,6 +1441,15 @@ class Text extends Base {
1440
1441
  me.update()
1441
1442
  }
1442
1443
 
1444
+ /**
1445
+ * The DOM based readonly attribute needs to honor the editable & readOnly configs
1446
+ */
1447
+ updateReadOnlyState() {
1448
+ let me = this;
1449
+
1450
+ me.changeInputElKey('readonly', !me.editable || me.readOnly || null);
1451
+ }
1452
+
1443
1453
  /**
1444
1454
  * Since triggers do not get rendered, assign the relevant props
1445
1455
  * todo: this could be handled by component.Base
@@ -34,14 +34,7 @@ class Picker extends Base {
34
34
  */
35
35
  onTriggerClick(data) {
36
36
  this.field.onPickerTriggerClick();
37
- }
38
-
39
- /**
40
- * @returns {Boolean} true in case the trigger should be hidden
41
- */
42
- getHiddenState() {
43
- return !this.field.editable;
44
- }
37
+ }
45
38
  }
46
39
 
47
40
  Neo.applyClassConfig(Picker);
@@ -432,34 +432,44 @@ class DomEvent extends Base {
432
432
  * @returns {Boolean|String} true in case the delegation string matches the event path
433
433
  */
434
434
  verifyDelegationPath(listener, path) {
435
- let delegationArray = listener.delegate.split(' '),
436
- j = 0,
437
- len = delegationArray.length,
438
- pathLen = path.length,
439
- hasMatch, i, item, isId, targetId;
440
-
441
- for (i=len-1; i >= 0; i--) {
442
- hasMatch = false;
443
- item = delegationArray[i];
444
- isId = item.startsWith('#');
445
-
446
- if (isId || item.startsWith('.')) {
447
- item = item.substr(1);
435
+ const { delegate } = listener;
436
+
437
+ let j = 0, pathLen = path.length, targetId;
438
+
439
+ if (typeof delegate === 'function') {
440
+ j = delegate(path);
441
+ if (j != null) {
442
+ targetId = path[j].id;
448
443
  }
444
+ }
445
+ else {
446
+ let delegationArray = delegate.split(' '),
447
+ len = delegationArray.length,
448
+ hasMatch, i, item, isId;
449
+
450
+ for (i=len-1; i >= 0; i--) {
451
+ hasMatch = false;
452
+ item = delegationArray[i];
453
+ isId = item.startsWith('#');
454
+
455
+ if (isId || item.startsWith('.')) {
456
+ item = item.substr(1);
457
+ }
449
458
 
450
- for (; j < pathLen; j++) {
451
- if (
452
- (isId && path[j].id === item) ||
453
- path[j].cls.includes(item)
454
- ) {
455
- hasMatch = true;
456
- targetId = path[j].id;
457
- break
459
+ for (; j < pathLen; j++) {
460
+ if (
461
+ (isId && path[j].id === item) ||
462
+ path[j].cls.includes(item)
463
+ ) {
464
+ hasMatch = true;
465
+ targetId = path[j].id;
466
+ break
467
+ }
458
468
  }
459
- }
460
469
 
461
- if (!hasMatch) {
462
- return false
470
+ if (!hasMatch) {
471
+ return false
472
+ }
463
473
  }
464
474
  }
465
475
 
@@ -196,17 +196,18 @@ class Base extends Container {
196
196
  componentId : app.mainView.id,
197
197
  resetCfg : {},
198
198
  isShared : true,
199
- delegate : '.neo-uses-shared-tooltip',
199
+ delegate : this.delegateFilter,
200
200
  listeners : {
201
201
  // Reconfigure on over a target
202
- async targetOver({ target }) {
202
+ async targetOver({ target, data }) {
203
203
  // Revert last pointerOver config set to initial setting.
204
204
  this.set(this.resetCfg);
205
205
  this.resetCfg = {};
206
206
 
207
207
  // Use the tooltip config block that the target was configured with
208
- // to reconfogure tis instance
209
- const config = target?._tooltip;
208
+ // to reconfigure this instance, or if there was none, check the
209
+ // data-neo-tooltip property for a text string.
210
+ const config = target?._tooltip || { text : data.target.data.neoTooltip };
210
211
 
211
212
  // Cache things we have to reset
212
213
  for (const key in config) {
@@ -264,7 +265,8 @@ class Base extends Container {
264
265
  me.align.targetMargin = 10;
265
266
 
266
267
  me.fire('targetOver', {
267
- target : me.activeTarget
268
+ target : me.activeTarget,
269
+ data
268
270
  });
269
271
 
270
272
  // Still visible, just realign
@@ -278,6 +280,15 @@ class Base extends Container {
278
280
  }
279
281
  }
280
282
 
283
+ // Used as a delegate filter to activate on targets which have a tooltip configuration
284
+ static delegateFilter(path) {
285
+ for (let i = 0, { length } = path; i < length; i++) {
286
+ if (path[i].cls.includes('neo-uses-shared-tooltip') || path[i].data['neoTooltip']) {
287
+ return i;
288
+ }
289
+ }
290
+ }
291
+
281
292
  /**
282
293
  * @param {Object} data
283
294
  */
@@ -287,7 +298,8 @@ class Base extends Container {
287
298
  // If it's an internal move within the delegate, do nothing
288
299
  if (data.currentTarget === me.activeTarget?.id) {
289
300
  me.fire('targetOut', {
290
- target : this.activeTarget
301
+ target : me.activeTarget,
302
+ data
291
303
  });
292
304
  me.activeTarget = null;
293
305
  me.hideDelayed(data);
@@ -750,6 +750,13 @@ class Helper extends Base {
750
750
  node.className = value;
751
751
  }
752
752
  break;
753
+ case 'data':
754
+ if (value && Neo.typeOf(value) === 'Object') {
755
+ Object.entries(value).forEach(([key, val]) => {
756
+ node.attributes[`data-${Neo.decamel(key)}`] = val
757
+ })
758
+ }
759
+ break;
753
760
  case 'height':
754
761
  case 'maxHeight':
755
762
  case 'maxWidth':
@@ -134,6 +134,10 @@ class App extends Base {
134
134
  }
135
135
  }
136
136
  } else {
137
+ // default parentId='document.body' => we want it to get shown
138
+ config.autoMount = true;
139
+ config.autoRender = true;
140
+
137
141
  instance = Neo[config.ntype ? 'ntype' : 'create'](config)
138
142
  }
139
143
 
@@ -0,0 +1,8 @@
1
+ // Important: You need to import all classes which you want to use inside tests here
2
+ // (excluding base classes (prototypes) of already imported classes)
3
+
4
+ import Button from '../../src/button/Base.mjs';
5
+ import DateSelector from '../../src/component/DateSelector.mjs';
6
+ import SelectField from '../../src/form/field/Select.mjs';
7
+
8
+ export const onStart = () => Neo.app({name: 'AppEmpty'})
@@ -0,0 +1,17 @@
1
+ StartTest(t => {
2
+ t.it('Checking if neo.mjs got started', async t => {
3
+ if (!globalThis.Neo?.Main) {
4
+ console.log('Starting the neo.mjs workers setup');
5
+
6
+ await import('../../../../src/MicroLoader.mjs');
7
+ }
8
+
9
+ setTimeout(() => {
10
+ Neo.worker.App.createNeoInstance({
11
+ ntype : 'button',
12
+ iconCls: 'fa fa-home',
13
+ text : 'Hello Siesta'
14
+ })
15
+ }, 300)
16
+ });
17
+ });
@@ -0,0 +1,18 @@
1
+ StartTest(t => {
2
+ t.it('Checking if neo.mjs got started', async t => {
3
+ if (!globalThis.Neo?.Main) {
4
+ console.log('Starting the neo.mjs workers setup');
5
+
6
+ await import('../../../../src/MicroLoader.mjs');
7
+ }
8
+
9
+ setTimeout(() => {
10
+ Neo.worker.App.createNeoInstance({
11
+ ntype : 'dateselector',
12
+ height: 250,
13
+ value : '2023-10-15',
14
+ width : 300
15
+ })
16
+ }, 300)
17
+ });
18
+ });
@@ -0,0 +1,35 @@
1
+ StartTest(t => {
2
+ t.it('Checking if neo.mjs got started', async t => {
3
+ if (!globalThis.Neo?.Main) {
4
+ console.log('Starting the neo.mjs workers setup');
5
+
6
+ await import('../../../../../src/MicroLoader.mjs');
7
+ }
8
+
9
+ setTimeout(() => {
10
+ Neo.worker.App.createNeoInstance({
11
+ ntype : 'selectfield',
12
+ labelPosition: 'inline',
13
+ labelText : 'US States',
14
+ labelWidth : 80,
15
+ width : 300,
16
+
17
+ store : {
18
+ autoLoad : true,
19
+ keyProperty: 'abbreviation',
20
+ url : '../../resources/examples/data/us_states.json',
21
+
22
+ model: {
23
+ fields: [{
24
+ name: 'abbreviation',
25
+ type: 'string'
26
+ }, {
27
+ name: 'name',
28
+ type: 'string'
29
+ }]
30
+ }
31
+ }
32
+ })
33
+ }, 300)
34
+ });
35
+ });
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
6
+
7
+ <!-- Web interface -->
8
+ <link rel="stylesheet" type="text/css" href="../../node_modules/siesta-lite/resources/css/siesta-all.css">
9
+ <script type="text/javascript" src="../../node_modules/siesta-lite/siesta-all.js"></script>
10
+
11
+ <!-- Project file -->
12
+ <script type="text/javascript" src="siesta.js"></script>
13
+ </head>
14
+
15
+ <body>
16
+ </body>
17
+ </html>
@@ -0,0 +1,6 @@
1
+ {
2
+ "appPath" : "test/components/app.mjs",
3
+ "basePath" : "../../",
4
+ "environment": "development",
5
+ "mainPath" : "./Main.mjs"
6
+ }
@@ -0,0 +1,14 @@
1
+ const project = new Siesta.Project.Browser();
2
+
3
+ project.configure({
4
+ title : 'Neo Component Tests',
5
+ isEcmaModule: true
6
+ });
7
+
8
+ project.plan(
9
+ 'files/button/Base.mjs',
10
+ 'files/component/DateSelector.mjs',
11
+ 'files/form/field/Select.mjs'
12
+ );
13
+
14
+ project.start();