cavalion-vcl 1.1.90 → 1.1.91
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 +30 -0
- package/README.md +1 -0
- package/package.json +1 -1
- package/src/Application.js +0 -2
- package/src/Component.js +15 -0
- package/src/Control.js +25 -15
- package/src/data/Array.js +20 -1
- package/src/prototypes/App.toast.js +2 -2
- package/src/prototypes/App.v1.js +2 -8
- package/src/prototypes/ui/forms/util/Console.js +14 -9
- package/src/ui/Element.js +16 -0
- package/src/ui/List.js +27 -4
- package/src/ui/ListColumn.js +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
### `2026/01/18` 1.1.91 — UI/UX utility upgrades: visibility, filtering, sorting, and hints
|
|
2
|
+
|
|
3
|
+
#### List sorting/selection fixes + date/numeric heuristics
|
|
4
|
+
|
|
5
|
+
- Defers row selection updates to the next tick after click dispatch to avoid selection timing issues.
|
|
6
|
+
- Broadens date detection to treat epoch-millis ranges as dates (in addition to `Date` instances and ISO `...Z` strings).
|
|
7
|
+
- Improves date formatting to accept numeric-string timestamps by coercing before constructing a `Date`.
|
|
8
|
+
- Adds automatic numeric-column detection (samples first 100 rows) when the caller doesn’t specify `numeric`.
|
|
9
|
+
- Coerces finite numeric cell values to floats during sort comparisons for consistent numeric ordering.
|
|
10
|
+
- Avoids a generated CSS class name collision by renaming attribute `"fa"` to `"fa_"` for list columns.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
#### Changes
|
|
14
|
+
|
|
15
|
+
- Adds `Component.debounce(name, ms, fn)` as a shorthand over named timeouts.
|
|
16
|
+
- Extends component lookup to accept a predicate function and walk owners until it matches.
|
|
17
|
+
- Enhances `Control.whenVisible(...)` to accept an optional callback, reuse cached promises, and optionally reject on destroy (`rejectOnDestroy`); default destroy now resolves with `null`.
|
|
18
|
+
- Adds `data/Array` support for filtering out `Source.Pending` items, plus a new `filterPending` property and `setFilterPending(...)`.
|
|
19
|
+
- Reduces toast spam by silencing clipboard debug prints while keeping user feedback toasts.
|
|
20
|
+
- Improves the developer console helpers: adds `tap`, paste helper, and safer/clearer HTML rendering for selection metadata.
|
|
21
|
+
- Adds `Element.hint` (sets DOM `title`) and ensures hints are applied when attributes update.
|
|
22
|
+
- Adjusts `List` click handling to avoid selection timing issues by deferring selection to the next tick.
|
|
23
|
+
- Broadens date detection/formatting in `List` to handle epoch-millis (including numeric strings) in addition to `Date` and ISO Z strings.
|
|
24
|
+
- Improves `List` sorting by auto-detecting numeric columns and coercing finite numeric values before comparing.
|
|
25
|
+
- Adds `List.isNumericColumn(...)` heuristic (samples up to 100 rows) to drive numeric sorting defaults.
|
|
26
|
+
- Hardens `ListColumn.getAttributeClassName()` to avoid CSS-class collisions for attribute `"fa"` by renaming to `"fa_"`.
|
|
27
|
+
- Minor whitespace cleanup in `Application.js` (no functional change).
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
1
31
|
### `2025/10/27` 1.1.90 — List cell formatting & selection handling
|
|
2
32
|
|
|
3
33
|
* Adds `_formatNumbers: true` and `_renderCellTitles: true` defaults in `List` prototype.
|
package/README.md
CHANGED
package/package.json
CHANGED
package/src/Application.js
CHANGED
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
|
|
@@ -16,10 +16,10 @@ const Clipboard = req("util/Clipboard");
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
Clipboard.onPaste.addListener(e => {
|
|
19
|
-
this.print("onPaste", e);
|
|
19
|
+
// this.print("onPaste", e);
|
|
20
20
|
this.toast(js.sf("Pasted %d bytes...", e.length ))});
|
|
21
21
|
Clipboard.onCopy.addListener(e => {
|
|
22
|
-
this.print("onCopy", e);
|
|
22
|
+
// this.print("onCopy", e);
|
|
23
23
|
if(typeof e === "string" && e.length > 150) {
|
|
24
24
|
this.toast(js.sf("Copied %d bytes", e.length ));
|
|
25
25
|
} else {
|
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,16 +78,18 @@ 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
94
|
var props = [], hashAndNameOrUri = (c) => [c.hashCode(), c._name ? "#" + c._name : " " + c._uri].filter(s => s !== "").join("");
|
|
94
95
|
if(value.up()) {
|
|
@@ -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
|
@@ -349,8 +349,8 @@ workaroundColumnAlignment(this);
|
|
|
349
349
|
selection = [component._rowIndex];
|
|
350
350
|
evt.preventDefault();
|
|
351
351
|
}
|
|
352
|
-
this.setSelection(selection);
|
|
353
352
|
this.dispatch("click", evt);
|
|
353
|
+
this.nextTick(() => this.setSelection(selection));
|
|
354
354
|
}
|
|
355
355
|
}
|
|
356
356
|
return this.inherited(arguments);
|
|
@@ -531,11 +531,18 @@ workaroundColumnAlignment(this);
|
|
|
531
531
|
workaroundColumnAlignment(this);
|
|
532
532
|
},
|
|
533
533
|
isDate: function(value) {
|
|
534
|
-
return (value instanceof Date) ||
|
|
535
|
-
value
|
|
534
|
+
return (value instanceof Date) ||
|
|
535
|
+
(value > 946706400000 && value < 1893477600000) ||
|
|
536
|
+
(typeof value === "string" && value.length === 24 && value.endsWith("Z"));
|
|
536
537
|
},
|
|
537
538
|
formatDate: function(value, opts) {
|
|
538
|
-
if(!(value instanceof Date))
|
|
539
|
+
if(!(value instanceof Date)) {
|
|
540
|
+
if(typeof value === "string" && value.match(/^\d+$/)) {
|
|
541
|
+
value = parseInt(value, 10);
|
|
542
|
+
}
|
|
543
|
+
value = new Date(value);
|
|
544
|
+
}
|
|
545
|
+
|
|
539
546
|
if(opts && opts.utc) {
|
|
540
547
|
return js.sf("%d/%02d/%02d %02d:%02d", value.getUTCFullYear(), value.getUTCMonth() + 1,
|
|
541
548
|
value.getUTCDate(), value.getUTCHours(), value.getUTCMinutes());
|
|
@@ -955,6 +962,10 @@ workaroundColumnAlignment(this);
|
|
|
955
962
|
} else if(typeof column === "number") {
|
|
956
963
|
column = this.getColumn(column);
|
|
957
964
|
}
|
|
965
|
+
|
|
966
|
+
if(numeric === undefined) {
|
|
967
|
+
numeric = this.isNumericColumn(column);
|
|
968
|
+
}
|
|
958
969
|
|
|
959
970
|
const sv = column.get("onSortValues") || Array.sortValues;
|
|
960
971
|
this._source.sort((i1, i2) => {
|
|
@@ -964,6 +975,11 @@ workaroundColumnAlignment(this);
|
|
|
964
975
|
i1 = this.valueByColumnAndRow(column, row1);
|
|
965
976
|
i2 = this.valueByColumnAndRow(column, row2);
|
|
966
977
|
|
|
978
|
+
if(numeric) {
|
|
979
|
+
i1 = isFinite(i1) ? parseFloat(i1) : i1;
|
|
980
|
+
i2 = isFinite(i2) ? parseFloat(i2) : i2;
|
|
981
|
+
}
|
|
982
|
+
|
|
967
983
|
return dir * sv(i1, i2);
|
|
968
984
|
});
|
|
969
985
|
// (i1, i2) => {
|
|
@@ -994,6 +1010,13 @@ workaroundColumnAlignment(this);
|
|
|
994
1010
|
// });
|
|
995
1011
|
},
|
|
996
1012
|
|
|
1013
|
+
isNumericColumn(column) {
|
|
1014
|
+
return this._source._array
|
|
1015
|
+
.slice(0, 100)
|
|
1016
|
+
.every((o, i) =>
|
|
1017
|
+
isFinite(this.valueByColumnAndRow(column, i)));
|
|
1018
|
+
},
|
|
1019
|
+
|
|
997
1020
|
hasSelection: function() {
|
|
998
1021
|
return this._selection.length > 0;
|
|
999
1022
|
},
|
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, "-").
|