cavalion-vcl 1.1.91 → 1.1.92

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/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ ### `2026/03/16` 1.1.92 — UI selection and data query aliases
2
+
3
+ Tightens several UI and data-layer behaviors across the framework. It fixes icon state persistence, adds `from`/`select` query aliases, makes array filtering more explicit around `Source.Pending`, and improves toast ergonomics with shorthand options and live updates. On the UI side, it refines `List` click/selection handling to better distinguish clicks from drag gestures, broadens date parsing/formatting, and adds small support utilities such as column lookup by DOM node and improved root-component labeling in the console.
4
+
5
+ * Fixes `Application.setIcon()` to persist `_icon` before updating the favicon link.
6
+ * Adds `filterPending` to `src/data/Array.js` and refreshes filtering when the flag changes.
7
+ * Changes array filtering so `Source.Pending` entries can be kept or filtered explicitly.
8
+ * Fixes `getAttributeValue()` in `src/data/Array.js` for `null` and non-object values.
9
+ * Adds query aliases `from` → `entity` and `select` → `attributes` in `src/entities/Query.js`.
10
+ * Improves `App.toast` with shorthand options (`t`, `c`, `cl`), default `glassy fade` styling, and `loading` content support.
11
+ * Adds `update()` plus `el`/`elem` aliases to the toast controller API.
12
+ * Fixes root-component labeling in `ui/forms/util/Console.js` by preferring `_uri` for root instances.
13
+ * Improves `src/ui/List.js` click handling by tracking short presses and reducing accidental selection during drag interactions.
14
+ * Adds temporary `.no-select` behavior after short clicks to suppress unwanted text selection in lists.
15
+ * Expands date detection and formatting in `src/ui/List.js` for `DD-MM-YYYY`, `YYYY-MM-DD`, numeric timestamps, and date-only display.
16
+ * Adds `getColumnByNode()` to resolve a list column from a DOM node.
17
+ * Updates `App.glassy` overlay styling with `z-index: 2`.
18
+ * Adds `_draggable` state to `src/ui/ListRow.js`.
19
+
20
+
1
21
  ### `2026/01/18` 1.1.91 — UI/UX utility upgrades: visibility, filtering, sorting, and hints
2
22
 
