x4js 2.3.17 → 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 +211 -47
- 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 +352 -176
- package/src/components/input/input.ts +63 -38
- package/src/components/keyboard/keyboard.module.scss +35 -4
- package/src/components/keyboard/keyboard.ts +295 -80
- 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 +167 -132
- package/src/components/switch/switch.ts +2 -1
- package/src/components/tag/tag.ts +1 -2
- package/src/components/textarea/textarea.module.scss +6 -2
- package/src/components/textarea/textarea.ts +50 -11
- package/src/components/textedit/textedit.ts +12 -2
- package/src/core/component.ts +16 -9
- package/src/core/core_application.ts +8 -4
- package/src/core/core_clipboard.ts +239 -0
- package/src/core/core_data.ts +118 -49
- package/src/core/core_dom.ts +36 -6
- package/src/core/core_dragdrop.ts +11 -5
- package/src/core/core_element.ts +1 -1
- package/src/core/core_pdf.ts +696 -46
- package/src/core/core_router.ts +42 -44
- package/src/core/core_shortcuts.ts +1 -1
- package/src/core/core_state.ts +22 -5
- package/src/x4.d.ts +1 -0
- package/src/x4.ts +1 -0
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
|
|
|
@@ -1152,41 +1190,72 @@ export class DataView extends CoreElement<DataViewEventMap>
|
|
|
1152
1190
|
|
|
1153
1191
|
protected m_props: DataViewProps;
|
|
1154
1192
|
|
|
1193
|
+
private m_unlink: ( ) => void; // stops listening to the store
|
|
1194
|
+
|
|
1155
1195
|
constructor( props: DataViewProps ) {
|
|
1156
1196
|
super( );
|
|
1157
1197
|
|
|
1158
1198
|
this.m_props = props;
|
|
1159
1199
|
this.m_store = props.store;
|
|
1160
1200
|
this.m_index = null;
|
|
1161
|
-
this.m_filter = null;
|
|
1162
|
-
this.m_sort = null;
|
|
1163
1201
|
this.m_model = this.m_store.getModel();
|
|
1202
|
+
this.m_filter = props.filter;
|
|
1164
1203
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
}
|
|
1174
|
-
else {
|
|
1175
|
-
this.sort( [props.order] );
|
|
1176
|
-
}
|
|
1204
|
+
if( !props.order ) {
|
|
1205
|
+
this.m_sort = null;
|
|
1206
|
+
}
|
|
1207
|
+
else if( isString(props.order) ) {
|
|
1208
|
+
this.m_sort = [ { field: props.order, ascending: true } ];
|
|
1209
|
+
}
|
|
1210
|
+
else if( isArray(props.order) ) {
|
|
1211
|
+
this.m_sort = props.order;
|
|
1177
1212
|
}
|
|
1178
1213
|
else {
|
|
1179
|
-
this.
|
|
1214
|
+
this.m_sort = [props.order];
|
|
1180
1215
|
}
|
|
1181
1216
|
|
|
1182
|
-
|
|
1217
|
+
// listens to the store, then filters and sorts
|
|
1218
|
+
this.open( );
|
|
1183
1219
|
}
|
|
1184
1220
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1221
|
+
/**
|
|
1222
|
+
* The view follows its store: it is filtered and sorted with what the
|
|
1223
|
+
* store holds now, and again at each of its changes.
|
|
1224
|
+
* Called when the view is made, and to use it again after a close( ).
|
|
1225
|
+
*/
|
|
1226
|
+
|
|
1227
|
+
open( ) {
|
|
1228
|
+
if( this.m_unlink ) {
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
this.m_unlink = this.m_store.addListener( 'data_change', ( ) => this._storeChange( ) );
|
|
1233
|
+
this._storeChange( );
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
/**
|
|
1237
|
+
* The view stops following its store, until open( ) is called.
|
|
1238
|
+
* A store keeps every view that listens to it: without this, a view
|
|
1239
|
+
* that is not used anymore would still be filtered, sorted and heard at
|
|
1240
|
+
* each change.
|
|
1241
|
+
*/
|
|
1242
|
+
|
|
1243
|
+
close( ) {
|
|
1244
|
+
if( this.m_unlink ) {
|
|
1245
|
+
this.m_unlink( );
|
|
1246
|
+
this.m_unlink = null;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
private _storeChange( ) {
|
|
1251
|
+
|
|
1252
|
+
// filtered and sorted again without telling: the event that follows says it all.
|
|
1253
|
+
// _filter sorts when the view is sorted; when it is not, it is by id
|
|
1254
|
+
this._filter( this.m_filter, false );
|
|
1255
|
+
if( !this.m_sort ) {
|
|
1256
|
+
this._sort( null, false );
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1190
1259
|
this.fire( 'view_change', { change_type: 'change' } );
|
|
1191
1260
|
}
|
|
1192
1261
|
|
|
@@ -1206,9 +1275,9 @@ export class DataView extends CoreElement<DataViewEventMap>
|
|
|
1206
1275
|
this.m_index = this.m_store.createIndex( filter );
|
|
1207
1276
|
this.m_filter = filter;
|
|
1208
1277
|
|
|
1209
|
-
// need to sort again:
|
|
1278
|
+
// need to sort again, without telling: the filter is what changed
|
|
1210
1279
|
if( this.m_sort ) {
|
|
1211
|
-
this.
|
|
1280
|
+
this._sort( this.m_sort, false );
|
|
1212
1281
|
}
|
|
1213
1282
|
|
|
1214
1283
|
if( notify ) {
|
package/src/core/core_dom.ts
CHANGED
|
@@ -19,7 +19,7 @@ export const COMPONENT = Symbol( "component" );
|
|
|
19
19
|
/** @ignore this events must be defined on domNode (do not bubble) */
|
|
20
20
|
export const unbubbleEvents = {
|
|
21
21
|
mouseleave: 1, mouseenter: 1, load: 1, unload: 1, scroll: 1, focus: 1, blur: 1, rowexit: 1, beforeunload: 1, stop: 1,
|
|
22
|
-
dragdrop: 1, dragenter: 1, dragexit: 1, draggesture: 1, dragover: 1, contextmenu: 1, created: 2, removed: 2,
|
|
22
|
+
dragdrop: 1, dragenter: 1, dragexit: 1, draggesture: 1, dragover: 1, contextmenu: 1, created: 2, removed: 2, resized: 2
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
export type DOMEventHandler = ( ev: Event ) => void;
|
|
@@ -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
|
}
|
|
@@ -118,8 +126,12 @@ function observeSize(entries: ResizeObserverEntry[]) {
|
|
|
118
126
|
|
|
119
127
|
export function dispatchEvent(ev: Event) {
|
|
120
128
|
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
// 2: an event of x4, for its node only.
|
|
130
|
+
// 1: stops at its node when the browser does not bubble it (focus, mouseenter...)
|
|
131
|
+
const flag = (unbubbleEvents as any)[ev.type];
|
|
132
|
+
const noup = flag===2 || ( flag===1 && !ev.bubbles );
|
|
133
|
+
|
|
134
|
+
let target = ev.target as Node;
|
|
123
135
|
|
|
124
136
|
while (target) {
|
|
125
137
|
const store = event_handlers.get( target );
|
|
@@ -127,8 +139,11 @@ export function dispatchEvent(ev: Event) {
|
|
|
127
139
|
const callback = store[ev.type];
|
|
128
140
|
if( callback ) {
|
|
129
141
|
if( Array.isArray(callback) ) {
|
|
130
|
-
|
|
131
|
-
|
|
142
|
+
// a handler that returns true stops those that follow it on this node.
|
|
143
|
+
// true only: an arrow function returns what its expression does
|
|
144
|
+
// (ev => this.show( ) returns this), and this must stop nothing
|
|
145
|
+
callback.some( c => (c( ev ) as any)===true );
|
|
146
|
+
}
|
|
132
147
|
else {
|
|
133
148
|
callback( ev );
|
|
134
149
|
}
|
|
@@ -255,7 +270,17 @@ export interface GlobalDOMEvents {
|
|
|
255
270
|
* @param ev The mouse event.
|
|
256
271
|
*/
|
|
257
272
|
contextmenu?: (ev: MouseEvent) => any;
|
|
273
|
+
/**
|
|
274
|
+
* Fires when the user copies (the keys, or the menu of the browser).
|
|
275
|
+
* @param ev The clipboard event: see setEventClipboard.
|
|
276
|
+
*/
|
|
277
|
+
copy?: (ev: ClipboardEvent) => any;
|
|
258
278
|
cuechange?: (ev: Event) => any;
|
|
279
|
+
/**
|
|
280
|
+
* Fires when the user cuts.
|
|
281
|
+
* @param ev The clipboard event: see setEventClipboard.
|
|
282
|
+
*/
|
|
283
|
+
cut?: (ev: ClipboardEvent) => any;
|
|
259
284
|
/**
|
|
260
285
|
* Fires when the user double-clicks the object.
|
|
261
286
|
* @param ev The mouse event.
|
|
@@ -391,6 +416,11 @@ export interface GlobalDOMEvents {
|
|
|
391
416
|
* @param ev The event.
|
|
392
417
|
*/
|
|
393
418
|
pause?: (ev: Event) => any;
|
|
419
|
+
/**
|
|
420
|
+
* Fires when the user pastes.
|
|
421
|
+
* @param ev The clipboard event: see getEventClipboard.
|
|
422
|
+
*/
|
|
423
|
+
paste?: (ev: ClipboardEvent) => any;
|
|
394
424
|
/**
|
|
395
425
|
* Occurs when the play method is requested.
|
|
396
426
|
* @param ev The event.
|
|
@@ -19,13 +19,13 @@ import { Point } from './core_tools';
|
|
|
19
19
|
|
|
20
20
|
const x_drag_cb = Symbol( 'x-drag-cb' );
|
|
21
21
|
|
|
22
|
-
interface DropInfo {
|
|
22
|
+
export interface DropInfo {
|
|
23
23
|
pt: Point;
|
|
24
24
|
data: DataTransfer;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
type DropCallback = ( command: 'enter' | 'leave' | 'drag' | 'drop', el: Component, infos: DropInfo ) => void;
|
|
28
|
-
type
|
|
27
|
+
export type DropCallback = ( command: 'enter' | 'leave' | 'drag' | 'drop', el: Component, infos: DropInfo ) => void;
|
|
28
|
+
export type DropFilterCallback = ( el: Component, data: DataTransfer ) => boolean;
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Manages drag and drop operations within the application.
|
|
@@ -78,6 +78,12 @@ class DragManager {
|
|
|
78
78
|
el.addDOMEvent('dragend', (ev: DragEvent) => {
|
|
79
79
|
el.removeClass( 'dragging' );
|
|
80
80
|
this.dragGhost.remove();
|
|
81
|
+
|
|
82
|
+
// the target that was left is told now, while the source is known.
|
|
83
|
+
// Then the source is forgotten: the next drag may be one of files
|
|
84
|
+
// that come from the desktop, which has none
|
|
85
|
+
this._check( );
|
|
86
|
+
this.dragSource = null;
|
|
81
87
|
});
|
|
82
88
|
|
|
83
89
|
el.setAttribute('draggable', "true");
|
|
@@ -86,7 +92,7 @@ class DragManager {
|
|
|
86
92
|
/**
|
|
87
93
|
* Registers a component as a drop target.
|
|
88
94
|
* This sets up DOM event listeners for `dragenter`, `dragover`, `dragleave`, and `drop`.
|
|
89
|
-
* It uses a `DropCallback` to notify the component about drag events and an optional `
|
|
95
|
+
* It uses a `DropCallback` to notify the component about drag events and an optional `DropFilterCallback`
|
|
90
96
|
* to determine if the target should accept the dragged item.
|
|
91
97
|
*
|
|
92
98
|
* @param el - The component to make a drop target.
|
|
@@ -94,7 +100,7 @@ class DragManager {
|
|
|
94
100
|
* @param filterCB - An optional callback to filter which draggable items can be dropped.
|
|
95
101
|
*/
|
|
96
102
|
|
|
97
|
-
registerDropTarget(el: Component, cb: DropCallback, filterCB?:
|
|
103
|
+
registerDropTarget(el: Component, cb: DropCallback, filterCB?: DropFilterCallback ) {
|
|
98
104
|
|
|
99
105
|
const dragEnter = (ev: DragEvent) => {
|
|
100
106
|
if( filterCB && !filterCB(this.dragSource,ev.dataTransfer) ) {
|
package/src/core/core_element.ts
CHANGED
|
@@ -232,7 +232,7 @@ export class CoreElement<E extends EventMap = EventMap> {
|
|
|
232
232
|
return;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
//
|
|
235
|
+
// clear() of an entry does not touch the map, so plain iteration is safe.
|
|
236
236
|
for (const entry of this.#timers.values()) {
|
|
237
237
|
entry.clear();
|
|
238
238
|
}
|