cavalion-vcl 1.1.90 → 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,53 @@
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
+
21
+ ### `2026/01/18` 1.1.91 — UI/UX utility upgrades: visibility, filtering, sorting, and hints
22
+
23
+ #### List sorting/selection fixes + date/numeric heuristics
24
+
25
+ - Defers row selection updates to the next tick after click dispatch to avoid selection timing issues.
26
+ - Broadens date detection to treat epoch-millis ranges as dates (in addition to `Date` instances and ISO `...Z` strings).
27
+ - Improves date formatting to accept numeric-string timestamps by coercing before constructing a `Date`.
28
+ - Adds automatic numeric-column detection (samples first 100 rows) when the caller doesn’t specify `numeric`.
29
+ - Coerces finite numeric cell values to floats during sort comparisons for consistent numeric ordering.
30
+ - Avoids a generated CSS class name collision by renaming attribute `"fa"` to `"fa_"` for list columns.
31
+
32
+
33
+ #### Changes
34
+
35
+ - Adds `Component.debounce(name, ms, fn)` as a shorthand over named timeouts.
36
+ - Extends component lookup to accept a predicate function and walk owners until it matches.
37
+ - Enhances `Control.whenVisible(...)` to accept an optional callback, reuse cached promises, and optionally reject on destroy (`rejectOnDestroy`); default destroy now resolves with `null`.
38
+ - Adds `data/Array` support for filtering out `Source.Pending` items, plus a new `filterPending` property and `setFilterPending(...)`.
39
+ - Reduces toast spam by silencing clipboard debug prints while keeping user feedback toasts.
40
+ - Improves the developer console helpers: adds `tap`, paste helper, and safer/clearer HTML rendering for selection metadata.
41
+ - Adds `Element.hint` (sets DOM `title`) and ensures hints are applied when attributes update.
42
+ - Adjusts `List` click handling to avoid selection timing issues by deferring selection to the next tick.
43
+ - Broadens date detection/formatting in `List` to handle epoch-millis (including numeric strings) in addition to `Date` and ISO Z strings.
44
+ - Improves `List` sorting by auto-detecting numeric columns and coercing finite numeric values before comparing.
45
+ - Adds `List.isNumericColumn(...)` heuristic (samples up to 100 rows) to drive numeric sorting defaults.
46
+ - Hardens `ListColumn.getAttributeClassName()` to avoid CSS-class collisions for attribute `"fa"` by renaming to `"fa_"`.
47
+ - Minor whitespace cleanup in `Application.js` (no functional change).
48
+
49
+
50
+
1
51
  ### `2025/10/27` 1.1.90 — List cell formatting & selection handling
2
52
 
3
53
  * Adds `_formatNumbers: true` and `_renderCellTitles: true` defaults in `List` prototype.
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  * [Declarative Component Format.md](docs/:)
2
2
  * [Naming Conventions.md](docs/:)
3
3
  * ...
4
+ * [Application Events.md](docs/:)
4
5
 
5
6
  # cavalion-vcl
