@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/shared",
3
- "version": "1.1.26",
3
+ "version": "1.1.29",
4
4
  "description": "TypeScript shared utilities and functions",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/mjs/index.js",
@@ -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
+ }