x4js 2.3.18 → 2.3.19
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/aicli.md +3 -3
- package/aicontext.md +49 -23
- package/cli/config.mjs +14 -7
- package/cli/copy-plugin.mjs +8 -0
- package/cli/create.mjs +8 -2
- package/cli/dev.mjs +4 -1
- package/cli/less-plugin.mjs +5 -1
- package/cli/main.mjs +15 -5
- package/cli/rawfile-plugin.mjs +23 -3
- package/cli/templates.mjs +5 -1
- package/package.json +1 -1
- package/src/components/calendar/calendar.module.scss +16 -1
- package/src/components/calendar/calendar.ts +52 -9
- package/src/components/checkbox/checkbox.ts +1 -0
- package/src/components/combobox/combobox.ts +51 -23
- package/src/components/dialog/dialog.ts +4 -1
- package/src/components/form/form.ts +9 -5
- package/src/components/gridview/gridview.ts +2 -1
- package/src/components/input/input.ts +63 -38
- package/src/components/keyboard/keyboard.module.scss +35 -4
- package/src/components/keyboard/keyboard.ts +120 -21
- package/src/components/listbox/listbox.ts +6 -1
- package/src/components/messages/messages.ts +12 -3
- package/src/components/popup/popup.ts +19 -20
- package/src/components/radio/radio.ts +8 -4
- package/src/components/rating/rating.ts +38 -48
- package/src/components/slider/slider.ts +38 -8
- package/src/components/spreadsheet/spreadsheet.ts +140 -131
- package/src/components/switch/switch.ts +2 -1
- package/src/components/textarea/textarea.module.scss +2 -4
- package/src/components/textarea/textarea.ts +2 -1
- package/src/components/textedit/textedit.ts +12 -2
- package/src/core/core_application.ts +8 -4
- package/src/core/core_data.ts +64 -26
- package/src/core/core_dom.ts +9 -1
- package/src/core/core_pdf.ts +696 -46
- package/src/core/core_router.ts +42 -44
- package/src/core/core_state.ts +22 -5
|
@@ -348,6 +348,11 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
|
|
|
348
348
|
this.lock(false);
|
|
349
349
|
});
|
|
350
350
|
|
|
351
|
+
// out of the document: nothing is drawn, as before its first insertion
|
|
352
|
+
this.addDOMEvent("removed", () => {
|
|
353
|
+
this.lock(true);
|
|
354
|
+
});
|
|
355
|
+
|
|
351
356
|
this.addDOMEvent("resized", () => {
|
|
352
357
|
this._updateFlexs();
|
|
353
358
|
this._computeFullSize();
|
|
@@ -358,6 +363,139 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
|
|
|
358
363
|
this._on_key(e);
|
|
359
364
|
})
|
|
360
365
|
|
|
366
|
+
// the handlers of the grid itself are put once, here. _init builds what is
|
|
367
|
+
// inside the grid, each time it comes in the document
|
|
368
|
+
|
|
369
|
+
// WHEEL
|
|
370
|
+
this.addDOMEvent("wheel", (ev: WheelEvent) => {
|
|
371
|
+
// many rows: one pixel of the scroll bar is one row, wherever the pointer is
|
|
372
|
+
if (ev.deltaY && this._store && this._store.getRowCount() >= SCROLL_LIMIT) {
|
|
373
|
+
this._viewport.dom.scrollBy(0, ev.deltaY < 0 ? -1 : 1);
|
|
374
|
+
ev.stopPropagation();
|
|
375
|
+
ev.preventDefault();
|
|
376
|
+
}
|
|
377
|
+
else if (this._has_fixed && ev.deltaY) {
|
|
378
|
+
// wheel on fixed part
|
|
379
|
+
// fixed part do not have scrollbar, so we need to handle it by hand
|
|
380
|
+
let t = ev.target as Node;
|
|
381
|
+
while (t != this.dom) {
|
|
382
|
+
if (t == this._vheader.dom) {
|
|
383
|
+
this._viewport.dom.scrollBy(0, ev.deltaY < 0 ? -this._row_height : this._row_height);
|
|
384
|
+
ev.stopPropagation();
|
|
385
|
+
ev.preventDefault();
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
t = t.parentNode;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
const targetCell = (e: MouseEvent) => {
|
|
395
|
+
let el = e.target as Element;
|
|
396
|
+
while (el && !el.classList.contains("cell")) {
|
|
397
|
+
el = el.parentElement;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (el) {
|
|
401
|
+
const cel = componentFromDOM(el);
|
|
402
|
+
return {
|
|
403
|
+
ref: cel.getIntData("ref"),
|
|
404
|
+
row: cel.getInternalData("row"),
|
|
405
|
+
col: cel.getInternalData("col"),
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return undefined;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
// MOUSEDOWN
|
|
414
|
+
this.addDOMEvent("mousedown", (e) => {
|
|
415
|
+
if( !this._edit || this._inEditor(e) ) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// click on another cell while editing:
|
|
420
|
+
// validate now, rows may be rebuilt before the click event (and the click lost)
|
|
421
|
+
const ref = targetCell(e);
|
|
422
|
+
if( ref && ref.row!==undefined ) {
|
|
423
|
+
this._endEdit( true, true );
|
|
424
|
+
|
|
425
|
+
if (!this._selection.has(ref.ref)) {
|
|
426
|
+
this._clearSelection( false );
|
|
427
|
+
this._addSelection(ref.ref,true);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
e.preventDefault( );
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// CLICK
|
|
435
|
+
this.addDOMEvent("click", (e) => {
|
|
436
|
+
if( this._inEditor(e) ) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const ref = targetCell(e);
|
|
441
|
+
if (ref) {
|
|
442
|
+
//TODO: multiselection
|
|
443
|
+
if (!this._selection.has(ref.ref)) {
|
|
444
|
+
this._clearSelection( false );
|
|
445
|
+
this._addSelection(ref.ref,true);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// DBLCLICK
|
|
451
|
+
this.addDOMEvent("dblclick", (e) => {
|
|
452
|
+
if( this._inEditor(e) ) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const ref = targetCell(e);
|
|
457
|
+
if (ref) {
|
|
458
|
+
//TODO: multiselection
|
|
459
|
+
if (!this._selection.has(ref.ref)) {
|
|
460
|
+
this._clearSelection(false);
|
|
461
|
+
this._addSelection(ref.ref,true);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const dev: EvDblClick = { context: { row: ref.row, col: ref.col } };
|
|
465
|
+
this.fire( "dblClick", dev );
|
|
466
|
+
|
|
467
|
+
// checkboxes are toggled, other cells are edited
|
|
468
|
+
if( !dev.defaultPrevented && !this._toggleCell( ref.row, ref.col ) ) {
|
|
469
|
+
this.editCell( ref.row, ref.col );
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// CONTEXT
|
|
475
|
+
this.addDOMEvent("contextmenu", (e) => {
|
|
476
|
+
// keep the native menu of the editor (copy/paste)
|
|
477
|
+
if( this._inEditor(e) ) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const ref = targetCell(e);
|
|
482
|
+
if (ref) {
|
|
483
|
+
//TODO: multiselection
|
|
484
|
+
if (!this._selection.has(ref.ref)) {
|
|
485
|
+
this._clearSelection( false );
|
|
486
|
+
this._addSelection(ref.ref, true );
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
this.fire( "contextMenu", { uievent: e, context: { row: ref.row, col: ref.col } } );
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
this.fire( "contextMenu", { uievent: e, context: null } );
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
e.preventDefault();
|
|
496
|
+
e.stopPropagation();
|
|
497
|
+
});
|
|
498
|
+
|
|
361
499
|
if (props.store) {
|
|
362
500
|
this.setStore(props.store);
|
|
363
501
|
}
|
|
@@ -643,7 +781,8 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
|
|
|
643
781
|
if (lock) {
|
|
644
782
|
this._lock++;
|
|
645
783
|
}
|
|
646
|
-
|
|
784
|
+
// an unlock too many leaves it at 0: the grid goes on drawing
|
|
785
|
+
else if (this._lock > 0) {
|
|
647
786
|
if (--this._lock == 0 && this._dirty) {
|
|
648
787
|
this._update(true);
|
|
649
788
|
}
|
|
@@ -1121,136 +1260,6 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
|
|
|
1121
1260
|
this._update()
|
|
1122
1261
|
});
|
|
1123
1262
|
|
|
1124
|
-
// WHEEL
|
|
1125
|
-
this.addDOMEvent("wheel", (ev: WheelEvent) => {
|
|
1126
|
-
if (ev.deltaY && this._store && this._store.getRowCount() >= SCROLL_LIMIT) {
|
|
1127
|
-
this._viewport.dom.scrollBy(0, ev.deltaY < 0 ? -1 : 1);
|
|
1128
|
-
ev.stopPropagation();
|
|
1129
|
-
ev.preventDefault();
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
if (this._has_fixed && ev.deltaY) {
|
|
1133
|
-
// wheel on fixed part
|
|
1134
|
-
// fixed part do not have scrollbar, so we need to handle it by hand
|
|
1135
|
-
let t = ev.target as Node;
|
|
1136
|
-
while (t != this.dom) {
|
|
1137
|
-
if (t == this._vheader.dom) {
|
|
1138
|
-
this._viewport.dom.scrollBy(0, ev.deltaY < 0 ? -this._row_height : this._row_height);
|
|
1139
|
-
ev.stopPropagation();
|
|
1140
|
-
ev.preventDefault();
|
|
1141
|
-
break;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
t = t.parentNode;
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
})
|
|
1148
|
-
|
|
1149
|
-
const targetCell = (e: MouseEvent) => {
|
|
1150
|
-
let el = e.target as Element;
|
|
1151
|
-
while (el && !el.classList.contains("cell")) {
|
|
1152
|
-
el = el.parentElement;
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
if (el) {
|
|
1156
|
-
const cel = componentFromDOM(el);
|
|
1157
|
-
return {
|
|
1158
|
-
ref: cel.getIntData("ref"),
|
|
1159
|
-
row: cel.getInternalData("row"),
|
|
1160
|
-
col: cel.getInternalData("col"),
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
return undefined;
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
// MOUSEDOWN
|
|
1169
|
-
this.addDOMEvent("mousedown", (e) => {
|
|
1170
|
-
if( !this._edit || this._inEditor(e) ) {
|
|
1171
|
-
return;
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
// click on another cell while editing:
|
|
1175
|
-
// validate now, rows may be rebuilt before the click event (and the click lost)
|
|
1176
|
-
const ref = targetCell(e);
|
|
1177
|
-
if( ref && ref.row!==undefined ) {
|
|
1178
|
-
this._endEdit( true, true );
|
|
1179
|
-
|
|
1180
|
-
if (!this._selection.has(ref.ref)) {
|
|
1181
|
-
this._clearSelection( false );
|
|
1182
|
-
this._addSelection(ref.ref,true);
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
e.preventDefault( );
|
|
1186
|
-
}
|
|
1187
|
-
});
|
|
1188
|
-
|
|
1189
|
-
// CLICK
|
|
1190
|
-
this.addDOMEvent("click", (e) => {
|
|
1191
|
-
if( this._inEditor(e) ) {
|
|
1192
|
-
return;
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
const ref = targetCell(e);
|
|
1196
|
-
if (ref) {
|
|
1197
|
-
//TODO: multiselection
|
|
1198
|
-
if (!this._selection.has(ref.ref)) {
|
|
1199
|
-
this._clearSelection( false );
|
|
1200
|
-
this._addSelection(ref.ref,true);
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
});
|
|
1204
|
-
|
|
1205
|
-
// DBLCLICK
|
|
1206
|
-
this.addDOMEvent("dblclick", (e) => {
|
|
1207
|
-
if( this._inEditor(e) ) {
|
|
1208
|
-
return;
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
const ref = targetCell(e);
|
|
1212
|
-
if (ref) {
|
|
1213
|
-
//TODO: multiselection
|
|
1214
|
-
if (!this._selection.has(ref.ref)) {
|
|
1215
|
-
this._clearSelection(false);
|
|
1216
|
-
this._addSelection(ref.ref,true);
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
const dev: EvDblClick = { context: { row: ref.row, col: ref.col } };
|
|
1220
|
-
this.fire( "dblClick", dev );
|
|
1221
|
-
|
|
1222
|
-
// checkboxes are toggled, other cells are edited
|
|
1223
|
-
if( !dev.defaultPrevented && !this._toggleCell( ref.row, ref.col ) ) {
|
|
1224
|
-
this.editCell( ref.row, ref.col );
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
});
|
|
1228
|
-
|
|
1229
|
-
// CONTEXT
|
|
1230
|
-
this.addDOMEvent("contextmenu", (e) => {
|
|
1231
|
-
// keep the native menu of the editor (copy/paste)
|
|
1232
|
-
if( this._inEditor(e) ) {
|
|
1233
|
-
return;
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
const ref = targetCell(e);
|
|
1237
|
-
if (ref) {
|
|
1238
|
-
//TODO: multiselection
|
|
1239
|
-
if (!this._selection.has(ref.ref)) {
|
|
1240
|
-
this._clearSelection( false );
|
|
1241
|
-
this._addSelection(ref.ref, true );
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
this.fire( "contextMenu", { uievent: e, context: { row: ref.row, col: ref.col } } );
|
|
1245
|
-
}
|
|
1246
|
-
else {
|
|
1247
|
-
this.fire( "contextMenu", { uievent: e, context: null } );
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
e.preventDefault();
|
|
1251
|
-
e.stopPropagation();
|
|
1252
|
-
});
|
|
1253
|
-
|
|
1254
1263
|
this._updateFlexs();
|
|
1255
1264
|
|
|
1256
1265
|
this._fheader = this._buildColHeader(true);
|
|
@@ -53,7 +53,8 @@ export class Switch extends HBox<SwitchProps> {
|
|
|
53
53
|
new Component( {
|
|
54
54
|
cls: "switch",
|
|
55
55
|
content: [
|
|
56
|
-
|
|
56
|
+
// value: sent by a <form> of the browser when checked; a Form of x4 reads a boolean
|
|
57
|
+
new Input( { type: "checkbox", id: inputId, name: props.name, value: props.value, checked: props.checked } ),
|
|
57
58
|
new Component( { cls: "track" } ),
|
|
58
59
|
new Component( { cls: "thumb" } ),
|
|
59
60
|
]
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
|
|
25
25
|
.x4simpletextarea {
|
|
26
26
|
|
|
27
|
+
flex-shrink: 0; // alone in a box, it keeps its height: the box does not crush it
|
|
28
|
+
resize: none; // no handle unless the prop resize asks for one
|
|
27
29
|
padding: 4px;
|
|
28
30
|
outline: none;
|
|
29
31
|
font: inherit;
|
|
@@ -44,10 +46,6 @@
|
|
|
44
46
|
color: var( --input-placeholder );
|
|
45
47
|
font-style: italic;
|
|
46
48
|
}
|
|
47
|
-
|
|
48
|
-
&[resize=false] {
|
|
49
|
-
resize: none;
|
|
50
|
-
}
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
.x4textarea {
|
|
@@ -63,8 +63,9 @@ export class SimpleTextArea extends Component<SimpleTextAreaProps,TextAreaEvents
|
|
|
63
63
|
this.setText( props.value+'' );
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// none by default (the stylesheet); true: "both", false: "none"
|
|
66
67
|
if( props.resize!==undefined ) {
|
|
67
|
-
this.setStyleValue( "resize", props.resize===false ? "none" : props.resize
|
|
68
|
+
this.setStyleValue( "resize", props.resize===true ? "both" : props.resize===false ? "none" : props.resize );
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
if( props.readonly ) {
|
|
@@ -87,7 +87,9 @@ export class TextEdit<E extends ComponentEvents = ComponentEvents> extends HBox<
|
|
|
87
87
|
private input: Input;
|
|
88
88
|
|
|
89
89
|
constructor( props: TextEditProps ) {
|
|
90
|
-
|
|
90
|
+
// dom_events are for the input (focus, blur, keydown...): not on the box too,
|
|
91
|
+
// where keydown would run a second time, and focus never (it does not bubble)
|
|
92
|
+
super( { ...props, dom_events: undefined } );
|
|
91
93
|
|
|
92
94
|
if( !props.inputId ) {
|
|
93
95
|
props.inputId = makeUniqueComponentId()
|
|
@@ -104,8 +106,16 @@ export class TextEdit<E extends ComponentEvents = ComponentEvents> extends HBox<
|
|
|
104
106
|
|
|
105
107
|
|
|
106
108
|
const iprops: InputProps = { ...props, id: props.inputId, attrs: props.inputAttrs, width: props.inputWidth };
|
|
107
|
-
|
|
109
|
+
|
|
110
|
+
// the props of the box stay on it: given to the input too, a style would be put
|
|
111
|
+
// twice, a tooltip twice, the height of the box on the field...
|
|
108
112
|
delete iprops.cls;
|
|
113
|
+
delete iprops.style;
|
|
114
|
+
delete iprops.tooltip;
|
|
115
|
+
delete iprops.hidden;
|
|
116
|
+
delete iprops.flex;
|
|
117
|
+
delete iprops.stretch;
|
|
118
|
+
delete iprops.height;
|
|
109
119
|
|
|
110
120
|
this.setContent( [
|
|
111
121
|
props.label ? new HBox( { id: "label", width: props.labelWidth, content: [
|
|
@@ -104,8 +104,10 @@ export class Application<E extends ApplicationEvents = ApplicationEvents> extend
|
|
|
104
104
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
105
105
|
main_app = this;
|
|
106
106
|
|
|
107
|
+
this.props = props;
|
|
108
|
+
|
|
107
109
|
const loaded = ( ) => {
|
|
108
|
-
this.mount( props.mountPoint
|
|
110
|
+
this.mount( this.props.mountPoint )
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
if( document.readyState=='complete' ) {
|
|
@@ -141,13 +143,14 @@ export class Application<E extends ApplicationEvents = ApplicationEvents> extend
|
|
|
141
143
|
|
|
142
144
|
/**
|
|
143
145
|
* Sets the main view component for the application.
|
|
144
|
-
* This component will be mounted to the DOM
|
|
146
|
+
* This component will be mounted to the DOM, in `mountPoint`.
|
|
147
|
+
* Called once: each call sets up the keyboard (Tab, shortcuts) again.
|
|
145
148
|
* @param view - The component to set as the main view.
|
|
146
149
|
*/
|
|
147
150
|
|
|
148
151
|
setMainView( view: Component ) {
|
|
149
152
|
this.mainview = view;
|
|
150
|
-
this.mount( );
|
|
153
|
+
this.mount( this.props.mountPoint );
|
|
151
154
|
this._setupKeyboard( );
|
|
152
155
|
}
|
|
153
156
|
|
|
@@ -321,7 +324,8 @@ export class Application<E extends ApplicationEvents = ApplicationEvents> extend
|
|
|
321
324
|
return;
|
|
322
325
|
}
|
|
323
326
|
|
|
324
|
-
|
|
327
|
+
// send throws while the socket is connecting
|
|
328
|
+
if( msg_socket && opened ) {
|
|
325
329
|
msg_socket.send( JSON.stringify( {
|
|
326
330
|
msg: e.msg,
|
|
327
331
|
params: e.params,
|
package/src/core/core_data.ts
CHANGED
|
@@ -91,11 +91,8 @@ function _getMetas( obj: object, create = true ) : MetaInfos {
|
|
|
91
91
|
mfld = Object.prototype.hasOwnProperty.call(ctor,metaFields) ? ctor[metaFields] : undefined;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// a class without decorators of its own comes here too: its metas are made from its parent's
|
|
94
95
|
if( mfld===undefined ) {
|
|
95
|
-
if( !create && ctor!=DataModel ) {
|
|
96
|
-
console.assert( mfld!==undefined );
|
|
97
|
-
}
|
|
98
|
-
|
|
99
96
|
// construct our metas
|
|
100
97
|
mfld = new MetaInfos( ctor.name );
|
|
101
98
|
|
|
@@ -105,8 +102,6 @@ function _getMetas( obj: object, create = true ) : MetaInfos {
|
|
|
105
102
|
if( pctor!=DataModel ) {
|
|
106
103
|
let pmetas = pctor[metaFields];
|
|
107
104
|
mfld.fields = [...pmetas.fields, ...mfld.fields ]
|
|
108
|
-
|
|
109
|
-
console.assert( mfld.id===undefined, 'cannot define mutiple record id' );
|
|
110
105
|
if( !mfld.id ) {
|
|
111
106
|
mfld.id = pmetas.id;
|
|
112
107
|
}
|
|
@@ -143,6 +138,7 @@ export namespace data {
|
|
|
143
138
|
required: true,
|
|
144
139
|
});
|
|
145
140
|
|
|
141
|
+
console.assert( !metas.id, 'cannot define multiple record id' ); // the parent class has one already
|
|
146
142
|
metas.id = fldName;
|
|
147
143
|
}
|
|
148
144
|
}
|
|
@@ -371,7 +367,7 @@ export class DataModel<T = any> {
|
|
|
371
367
|
});
|
|
372
368
|
|
|
373
369
|
if( id!==undefined ) {
|
|
374
|
-
rec[
|
|
370
|
+
rec[_getMetas( this, false ).id] = id; // the field of the id, as getID reads it
|
|
375
371
|
}
|
|
376
372
|
else {
|
|
377
373
|
console.assert( this.getID(rec)!==undefined ); // store do not have ID field
|
|
@@ -517,6 +513,10 @@ export interface DataProxyProps {
|
|
|
517
513
|
solver?: DataSolver;
|
|
518
514
|
}
|
|
519
515
|
|
|
516
|
+
/**
|
|
517
|
+
* @deprecated the loading from an url is to be reviewed
|
|
518
|
+
*/
|
|
519
|
+
|
|
520
520
|
export class DataProxy extends CoreElement<DataEventMap> {
|
|
521
521
|
|
|
522
522
|
protected m_props: DataProxyProps;
|
|
@@ -561,8 +561,11 @@ export class DataProxy extends CoreElement<DataEventMap> {
|
|
|
561
561
|
interface DataStoreProps {
|
|
562
562
|
model: DataModel;
|
|
563
563
|
data?: any[];
|
|
564
|
+
/** @deprecated the loading from an url is to be reviewed: load the data, then setData */
|
|
564
565
|
url?: string;
|
|
566
|
+
/** @deprecated see url */
|
|
565
567
|
autoload?: false;
|
|
568
|
+
/** @deprecated see url */
|
|
566
569
|
solver?: DataSolver;
|
|
567
570
|
}
|
|
568
571
|
|
|
@@ -615,14 +618,15 @@ export class DataStore<T = any> extends EventSource<DataStoreEventMap> {
|
|
|
615
618
|
}
|
|
616
619
|
|
|
617
620
|
/**
|
|
618
|
-
*
|
|
619
|
-
*
|
|
621
|
+
* @deprecated the loading from an url is to be reviewed: load the data, then setData.
|
|
622
|
+
* Only for a store created with url and no data; an HTTP error is not reported
|
|
620
623
|
*/
|
|
621
624
|
|
|
622
625
|
async load( url?: string ) {
|
|
623
626
|
return this.m_proxy.load( url );
|
|
624
627
|
}
|
|
625
628
|
|
|
629
|
+
/** @deprecated see load */
|
|
626
630
|
async reload( ) {
|
|
627
631
|
return this.m_proxy.load( );
|
|
628
632
|
}
|
|
@@ -813,8 +817,13 @@ export class DataStore<T = any> extends EventSource<DataStoreEventMap> {
|
|
|
813
817
|
return this.m_records[index] ?? null;
|
|
814
818
|
}
|
|
815
819
|
|
|
820
|
+
/**
|
|
821
|
+
* gives the records to other: this store is emptied, they do not share them
|
|
822
|
+
*/
|
|
823
|
+
|
|
816
824
|
public moveTo( other: DataStore ) {
|
|
817
825
|
other.setRawData( this.m_records );
|
|
826
|
+
this.setRawData( [] );
|
|
818
827
|
}
|
|
819
828
|
|
|
820
829
|
/**
|
|
@@ -871,32 +880,32 @@ export class DataStore<T = any> extends EventSource<DataStoreEventMap> {
|
|
|
871
880
|
return new Uint32Array(0);
|
|
872
881
|
}
|
|
873
882
|
|
|
874
|
-
let filterValue = filter.value;
|
|
883
|
+
let filterValue: any = filter.value;
|
|
875
884
|
if( isString(filterValue) && !filter.caseSensitive ) {
|
|
876
885
|
filterValue = filterValue.toUpperCase( );
|
|
877
886
|
}
|
|
878
|
-
|
|
879
|
-
const _lt = ( recval:
|
|
887
|
+
|
|
888
|
+
const _lt = ( recval: any ) : boolean => {
|
|
880
889
|
return recval < filterValue;
|
|
881
890
|
}
|
|
882
891
|
|
|
883
|
-
const _le = ( recval:
|
|
892
|
+
const _le = ( recval: any ) : boolean => {
|
|
884
893
|
return recval <= filterValue;
|
|
885
894
|
}
|
|
886
895
|
|
|
887
|
-
const _eq = ( recval:
|
|
896
|
+
const _eq = ( recval: any ) : boolean => {
|
|
888
897
|
return recval == filterValue;
|
|
889
898
|
}
|
|
890
899
|
|
|
891
|
-
const _neq = ( recval:
|
|
900
|
+
const _neq = ( recval: any ) : boolean => {
|
|
892
901
|
return recval != filterValue;
|
|
893
902
|
}
|
|
894
903
|
|
|
895
|
-
const _ge = ( recval:
|
|
904
|
+
const _ge = ( recval: any ) : boolean => {
|
|
896
905
|
return recval >= filterValue;
|
|
897
906
|
}
|
|
898
907
|
|
|
899
|
-
const _gt = ( recval:
|
|
908
|
+
const _gt = ( recval: any ) : boolean => {
|
|
900
909
|
return recval > filterValue;
|
|
901
910
|
}
|
|
902
911
|
|
|
@@ -905,9 +914,10 @@ export class DataStore<T = any> extends EventSource<DataStoreEventMap> {
|
|
|
905
914
|
return filterRe.test( recval );
|
|
906
915
|
}
|
|
907
916
|
|
|
908
|
-
let filterFn: ( rec:
|
|
917
|
+
let filterFn: ( rec: any ) => boolean; // filter fn
|
|
909
918
|
let filterRe: RegExp; // if fielter is regexp
|
|
910
919
|
if( filterValue instanceof RegExp ) {
|
|
920
|
+
// tested on the field as it is: the regexp says if the case counts (flag i)
|
|
911
921
|
filterRe = filterValue;
|
|
912
922
|
filterFn = _re;
|
|
913
923
|
}
|
|
@@ -921,7 +931,14 @@ export class DataStore<T = any> extends EventSource<DataStoreEventMap> {
|
|
|
921
931
|
case '<>': { filterFn = _neq; break; }
|
|
922
932
|
}
|
|
923
933
|
}
|
|
924
|
-
|
|
934
|
+
|
|
935
|
+
// int, float, date: compared as numbers, a date by its time
|
|
936
|
+
const type = this.m_fields[filterFld].type;
|
|
937
|
+
const numeric = !filterRe && (type==='int' || type==='float' || type==='date');
|
|
938
|
+
if( numeric ) {
|
|
939
|
+
filterValue = type==='date' ? new Date( filter.value as any ).getTime( ) : Number( filter.value );
|
|
940
|
+
}
|
|
941
|
+
|
|
925
942
|
// scan all records and append only interesting ones
|
|
926
943
|
const m = this.m_model;
|
|
927
944
|
|
|
@@ -933,12 +950,16 @@ export class DataStore<T = any> extends EventSource<DataStoreEventMap> {
|
|
|
933
950
|
}
|
|
934
951
|
|
|
935
952
|
let field = m.getRaw( filterFld, rec );
|
|
936
|
-
if(
|
|
953
|
+
if( numeric ) {
|
|
954
|
+
// +: a Date gives its time, without a new object. A null field: NaN, every comparison is false but <>
|
|
955
|
+
field = field===null || field===undefined ? NaN : +field;
|
|
956
|
+
}
|
|
957
|
+
else if( field===null || field===undefined ) {
|
|
937
958
|
field = '';
|
|
938
959
|
}
|
|
939
960
|
else {
|
|
940
961
|
field = ''+field;
|
|
941
|
-
if( !filter.caseSensitive ) {
|
|
962
|
+
if( !filter.caseSensitive && !filterRe ) {
|
|
942
963
|
field = field.toUpperCase( );
|
|
943
964
|
}
|
|
944
965
|
}
|
|
@@ -960,14 +981,17 @@ export class DataStore<T = any> extends EventSource<DataStoreEventMap> {
|
|
|
960
981
|
fidx: number,
|
|
961
982
|
asc: boolean,
|
|
962
983
|
cb: SortCallback,
|
|
984
|
+
numeric: boolean,
|
|
963
985
|
}
|
|
964
986
|
|
|
965
987
|
let bads = 0; // unknown fields
|
|
966
988
|
let fidxs: sort_info[] = []; // fields indexes
|
|
967
989
|
|
|
968
|
-
// if no fields are given, reset sort by id
|
|
990
|
+
// if no fields are given, reset sort by id: the field marked by data.id( ),
|
|
991
|
+
// which is not always the first one, and the order in which indexOfId searches
|
|
969
992
|
if ( sort===null ) {
|
|
970
|
-
|
|
993
|
+
const id = _getMetas( this.m_model, false ).id;
|
|
994
|
+
fidxs.push( { fidx: this.m_model.getFieldIndex( id ), asc: true, cb: null, numeric: false } );
|
|
971
995
|
}
|
|
972
996
|
else {
|
|
973
997
|
fidxs = sort.map( (si) => {
|
|
@@ -978,7 +1002,7 @@ export class DataStore<T = any> extends EventSource<DataStoreEventMap> {
|
|
|
978
1002
|
bads++;
|
|
979
1003
|
}
|
|
980
1004
|
|
|
981
|
-
return { fidx: fi, asc: si.ascending, cb: si.callback };
|
|
1005
|
+
return { fidx: fi, asc: si.ascending, cb: si.callback, numeric: si.numeric };
|
|
982
1006
|
});
|
|
983
1007
|
}
|
|
984
1008
|
|
|
@@ -994,6 +1018,7 @@ export class DataStore<T = any> extends EventSource<DataStoreEventMap> {
|
|
|
994
1018
|
|
|
995
1019
|
const field = fidxs[0].fidx;
|
|
996
1020
|
const cb = fidxs[0].cb;
|
|
1021
|
+
const numeric = fidxs[0].numeric;
|
|
997
1022
|
|
|
998
1023
|
index.sort( ( ia, ib ) => {
|
|
999
1024
|
|
|
@@ -1004,6 +1029,10 @@ export class DataStore<T = any> extends EventSource<DataStoreEventMap> {
|
|
|
1004
1029
|
return cb( va, vb );
|
|
1005
1030
|
}
|
|
1006
1031
|
|
|
1032
|
+
if( numeric ) {
|
|
1033
|
+
return Number( va ) - Number( vb );
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1007
1036
|
if (va > vb) { return 1; }
|
|
1008
1037
|
if (va < vb) { return -1; }
|
|
1009
1038
|
return 0;
|
|
@@ -1024,8 +1053,17 @@ export class DataStore<T = any> extends EventSource<DataStoreEventMap> {
|
|
|
1024
1053
|
let va = m.getRaw( fidx, this.getByIndex(ia) ) ?? '';
|
|
1025
1054
|
let vb = m.getRaw( fidx, this.getByIndex(ib) ) ?? '';
|
|
1026
1055
|
|
|
1056
|
+
// a field that finds them equal gives the hand to the next one
|
|
1027
1057
|
if( fi.cb ) {
|
|
1028
|
-
|
|
1058
|
+
const r = fi.cb( va, vb );
|
|
1059
|
+
if( r ) { return r*mul; }
|
|
1060
|
+
continue;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if( fi.numeric ) {
|
|
1064
|
+
const r = Number( va ) - Number( vb );
|
|
1065
|
+
if( r ) { return r*mul; } // NaN, not a number: equal
|
|
1066
|
+
continue;
|
|
1029
1067
|
}
|
|
1030
1068
|
|
|
1031
1069
|
if (va > vb) { return mul; }
|
|
@@ -1121,7 +1159,7 @@ export type FilterFunc = ( rec: DataRecord ) => boolean;
|
|
|
1121
1159
|
export interface FilterInfo {
|
|
1122
1160
|
op: '<' | '<=' | '=' | '>=' | '>' | '<>' | 'empty-result' | FilterFunc, // emptydb mean return an empty result always
|
|
1123
1161
|
field?: string;
|
|
1124
|
-
value?: string | RegExp; // if regexp then operator is =
|
|
1162
|
+
value?: string | number | RegExp; // if regexp then operator is =
|
|
1125
1163
|
caseSensitive?: boolean;
|
|
1126
1164
|
}
|
|
1127
1165
|
|
package/src/core/core_dom.ts
CHANGED
|
@@ -38,12 +38,14 @@ const observeMutation = (mutations: MutationRecord[], observer: MutationObserver
|
|
|
38
38
|
// moves are handled this way
|
|
39
39
|
const added = new Set<Node>( );
|
|
40
40
|
const removed = new Set<Node>( );
|
|
41
|
+
const moved = new Set<Node>( ); // out of their place and back in the document in the same task
|
|
41
42
|
|
|
42
43
|
for( const mutation of mutations ) {
|
|
43
44
|
if( mutation.type == "childList" ) {
|
|
44
45
|
mutation.addedNodes.forEach( n => {
|
|
45
46
|
if( removed.has( n ) ) {
|
|
46
|
-
removed.delete( n );
|
|
47
|
+
removed.delete( n );
|
|
48
|
+
moved.add( n );
|
|
47
49
|
}
|
|
48
50
|
else {
|
|
49
51
|
added.add( n );
|
|
@@ -70,6 +72,12 @@ const observeMutation = (mutations: MutationRecord[], observer: MutationObserver
|
|
|
70
72
|
|
|
71
73
|
const notify = ( node: Node, create: boolean ) => {
|
|
72
74
|
|
|
75
|
+
// a node that was only moved was created before, as all that it holds,
|
|
76
|
+
// even when it is moved into a node that is new
|
|
77
|
+
if( create && moved.has( node ) ) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
73
81
|
if( create ) {
|
|
74
82
|
sendEvent( node, "created" );
|
|
75
83
|
}
|