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 +50 -0
- package/README.md +1 -0
- package/package.json +1 -1
- package/src/Application.js +1 -2
- package/src/Component.js +15 -0
- package/src/Control.js +25 -15
- package/src/data/Array.js +20 -1
- package/src/entities/Query.js +9 -0
- package/src/prototypes/App.glassy.js +1 -1
- package/src/prototypes/App.toast.js +20 -10
- package/src/prototypes/App.v1.js +2 -8
- package/src/prototypes/ui/forms/util/Console.js +15 -10
- package/src/ui/Element.js +16 -0
- package/src/ui/List.js +193 -89
- package/src/ui/ListColumn.js +7 -1
- package/src/ui/ListRow.js +1 -0
- package/src/ui/Select.js +4 -2
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
package/package.json
CHANGED
package/src/Application.js
CHANGED
|
@@ -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
|
-
|
|
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)
|
|
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
|
-
|
|
1129
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
package/src/entities/Query.js
CHANGED
|
@@ -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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
const Element = require("vcl/ui/Element");
|
|
46
|
+
const scope = this.getScope();
|
|
47
|
+
const elem = new Element(this);
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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 += " " + 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
|
|
package/src/prototypes/App.v1.js
CHANGED
|
@@ -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
|
|
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("%
|
|
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("[%
|
|
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
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
} else
|
|
275
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
535
|
-
|
|
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
|
-
|
|
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
|
|
541
|
-
value.getUTCDate()
|
|
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
|
|
545
|
-
value.getDate()
|
|
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
|
},
|
package/src/ui/ListColumn.js
CHANGED
|
@@ -281,7 +281,13 @@ define(function(require) {
|
|
|
281
281
|
}
|
|
282
282
|
},
|
|
283
283
|
getAttributeClassName: function() {
|
|
284
|
-
|
|
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
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.
|
|
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() {
|