@etsoo/shared 1.1.26 → 1.1.29
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/README.md +30 -2
- package/__tests__/EHistory.ts +81 -0
- package/lib/cjs/types/EHistory.d.ts +69 -0
- package/lib/cjs/types/EHistory.js +101 -0
- package/lib/cjs/types/EventClass.d.ts +104 -0
- package/lib/cjs/types/EventClass.js +159 -0
- package/lib/mjs/types/EHistory.d.ts +69 -0
- package/lib/mjs/types/EHistory.js +97 -0
- package/lib/mjs/types/EventClass.d.ts +104 -0
- package/lib/mjs/types/EventClass.js +154 -0
- package/package.json +1 -1
- package/src/types/EHistory.ts +134 -0
- package/src/types/EventClass.ts +253 -0
package/README.md
CHANGED
|
@@ -26,6 +26,7 @@ Etsoo implmented Color
|
|
|
26
26
|
|static getColors|Get HEX or RGB colors|
|
|
27
27
|
|static getEColors|Get EColors|
|
|
28
28
|
|static parse|Parse HTML color to EColor|
|
|
29
|
+
|Methods||
|
|
29
30
|
|clone|Clone color with adjustments|
|
|
30
31
|
|getContrastRatio|Get contrast ratio, a value between 0 and 1|
|
|
31
32
|
|getDeltaValue|Get Delta value (perceptible by human eyes)|
|
|
@@ -34,6 +35,16 @@ Etsoo implmented Color
|
|
|
34
35
|
|toLabValue|To Lab value|
|
|
35
36
|
|toRGBColor|To RGB color string|
|
|
36
37
|
|
|
38
|
+
## EventClass
|
|
39
|
+
Etsoo implmented abstract Event Class
|
|
40
|
+
|
|
41
|
+
|Name|Description|
|
|
42
|
+
|---:|---|
|
|
43
|
+
|hasEvents|Has specific type and callback events|
|
|
44
|
+
|off|Remove specific type and callback event|
|
|
45
|
+
|on|Add event listener|
|
|
46
|
+
|trigger|Trigger event|
|
|
47
|
+
|
|
37
48
|
## Keyboard
|
|
38
49
|
Keyboard keys and codes
|
|
39
50
|
|
|
@@ -44,6 +55,23 @@ Keyboard keys and codes
|
|
|
44
55
|
|
|
45
56
|
|isTypingContent|Is typing content or press command key|
|
|
46
57
|
|
|
58
|
+
## EHistory
|
|
59
|
+
ETSOO Extended abstract history class
|
|
60
|
+
|
|
61
|
+
|Name|Description|
|
|
62
|
+
|---:|---|
|
|
63
|
+
|index|Current index|
|
|
64
|
+
|length|States length|
|
|
65
|
+
|state|Current state|
|
|
66
|
+
|states|States|
|
|
67
|
+
|Methods||
|
|
68
|
+
|back|Back to the previous state|
|
|
69
|
+
|clear|Clear all states but keep event listeners|
|
|
70
|
+
|forward|Forward to the next state|
|
|
71
|
+
|go|Go to the specific state|
|
|
72
|
+
|pushState|Adds an entry to the history stack|
|
|
73
|
+
|replaceState|Modifies the current history entry|
|
|
74
|
+
|
|
47
75
|
## DataTypes
|
|
48
76
|
Data type definitions and type safe functions
|
|
49
77
|
|
|
@@ -77,7 +105,7 @@ Data type definitions and type safe functions
|
|
|
77
105
|
|StringRecord|String key, unknown value Record|
|
|
78
106
|
|VAlign|Vertical align|
|
|
79
107
|
|VAlignEnum|Vertical align enum|
|
|
80
|
-
|
|
108
|
+
|Methods||
|
|
81
109
|
|convert|Convert value to target type|
|
|
82
110
|
|convertByType|Convert by type name like 'string'|
|
|
83
111
|
|convertSimple|Convert value to target enum type|
|
|
@@ -105,7 +133,7 @@ Dates related utilities
|
|
|
105
133
|
|DayFormat|YYYY-MM-DD|
|
|
106
134
|
|MinuteFormat|YYYY-MM-DD hh:mm|
|
|
107
135
|
|SecondFormat|YYYY-MM-DD hh:mm:ss|
|
|
108
|
-
|
|
136
|
+
|Methods||
|
|
109
137
|
|getDays|Get month's days|
|
|
110
138
|
|forma|Format dates|
|
|
111
139
|
|jsonParser|JSON parser|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EHistory,
|
|
3
|
+
EHistoryEventData,
|
|
4
|
+
EHistoryEventType
|
|
5
|
+
} from '../src/types/EHistory';
|
|
6
|
+
import { EventBase } from '../src/types/EventClass';
|
|
7
|
+
|
|
8
|
+
// Extended for tests
|
|
9
|
+
class LHistoryEvent extends EventBase<EHistoryEventType, EHistoryEventData> {}
|
|
10
|
+
class LHistory extends EHistory<number, EHistoryEventData> {
|
|
11
|
+
protected createEvent(type: EHistoryEventType, index: number) {
|
|
12
|
+
return new LHistoryEvent(this, type, { index });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test('Tests for history', () => {
|
|
17
|
+
const history = new LHistory();
|
|
18
|
+
expect(history.index).toBe(-1);
|
|
19
|
+
history.pushState(1);
|
|
20
|
+
history.pushState(2);
|
|
21
|
+
history.pushState(3);
|
|
22
|
+
expect(history.states).toStrictEqual([1, 2, 3]);
|
|
23
|
+
expect(history.index).toBe(2);
|
|
24
|
+
history.back();
|
|
25
|
+
history.pushState(4);
|
|
26
|
+
expect(history.index).toBe(2);
|
|
27
|
+
expect(history.states).toStrictEqual([1, 2, 4]);
|
|
28
|
+
history.go(-2);
|
|
29
|
+
expect(history.state).toBe(1);
|
|
30
|
+
history.forward();
|
|
31
|
+
expect(history.index).toBe(1);
|
|
32
|
+
history.replaceState(0);
|
|
33
|
+
expect(history.state).toBe(0);
|
|
34
|
+
history.clear();
|
|
35
|
+
expect(history.length).toBe(0);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('Tests for events', () => {
|
|
39
|
+
const navigatorFn = jest.fn();
|
|
40
|
+
const navigatorStopFn = jest.fn((event: LHistoryEvent) => {
|
|
41
|
+
event.stopImmediatePropagation();
|
|
42
|
+
});
|
|
43
|
+
const clearFn = jest.fn();
|
|
44
|
+
const pushFn = jest.fn();
|
|
45
|
+
const replaceFn = jest.fn();
|
|
46
|
+
|
|
47
|
+
const history = new LHistory();
|
|
48
|
+
|
|
49
|
+
history.on({
|
|
50
|
+
clear: clearFn,
|
|
51
|
+
push: pushFn,
|
|
52
|
+
replace: replaceFn,
|
|
53
|
+
navigate: navigatorFn
|
|
54
|
+
});
|
|
55
|
+
history.on('navigate', navigatorFn);
|
|
56
|
+
history.on('navigate', navigatorFn, { capture: true, once: true });
|
|
57
|
+
|
|
58
|
+
history.clear();
|
|
59
|
+
expect(clearFn).toBeCalled();
|
|
60
|
+
|
|
61
|
+
history.pushState(1);
|
|
62
|
+
expect(pushFn).toBeCalled();
|
|
63
|
+
|
|
64
|
+
history.replaceState(11);
|
|
65
|
+
expect(replaceFn).toBeCalled();
|
|
66
|
+
|
|
67
|
+
history.pushState(2);
|
|
68
|
+
history.back();
|
|
69
|
+
expect(navigatorFn).toBeCalledTimes(3);
|
|
70
|
+
|
|
71
|
+
history.forward();
|
|
72
|
+
// Once handler was removed
|
|
73
|
+
expect(navigatorFn).toBeCalledTimes(5);
|
|
74
|
+
|
|
75
|
+
history.on('navigate', navigatorStopFn, { capture: true });
|
|
76
|
+
history.go(-1);
|
|
77
|
+
expect(navigatorStopFn).toBeCalled();
|
|
78
|
+
|
|
79
|
+
// Previous handler stopped propagation
|
|
80
|
+
expect(navigatorFn).toBeCalledTimes(5);
|
|
81
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { EventBase, EventClass } from './EventClass';
|
|
2
|
+
/**
|
|
3
|
+
* ETSOO Extended history event type
|
|
4
|
+
*/
|
|
5
|
+
export declare type EHistoryEventType = 'navigate' | 'push' | 'replace' | 'clear';
|
|
6
|
+
/**
|
|
7
|
+
* ETSOO Extended history event data
|
|
8
|
+
*/
|
|
9
|
+
export interface EHistoryEventData {
|
|
10
|
+
/**
|
|
11
|
+
* Current index
|
|
12
|
+
*/
|
|
13
|
+
index: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* ETSOO Extended abstract history class
|
|
17
|
+
*/
|
|
18
|
+
export declare abstract class EHistory<T, D extends EHistoryEventData> extends EventClass<EHistoryEventType, D> {
|
|
19
|
+
private _index;
|
|
20
|
+
/**
|
|
21
|
+
* States
|
|
22
|
+
*/
|
|
23
|
+
readonly states: T[];
|
|
24
|
+
/**
|
|
25
|
+
* States length
|
|
26
|
+
*/
|
|
27
|
+
get length(): number;
|
|
28
|
+
/**
|
|
29
|
+
* Get current state index
|
|
30
|
+
*/
|
|
31
|
+
get index(): number;
|
|
32
|
+
/**
|
|
33
|
+
* Get current state
|
|
34
|
+
*/
|
|
35
|
+
get state(): T | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Back to the previous state
|
|
38
|
+
*/
|
|
39
|
+
back(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Clear all states but keep event listeners
|
|
42
|
+
*/
|
|
43
|
+
clear(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Create event
|
|
46
|
+
* @param type Type
|
|
47
|
+
* @param index Current index
|
|
48
|
+
*/
|
|
49
|
+
protected abstract createEvent(type: EHistoryEventType, index: number): EventBase<EHistoryEventType, D>;
|
|
50
|
+
/**
|
|
51
|
+
* Forward to the next state
|
|
52
|
+
*/
|
|
53
|
+
forward(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Go to the specific state
|
|
56
|
+
* @param delta A negative value moves backwards, a positive value moves forwards
|
|
57
|
+
*/
|
|
58
|
+
go(delta: number): undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Adds an entry to the history stack
|
|
61
|
+
* @param state State
|
|
62
|
+
*/
|
|
63
|
+
pushState(state: T): void;
|
|
64
|
+
/**
|
|
65
|
+
* Modifies the current history entry
|
|
66
|
+
* @param state State
|
|
67
|
+
*/
|
|
68
|
+
replaceState(state: T): void;
|
|
69
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EHistory = void 0;
|
|
4
|
+
const EventClass_1 = require("./EventClass");
|
|
5
|
+
/**
|
|
6
|
+
* ETSOO Extended abstract history class
|
|
7
|
+
*/
|
|
8
|
+
class EHistory extends EventClass_1.EventClass {
|
|
9
|
+
constructor() {
|
|
10
|
+
super(...arguments);
|
|
11
|
+
// Index
|
|
12
|
+
this._index = -1;
|
|
13
|
+
/**
|
|
14
|
+
* States
|
|
15
|
+
*/
|
|
16
|
+
this.states = [];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* States length
|
|
20
|
+
*/
|
|
21
|
+
get length() {
|
|
22
|
+
return this.states.length;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get current state index
|
|
26
|
+
*/
|
|
27
|
+
get index() {
|
|
28
|
+
return this._index;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get current state
|
|
32
|
+
*/
|
|
33
|
+
get state() {
|
|
34
|
+
if (this._index === -1)
|
|
35
|
+
return undefined;
|
|
36
|
+
return this.states[this._index];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Back to the previous state
|
|
40
|
+
*/
|
|
41
|
+
back() {
|
|
42
|
+
this.go(-1);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Clear all states but keep event listeners
|
|
46
|
+
*/
|
|
47
|
+
clear() {
|
|
48
|
+
// https://stackoverflow.com/questions/1232040/how-do-i-empty-an-array-in-javascript
|
|
49
|
+
this.states.length = 0;
|
|
50
|
+
this._index = -1;
|
|
51
|
+
this.trigger(this.createEvent('clear', this._index));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Forward to the next state
|
|
55
|
+
*/
|
|
56
|
+
forward() {
|
|
57
|
+
this.go(1);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Go to the specific state
|
|
61
|
+
* @param delta A negative value moves backwards, a positive value moves forwards
|
|
62
|
+
*/
|
|
63
|
+
go(delta) {
|
|
64
|
+
// No data
|
|
65
|
+
if (this._index === -1)
|
|
66
|
+
return undefined;
|
|
67
|
+
// New index
|
|
68
|
+
const newIndex = this._index + delta;
|
|
69
|
+
// Not in the range
|
|
70
|
+
if (newIndex < 0 || newIndex >= this.length)
|
|
71
|
+
return undefined;
|
|
72
|
+
// Update the index
|
|
73
|
+
this._index = newIndex;
|
|
74
|
+
// Trigger event
|
|
75
|
+
this.trigger(this.createEvent('navigate', newIndex));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Adds an entry to the history stack
|
|
79
|
+
* @param state State
|
|
80
|
+
*/
|
|
81
|
+
pushState(state) {
|
|
82
|
+
if (this._index >= 0) {
|
|
83
|
+
// Remove states after the index
|
|
84
|
+
this.states.splice(this._index + 1);
|
|
85
|
+
}
|
|
86
|
+
this.states.push(state);
|
|
87
|
+
this._index++;
|
|
88
|
+
this.trigger(this.createEvent('push', this._index));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Modifies the current history entry
|
|
92
|
+
* @param state State
|
|
93
|
+
*/
|
|
94
|
+
replaceState(state) {
|
|
95
|
+
if (this._index === -1)
|
|
96
|
+
return;
|
|
97
|
+
this.states[this._index] = state;
|
|
98
|
+
this.trigger(this.createEvent('replace', this._index));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.EHistory = EHistory;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract event base class
|
|
3
|
+
* T for type
|
|
4
|
+
* D for data
|
|
5
|
+
*/
|
|
6
|
+
export declare abstract class EventBase<T extends string, D> {
|
|
7
|
+
readonly target: EventClass<T, D>;
|
|
8
|
+
readonly type: T;
|
|
9
|
+
readonly data: D;
|
|
10
|
+
private _propagationStopped;
|
|
11
|
+
/**
|
|
12
|
+
* stopImmediatePropagation called
|
|
13
|
+
*/
|
|
14
|
+
get propagationStopped(): boolean;
|
|
15
|
+
private _timeStamp;
|
|
16
|
+
/**
|
|
17
|
+
* Time stamp
|
|
18
|
+
*/
|
|
19
|
+
get timeStamp(): number;
|
|
20
|
+
/**
|
|
21
|
+
* Constructor
|
|
22
|
+
* @param type Type
|
|
23
|
+
*/
|
|
24
|
+
constructor(target: EventClass<T, D>, type: T, data: D);
|
|
25
|
+
/**
|
|
26
|
+
* Prevent all other listeners from being called
|
|
27
|
+
*/
|
|
28
|
+
stopImmediatePropagation(): void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Event options
|
|
32
|
+
*/
|
|
33
|
+
interface EventOptions {
|
|
34
|
+
/**
|
|
35
|
+
* A boolean value indicating that events of this type will be dispatched first
|
|
36
|
+
*/
|
|
37
|
+
capture?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* A boolean value indicating that the listener should be invoked at most once after being added
|
|
40
|
+
*/
|
|
41
|
+
once?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Event class callback
|
|
45
|
+
* T for type
|
|
46
|
+
* D for data
|
|
47
|
+
*/
|
|
48
|
+
export declare type EventClassCallback<T extends string, D> = (event: EventBase<T, D>) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Event class collection definition
|
|
51
|
+
* T for type
|
|
52
|
+
* D for data
|
|
53
|
+
*/
|
|
54
|
+
export declare type EventClassCollection<T extends string, D> = {
|
|
55
|
+
[key in T]?: EventClassCallback<T, D>;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Event class
|
|
59
|
+
* T for type
|
|
60
|
+
* D for data
|
|
61
|
+
*/
|
|
62
|
+
export declare abstract class EventClass<T extends string, D> {
|
|
63
|
+
private readonly listeners;
|
|
64
|
+
/**
|
|
65
|
+
* Has specific type events
|
|
66
|
+
* @param type Type
|
|
67
|
+
*/
|
|
68
|
+
hasEvents(type: T): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Has specific type and callback events
|
|
71
|
+
* @param type Type
|
|
72
|
+
* @param callback Callback
|
|
73
|
+
*/
|
|
74
|
+
hasEvents(type: T, callback: EventClassCallback<T, D>): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Remove all specific type events
|
|
77
|
+
* @param type Type
|
|
78
|
+
*/
|
|
79
|
+
off(type: T): void;
|
|
80
|
+
/**
|
|
81
|
+
* Remove specific type and callback event
|
|
82
|
+
* @param type Type
|
|
83
|
+
* @param callback Callback
|
|
84
|
+
*/
|
|
85
|
+
off(type: T, callback: EventClassCallback<T, D>): void;
|
|
86
|
+
/**
|
|
87
|
+
* Add event listeners
|
|
88
|
+
* @param collection Collection of events
|
|
89
|
+
*/
|
|
90
|
+
on(collection: EventClassCollection<T, D>): void;
|
|
91
|
+
/**
|
|
92
|
+
* Add event listener
|
|
93
|
+
* @param type Type
|
|
94
|
+
* @param callback Callback
|
|
95
|
+
* @param options Options
|
|
96
|
+
*/
|
|
97
|
+
on(type: T, callback: EventClassCallback<T, D>, options?: EventOptions): void;
|
|
98
|
+
/**
|
|
99
|
+
* Trigger event
|
|
100
|
+
* @param event Event
|
|
101
|
+
*/
|
|
102
|
+
trigger(event: EventBase<T, D>): void;
|
|
103
|
+
}
|
|
104
|
+
export {};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EventClass = exports.EventBase = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Abstract event base class
|
|
6
|
+
* T for type
|
|
7
|
+
* D for data
|
|
8
|
+
*/
|
|
9
|
+
class EventBase {
|
|
10
|
+
/**
|
|
11
|
+
* Constructor
|
|
12
|
+
* @param type Type
|
|
13
|
+
*/
|
|
14
|
+
constructor(target, type, data) {
|
|
15
|
+
this.target = target;
|
|
16
|
+
this.type = type;
|
|
17
|
+
this.data = data;
|
|
18
|
+
this._propagationStopped = false;
|
|
19
|
+
this._timeStamp = Date.now();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* stopImmediatePropagation called
|
|
23
|
+
*/
|
|
24
|
+
get propagationStopped() {
|
|
25
|
+
return this._propagationStopped;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Time stamp
|
|
29
|
+
*/
|
|
30
|
+
get timeStamp() {
|
|
31
|
+
return this._timeStamp;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Prevent all other listeners from being called
|
|
35
|
+
*/
|
|
36
|
+
stopImmediatePropagation() {
|
|
37
|
+
this._propagationStopped = true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.EventBase = EventBase;
|
|
41
|
+
/**
|
|
42
|
+
* Event class
|
|
43
|
+
* T for type
|
|
44
|
+
* D for data
|
|
45
|
+
*/
|
|
46
|
+
class EventClass {
|
|
47
|
+
constructor() {
|
|
48
|
+
// Listeners
|
|
49
|
+
this.listeners = new Map();
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Has specific type and callback events
|
|
53
|
+
* @param type Type
|
|
54
|
+
* @param callback Callback
|
|
55
|
+
* @returns Result
|
|
56
|
+
*/
|
|
57
|
+
hasEvents(type, callback) {
|
|
58
|
+
const items = this.listeners.get(type);
|
|
59
|
+
if (items == null || items.length === 0)
|
|
60
|
+
return false;
|
|
61
|
+
if (callback) {
|
|
62
|
+
return items.some((item) => item[0] == callback);
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Remove specific type and callback event
|
|
68
|
+
* @param type Type
|
|
69
|
+
* @param callback Callback
|
|
70
|
+
*/
|
|
71
|
+
off(type, callback) {
|
|
72
|
+
if (callback == null) {
|
|
73
|
+
this.listeners.delete(type);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const items = this.listeners.get(type);
|
|
77
|
+
if (items == null)
|
|
78
|
+
return;
|
|
79
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
80
|
+
if (items[i][0] == callback) {
|
|
81
|
+
items.splice(i, 1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Add events
|
|
87
|
+
* @param type Type
|
|
88
|
+
* @param callback Callback
|
|
89
|
+
* @param options Options
|
|
90
|
+
*/
|
|
91
|
+
on(type, callback, options) {
|
|
92
|
+
var _a, _b;
|
|
93
|
+
if (typeof type === 'object') {
|
|
94
|
+
for (const key in type) {
|
|
95
|
+
const item = key;
|
|
96
|
+
const itemCallback = (_a = type[item]) !== null && _a !== void 0 ? _a : callback;
|
|
97
|
+
if (itemCallback)
|
|
98
|
+
this.on(item, itemCallback, options);
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (callback == null)
|
|
103
|
+
return;
|
|
104
|
+
this.listeners.has(type) || this.listeners.set(type, []);
|
|
105
|
+
(_b = this.listeners.get(type)) === null || _b === void 0 ? void 0 : _b.push([callback, options]);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Trigger event
|
|
109
|
+
* @param event Event
|
|
110
|
+
*/
|
|
111
|
+
trigger(event) {
|
|
112
|
+
const items = this.listeners.get(event.type);
|
|
113
|
+
if (items == null)
|
|
114
|
+
return;
|
|
115
|
+
// Len
|
|
116
|
+
const len = items.length;
|
|
117
|
+
if (len === 0)
|
|
118
|
+
return;
|
|
119
|
+
// Need to be removed indicies
|
|
120
|
+
const indicies = [];
|
|
121
|
+
// Capture items first
|
|
122
|
+
let stopped = false;
|
|
123
|
+
for (let c = 0; c < len; c++) {
|
|
124
|
+
const item = items[c];
|
|
125
|
+
const [callback, options] = item;
|
|
126
|
+
if (options == null || !options.capture)
|
|
127
|
+
continue;
|
|
128
|
+
callback(event);
|
|
129
|
+
if (options.once) {
|
|
130
|
+
indicies.push(c);
|
|
131
|
+
}
|
|
132
|
+
if (event.propagationStopped) {
|
|
133
|
+
stopped = true;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (!stopped) {
|
|
138
|
+
for (let c = 0; c < len; c++) {
|
|
139
|
+
const item = items[c];
|
|
140
|
+
const [callback, options] = item;
|
|
141
|
+
if (options === null || options === void 0 ? void 0 : options.capture)
|
|
142
|
+
continue;
|
|
143
|
+
callback(event);
|
|
144
|
+
if (options === null || options === void 0 ? void 0 : options.once) {
|
|
145
|
+
indicies.push(c);
|
|
146
|
+
}
|
|
147
|
+
if (event.propagationStopped) {
|
|
148
|
+
stopped = true;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Remove all once handlers
|
|
154
|
+
for (let i = indicies.length - 1; i >= 0; i--) {
|
|
155
|
+
items.splice(indicies[i], 1);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
exports.EventClass = EventClass;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { EventBase, EventClass } from './EventClass';
|
|
2
|
+
/**
|
|
3
|
+
* ETSOO Extended history event type
|
|
4
|
+
*/
|
|
5
|
+
export declare type EHistoryEventType = 'navigate' | 'push' | 'replace' | 'clear';
|
|
6
|
+
/**
|
|
7
|
+
* ETSOO Extended history event data
|
|
8
|
+
*/
|
|
9
|
+
export interface EHistoryEventData {
|
|
10
|
+
/**
|
|
11
|
+
* Current index
|
|
12
|
+
*/
|
|
13
|
+
index: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* ETSOO Extended abstract history class
|
|
17
|
+
*/
|
|
18
|
+
export declare abstract class EHistory<T, D extends EHistoryEventData> extends EventClass<EHistoryEventType, D> {
|
|
19
|
+
private _index;
|
|
20
|
+
/**
|
|
21
|
+
* States
|
|
22
|
+
*/
|
|
23
|
+
readonly states: T[];
|
|
24
|
+
/**
|
|
25
|
+
* States length
|
|
26
|
+
*/
|
|
27
|
+
get length(): number;
|
|
28
|
+
/**
|
|
29
|
+
* Get current state index
|
|
30
|
+
*/
|
|
31
|
+
get index(): number;
|
|
32
|
+
/**
|
|
33
|
+
* Get current state
|
|
34
|
+
*/
|
|
35
|
+
get state(): T | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Back to the previous state
|
|
38
|
+
*/
|
|
39
|
+
back(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Clear all states but keep event listeners
|
|
42
|
+
*/
|
|
43
|
+
clear(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Create event
|
|
46
|
+
* @param type Type
|
|
47
|
+
* @param index Current index
|
|
48
|
+
*/
|
|
49
|
+
protected abstract createEvent(type: EHistoryEventType, index: number): EventBase<EHistoryEventType, D>;
|
|
50
|
+
/**
|
|
51
|
+
* Forward to the next state
|
|
52
|
+
*/
|
|
53
|
+
forward(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Go to the specific state
|
|
56
|
+
* @param delta A negative value moves backwards, a positive value moves forwards
|
|
57
|
+
*/
|
|
58
|
+
go(delta: number): undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Adds an entry to the history stack
|
|
61
|
+
* @param state State
|
|
62
|
+
*/
|
|
63
|
+
pushState(state: T): void;
|
|
64
|
+
/**
|
|
65
|
+
* Modifies the current history entry
|
|
66
|
+
* @param state State
|
|
67
|
+
*/
|
|
68
|
+
replaceState(state: T): void;
|
|
69
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { EventClass } from './EventClass';
|
|
2
|
+
/**
|
|
3
|
+
* ETSOO Extended abstract history class
|
|
4
|
+
*/
|
|
5
|
+
export class EHistory extends EventClass {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(...arguments);
|
|
8
|
+
// Index
|
|
9
|
+
this._index = -1;
|
|
10
|
+
/**
|
|
11
|
+
* States
|
|
12
|
+
*/
|
|
13
|
+
this.states = [];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* States length
|
|
17
|
+
*/
|
|
18
|
+
get length() {
|
|
19
|
+
return this.states.length;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get current state index
|
|
23
|
+
*/
|
|
24
|
+
get index() {
|
|
25
|
+
return this._index;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get current state
|
|
29
|
+
*/
|
|
30
|
+
get state() {
|
|
31
|
+
if (this._index === -1)
|
|
32
|
+
return undefined;
|
|
33
|
+
return this.states[this._index];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Back to the previous state
|
|
37
|
+
*/
|
|
38
|
+
back() {
|
|
39
|
+
this.go(-1);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Clear all states but keep event listeners
|
|
43
|
+
*/
|
|
44
|
+
clear() {
|
|
45
|
+
// https://stackoverflow.com/questions/1232040/how-do-i-empty-an-array-in-javascript
|
|
46
|
+
this.states.length = 0;
|
|
47
|
+
this._index = -1;
|
|
48
|
+
this.trigger(this.createEvent('clear', this._index));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Forward to the next state
|
|
52
|
+
*/
|
|
53
|
+
forward() {
|
|
54
|
+
this.go(1);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Go to the specific state
|
|
58
|
+
* @param delta A negative value moves backwards, a positive value moves forwards
|
|
59
|
+
*/
|
|
60
|
+
go(delta) {
|
|
61
|
+
// No data
|
|
62
|
+
if (this._index === -1)
|
|
63
|
+
return undefined;
|
|
64
|
+
// New index
|
|
65
|
+
const newIndex = this._index + delta;
|
|
66
|
+
// Not in the range
|
|
67
|
+
if (newIndex < 0 || newIndex >= this.length)
|
|
68
|
+
return undefined;
|
|
69
|
+
// Update the index
|
|
70
|
+
this._index = newIndex;
|
|
71
|
+
// Trigger event
|
|
72
|
+
this.trigger(this.createEvent('navigate', newIndex));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Adds an entry to the history stack
|
|
76
|
+
* @param state State
|
|
77
|
+
*/
|
|
78
|
+
pushState(state) {
|
|
79
|
+
if (this._index >= 0) {
|
|
80
|
+
// Remove states after the index
|
|
81
|
+
this.states.splice(this._index + 1);
|
|
82
|
+
}
|
|
83
|
+
this.states.push(state);
|
|
84
|
+
this._index++;
|
|
85
|
+
this.trigger(this.createEvent('push', this._index));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Modifies the current history entry
|
|
89
|
+
* @param state State
|
|
90
|
+
*/
|
|
91
|
+
replaceState(state) {
|
|
92
|
+
if (this._index === -1)
|
|
93
|
+
return;
|
|
94
|
+
this.states[this._index] = state;
|
|
95
|
+
this.trigger(this.createEvent('replace', this._index));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract event base class
|
|
3
|
+
* T for type
|
|
4
|
+
* D for data
|
|
5
|
+
*/
|
|
6
|
+
export declare abstract class EventBase<T extends string, D> {
|
|
7
|
+
readonly target: EventClass<T, D>;
|
|
8
|
+
readonly type: T;
|
|
9
|
+
readonly data: D;
|
|
10
|
+
private _propagationStopped;
|
|
11
|
+
/**
|
|
12
|
+
* stopImmediatePropagation called
|
|
13
|
+
*/
|
|
14
|
+
get propagationStopped(): boolean;
|
|
15
|
+
private _timeStamp;
|
|
16
|
+
/**
|
|
17
|
+
* Time stamp
|
|
18
|
+
*/
|
|
19
|
+
get timeStamp(): number;
|
|
20
|
+
/**
|
|
21
|
+
* Constructor
|
|
22
|
+
* @param type Type
|
|
23
|
+
*/
|
|
24
|
+
constructor(target: EventClass<T, D>, type: T, data: D);
|
|
25
|
+
/**
|
|
26
|
+
* Prevent all other listeners from being called
|
|
27
|
+
*/
|
|
28
|
+
stopImmediatePropagation(): void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Event options
|
|
32
|
+
*/
|
|
33
|
+
interface EventOptions {
|
|
34
|
+
/**
|
|
35
|
+
* A boolean value indicating that events of this type will be dispatched first
|
|
36
|
+
*/
|
|
37
|
+
capture?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* A boolean value indicating that the listener should be invoked at most once after being added
|
|
40
|
+
*/
|
|
41
|
+
once?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Event class callback
|
|
45
|
+
* T for type
|
|
46
|
+
* D for data
|
|
47
|
+
*/
|
|
48
|
+
export declare type EventClassCallback<T extends string, D> = (event: EventBase<T, D>) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Event class collection definition
|
|
51
|
+
* T for type
|
|
52
|
+
* D for data
|
|
53
|
+
*/
|
|
54
|
+
export declare type EventClassCollection<T extends string, D> = {
|
|
55
|
+
[key in T]?: EventClassCallback<T, D>;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Event class
|
|
59
|
+
* T for type
|
|
60
|
+
* D for data
|
|
61
|
+
*/
|
|
62
|
+
export declare abstract class EventClass<T extends string, D> {
|
|
63
|
+
private readonly listeners;
|
|
64
|
+
/**
|
|
65
|
+
* Has specific type events
|
|
66
|
+
* @param type Type
|
|
67
|
+
*/
|
|
68
|
+
hasEvents(type: T): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Has specific type and callback events
|
|
71
|
+
* @param type Type
|
|
72
|
+
* @param callback Callback
|
|
73
|
+
*/
|
|
74
|
+
hasEvents(type: T, callback: EventClassCallback<T, D>): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Remove all specific type events
|
|
77
|
+
* @param type Type
|
|
78
|
+
*/
|
|
79
|
+
off(type: T): void;
|
|
80
|
+
/**
|
|
81
|
+
* Remove specific type and callback event
|
|
82
|
+
* @param type Type
|
|
83
|
+
* @param callback Callback
|
|
84
|
+
*/
|
|
85
|
+
off(type: T, callback: EventClassCallback<T, D>): void;
|
|
86
|
+
/**
|
|
87
|
+
* Add event listeners
|
|
88
|
+
* @param collection Collection of events
|
|
89
|
+
*/
|
|
90
|
+
on(collection: EventClassCollection<T, D>): void;
|
|
91
|
+
/**
|
|
92
|
+
* Add event listener
|
|
93
|
+
* @param type Type
|
|
94
|
+
* @param callback Callback
|
|
95
|
+
* @param options Options
|
|
96
|
+
*/
|
|
97
|
+
on(type: T, callback: EventClassCallback<T, D>, options?: EventOptions): void;
|
|
98
|
+
/**
|
|
99
|
+
* Trigger event
|
|
100
|
+
* @param event Event
|
|
101
|
+
*/
|
|
102
|
+
trigger(event: EventBase<T, D>): void;
|
|
103
|
+
}
|
|
104
|
+
export {};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract event base class
|
|
3
|
+
* T for type
|
|
4
|
+
* D for data
|
|
5
|
+
*/
|
|
6
|
+
export class EventBase {
|
|
7
|
+
/**
|
|
8
|
+
* Constructor
|
|
9
|
+
* @param type Type
|
|
10
|
+
*/
|
|
11
|
+
constructor(target, type, data) {
|
|
12
|
+
this.target = target;
|
|
13
|
+
this.type = type;
|
|
14
|
+
this.data = data;
|
|
15
|
+
this._propagationStopped = false;
|
|
16
|
+
this._timeStamp = Date.now();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* stopImmediatePropagation called
|
|
20
|
+
*/
|
|
21
|
+
get propagationStopped() {
|
|
22
|
+
return this._propagationStopped;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Time stamp
|
|
26
|
+
*/
|
|
27
|
+
get timeStamp() {
|
|
28
|
+
return this._timeStamp;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Prevent all other listeners from being called
|
|
32
|
+
*/
|
|
33
|
+
stopImmediatePropagation() {
|
|
34
|
+
this._propagationStopped = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Event class
|
|
39
|
+
* T for type
|
|
40
|
+
* D for data
|
|
41
|
+
*/
|
|
42
|
+
export class EventClass {
|
|
43
|
+
constructor() {
|
|
44
|
+
// Listeners
|
|
45
|
+
this.listeners = new Map();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Has specific type and callback events
|
|
49
|
+
* @param type Type
|
|
50
|
+
* @param callback Callback
|
|
51
|
+
* @returns Result
|
|
52
|
+
*/
|
|
53
|
+
hasEvents(type, callback) {
|
|
54
|
+
const items = this.listeners.get(type);
|
|
55
|
+
if (items == null || items.length === 0)
|
|
56
|
+
return false;
|
|
57
|
+
if (callback) {
|
|
58
|
+
return items.some((item) => item[0] == callback);
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Remove specific type and callback event
|
|
64
|
+
* @param type Type
|
|
65
|
+
* @param callback Callback
|
|
66
|
+
*/
|
|
67
|
+
off(type, callback) {
|
|
68
|
+
if (callback == null) {
|
|
69
|
+
this.listeners.delete(type);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const items = this.listeners.get(type);
|
|
73
|
+
if (items == null)
|
|
74
|
+
return;
|
|
75
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
76
|
+
if (items[i][0] == callback) {
|
|
77
|
+
items.splice(i, 1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Add events
|
|
83
|
+
* @param type Type
|
|
84
|
+
* @param callback Callback
|
|
85
|
+
* @param options Options
|
|
86
|
+
*/
|
|
87
|
+
on(type, callback, options) {
|
|
88
|
+
var _a, _b;
|
|
89
|
+
if (typeof type === 'object') {
|
|
90
|
+
for (const key in type) {
|
|
91
|
+
const item = key;
|
|
92
|
+
const itemCallback = (_a = type[item]) !== null && _a !== void 0 ? _a : callback;
|
|
93
|
+
if (itemCallback)
|
|
94
|
+
this.on(item, itemCallback, options);
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (callback == null)
|
|
99
|
+
return;
|
|
100
|
+
this.listeners.has(type) || this.listeners.set(type, []);
|
|
101
|
+
(_b = this.listeners.get(type)) === null || _b === void 0 ? void 0 : _b.push([callback, options]);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Trigger event
|
|
105
|
+
* @param event Event
|
|
106
|
+
*/
|
|
107
|
+
trigger(event) {
|
|
108
|
+
const items = this.listeners.get(event.type);
|
|
109
|
+
if (items == null)
|
|
110
|
+
return;
|
|
111
|
+
// Len
|
|
112
|
+
const len = items.length;
|
|
113
|
+
if (len === 0)
|
|
114
|
+
return;
|
|
115
|
+
// Need to be removed indicies
|
|
116
|
+
const indicies = [];
|
|
117
|
+
// Capture items first
|
|
118
|
+
let stopped = false;
|
|
119
|
+
for (let c = 0; c < len; c++) {
|
|
120
|
+
const item = items[c];
|
|
121
|
+
const [callback, options] = item;
|
|
122
|
+
if (options == null || !options.capture)
|
|
123
|
+
continue;
|
|
124
|
+
callback(event);
|
|
125
|
+
if (options.once) {
|
|
126
|
+
indicies.push(c);
|
|
127
|
+
}
|
|
128
|
+
if (event.propagationStopped) {
|
|
129
|
+
stopped = true;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (!stopped) {
|
|
134
|
+
for (let c = 0; c < len; c++) {
|
|
135
|
+
const item = items[c];
|
|
136
|
+
const [callback, options] = item;
|
|
137
|
+
if (options === null || options === void 0 ? void 0 : options.capture)
|
|
138
|
+
continue;
|
|
139
|
+
callback(event);
|
|
140
|
+
if (options === null || options === void 0 ? void 0 : options.once) {
|
|
141
|
+
indicies.push(c);
|
|
142
|
+
}
|
|
143
|
+
if (event.propagationStopped) {
|
|
144
|
+
stopped = true;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Remove all once handlers
|
|
150
|
+
for (let i = indicies.length - 1; i >= 0; i--) {
|
|
151
|
+
items.splice(indicies[i], 1);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { EventBase, EventClass } from './EventClass';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ETSOO Extended history event type
|
|
5
|
+
*/
|
|
6
|
+
export type EHistoryEventType = 'navigate' | 'push' | 'replace' | 'clear';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* ETSOO Extended history event data
|
|
10
|
+
*/
|
|
11
|
+
export interface EHistoryEventData {
|
|
12
|
+
/**
|
|
13
|
+
* Current index
|
|
14
|
+
*/
|
|
15
|
+
index: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* ETSOO Extended abstract history class
|
|
20
|
+
*/
|
|
21
|
+
export abstract class EHistory<
|
|
22
|
+
T,
|
|
23
|
+
D extends EHistoryEventData
|
|
24
|
+
> extends EventClass<EHistoryEventType, D> {
|
|
25
|
+
// Index
|
|
26
|
+
private _index: number = -1;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* States
|
|
30
|
+
*/
|
|
31
|
+
readonly states: T[] = [];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* States length
|
|
35
|
+
*/
|
|
36
|
+
get length() {
|
|
37
|
+
return this.states.length;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get current state index
|
|
42
|
+
*/
|
|
43
|
+
get index() {
|
|
44
|
+
return this._index;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get current state
|
|
49
|
+
*/
|
|
50
|
+
get state() {
|
|
51
|
+
if (this._index === -1) return undefined;
|
|
52
|
+
return this.states[this._index];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Back to the previous state
|
|
57
|
+
*/
|
|
58
|
+
back() {
|
|
59
|
+
this.go(-1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Clear all states but keep event listeners
|
|
64
|
+
*/
|
|
65
|
+
clear() {
|
|
66
|
+
// https://stackoverflow.com/questions/1232040/how-do-i-empty-an-array-in-javascript
|
|
67
|
+
this.states.length = 0;
|
|
68
|
+
this._index = -1;
|
|
69
|
+
this.trigger(this.createEvent('clear', this._index));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Create event
|
|
74
|
+
* @param type Type
|
|
75
|
+
* @param index Current index
|
|
76
|
+
*/
|
|
77
|
+
protected abstract createEvent(
|
|
78
|
+
type: EHistoryEventType,
|
|
79
|
+
index: number
|
|
80
|
+
): EventBase<EHistoryEventType, D>;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Forward to the next state
|
|
84
|
+
*/
|
|
85
|
+
forward() {
|
|
86
|
+
this.go(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Go to the specific state
|
|
91
|
+
* @param delta A negative value moves backwards, a positive value moves forwards
|
|
92
|
+
*/
|
|
93
|
+
go(delta: number) {
|
|
94
|
+
// No data
|
|
95
|
+
if (this._index === -1) return undefined;
|
|
96
|
+
|
|
97
|
+
// New index
|
|
98
|
+
const newIndex = this._index + delta;
|
|
99
|
+
|
|
100
|
+
// Not in the range
|
|
101
|
+
if (newIndex < 0 || newIndex >= this.length) return undefined;
|
|
102
|
+
|
|
103
|
+
// Update the index
|
|
104
|
+
this._index = newIndex;
|
|
105
|
+
|
|
106
|
+
// Trigger event
|
|
107
|
+
this.trigger(this.createEvent('navigate', newIndex));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Adds an entry to the history stack
|
|
112
|
+
* @param state State
|
|
113
|
+
*/
|
|
114
|
+
pushState(state: T) {
|
|
115
|
+
if (this._index >= 0) {
|
|
116
|
+
// Remove states after the index
|
|
117
|
+
this.states.splice(this._index + 1);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this.states.push(state);
|
|
121
|
+
this._index++;
|
|
122
|
+
this.trigger(this.createEvent('push', this._index));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Modifies the current history entry
|
|
127
|
+
* @param state State
|
|
128
|
+
*/
|
|
129
|
+
replaceState(state: T) {
|
|
130
|
+
if (this._index === -1) return;
|
|
131
|
+
this.states[this._index] = state;
|
|
132
|
+
this.trigger(this.createEvent('replace', this._index));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract event base class
|
|
3
|
+
* T for type
|
|
4
|
+
* D for data
|
|
5
|
+
*/
|
|
6
|
+
export abstract class EventBase<T extends string, D> {
|
|
7
|
+
private _propagationStopped: boolean = false;
|
|
8
|
+
/**
|
|
9
|
+
* stopImmediatePropagation called
|
|
10
|
+
*/
|
|
11
|
+
get propagationStopped() {
|
|
12
|
+
return this._propagationStopped;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private _timeStamp: number;
|
|
16
|
+
/**
|
|
17
|
+
* Time stamp
|
|
18
|
+
*/
|
|
19
|
+
get timeStamp() {
|
|
20
|
+
return this._timeStamp;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Constructor
|
|
25
|
+
* @param type Type
|
|
26
|
+
*/
|
|
27
|
+
constructor(
|
|
28
|
+
public readonly target: EventClass<T, D>,
|
|
29
|
+
public readonly type: T,
|
|
30
|
+
public readonly data: D
|
|
31
|
+
) {
|
|
32
|
+
this._timeStamp = Date.now();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Prevent all other listeners from being called
|
|
37
|
+
*/
|
|
38
|
+
stopImmediatePropagation() {
|
|
39
|
+
this._propagationStopped = true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Event options
|
|
45
|
+
*/
|
|
46
|
+
interface EventOptions {
|
|
47
|
+
/**
|
|
48
|
+
* A boolean value indicating that events of this type will be dispatched first
|
|
49
|
+
*/
|
|
50
|
+
capture?: boolean;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A boolean value indicating that the listener should be invoked at most once after being added
|
|
54
|
+
*/
|
|
55
|
+
once?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Event class callback
|
|
60
|
+
* T for type
|
|
61
|
+
* D for data
|
|
62
|
+
*/
|
|
63
|
+
export type EventClassCallback<T extends string, D> = (
|
|
64
|
+
event: EventBase<T, D>
|
|
65
|
+
) => void;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Event class collection definition
|
|
69
|
+
* T for type
|
|
70
|
+
* D for data
|
|
71
|
+
*/
|
|
72
|
+
export type EventClassCollection<T extends string, D> = {
|
|
73
|
+
[key in T]?: EventClassCallback<T, D>;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Event class
|
|
78
|
+
* T for type
|
|
79
|
+
* D for data
|
|
80
|
+
*/
|
|
81
|
+
export abstract class EventClass<T extends string, D> {
|
|
82
|
+
// Listeners
|
|
83
|
+
private readonly listeners = new Map<
|
|
84
|
+
T,
|
|
85
|
+
[EventClassCallback<T, D>, EventOptions?][]
|
|
86
|
+
>();
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Has specific type events
|
|
90
|
+
* @param type Type
|
|
91
|
+
*/
|
|
92
|
+
hasEvents(type: T): boolean;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Has specific type and callback events
|
|
96
|
+
* @param type Type
|
|
97
|
+
* @param callback Callback
|
|
98
|
+
*/
|
|
99
|
+
hasEvents(type: T, callback: EventClassCallback<T, D>): boolean;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Has specific type and callback events
|
|
103
|
+
* @param type Type
|
|
104
|
+
* @param callback Callback
|
|
105
|
+
* @returns Result
|
|
106
|
+
*/
|
|
107
|
+
hasEvents(type: T, callback?: EventClassCallback<T, D>) {
|
|
108
|
+
const items = this.listeners.get(type);
|
|
109
|
+
if (items == null || items.length === 0) return false;
|
|
110
|
+
|
|
111
|
+
if (callback) {
|
|
112
|
+
return items.some((item) => item[0] == callback);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Remove all specific type events
|
|
120
|
+
* @param type Type
|
|
121
|
+
*/
|
|
122
|
+
off(type: T): void;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Remove specific type and callback event
|
|
126
|
+
* @param type Type
|
|
127
|
+
* @param callback Callback
|
|
128
|
+
*/
|
|
129
|
+
off(type: T, callback: EventClassCallback<T, D>): void;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Remove specific type and callback event
|
|
133
|
+
* @param type Type
|
|
134
|
+
* @param callback Callback
|
|
135
|
+
*/
|
|
136
|
+
off(type: T, callback?: EventClassCallback<T, D>) {
|
|
137
|
+
if (callback == null) {
|
|
138
|
+
this.listeners.delete(type);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const items = this.listeners.get(type);
|
|
143
|
+
if (items == null) return;
|
|
144
|
+
|
|
145
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
146
|
+
if (items[i][0] == callback) {
|
|
147
|
+
items.splice(i, 1);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Add event listeners
|
|
154
|
+
* @param collection Collection of events
|
|
155
|
+
*/
|
|
156
|
+
on(collection: EventClassCollection<T, D>): void;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Add event listener
|
|
160
|
+
* @param type Type
|
|
161
|
+
* @param callback Callback
|
|
162
|
+
* @param options Options
|
|
163
|
+
*/
|
|
164
|
+
on(
|
|
165
|
+
type: T,
|
|
166
|
+
callback: EventClassCallback<T, D>,
|
|
167
|
+
options?: EventOptions
|
|
168
|
+
): void;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Add events
|
|
172
|
+
* @param type Type
|
|
173
|
+
* @param callback Callback
|
|
174
|
+
* @param options Options
|
|
175
|
+
*/
|
|
176
|
+
on(
|
|
177
|
+
type: EventClassCollection<T, D> | T,
|
|
178
|
+
callback?: EventClassCallback<T, D>,
|
|
179
|
+
options?: EventOptions
|
|
180
|
+
) {
|
|
181
|
+
if (typeof type === 'object') {
|
|
182
|
+
for (const key in type) {
|
|
183
|
+
const item = key as T;
|
|
184
|
+
const itemCallback = type[item] ?? callback;
|
|
185
|
+
if (itemCallback) this.on(item, itemCallback, options);
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (callback == null) return;
|
|
191
|
+
this.listeners.has(type) || this.listeners.set(type, []);
|
|
192
|
+
this.listeners.get(type)?.push([callback, options]);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Trigger event
|
|
197
|
+
* @param event Event
|
|
198
|
+
*/
|
|
199
|
+
trigger(event: EventBase<T, D>) {
|
|
200
|
+
const items = this.listeners.get(event.type);
|
|
201
|
+
if (items == null) return;
|
|
202
|
+
|
|
203
|
+
// Len
|
|
204
|
+
const len = items.length;
|
|
205
|
+
if (len === 0) return;
|
|
206
|
+
|
|
207
|
+
// Need to be removed indicies
|
|
208
|
+
const indicies: number[] = [];
|
|
209
|
+
|
|
210
|
+
// Capture items first
|
|
211
|
+
let stopped: boolean = false;
|
|
212
|
+
for (let c = 0; c < len; c++) {
|
|
213
|
+
const item = items[c];
|
|
214
|
+
const [callback, options] = item;
|
|
215
|
+
if (options == null || !options.capture) continue;
|
|
216
|
+
|
|
217
|
+
callback(event);
|
|
218
|
+
|
|
219
|
+
if (options.once) {
|
|
220
|
+
indicies.push(c);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (event.propagationStopped) {
|
|
224
|
+
stopped = true;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!stopped) {
|
|
230
|
+
for (let c = 0; c < len; c++) {
|
|
231
|
+
const item = items[c];
|
|
232
|
+
const [callback, options] = item;
|
|
233
|
+
if (options?.capture) continue;
|
|
234
|
+
|
|
235
|
+
callback(event);
|
|
236
|
+
|
|
237
|
+
if (options?.once) {
|
|
238
|
+
indicies.push(c);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (event.propagationStopped) {
|
|
242
|
+
stopped = true;
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Remove all once handlers
|
|
249
|
+
for (let i = indicies.length - 1; i >= 0; i--) {
|
|
250
|
+
items.splice(indicies[i], 1);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|