3
23
  #### List sorting/selection fixes + date/numeric heuristics
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cavalion-vcl",
3
- "version": "1.1.91",
3
+ "version": "1.1.92",
4
4
  "description": "Visual Component Library for vcl-comps",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -301,6 +301,7 @@ define(function(require) {
301
301
  },
302
302
  setIcon: function(value) {
303
303
  if(this._icon !== value) {
304
+ this._icon = value;
304
305
  var link = document.querySelectorAll("html head link[rel='icon shortcut']")[0];
305
306
  if(link === undefined) {
306
307
  link = document.createElement("link");
@@ -609,6 +609,11 @@ define(function(require) {
609
609
  this.notify(SourceEvent.changed);
610
610
  }
611
611
  },
612
+
613
+ getFrom() { return this._entity; },
614
+ setFrom(value) { return this.setEntity(value); },
615
+ getSelect() { return this._attributes; },
616
+ setSelect(value) { return this.setAttributes(value); },
612
617
 
613
618
  assign: function(query) {
614
619
  var wasActive = this.isActive();
@@ -651,6 +656,10 @@ define(function(require) {
651
656
  "entity": {
652
657
  type: Type.STRING
653
658
  },
659
+
660
+ "from": { type: Type.STRING, get: Function, set: Function },
661
+ "select": { type: Type.STRING, get: Function, set: Function },
662
+
654
663
  "filterBy": {
655
664
  type: Type.ARRAY
656
665
  },
@@ -41,7 +41,7 @@ function animate() {
41
41
  ".transparent": "background-color:transparent;",
42
42
  ".glassy-overlay": {
43
43
  "": "pointer-events: none; color:rgba(5,5,5,0.95);",
44
- ".glassy": "background-color: rgba(215, 215, 215, 0.35); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);",
44
+ ".glassy": "background-color: rgba(215, 215, 215, 0.35); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); z-index: 2;",
45
45
  ".loading": "background: url(/shared/vcl/images/loading.gif) 50% 50% no-repeat;",
46
46
  ".rounded": "padding: 4px; border-radius: 5px;",
47
47
  ".animate-width-height": "transition: width 250ms ease-in, height 250ms ease-in;",
@@ -2,6 +2,8 @@
2
2
 
3
3
  const Clipboard = req("util/Clipboard");
4
4
 
5
+ let IMG_LOADING = "https://veldapps.com/shared/vcl/images/loading.gif";
6
+
5
7
  ["", {
6
8
  onLoad() {
7
9
 
@@ -40,17 +42,23 @@ const Clipboard = req("util/Clipboard");
40
42
  - remove: API to remove toast
41
43
  */
42
44
 
43
- var Element = require("vcl/ui/Element");
44
- var scope = this.getScope();
45
- var elem = new Element(this);
45
+ const Element = require("vcl/ui/Element");
46
+ const scope = this.getScope();
47
+ const elem = new Element(this);
46
48
 
47
- var timeout = options.ms || (options.hasOwnProperty("timeout") ? options.timeout : 1500);
48
- var content = options.content || "No toast content";
49
- var classes = options.classes || "fade";
49
+ const timeout = options.ms ?? (options.hasOwnProperty("timeout") ? options.timeout : 1500);
50
+ const classes = options.cl ?? options.classes ?? "glassy fade";
51
+ const title = options.t ?? options.title;
50
52
 
51
- if(options.title !== undefined) {
53
+ let content = options.c ?? options.content ?? "No toast content";
54
+
55
+ if(title !== undefined) {
52
56
  content = js.sf("<b>%s</b><div>%s</div>", options.title, content);
53
57
  }
58
+
59
+ if(options.loading === true) {
60
+ content += " &nbsp; " + IMG_LOADING;
61
+ }
54
62
 
55
63
  elem.setContent(content);
56
64
  elem.setParent(scope.toasts);
@@ -58,7 +66,9 @@ const Clipboard = req("util/Clipboard");
58
66
  elem.update(() => elem.addClass("appear"));
59
67
 
60
68
  const controller = {
61
- element: elem,
69
+ element: elem, el: elem, elem,
70
+
71
+ update: (content) => controller.el.setContent(content),
62
72
  remove(timeout_) {
63
73
  elem.setTimeout("disappear", () => {
64
74
 
@@ -91,7 +91,7 @@ js.mi(window, { B, H, facts, cc, cp, cl, tap });
91
91
 
92
92
  content.push(js.sf("<b>[%H]</b>", value));
93
93
 
94
- var props = [], hashAndNameOrUri = (c) => [c.hashCode(), c._name ? "#" + c._name : " " + c._uri].filter(s => s !== "").join("");
94
+ var props = [], hashAndNameOrUri = (c) => [c.hashCode(), c.isRootComponent() ? c._uri : c._name ? "#" + c._name : " " + c._uri].filter(s => s !== "").join("");
95
95
  if(value.up()) {
96
96
  props.push(js.sf("up(): #%s", hashAndNameOrUri(value.up())));
97
97
  }
package/src/ui/List.js CHANGED
@@ -10,6 +10,7 @@ define(function(require) {
10
10
  var Source = require("../../data/Source");
11
11
  var SourceEvent = require("../../data/SourceEvent");
12
12
  var Component = require("../Component");
13
+ var Control = require("../Control");
13
14
  var Panel = require("./Panel");
14
15
  var ListColumn = require("./ListColumn");
15
16
  var ListHeader = require("./ListHeader");
@@ -17,6 +18,9 @@ define(function(require) {
17
18
  var ListBody = require("./ListBody");
18
19
  var ListRow = require("./ListRow");
19
20
 
21
+ const CLICK_INTERVAL = 350;
22
+ const MAX_PRESS_DURATION = 180;
23
+
20
24
  // require("stylesheet!./List.less");
21
25
 
22
26
  // TODO centralize/utilize :-p
@@ -52,6 +56,7 @@ define(function(require) {
52
56
  '@css': {
53
57
  overflow: "hidden",
54
58
  'overflow-x': "auto",
59
+ '&.no-select': "user-select: none; -webkit-user-select: none;",
55
60
  '&.busy': {
56
61
  'background': "url(/shared/vcl/images/loading.gif) no-repeat 4px 32px",
57
62
  '.body': {
@@ -268,90 +273,100 @@ workaroundColumnAlignment(this);
268
273
  },
269
274
  dispatchChildEvent: function(component, name, evt, f, args) {
270
275
  /** @overrides ../Control.prototype.dispatchChildEvent */
271
- if(name === "mousedown" && evt.shiftKey === true) {
272
- // prevent selection with mouse
273
- evt.preventDefault();
274
- } else if(component instanceof ListRow) {
275
- if(["dblclick", "dragenter", "dragover", "dragleave", "drop"].indexOf(name) !== -1) {
276
+ // if(name === "mousedown" && evt.shiftKey === true) {
277
+ // // prevent selection with mouse
278
+ // evt.preventDefault();
279
+ // } else
280
+ if(component instanceof ListRow) {
281
+ if(["dblclick", "dragenter", "dragover", "dragleave", "drop",
282
+ "mousedown", "mouseup"].indexOf(name) !== -1) {
276
283
  this.dispatch(name, evt);
284
+
285
+ if(name === "mousedown") {
286
+ this._lastMouseDown = Date.now();
287
+ }
288
+
277
289
  } else if(name === "click") {
278
- var rowIndex = component._rowIndex;
279
- var selection;
280
- if(evt.ctrlKey === true || evt.metaKey === true) {
281
- if(this.isRowSelected(rowIndex)) {
282
- var index = this._selection.indexOf(rowIndex);
283
- selection = [].concat(this._selection);
284
- selection.splice(index, 1);
285
- } else {
286
- selection = this._selection.concat([rowIndex]);
287
- }
288
-
289
- } else if(evt.shiftKey === true) {
290
- var length = this._selection.length;
291
- var i;
292
-
293
- HtmlElement.clearSelection();
294
-
295
- if(this._shiftSelectFromLast === true) {
296
- var anchor = length > 0 ? this._selection[length - 1] : 0;
297
- if(anchor === rowIndex) {
298
- selection = this._selection;
299
- } else {
300
- selection = [];
301
- if(this.isRowSelected(anchor)) {
302
- selection.push(anchor);
303
- }
304
- if(anchor < rowIndex) {
305
- for(i = anchor + 1; i <= rowIndex; ++i) {
306
- if(!this.isRowSelected(i)) {
307
- selection.push(i);
308
- }
309
- }
310
- } else {
311
- for(i = anchor - 1; i >= rowIndex; --i) {
312
- if(!this.isRowSelected(i)) {
313
- selection.push(i);
314
- }
315
- }
316
- }
317
- }
318
- } else {
319
- var start = length > 0 ? Math.min.apply(Math, this._selection) : 0;
320
- var end = length > 0 ? Math.max.apply(Math, this._selection) : 0;
321
-
322
- if(length === 0) {
323
- start = Math.min(0, rowIndex);
324
- end = Math.max(0, rowIndex);
325
- } else if(rowIndex < start) {
326
- start = rowIndex;
327
- } else if(rowIndex > end) {
328
- end = rowIndex;
329
- } else {
330
- if(rowIndex - start < end - rowIndex) {
331
- start = rowIndex;
332
- } else {
333
- end = rowIndex;
334
- }
335
- }
336
-
337
- selection = [];
338
- if(start <= end) {
339
- for(i = start; i <= end; ++i) {
340
- selection.push(i);
341
- }
342
- } else {
343
- for(i = start; i >= end; --i) {
344
- selection.push(i);
345
- }
346
- }
347
- }
348
- } else {
349
- selection = [component._rowIndex];
350
- evt.preventDefault();
351
- }
352
- this.dispatch("click", evt);
353
- this.nextTick(() => this.setSelection(selection));
354
- }
290
+ if(Date.now() - this._lastMouseDown < 150) {
291
+ var rowIndex = component._rowIndex;
292
+ var selection;
293
+ if(evt.ctrlKey === true || evt.metaKey === true) {
294
+ if(this.isRowSelected(rowIndex)) {
295
+ var index = this._selection.indexOf(rowIndex);
296
+ selection = [].concat(this._selection);
297
+ selection.splice(index, 1);
298
+ } else {
299
+ selection = this._selection.concat([rowIndex]);
300
+ }
301
+
302
+ } else if(evt.shiftKey === true) {
303
+ var length = this._selection.length;
304
+ var i;
305
+
306
+ HtmlElement.clearSelection();
307
+
308
+ if(this._shiftSelectFromLast === true) {
309
+ var anchor = length > 0 ? this._selection[length - 1] : 0;
310
+ if(anchor === rowIndex) {
311
+ selection = this._selection;
312
+ } else {
313
+ selection = [];
314
+ if(this.isRowSelected(anchor)) {
315
+ selection.push(anchor);
316
+ }
317
+ if(anchor < rowIndex) {
318
+ for(i = anchor + 1; i <= rowIndex; ++i) {
319
+ if(!this.isRowSelected(i)) {
320
+ selection.push(i);
321
+ }
322
+ }
323
+ } else {
324
+ for(i = anchor - 1; i >= rowIndex; --i) {
325
+ if(!this.isRowSelected(i)) {
326
+ selection.push(i);
327
+ }
328
+ }
329
+ }
330
+ }
331
+ } else {
332
+ var start = length > 0 ? Math.min.apply(Math, this._selection) : 0;
333
+ var end = length > 0 ? Math.max.apply(Math, this._selection) : 0;
334
+
335
+ if(length === 0) {
336
+ start = Math.min(0, rowIndex);
337
+ end = Math.max(0, rowIndex);
338
+ } else if(rowIndex < start) {
339
+ start = rowIndex;
340
+ } else if(rowIndex > end) {
341
+ end = rowIndex;
342
+ } else {
343
+ if(rowIndex - start < end - rowIndex) {
344
+ start = rowIndex;
345
+ } else {
346
+ end = rowIndex;
347
+ }
348
+ }
349
+
350
+ selection = [];
351
+ if(start <= end) {
352
+ for(i = start; i <= end; ++i) {
353
+ selection.push(i);
354
+ }
355
+ } else {
356
+ for(i = start; i >= end; --i) {
357
+ selection.push(i);
358
+ }
359
+ }
360
+ }
361
+ } else {
362
+ selection = [component._rowIndex];
363
+ evt.preventDefault();
364
+ }
365
+ //this.nextTick(() => this.setSelection(selection));
366
+ this.setSelection(selection);
367
+ this.dispatch("click", evt);
368
+ }
369
+ }
355
370
  }
356
371
  return this.inherited(arguments);
357
372
  },
@@ -396,6 +411,29 @@ workaroundColumnAlignment(this);
396
411
  }
397
412
  return r;
398
413
  },
414
+ onmouseup: function(evt) {
415
+ /** @overrides ../Control.prototype.onmouseup */
416
+ var r = this.inherited(arguments);
417
+
418
+ this.nextTick(() => {
419
+ if(r === false || evt.button !== 0) return r;
420
+
421
+ const now = Date.now();
422
+ const pressDuration = now - (this._lastMouseDown || now);
423
+ const sinceLastUp = now - (this._lastMouseUpTime || 0);
424
+
425
+ this._lastMouseUpTime = now;
426
+
427
+ if(pressDuration <= MAX_PRESS_DURATION) {
428
+ // Only arms no-select when the press was short (so, probably not a drag)
429
+ this.update(() => this.syncClass("no-select", true));
430
+ }
431
+
432
+ this.setTimeout("no-select", () => this.syncClass("no-select", false), CLICK_INTERVAL);
433
+ });
434
+
435
+ return r;
436
+ },
399
437
 
400
438
  notifyEvent: function(event, data) {
401
439
  if(event === "columnsChanged") {
@@ -531,25 +569,64 @@ workaroundColumnAlignment(this);
531
569
  workaroundColumnAlignment(this);
532
570
  },
533
571
  isDate: function(value) {
534
- return (value instanceof Date) ||
535
- (value > 946706400000 && value < 1893477600000) ||
536
- (typeof value === "string" && value.length === 24 && value.endsWith("Z"));
572
+ if(value instanceof Date) return true;
573
+
574
+ if((value > 946706400000 && value < 1893477600000)) return true;
575
+
576
+ if(typeof value === "string") {
577
+ if(value.length === 24 && value.endsWith("Z")) {
578
+ return true;
579
+ }
580
+ if(value.length === 10 && /\d\d[\-\/]\d\d[\-\/]\d\d\d\d/.test(value)) {
581
+ return true;
582
+ }
583
+ if(value.length === 10 && /\d\d\d\d[\-\/]\d\d[\-\/]\d\d/.test(value)) {
584
+ return true;
585
+ }
586
+ if(value.endsWith(", JJJJ-MM-DD)")) {
587
+ return true;
588
+ }
589
+ }
537
590
  },
538
591
  formatDate: function(value, opts) {
592
+ let time = true;
539
593
  if(!(value instanceof Date)) {
540
- if(typeof value === "string" && value.match(/^\d+$/)) {
541
- value = parseInt(value, 10);
594
+ if(typeof value === "string") {
595
+ if(value.endsWith(", JJJJ-MM-DD)")) {
596
+ value = value.substring(1, 11);
597
+ time = false;
598
+ }
599
+
600
+ if(value.match(/^\d+$/)) {
601
+ value = parseInt(value, 10);
602
+
603
+ } else if(/\d\d[\-\/]\d\d[\-\/]\d\d\d\d/.test(value)) {
604
+ const d = value.charAt(2);
605
+ value = value.split(d).reverse().join("/");
606
+ time = false;
607
+ }
542
608
  }
543
609
  value = new Date(value);
544
610
  }
545
611
 
612
+ if(time === true) {
613
+ if(opts && opts.utc) {
614
+ return js.sf("%d/%02d/%02d %02d:%02d", value.getUTCFullYear(), value.getUTCMonth() + 1,
615
+ value.getUTCDate(), value.getUTCHours(), value.getUTCMinutes());
616
+ }
617
+
618
+ return js.sf("%d/%02d/%02d %02d:%02d", value.getFullYear(), value.getMonth() + 1,
619
+ value.getDate(), value.getHours(), value.getMinutes());
620
+ }
621
+
546
622
  if(opts && opts.utc) {
547
- return js.sf("%d/%02d/%02d %02d:%02d", value.getUTCFullYear(), value.getUTCMonth() + 1,
548
- value.getUTCDate(), value.getUTCHours(), value.getUTCMinutes());
623
+ return js.sf("%d/%02d/%02d", value.getUTCFullYear(), value.getUTCMonth() + 1,
624
+ value.getUTCDate());
549
625
  }
550
626
 
551
- return js.sf("%d/%02d/%02d %02d:%02d", value.getFullYear(), value.getMonth() + 1,
552
- value.getDate(), value.getHours(), value.getMinutes());
627
+ return js.sf("%d/%02d/%02d", value.getFullYear(), value.getMonth() + 1,
628
+ value.getDate());
629
+
553
630
  },
554
631
 
555
632
  getBodyWidth: function() {
@@ -675,6 +752,10 @@ workaroundColumnAlignment(this);
675
752
  }
676
753
  return null;
677
754
  },
755
+ getColumnByNode: function(node) {
756
+ node = node.soup(".ListCell");
757
+ return node && this._columns.find(col => node.matches(col._rule.selectorText));
758
+ },
678
759
  getColumnByName: function(name) {
679
760
  for(var i = 0, l = this._columns.length; i < l; ++i) {
680
761
  var c = this._columns[i];
package/src/ui/ListRow.js CHANGED
@@ -33,6 +33,7 @@ define(function(require) {
33
33
  _autoSize: "width",
34
34
  _rowIndex: -1,
35
35
  _list: null,
36
+ _draggable: false,
36
37
 
37
38
  getInnerHtml: function() {
38
39
  /** @overrides ../Control.prototype.getInnerHtml */
package/src/ui/Select.js CHANGED
@@ -61,10 +61,10 @@ define(function(require) {
61
61
  },
62
62
  hasValue: function() { return !!this.getValue(); },
63
63
  getValue: function() {
64
- if(this.isLoading() || this.hasState("invalidated")) {
64
+ if(!this._node || this.isLoading() || this.hasState("invalidated")) {
65
65
  return this._value;
66
66
  }
67
- return this.nodeNeeded().value;
67
+ return this._node ? this._node.value : this._value;
68
68
  },
69
69
  setValue: function(value, force) {
70
70
  if(this.isLoading()) {
@@ -72,6 +72,8 @@ define(function(require) {
72
72
  } else if(force || (this._value !== value || (this._node && this._node.value !== value))) {
73
73
  this.nodeNeeded().value = (this._value = value);
74
74
  this.dispatch("change");
75
+ } else {
76
+ this._value = value;
75
77
  }
76
78
  },
77
79
  getOption: function() {