x4js 2.0.26 → 2.0.30
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/.vscode/launch.json +14 -0
- package/.vscode/settings.json +2 -0
- package/ai-comments.txt +97 -0
- package/demo/assets/house-light.svg +1 -0
- package/demo/assets/radio.svg +4 -0
- package/demo/index.html +12 -0
- package/demo/main.scss +23 -0
- package/demo/main.ts +324 -0
- package/demo/package.json +26 -0
- package/demo/scss.d.ts +4 -0
- package/demo/svg.d.ts +1 -0
- package/demo/tsconfig.json +14 -0
- package/lib/types/x4js.d.ts +0 -2374
- package/package.json +23 -47
- package/prepack.mjs +3 -0
- package/scripts/prepack.mjs +342 -0
- package/src/colors.scss +246 -0
- package/src/components/boxes/boxes.module.scss +1 -1
- package/src/components/boxes/boxes.ts +139 -28
- package/src/components/button/button.ts +80 -33
- package/src/components/combobox/combobox.ts +1 -1
- package/src/components/dialog/dialog.ts +4 -0
- package/src/components/gauge/gauge.module.scss +3 -0
- package/src/components/gauge/gauge.ts +1 -1
- package/src/components/gridview/gridview.ts +106 -8
- package/src/components/icon/icon.ts +42 -14
- package/src/components/input/input.ts +155 -76
- package/src/components/keyboard/keyboard.module.scss +1 -1
- package/src/components/keyboard/keyboard.ts +31 -9
- package/src/components/label/label.module.scss +9 -0
- package/src/components/label/label.ts +10 -6
- package/src/components/link/link.module.scss +44 -0
- package/src/components/link/link.ts +7 -1
- package/src/components/listbox/listbox.module.scss +18 -4
- package/src/components/listbox/listbox.ts +34 -15
- package/src/components/menu/menu.module.scss +14 -2
- package/src/components/menu/menu.ts +1 -1
- package/src/components/messages/messages.ts +13 -5
- package/src/components/panel/panel.module.scss +7 -0
- package/src/components/popup/popup.ts +14 -10
- package/src/components/propgrid/propgrid.ts +13 -3
- package/src/components/shared.scss +4 -0
- package/src/components/spreadsheet/spreadsheet.module.scss +308 -0
- package/src/components/spreadsheet/spreadsheet.ts +1223 -0
- package/src/components/tabs/tabs.module.scss +1 -0
- package/src/components/textarea/textarea.ts +8 -2
- package/src/components/textedit/textedit.ts +7 -0
- package/src/components/themes.scss +2 -0
- package/src/components/tooltips/tooltips.ts +15 -3
- package/src/core/component.ts +358 -162
- package/src/core/core_application.ts +129 -32
- package/src/core/core_colors.ts +382 -119
- package/src/core/core_data.ts +73 -86
- package/src/core/core_dom.ts +10 -0
- package/src/core/core_dragdrop.ts +32 -7
- package/src/core/core_element.ts +111 -4
- package/src/core/core_events.ts +48 -11
- package/src/core/core_i18n.ts +2 -0
- package/src/core/core_pdf.ts +454 -0
- package/src/core/core_router.ts +64 -5
- package/src/core/core_state.ts +1 -0
- package/src/core/core_styles.ts +11 -12
- package/src/core/core_svg.ts +348 -51
- package/src/core/core_tools.ts +105 -17
- package/src/x4.ts +1 -0
- package/src/x4tsx.d.ts +2 -1
- package/tsconfig.json +11 -0
- package/lib/README.txt +0 -20
- package/lib/cjs/x4.css +0 -1
- package/lib/cjs/x4.js +0 -2
- package/lib/esm/x4.css +0 -1
- package/lib/esm/x4.mjs +0 -2
- package/lib/styles/x4.css +0 -1
package/src/core/core_data.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { EvChange } from './component';
|
|
19
19
|
import { CoreElement } from './core_element';
|
|
20
|
-
import { CoreEvent,
|
|
20
|
+
import { CoreEvent, EventMap, EventSource } from './core_events';
|
|
21
21
|
import { isArray, isString } from './core_tools';
|
|
22
22
|
|
|
23
23
|
export type DataRecordID = any;
|
|
@@ -80,24 +80,26 @@ function _getMetas( obj: object, create = true ) : MetaInfos {
|
|
|
80
80
|
let mfld = Object.prototype.hasOwnProperty.call(ctor,metaFields) ? ctor[metaFields] : undefined;
|
|
81
81
|
|
|
82
82
|
if( mfld===undefined ) {
|
|
83
|
-
if( !create ) {
|
|
83
|
+
if( !create && ctor!=DataModel ) {
|
|
84
84
|
console.assert( mfld!==undefined );
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
// construct our metas
|
|
88
88
|
mfld = new MetaInfos( ctor.name );
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
90
|
+
if( ctor!=DataModel ) { //<eco: allow addFields on DataModel
|
|
91
|
+
// merge with parent class metas
|
|
92
|
+
let pctor = Object.getPrototypeOf(ctor);
|
|
93
|
+
if( pctor!=DataModel ) {
|
|
94
|
+
let pmetas = pctor[metaFields];
|
|
95
|
+
mfld.fields = [...pmetas.fields, ...mfld.fields ]
|
|
96
|
+
|
|
97
|
+
console.assert( mfld.id===undefined, 'cannot define mutiple record id' );
|
|
98
|
+
if( !mfld.id ) {
|
|
99
|
+
mfld.id = pmetas.id;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
101
103
|
|
|
102
104
|
(obj.constructor as any)[metaFields] = mfld;
|
|
103
105
|
}
|
|
@@ -249,7 +251,24 @@ export namespace data {
|
|
|
249
251
|
* record model
|
|
250
252
|
*/
|
|
251
253
|
|
|
252
|
-
export class DataModel {
|
|
254
|
+
export class DataModel<T = any> {
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* dynamic DataModel
|
|
258
|
+
*/
|
|
259
|
+
|
|
260
|
+
addField( ...fields: FieldInfo[] ) {
|
|
261
|
+
if( fields.length==0 ) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
let metas = _getMetas( this, false );
|
|
266
|
+
if( metas.fields.length==0 ) {
|
|
267
|
+
metas.id = fields[0].name;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
metas.fields.push( ...fields );
|
|
271
|
+
}
|
|
253
272
|
|
|
254
273
|
/**
|
|
255
274
|
* MUST IMPLEMENT
|
|
@@ -309,8 +328,6 @@ export class DataModel {
|
|
|
309
328
|
|
|
310
329
|
return rec as T;
|
|
311
330
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
331
|
|
|
315
332
|
/**
|
|
316
333
|
* default unserializer
|
|
@@ -318,10 +335,10 @@ export class DataModel {
|
|
|
318
335
|
* @returns a new Record
|
|
319
336
|
*/
|
|
320
337
|
|
|
321
|
-
unSerialize(data: any, id?: DataRecordID ) : DataRecord {
|
|
338
|
+
unSerialize(data: any, id?: DataRecordID ) : DataRecord<T> {
|
|
322
339
|
|
|
323
340
|
const fields = this.getFields();
|
|
324
|
-
const rec =
|
|
341
|
+
const rec: any = {};
|
|
325
342
|
|
|
326
343
|
fields.forEach( (sf) => {
|
|
327
344
|
let value = data[sf.name];
|
|
@@ -457,63 +474,9 @@ export class DataModel {
|
|
|
457
474
|
*
|
|
458
475
|
*/
|
|
459
476
|
|
|
460
|
-
export
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
/*
|
|
464
|
-
/ **
|
|
465
|
-
* @returns fields descriptors
|
|
466
|
-
* /
|
|
467
|
-
|
|
468
|
-
getFields(): FieldInfo[] {
|
|
469
|
-
let metas = _getMetas( this, false );
|
|
470
|
-
return metas.fields;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
/ **
|
|
476
|
-
*
|
|
477
|
-
* @param name
|
|
478
|
-
* @param data
|
|
479
|
-
* /
|
|
480
|
-
|
|
481
|
-
setRaw( name: string, data: string ) {
|
|
482
|
-
this[name] = data;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
/ **
|
|
488
|
-
* set field value
|
|
489
|
-
* @param name - field name
|
|
490
|
-
* @param value - value to set
|
|
491
|
-
* @example
|
|
492
|
-
* record.set( 'field1', 7 );
|
|
493
|
-
* /
|
|
494
|
-
|
|
495
|
-
setField(name: string, value: any) {
|
|
496
|
-
let fields = this.getFields( );
|
|
497
|
-
let idx = fields.findIndex( fi => fi.name == name );
|
|
498
|
-
|
|
499
|
-
if( idx < 0 ) {
|
|
500
|
-
console.assert( false, 'unknown field: '+name);
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
let fld = fields[idx];
|
|
505
|
-
if( fld.calc!==undefined ) {
|
|
506
|
-
console.assert( false, 'cannot set calc field: '+name);
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
this.setRaw( fld.name, value );
|
|
511
|
-
}
|
|
512
|
-
*/
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
516
|
-
|
|
477
|
+
export type DataRecord<T = any> = Partial<T> & {
|
|
478
|
+
[key: string]: any;
|
|
479
|
+
};
|
|
517
480
|
|
|
518
481
|
/**
|
|
519
482
|
*
|
|
@@ -591,11 +554,11 @@ interface DataStoreEventMap extends EventMap {
|
|
|
591
554
|
*
|
|
592
555
|
*/
|
|
593
556
|
|
|
594
|
-
export class DataStore extends EventSource<DataStoreEventMap> {
|
|
557
|
+
export class DataStore<T = any> extends EventSource<DataStoreEventMap> {
|
|
595
558
|
|
|
596
|
-
protected m_model: DataModel
|
|
559
|
+
protected m_model: DataModel<T>;
|
|
597
560
|
protected m_fields: FieldInfo[];
|
|
598
|
-
protected m_records: DataRecord[];
|
|
561
|
+
protected m_records: DataRecord<T>[];
|
|
599
562
|
|
|
600
563
|
protected m_proxy: DataProxy;
|
|
601
564
|
protected m_rec_index: DataIndex;
|
|
@@ -648,7 +611,7 @@ export class DataStore extends EventSource<DataStoreEventMap> {
|
|
|
648
611
|
|
|
649
612
|
public setData( records: any[] ) {
|
|
650
613
|
|
|
651
|
-
const realRecords: DataRecord[] = new Array( records.length );
|
|
614
|
+
const realRecords: DataRecord<T>[] = new Array( records.length );
|
|
652
615
|
|
|
653
616
|
records.forEach( (rec,idx) => {
|
|
654
617
|
realRecords[idx] = this.m_model.unSerialize(rec);
|
|
@@ -697,11 +660,11 @@ export class DataStore extends EventSource<DataStoreEventMap> {
|
|
|
697
660
|
* @param data
|
|
698
661
|
*/
|
|
699
662
|
|
|
700
|
-
public
|
|
663
|
+
public appendRaw( rec: T ) {
|
|
664
|
+
return this.append( this.m_model.unSerialize( rec ) );
|
|
665
|
+
}
|
|
701
666
|
|
|
702
|
-
|
|
703
|
-
rec = this.m_model.unSerialize( rec );
|
|
704
|
-
}
|
|
667
|
+
public append( rec: DataRecord ) {
|
|
705
668
|
|
|
706
669
|
const id = this.m_model.getID(rec);
|
|
707
670
|
console.assert( id!==undefined );
|
|
@@ -797,12 +760,13 @@ export class DataStore extends EventSource<DataStoreEventMap> {
|
|
|
797
760
|
return -1;
|
|
798
761
|
}
|
|
799
762
|
|
|
763
|
+
|
|
800
764
|
/**
|
|
801
765
|
* return the record by it's id
|
|
802
766
|
* @returns record or null
|
|
803
767
|
*/
|
|
804
768
|
|
|
805
|
-
public getById(id: DataRecordID): DataRecord {
|
|
769
|
+
public getById(id: DataRecordID): DataRecord<T> {
|
|
806
770
|
let idx = this.indexOfId( id );
|
|
807
771
|
if( idx<0 ) {
|
|
808
772
|
return null;
|
|
@@ -817,12 +781,12 @@ export class DataStore extends EventSource<DataStoreEventMap> {
|
|
|
817
781
|
* @returns record or null
|
|
818
782
|
*/
|
|
819
783
|
|
|
820
|
-
public getByIndex( index: number ): DataRecord {
|
|
784
|
+
public getByIndex( index: number ): DataRecord<T> {
|
|
821
785
|
let idx = this.m_rec_index[index];
|
|
822
786
|
return this._getRecord( idx );
|
|
823
787
|
}
|
|
824
788
|
|
|
825
|
-
private _getRecord( index: number ) : DataRecord {
|
|
789
|
+
private _getRecord( index: number ) : DataRecord<T> {
|
|
826
790
|
return this.m_records[index] ?? null;
|
|
827
791
|
}
|
|
828
792
|
|
|
@@ -1065,6 +1029,29 @@ export class DataStore extends EventSource<DataStoreEventMap> {
|
|
|
1065
1029
|
}
|
|
1066
1030
|
}
|
|
1067
1031
|
|
|
1032
|
+
find( cb: ( rec: DataRecord ) => boolean ) {
|
|
1033
|
+
let result;
|
|
1034
|
+
|
|
1035
|
+
if( this.m_rec_index ) {
|
|
1036
|
+
result = this.m_rec_index.find( ri => {
|
|
1037
|
+
if( cb( this.m_records[ri] ) ) {
|
|
1038
|
+
return true;
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
else {
|
|
1043
|
+
result = this.m_records.findIndex( rec => {
|
|
1044
|
+
if( rec ) {
|
|
1045
|
+
if( cb( rec ) ) {
|
|
1046
|
+
return true;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
} );
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
return result>=0 ? this.getByIndex(result) : null;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1068
1055
|
export( ) {
|
|
1069
1056
|
return this.m_records;
|
|
1070
1057
|
}
|
package/src/core/core_dom.ts
CHANGED
|
@@ -93,7 +93,10 @@ function observeSize(entries: ResizeObserverEntry[]) {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
|
+
* Manually dispatches an event through the custom event system.
|
|
97
|
+
* Simulates bubbling unless the event type is in the unbubbleEvents list.
|
|
96
98
|
*
|
|
99
|
+
* @param ev - The event to dispatch.
|
|
97
100
|
*/
|
|
98
101
|
|
|
99
102
|
export function dispatchEvent(ev: Event) {
|
|
@@ -129,7 +132,14 @@ export function dispatchEvent(ev: Event) {
|
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
/**
|
|
135
|
+
* Registers an event handler for a specific node.
|
|
136
|
+
* Automatically initializes MutationObserver or ResizeObserver if special events
|
|
137
|
+
* ('created', 'removed', 'resized') are requested.
|
|
132
138
|
*
|
|
139
|
+
* @param node - The target DOM node.
|
|
140
|
+
* @param name - The name of the event to listen for.
|
|
141
|
+
* @param handler - The function to call when the event occurs.
|
|
142
|
+
* @param prepend - Whether to prepend the handler to the list (default: false).
|
|
133
143
|
*/
|
|
134
144
|
|
|
135
145
|
export function addEvent( node: Node, name: string, handler: DOMEventHandler, prepend = false ) {
|
|
@@ -27,8 +27,10 @@ interface DropInfo {
|
|
|
27
27
|
type DropCallback = ( command: 'enter' | 'leave' | 'drag' | 'drop', el: Component, infos: DropInfo ) => void;
|
|
28
28
|
type FilterCallback = ( el: Component, data: DataTransfer ) => boolean;
|
|
29
29
|
|
|
30
|
-
/**
|
|
31
|
-
*
|
|
30
|
+
/**
|
|
31
|
+
* Manages drag and drop operations within the application.
|
|
32
|
+
* This class handles the registration of draggable elements and drop targets,
|
|
33
|
+
* and orchestrates the visual feedback and event dispatching during drag operations.
|
|
32
34
|
*/
|
|
33
35
|
|
|
34
36
|
|
|
@@ -43,9 +45,13 @@ class DragManager {
|
|
|
43
45
|
timer: any; // pb with name of settimeout return
|
|
44
46
|
|
|
45
47
|
/**
|
|
46
|
-
*
|
|
48
|
+
* Registers a component as a draggable element.
|
|
49
|
+
* This sets up DOM event listeners for `dragstart`, `drag`, and `dragend`
|
|
50
|
+
* to manage the drag operation, including creating a drag ghost and applying CSS classes.
|
|
51
|
+
*
|
|
52
|
+
* @param el - The component to make draggable.
|
|
47
53
|
*/
|
|
48
|
-
|
|
54
|
+
// TODO: Add support for custom drag data beyond 'text/string'.
|
|
49
55
|
registerDraggableElement(el: Component) {
|
|
50
56
|
|
|
51
57
|
el.addDOMEvent('dragstart', (ev: DragEvent) => {
|
|
@@ -78,7 +84,14 @@ class DragManager {
|
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
/**
|
|
81
|
-
*
|
|
87
|
+
* Registers a component as a drop target.
|
|
88
|
+
* 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 `FilterCallback`
|
|
90
|
+
* to determine if the target should accept the dragged item.
|
|
91
|
+
*
|
|
92
|
+
* @param el - The component to make a drop target.
|
|
93
|
+
* @param cb - The callback function to execute on drag events.
|
|
94
|
+
* @param filterCB - An optional callback to filter which draggable items can be dropped.
|
|
82
95
|
*/
|
|
83
96
|
|
|
84
97
|
registerDropTarget(el: Component, cb: DropCallback, filterCB?: FilterCallback ) {
|
|
@@ -151,7 +164,11 @@ class DragManager {
|
|
|
151
164
|
el.setInternalData( x_drag_cb, cb );
|
|
152
165
|
}
|
|
153
166
|
|
|
154
|
-
|
|
167
|
+
/**
|
|
168
|
+
* @internal
|
|
169
|
+
*/
|
|
170
|
+
|
|
171
|
+
private _startCheck() {
|
|
155
172
|
|
|
156
173
|
if (this.timer) {
|
|
157
174
|
clearInterval(this.timer);
|
|
@@ -161,7 +178,11 @@ class DragManager {
|
|
|
161
178
|
this.timer = setInterval( () => this._check(), 300 );
|
|
162
179
|
}
|
|
163
180
|
|
|
164
|
-
|
|
181
|
+
/**
|
|
182
|
+
* @internal
|
|
183
|
+
*/
|
|
184
|
+
|
|
185
|
+
private _check( ) {
|
|
165
186
|
|
|
166
187
|
const leaving = ( x: Component ) => {
|
|
167
188
|
x.removeClass('drop-over');
|
|
@@ -198,4 +219,8 @@ class DragManager {
|
|
|
198
219
|
}
|
|
199
220
|
}
|
|
200
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Singleton instance of the DragManager for global access.
|
|
224
|
+
*/
|
|
225
|
+
|
|
201
226
|
export const dragManager = new DragManager();
|
package/src/core/core_element.ts
CHANGED
|
@@ -16,10 +16,67 @@
|
|
|
16
16
|
|
|
17
17
|
import { EventMap, EventSource } from './core_events';
|
|
18
18
|
|
|
19
|
+
|
|
19
20
|
/**
|
|
20
|
-
*
|
|
21
|
+
* CoreElement
|
|
22
|
+
*
|
|
23
|
+
* A lightweight base class that provides two orthogonal utilities commonly needed by UI
|
|
24
|
+
* or domain objects:
|
|
25
|
+
* 1) Named timers (wraps setTimeout / setInterval by name so they can be started, stopped,
|
|
26
|
+
* and cleared by string identifier), and
|
|
27
|
+
* 2) A typed eventing surface (lazy-initialised EventSource) for attaching, detaching and
|
|
28
|
+
* firing events.
|
|
29
|
+
*
|
|
30
|
+
* The class is generic over an EventMap `E` which maps event name keys to the payload
|
|
31
|
+
* type for that event. This enables compile-time type safety for listeners and fired events.
|
|
32
|
+
*
|
|
33
|
+
* Template parameters:
|
|
34
|
+
* @template E - An {@link EventMap}-shaped type that maps event keys to the event payload types.
|
|
35
|
+
*
|
|
36
|
+
* Timer semantics:
|
|
37
|
+
* - Timers are referenced by a string `name`. Each instance of CoreElement maintains its own
|
|
38
|
+
* map of timers.
|
|
39
|
+
* - Starting a timer with a name that is already present will stop the previous timer first.
|
|
40
|
+
* - setTimeout(name, ms, callback) creates a single-shot timer (uses global setTimeout).
|
|
41
|
+
* - setInterval(name, ms, callback) creates a repeating timer (uses global setInterval).
|
|
42
|
+
* - clearTimeout(name) and clearInterval(name) both stop and remove the timer with the given
|
|
43
|
+
* name (they are aliases for the same internal stop logic).
|
|
44
|
+
* - clearTimeouts() will stop and remove all timers currently tracked by the instance.
|
|
45
|
+
* - The underlying timer handles are encapsulated; callers only interact via the string name.
|
|
46
|
+
*
|
|
47
|
+
* Event semantics:
|
|
48
|
+
* - Event support is provided via a lazily-initialised EventSource instance internal to the
|
|
49
|
+
* CoreElement. The EventSource is created on first use (first call to on()).
|
|
50
|
+
* - on(name, listener) registers a listener for the given event name and returns a small
|
|
51
|
+
* subscription object exposing an off() method for convenience.
|
|
52
|
+
* - off(name, listener) removes a previously-registered listener (no-op if there is no
|
|
53
|
+
* EventSource or listener).
|
|
54
|
+
* - fire(name, ev) will dispatch the given payload to all listeners registered for that
|
|
55
|
+
* event name (no-op if there is no EventSource).
|
|
56
|
+
* - Listener and event payload types are enforced by the generic `E`.
|
|
57
|
+
*
|
|
58
|
+
* Threading / reentrancy / error handling:
|
|
59
|
+
* - Timer callbacks execute via the platform timer mechanisms (setTimeout/setInterval). Any
|
|
60
|
+
* exceptions thrown by callbacks will propagate according to the environment's timer
|
|
61
|
+
* semantics (typically uncaught unless handled by the callback).
|
|
62
|
+
* - The class uses console.assert to validate non-null listeners on public attach/detach
|
|
63
|
+
* methods; callers should avoid passing undefined/null listeners.
|
|
64
|
+
* Example:
|
|
65
|
+
* ```ts
|
|
66
|
+
* // Strongly-typed events
|
|
67
|
+
* interface MyEvents { loaded: { ok: boolean }, tick: number }
|
|
68
|
+
* class MyElement extends CoreElement<MyEvents> {}
|
|
69
|
+
*
|
|
70
|
+
* const el = new MyElement();
|
|
71
|
+
* const sub = el.on("loaded", e => console.log(e.ok));
|
|
72
|
+
* el.fire("loaded", { ok: true });
|
|
73
|
+
* sub.off(); // convenience to remove listener
|
|
74
|
+
*
|
|
75
|
+
* // Timers
|
|
76
|
+
* el.setInterval("poll", 1000, () => el.fire("tick", Date.now()));
|
|
77
|
+
* el.clearInterval("poll");
|
|
78
|
+
* ```
|
|
21
79
|
*/
|
|
22
|
-
|
|
23
80
|
export class CoreElement<E extends EventMap = EventMap> {
|
|
24
81
|
|
|
25
82
|
#events: EventSource<E>;
|
|
@@ -46,22 +103,55 @@ export class CoreElement<E extends EventMap = EventMap> {
|
|
|
46
103
|
if (clear) { clear(); }
|
|
47
104
|
}
|
|
48
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Sets a timeout that executes a callback function after a specified delay.
|
|
108
|
+
* If a timeout with the same name already exists, it will be cleared before the new one is set.
|
|
109
|
+
* @param name - A unique string identifier for this timeout.
|
|
110
|
+
* @param ms - The delay in milliseconds before the callback is executed.
|
|
111
|
+
* @param callback - The function to execute after the delay.
|
|
112
|
+
*/
|
|
113
|
+
|
|
49
114
|
setTimeout( name: string, ms: number, callback: () => void ) {
|
|
50
115
|
this.__startTimer( name, ms, false, callback );
|
|
51
116
|
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Clears a previously set timeout.
|
|
120
|
+
* @param name - The name of the timeout to clear.
|
|
121
|
+
* @link setTimeout
|
|
122
|
+
*/
|
|
52
123
|
|
|
53
124
|
clearTimeout( name: string ) {
|
|
54
125
|
this.__stopTimer( name );
|
|
55
126
|
}
|
|
56
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Sets an interval that repeatedly executes a callback function after a specified delay.
|
|
130
|
+
* If a timeout with the same name already exists, it will be cleared before the new one is set.
|
|
131
|
+
* @param name - A unique string identifier for this timeout.
|
|
132
|
+
* @param ms - The delay in milliseconds before the callback is executed.
|
|
133
|
+
* @param callback - The function to execute after the delay.
|
|
134
|
+
*/
|
|
135
|
+
|
|
57
136
|
setInterval( name: string, ms: number, callback: ( ) => void ) {
|
|
58
137
|
this.__startTimer( name, ms, true, callback );
|
|
59
138
|
}
|
|
60
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Clears a previously set interval.
|
|
142
|
+
* @param name - The name of the interval to clear.
|
|
143
|
+
* @link setInterval
|
|
144
|
+
*/
|
|
145
|
+
|
|
61
146
|
clearInterval( name: string ) {
|
|
62
147
|
this.__stopTimer( name );
|
|
63
148
|
}
|
|
64
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Clears all timeouts and intervals currently managed by this instance.
|
|
152
|
+
* This stops all scheduled callbacks and removes their references.
|
|
153
|
+
* @link setTimeout
|
|
154
|
+
*/
|
|
65
155
|
clearTimeouts( ) {
|
|
66
156
|
for( const [id,val] of this.#timers ) {
|
|
67
157
|
val( );
|
|
@@ -73,6 +163,13 @@ export class CoreElement<E extends EventMap = EventMap> {
|
|
|
73
163
|
// :: EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
74
164
|
|
|
75
165
|
/**
|
|
166
|
+
* Registers an event listener for a specific event name.
|
|
167
|
+
* The listener will be invoked when an event with the given name is fired.
|
|
168
|
+
* Returns an object with an `off()` method, which can be used to conveniently remove this specific listener.
|
|
169
|
+
* @param name - The name of the event to listen for.
|
|
170
|
+
* @param listener - The callback function to execute when the event is fired.
|
|
171
|
+
* @returns An object containing an `off()` method to unsubscribe the listener.
|
|
172
|
+
* @link fire
|
|
76
173
|
* attach to an event
|
|
77
174
|
*/
|
|
78
175
|
|
|
@@ -92,7 +189,12 @@ export class CoreElement<E extends EventMap = EventMap> {
|
|
|
92
189
|
}
|
|
93
190
|
|
|
94
191
|
/**
|
|
95
|
-
*
|
|
192
|
+
* Removes a previously registered event listener.
|
|
193
|
+
* If the listener was not found or no events were registered, this method does nothing.
|
|
194
|
+
* @param name - The name of the event from which to remove the listener.
|
|
195
|
+
* @param listener - The specific listener function to remove.
|
|
196
|
+
* @link on
|
|
197
|
+
* @link fire
|
|
96
198
|
*/
|
|
97
199
|
|
|
98
200
|
off<K extends keyof E>( name: K, listener: ( ev: E[K] ) => void ) {
|
|
@@ -104,7 +206,12 @@ export class CoreElement<E extends EventMap = EventMap> {
|
|
|
104
206
|
}
|
|
105
207
|
|
|
106
208
|
/**
|
|
107
|
-
*
|
|
209
|
+
* Dispatches an event with a given name and payload to all registered listeners.
|
|
210
|
+
* If no listeners are registered for the event name, or if no EventSource has been initialized, this method does nothing.
|
|
211
|
+
* @param name - The name of the event to fire.
|
|
212
|
+
* @param ev - The payload (event object) to pass to the listeners.
|
|
213
|
+
* @link on
|
|
214
|
+
* @link off
|
|
108
215
|
*/
|
|
109
216
|
|
|
110
217
|
fire<K extends keyof E>( name: K, ev: E[K] ) {
|
package/src/core/core_events.ts
CHANGED
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
import { CoreElement } from './core_element';
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* Represents a base event interface for the framework.
|
|
21
|
+
* All custom events should implement this interface to ensure consistent behavior
|
|
22
|
+
* regarding event propagation and default action prevention.
|
|
21
23
|
*/
|
|
22
24
|
|
|
23
25
|
export interface CoreEvent {
|
|
@@ -28,7 +30,15 @@ export interface CoreEvent {
|
|
|
28
30
|
propagationStopped?: boolean; // if true, do not propagate the event
|
|
29
31
|
defaultPrevented?: boolean; // if true, do not call default handler (if any)
|
|
30
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Stops the propagation of the event to further listeners.
|
|
35
|
+
* Subsequent listeners for the same event on the same EventSource will not be called.
|
|
36
|
+
*/
|
|
31
37
|
stopPropagation?(): void; // stop the propagation
|
|
38
|
+
/**
|
|
39
|
+
* Prevents the default action associated with the event.
|
|
40
|
+
* If a default handler exists, it will not be executed.
|
|
41
|
+
*/
|
|
32
42
|
preventDefault?(): void; // prevent the default handler
|
|
33
43
|
}
|
|
34
44
|
|
|
@@ -44,20 +54,35 @@ const preventDefault = function ( this: CoreEvent ) {
|
|
|
44
54
|
|
|
45
55
|
|
|
46
56
|
/**
|
|
47
|
-
*
|
|
57
|
+
* A generic interface for mapping event names to their corresponding event payload types.
|
|
58
|
+
* This is used by `EventSource` and `CoreElement` to provide compile-time type safety
|
|
59
|
+
* for event listeners and fired events.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* interface MyCustomEvents {
|
|
64
|
+
* 'dataLoaded': { data: any, timestamp: number };
|
|
65
|
+
* 'itemSelected': { itemId: string };
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
48
68
|
*/
|
|
49
69
|
|
|
50
70
|
export interface EventMap {
|
|
51
71
|
}
|
|
52
72
|
|
|
53
73
|
/**
|
|
54
|
-
*
|
|
74
|
+
* Defines the signature for an event callback function.
|
|
75
|
+
* @template T - The specific type of `CoreEvent` that the callback handles.
|
|
55
76
|
*/
|
|
56
77
|
|
|
57
78
|
export type EventCallback<T extends CoreEvent = CoreEvent> = (event: T) => any;
|
|
58
79
|
|
|
59
80
|
/**
|
|
60
|
-
*
|
|
81
|
+
* A base class for objects that can emit and listen to custom events.
|
|
82
|
+
* It provides a typed eventing mechanism, allowing for the registration and removal of listeners,
|
|
83
|
+
* and the firing of events with associated payloads.
|
|
84
|
+
*
|
|
85
|
+
* @template E - An `EventMap`-shaped type that defines the events supported by this source.
|
|
61
86
|
*/
|
|
62
87
|
|
|
63
88
|
export class EventSource<E extends EventMap = EventMap > {
|
|
@@ -65,10 +90,21 @@ export class EventSource<E extends EventMap = EventMap > {
|
|
|
65
90
|
private _source: unknown;
|
|
66
91
|
private _registry: Map<string,EventCallback[]>;
|
|
67
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Creates an instance of EventSource.
|
|
95
|
+
* @param source - The object that will be reported as the `source` of events fired by this instance.
|
|
96
|
+
* If `null` or `undefined`, the `EventSource` instance itself will be the source.
|
|
97
|
+
*/
|
|
68
98
|
constructor(source: unknown = null) {
|
|
69
99
|
this._source = source ?? this;
|
|
70
100
|
}
|
|
71
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Registers an event listener for a specific event name.
|
|
104
|
+
* @param name - The name of the event to listen for (must be a key in `E`).
|
|
105
|
+
* @param callback - The function to be called when the event is fired.
|
|
106
|
+
* @param capturing - If `true`, the listener will be added to the beginning of the listener list (capturing phase).
|
|
107
|
+
*/
|
|
72
108
|
addListener<K extends keyof E>( name: K, callback: ( ev: E[K] ) => void, capturing = false ) {
|
|
73
109
|
|
|
74
110
|
if (!this._registry) {
|
|
@@ -98,9 +134,9 @@ export class EventSource<E extends EventMap = EventMap > {
|
|
|
98
134
|
}
|
|
99
135
|
|
|
100
136
|
/**
|
|
101
|
-
*
|
|
102
|
-
* @param
|
|
103
|
-
* @param callback - callback to remove
|
|
137
|
+
* Removes a previously registered event listener.
|
|
138
|
+
* @param name - The name of the event from which to remove the listener.
|
|
139
|
+
* @param callback - The specific callback function to remove. It must be the same function instance that was originally registered.
|
|
104
140
|
*/
|
|
105
141
|
|
|
106
142
|
removeListener<K extends keyof E>(name: K, callback: (ev: E[K]) => any) {
|
|
@@ -121,6 +157,11 @@ export class EventSource<E extends EventMap = EventMap > {
|
|
|
121
157
|
}
|
|
122
158
|
}
|
|
123
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Dispatches an event with a given name and payload to all registered listeners.
|
|
162
|
+
* @param name - The name of the event to fire (must be a key in `E`).
|
|
163
|
+
* @param evx - The event payload (event object) to pass to the listeners.
|
|
164
|
+
*/
|
|
124
165
|
fire<K extends keyof E>(name: K, evx: E[K]) {
|
|
125
166
|
|
|
126
167
|
let listeners = this._registry?.get(name as string);
|
|
@@ -171,7 +212,3 @@ export class EventSource<E extends EventMap = EventMap > {
|
|
|
171
212
|
//}
|
|
172
213
|
}
|
|
173
214
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
package/src/core/core_i18n.ts
CHANGED
|
@@ -289,6 +289,7 @@ let fr = {
|
|
|
289
289
|
|
|
290
290
|
date_input_formats: 'd/m/y|d.m.y|d m y|d-m-y|dmy',
|
|
291
291
|
date_format: 'D/M/Y',
|
|
292
|
+
date_time_format: 'D/M/Y H:I:S',
|
|
292
293
|
|
|
293
294
|
day_short: [ 'dim', 'lun', 'mar', 'mer', 'jeu', 'ven', 'sam' ],
|
|
294
295
|
day_long: [ 'dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi' ],
|
|
@@ -353,6 +354,7 @@ let en = {
|
|
|
353
354
|
|
|
354
355
|
date_input_formats: 'm/d/y|m.d.y|m d y|m-d-y|mdy',
|
|
355
356
|
date_format: 'M/D/Y',
|
|
357
|
+
date_time_format: 'M/D/Y H:I:S',
|
|
356
358
|
|
|
357
359
|
day_short: [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ],
|
|
358
360
|
day_long: [ 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday' ],
|