6
7
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cavalion-vcl",
3
- "version": "1.1.90",
3
+ "version": "1.1.92",
4
4
  "description": "Visual Component Library for vcl-comps",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -8,8 +8,6 @@ define(function(require) {
8
8
  var CssRules = require("./CssRules");
9
9
 
10
10
  var instances = [];
11
-
12
-
13
11
 
14
12
  return (Application = Application(require, {
15
13
  inherits: Component,
@@ -303,6 +301,7 @@ define(function(require) {
303
301
  },
304
302
  setIcon: function(value) {
305
303
  if(this._icon !== value) {
304
+ this._icon = value;
306
305
  var link = document.querySelectorAll("html head link[rel='icon shortcut']")[0];
307
306
  if(link === undefined) {
308
307
  link = document.createElement("link");
package/src/Component.js CHANGED
@@ -269,6 +269,9 @@ define(function (require) {
269
269
  }
270
270
  return this.setTimeout(name, f, 0, args);
271
271
  },
272
+ debounce(name, ms, f) {
273
+ return this.setTimeout(name, f, ms);
274
+ },
272
275
  setTimeout: function(name, f, ms, args) {
273
276
  /**
274
277
  * @param name {String} [optional] Used to identify the timeout. Successive calls will cancel a previous timeout with the same name.
@@ -820,6 +823,18 @@ define(function (require) {
820
823
  }
821
824
  return selector;
822
825
  }
826
+
827
+
828
+ if(typeof selector === "function") {
829
+ let c = this.getOwner();
830
+ while(c !== null) {
831
+ if(selector(c) == true) {
832
+ break;
833
+ }
834
+ c = c.getOwner();
835
+ }
836
+ return c;
837
+ }
823
838
 
824
839
  /*- Queries all components for the given selector and filters out
825
840
  those matches which are an owner of the calling component. The
package/src/Control.js CHANGED
@@ -1112,22 +1112,29 @@ define(function(require) {
1112
1112
  return r === true ? r : r === "always";
1113
1113
  },
1114
1114
  whenVisible: function(options) {
1115
+ let cb;
1116
+ if (typeof options === "function") {
1117
+ cb = options;
1118
+ options = {};
1119
+ }
1115
1120
  options = options || {};
1116
1121
 
1122
+ const rejectOnDestroy = options.rejectOnDestroy === true;
1123
+
1117
1124
  // Already visible → reuse a cached resolved promise (no new allocs).
1118
1125
  if (this.isShowing() === true) {
1119
- return this._wv_resolved || (this._wv_resolved = Promise.resolve(this));
1126
+ const p = this._wv_resolved || (this._wv_resolved = Promise.resolve(this));
1127
+ return cb ? p.then(() => cb(this), () => undefined) : p;
1120
1128
  }
1121
1129
 
1122
1130
  // Already waiting → return the same pending promise.
1123
- if (this._wv_pending) return this._wv_pending;
1131
+ if (this._wv_pending) {
1132
+ return cb ? this._wv_pending.then(v => (v && cb(v), v)) : this._wv_pending;
1133
+ }
1124
1134
 
1125
- // If a parent must become visible first, start our listeners now, but don't
1126
- // allocate a *second* promise for us — we still reuse _wv_pending below.
1127
1135
  if (this._parent && this._parent !== this &&
1128
- typeof this._parent.whenVisible === "function" &&
1129
- this._parent.isShowing() === false) {
1130
- // Fire and forget: when parent shows, we’ll re-check via resolveVisible().
1136
+ typeof this._parent.whenVisible === "function" &&
1137
+ this._parent.isShowing() === false) {
1131
1138
  this._parent.whenVisible(options).then(() => { this.update(); });
1132
1139
  }
1133
1140
 
@@ -1137,30 +1144,33 @@ define(function(require) {
1137
1144
  if (destroyLis !== undefined) { this.un(destroyLis); destroyLis = undefined; }
1138
1145
  };
1139
1146
 
1140
- // Create exactly one pending promise and cache it.
1141
1147
  this._wv_pending = new Promise((resolve, reject) => {
1142
1148
  const settle = (fn, v) => { cleanup(); this._wv_pending = null; return fn(v); };
1143
1149
 
1144
1150
  const resolveVisible = () => {
1145
1151
  if (this.isShowing() === true) {
1146
- // Cache a resolved promise for next time.
1147
1152
  this._wv_resolved = this._wv_resolved || Promise.resolve(this);
1148
1153
  return settle(resolve, this);
1149
1154
  }
1150
1155
  };
1151
1156
 
1152
- const rejectVisible = (err) => settle(reject, err);
1153
-
1154
1157
  showLis = this.on("show", resolveVisible);
1155
- destroyLis = this.on("destroy",
1156
- () => rejectVisible(new Error("Control destroyed before becoming visible")));
1157
1158
 
1158
- // Nudge layout/visibility and do a synchronous re-check.
1159
+ destroyLis = this.on("destroy", () => {
1160
+ if (rejectOnDestroy) {
1161
+ return settle(reject, new Error("Control destroyed before becoming visible"));
1162
+ }
1163
+ // Cancellation/no-op by default: resolves to null/undefined.
1164
+ return settle(resolve, null);
1165
+ });
1166
+
1159
1167
  this.update();
1160
1168
  resolveVisible();
1161
1169
  });
1162
1170
 
1163
- return this._wv_pending;
1171
+ return cb
1172
+ ? this._wv_pending.then(v => (v && cb(v), v))
1173
+ : this._wv_pending;
1164
1174
  },
1165
1175
  isControlVisible: function(control) {
1166
1176
  return this.hasState(ControlState.acceptChildNodes) && this.isVisible();
package/src/data/Array.js CHANGED
@@ -20,6 +20,8 @@ define(function(require) {
20
20
  _onFilterObject: null,
21
21
  _notifications: null,
22
22
 
23
+ _filterPending: false,
24
+
23
25
  _onUpdate: null,
24
26
  _onChange: null,
25
27
  _onBusyChanged: null,
@@ -121,6 +123,10 @@ define(function(require) {
121
123
  /** @overrides ../data/Source.prototype.getAttributeValue */
122
124
  this.assertArray(index);
123
125
  var obj = this.getObject(index || 0);
126
+ if(obj === null || typeof obj !== "object") {
127
+ return obj;
128
+ }
129
+
124
130
  if(name === ".") {
125
131
  return obj;
126
132
  }
@@ -189,7 +195,10 @@ define(function(require) {
189
195
  this._arr = this._array;
190
196
  for(var i = 0; i < this._array.length; ++i) {
191
197
  var obj = this._array[i];
192
- if(obj === Source.Pending || this.fire("onFilterObject", [obj, i, context]) !== true) {
198
+ if(
199
+ (obj === Source.Pending && !this._filterPending) ||
200
+ this.fire("onFilterObject", [obj, i, context]) !== true
201
+ ) {
193
202
  arr.push(obj);
194
203
  }
195
204
  }
@@ -250,6 +259,12 @@ define(function(require) {
250
259
  this.notify(SourceEvent.layoutChanged);
251
260
  }
252
261
  },
262
+ setFilterPending: function(value) {
263
+ if(this._filterPending !== value) {
264
+ this._filterPending = value;
265
+ this.updateFilter();
266
+ }
267
+ },
253
268
  arrayChanged: function() {
254
269
  this.updateFilter(false);
255
270
  //this.notify(SourceEvent.layoutChanged);
@@ -357,6 +372,10 @@ define(function(require) {
357
372
  "array": {
358
373
  type: Type.ARRAY,
359
374
  set: Function
375
+ },
376
+ "filterPending": {
377
+ type: Type.BOOLEAN,
378
+ set: Function
360
379
  },
361
380
  "onActiveChanged": {
362
381
  type: Type.EVENT
@@ -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
 
@@ -16,10 +18,10 @@ const Clipboard = req("util/Clipboard");
16
18
  };
17
19
 
18
20
  Clipboard.onPaste.addListener(e => {
19
- this.print("onPaste", e);
21
+ // this.print("onPaste", e);
20
22
  this.toast(js.sf("Pasted %d bytes...", e.length ))});
21
23
  Clipboard.onCopy.addListener(e => {
22
- this.print("onCopy", e);
24
+ // this.print("onCopy", e);
23
25
  if(typeof e === "string" && e.length > 150) {
24
26
  this.toast(js.sf("Copied %d bytes", e.length ));
25
27
  } else {
@@ -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
 
@@ -30,14 +30,6 @@ const default_zoom = Browser.win ? "zoom-109" : "zoom-112";
30
30
 
31
31
  return this.inherited(arguments);
32
32
  },
33
- onDispatchChildEvent: function(component, name, evt, f, args) {
34
- if(name === "touchstart") {
35
- if(!Fullscreen.hasRequested()) {
36
- Fullscreen.request(document.documentElement);
37
- }
38
- }
39
- return this.inherited(arguments);
40
- },
41
33
  onGetState: function() {
42
34
  var scope = this.getScope();
43
35
  var form = scope.client._form;
@@ -64,6 +56,8 @@ const default_zoom = Browser.win ? "zoom-109" : "zoom-112";
64
56
  'font-size': font_size,
65
57
  'letter-spacing': letter_spacing,
66
58
 
59
+ '.right': "float: right;",
60
+
67
61
  '.{Button}': {
68
62
  'font-size': font_size,
69
63
  'font-family': font_family,
@@ -36,14 +36,13 @@ const deselect = () => {
36
36
  const H = (uri, vars, opts) => B.i(["Hover<>", { vars: js.mi({ uri: uri }, vars)}], opts);
37
37
  H.i = (obj) => H("devtools/Alphaview.csv", { sel: [obj] });
38
38
 
39
+ const tap = fn => x => (fn(x), x);
39
40
  const cc = (text) => Clipboard.copy(text);
41
+ const cp = (cb) => Clipboard.paste(cb);
40
42
  const cl = console.log;
41
43
  const facts = (comp) => Component.getFactories(comp);
42
44
 
43
- window.B = B; window.H = H;
44
- window.facts = facts;
45
- window.cc = cc;
46
- window.cl = cl;
45
+ js.mi(window, { B, H, facts, cc, cp, cl, tap });
47
46
 
48
47
  [["ui/Form"], {
49
48
  activeControl: "console",
@@ -79,18 +78,20 @@ window.cl = cl;
79
78
 
80
79
  if (value !== null) {
81
80
  // `#CVLN-20200904-3`
82
- content.push(js.sf("%s%s%s",
81
+ content.push(js.sf("%H%H%H",
83
82
  value.isRootComponent() ? ":root" : "",
84
83
  value.isSelected && value.isSelected() ? ":selected" : "",
85
84
  value.isEnabled && value.isEnabled() ? "" : ":disabled"));
86
85
 
87
86
  if(value['@factory']) {
88
- content.push(js.n(value['@factory']).split("#").slice(0, -1).join("!"));
87
+ content.push(js.sf("<small>%H</small>", js.n(value['@factory']).split("#").slice(0, -1).join("!")));
88
+ } else {
89
+ content.push("<i>no-factory</i>");
89
90
  }
90
91
 
91
- content.push(js.sf("[%s]", value));
92
+ content.push(js.sf("<b>[%H]</b>", value));
92
93
 
93
- 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("");
94
95
  if(value.up()) {
95
96
  props.push(js.sf("up(): #%s", hashAndNameOrUri(value.up())));
96
97
  }
@@ -115,10 +116,14 @@ window.cl = cl;
115
116
  consoles.forEach(c => c.getNode("input").value = js.sf("#%d", value.hashCode()));
116
117
  consoles.forEach(c => c.focus());
117
118
  }
119
+
120
+ content.splice(0, 0, js.sf("<b>%H#%s</b>", value._name, value.hashCode()));
118
121
  }
119
- scope.sizer_selection.setContent(String.format("%H", content.join(" ")));
120
-
122
+
123
+ scope.sizer_selection.setContent(content.join(" "));
124
+
121
125
  if(value !== null) {
126
+
122
127
  if(!content[0]) content.shift();
123
128
  // content.pop();
124
129
  app.toast({ title: js.sf("%n", value), content: " " || js.sf("<ul style='padding:0;padding-left:8px;'><li>%s</li></ul>", content.join("</li><li>")), classes: "glassy fade"});
package/src/ui/Element.js CHANGED
@@ -93,6 +93,9 @@ define(function(require) {
93
93
  for(var k in value) {
94
94
  this._node.setAttribute(k, value[k]);
95
95
  }
96
+ if(this._hint) {
97
+ this._node.title = this._hint;
98
+ }
96
99
  return this.inherited(arguments);
97
100
  },
98
101
 
@@ -106,6 +109,15 @@ define(function(require) {
106
109
  this.recreateNode();
107
110
  }
108
111
  },
112
+ setHint: function(value) {
113
+ if(this._hint !== value) {
114
+ this._hint = value;
115
+
116
+ if(this._node) {
117
+ this._node.title = value;
118
+ }
119
+ }
120
+ },
109
121
  sourceNotifyEvent: function(event, data) {
110
122
  switch(event) {
111
123
 
@@ -149,6 +161,10 @@ define(function(require) {
149
161
  stored: false,
150
162
  type: Type.OBJECT,
151
163
  set: Function
164
+ },
165
+ "hint": {
166
+ type: Type.STRING,
167
+ set: Function
152
168
  }
153
169
  },
154
170
  statics: {
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.setSelection(selection);
353
- this.dispatch("click", evt);
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,18 +569,64 @@ workaroundColumnAlignment(this);
531
569
  workaroundColumnAlignment(this);
532
570
  },
533
571
  isDate: function(value) {
534
- return (value instanceof Date) || (typeof value === "string" &&
535
- 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
+ }
536
590
  },
537
591
  formatDate: function(value, opts) {
538
- if(!(value instanceof Date)) value = new Date(value);
592
+ let time = true;
593
+ if(!(value instanceof Date)) {
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
+ }
608
+ }
609
+ value = new Date(value);
610
+ }
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
+
539
622
  if(opts && opts.utc) {
540
- return js.sf("%d/%02d/%02d %02d:%02d", value.getUTCFullYear(), value.getUTCMonth() + 1,
541
- value.getUTCDate(), value.getUTCHours(), value.getUTCMinutes());
623
+ return js.sf("%d/%02d/%02d", value.getUTCFullYear(), value.getUTCMonth() + 1,
624
+ value.getUTCDate());
542
625
  }
543
626
 
544
- return js.sf("%d/%02d/%02d %02d:%02d", value.getFullYear(), value.getMonth() + 1,
545
- value.getDate(), value.getHours(), value.getMinutes());
627
+ return js.sf("%d/%02d/%02d", value.getFullYear(), value.getMonth() + 1,
628
+ value.getDate());
629
+
546
630
  },
547
631
 
548
632
  getBodyWidth: function() {
@@ -668,6 +752,10 @@ workaroundColumnAlignment(this);
668
752
  }
669
753
  return null;
670
754
  },
755
+ getColumnByNode: function(node) {
756
+ node = node.soup(".ListCell");
757
+ return node && this._columns.find(col => node.matches(col._rule.selectorText));
758
+ },
671
759
  getColumnByName: function(name) {
672
760
  for(var i = 0, l = this._columns.length; i < l; ++i) {
673
761
  var c = this._columns[i];
@@ -955,6 +1043,10 @@ workaroundColumnAlignment(this);
955
1043
  } else if(typeof column === "number") {
956
1044
  column = this.getColumn(column);
957
1045
  }
1046
+
1047
+ if(numeric === undefined) {
1048
+ numeric = this.isNumericColumn(column);
1049
+ }
958
1050
 
959
1051
  const sv = column.get("onSortValues") || Array.sortValues;
960
1052
  this._source.sort((i1, i2) => {
@@ -964,6 +1056,11 @@ workaroundColumnAlignment(this);
964
1056
  i1 = this.valueByColumnAndRow(column, row1);
965
1057
  i2 = this.valueByColumnAndRow(column, row2);
966
1058
 
1059
+ if(numeric) {
1060
+ i1 = isFinite(i1) ? parseFloat(i1) : i1;
1061
+ i2 = isFinite(i2) ? parseFloat(i2) : i2;
1062
+ }
1063
+
967
1064
  return dir * sv(i1, i2);
968
1065
  });
969
1066
  // (i1, i2) => {
@@ -994,6 +1091,13 @@ workaroundColumnAlignment(this);
994
1091
  // });
995
1092
  },
996
1093
 
1094
+ isNumericColumn(column) {
1095
+ return this._source._array
1096
+ .slice(0, 100)
1097
+ .every((o, i) =>
1098
+ isFinite(this.valueByColumnAndRow(column, i)));
1099
+ },
1100
+
997
1101
  hasSelection: function() {
998
1102
  return this._selection.length > 0;
999
1103
  },
@@ -281,7 +281,13 @@ define(function(require) {
281
281
  }
282
282
  },
283
283
  getAttributeClassName: function() {
284
- return Array.from(this._attribute).map(char => /[\w-]/.test(char) ? char : '_').join('');
284
+ let r = Array.from(this._attribute).map(char => /[\w-]/.test(char) ? char : '_').join('');
285
+
286
+ if(r === "fa") {
287
+ r += "_";
288
+ }
289
+
290
+ return r;
285
291
 
286
292
  // return this._attribute.
287
293
  // replace(/\#/g, "-").
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() {