@brandup/ui 1.0.1
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 +77 -0
- package/dist/cjs/index.js +229 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/mjs/index.js +223 -0
- package/dist/mjs/index.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# brandup-ui
|
|
2
|
+
|
|
3
|
+
[]()
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install NPM package [@brandup/ui](https://www.npmjs.com/package/@brandup/ui).
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
npm i @brandup/ui@latest
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## UIElement
|
|
14
|
+
|
|
15
|
+
`UIElement` - wrapper для `HTMLElement`, который позволяет привязать к нему свою бизнес логику.
|
|
16
|
+
|
|
17
|
+
Возможности:
|
|
18
|
+
- Обработка комманд вызванных внутри `HTMLElement`, который связан с `UIElement`.
|
|
19
|
+
- Обработка событий элемента `HTMLElement`, который связан с `UIElement`.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
abstract class UIElement {
|
|
23
|
+
abstract typeName: string;
|
|
24
|
+
readonly element: HTMLElement;
|
|
25
|
+
|
|
26
|
+
protected setElement(elem: HTMLElement): void;
|
|
27
|
+
|
|
28
|
+
protected defineEvent(eventName: string, eventOptions?: IEventOptions): void;
|
|
29
|
+
protected raiseEvent(eventName: string, eventArgs?: any): boolean;
|
|
30
|
+
|
|
31
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
32
|
+
removeEventListener(type: string, listener?: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
|
33
|
+
dispatchEvent(event: Event): boolean;
|
|
34
|
+
|
|
35
|
+
registerCommand(name: string, execute: CommandDelegate, canExecute: CommandCanExecuteDelegate = null): void;
|
|
36
|
+
registerAsyncCommand(name: string, delegate: CommandAsyncDelegate): void;
|
|
37
|
+
hasCommand(name: string): boolean;
|
|
38
|
+
execCommand(name: string, elem: HTMLElement): CommandExecutionResult;
|
|
39
|
+
|
|
40
|
+
protected _onRenderElement(_elem: HTMLElement);
|
|
41
|
+
protected _onCanExecCommand(_name: string, _elem: HTMLElement): boolean;
|
|
42
|
+
|
|
43
|
+
destroy(): void;
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Command handling
|
|
48
|
+
|
|
49
|
+
Класс UIElement позволяет регистрировать обработчики комманд, которые определяются в разметке HTML элемента.
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
<button data-command="send">Send</button>
|
|
53
|
+
|
|
54
|
+
this.registerCommand("send", (elem: HTMLElement, context: CommandContext) => { elem.innerHTML = "ok"; });
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Так же можно регистрировать асинхронные команды:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
this.registerAsyncCommand("command1-async", (context: CommandAsyncContext) => {
|
|
61
|
+
context.timeout = 3000;
|
|
62
|
+
|
|
63
|
+
context.target.innerHTML = "Loading...";
|
|
64
|
+
const t = window.setTimeout(() => {
|
|
65
|
+
context.target.innerHTML = "Ok";
|
|
66
|
+
context.complate();
|
|
67
|
+
}, 2000);
|
|
68
|
+
|
|
69
|
+
context.timeoutCallback = () => {
|
|
70
|
+
clearTimeout(t);
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Команды вызываются по событию click.
|
|
76
|
+
|
|
77
|
+
Во время выполнения команды, у элемента добавляется стиль **executing**.
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ElemAttributeName = "uiElement";
|
|
4
|
+
const ElemPropertyName = "brandupUiElement";
|
|
5
|
+
const CommandAttributeName = "command";
|
|
6
|
+
const CommandExecutingCssClassName = "executing";
|
|
7
|
+
class UIElement {
|
|
8
|
+
__element = null;
|
|
9
|
+
__events = {};
|
|
10
|
+
__commandHandlers = {};
|
|
11
|
+
// Element members
|
|
12
|
+
get element() { return this.__element; }
|
|
13
|
+
setElement(elem) {
|
|
14
|
+
if (!elem)
|
|
15
|
+
throw "Not set value elem.";
|
|
16
|
+
if (this.__element || UIElement.hasElement(elem))
|
|
17
|
+
throw "UIElement already defined";
|
|
18
|
+
this.__element = elem;
|
|
19
|
+
this.__element[ElemPropertyName] = this;
|
|
20
|
+
this.__element.dataset[ElemAttributeName] = this.typeName;
|
|
21
|
+
this.defineEvent("command", { cancelable: false, bubbles: true });
|
|
22
|
+
this._onRenderElement(elem);
|
|
23
|
+
}
|
|
24
|
+
// static members
|
|
25
|
+
static hasElement(elem) {
|
|
26
|
+
return !!elem.dataset[ElemAttributeName];
|
|
27
|
+
}
|
|
28
|
+
// HTMLElement event members
|
|
29
|
+
defineEvent(eventName, eventOptions) {
|
|
30
|
+
this.__events[eventName] = eventOptions ? eventOptions : null;
|
|
31
|
+
}
|
|
32
|
+
raiseEvent(eventName, eventArgs) {
|
|
33
|
+
if (!(eventName in this.__events))
|
|
34
|
+
throw new Error(`Not found event "${eventName}".`);
|
|
35
|
+
const eventOptions = this.__events[eventName];
|
|
36
|
+
const eventInit = {};
|
|
37
|
+
if (eventOptions) {
|
|
38
|
+
if (eventOptions.bubbles)
|
|
39
|
+
eventInit.bubbles = eventOptions.bubbles;
|
|
40
|
+
if (eventOptions.cancelable)
|
|
41
|
+
eventInit.cancelable = eventOptions.cancelable;
|
|
42
|
+
if (eventOptions.composed)
|
|
43
|
+
eventInit.composed = eventOptions.composed;
|
|
44
|
+
}
|
|
45
|
+
eventInit.detail = eventArgs ? eventArgs : {};
|
|
46
|
+
const event = new CustomEvent(eventName, eventInit);
|
|
47
|
+
return this.dispatchEvent(event);
|
|
48
|
+
}
|
|
49
|
+
addEventListener(type, listener, options) {
|
|
50
|
+
this.__element?.addEventListener(type, listener, options);
|
|
51
|
+
}
|
|
52
|
+
removeEventListener(type, listener, options) {
|
|
53
|
+
this.__element?.removeEventListener(type, listener, options);
|
|
54
|
+
}
|
|
55
|
+
dispatchEvent(event) {
|
|
56
|
+
if (!this.__element)
|
|
57
|
+
throw new Error("HTMLElement is not defined.");
|
|
58
|
+
return this.__element.dispatchEvent(event);
|
|
59
|
+
}
|
|
60
|
+
// Command members
|
|
61
|
+
registerCommand(name, execute, canExecute) {
|
|
62
|
+
name = this.verifyCommandName(name);
|
|
63
|
+
this.__commandHandlers[name] = {
|
|
64
|
+
name: name,
|
|
65
|
+
execute,
|
|
66
|
+
canExecute,
|
|
67
|
+
isExecuting: false
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
registerAsyncCommand(name, delegate, canExecute) {
|
|
71
|
+
name = this.verifyCommandName(name);
|
|
72
|
+
this.__commandHandlers[name] = {
|
|
73
|
+
name: name,
|
|
74
|
+
delegate,
|
|
75
|
+
canExecute,
|
|
76
|
+
isExecuting: false
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
hasCommand(name) {
|
|
80
|
+
return name.toLowerCase() in this.__commandHandlers;
|
|
81
|
+
}
|
|
82
|
+
execCommand(name, elem) {
|
|
83
|
+
if (!this.__element)
|
|
84
|
+
throw new Error("UIElement is not set HTMLElement.");
|
|
85
|
+
const key = name.toLowerCase();
|
|
86
|
+
if (!(key in this.__commandHandlers))
|
|
87
|
+
throw new Error(`Command "${name}" is not registered.`);
|
|
88
|
+
const context = {
|
|
89
|
+
target: elem,
|
|
90
|
+
uiElem: this,
|
|
91
|
+
transparent: false
|
|
92
|
+
};
|
|
93
|
+
const handler = this.__commandHandlers[key];
|
|
94
|
+
if (handler.isExecuting)
|
|
95
|
+
return { result: exports.CommandsExecStatus.AlreadyExecuting, context };
|
|
96
|
+
handler.isExecuting = true;
|
|
97
|
+
if (!this._onCanExecCommand(name, elem)) {
|
|
98
|
+
handler.isExecuting = false;
|
|
99
|
+
return { result: exports.CommandsExecStatus.NotAllow, context };
|
|
100
|
+
}
|
|
101
|
+
if (handler.canExecute && !handler.canExecute(elem, context)) {
|
|
102
|
+
handler.isExecuting = false;
|
|
103
|
+
return { result: exports.CommandsExecStatus.NotAllow, context };
|
|
104
|
+
}
|
|
105
|
+
this.raiseEvent("command", {
|
|
106
|
+
name: handler.name,
|
|
107
|
+
uiElem: this,
|
|
108
|
+
elem: this.__element
|
|
109
|
+
});
|
|
110
|
+
if (handler.execute) {
|
|
111
|
+
// Если команда синхронная.
|
|
112
|
+
try {
|
|
113
|
+
handler.execute(elem, context);
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
handler.isExecuting = false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (handler.delegate) {
|
|
120
|
+
// Если команда асинхронная.
|
|
121
|
+
elem.classList.add(CommandExecutingCssClassName);
|
|
122
|
+
let timeoutId = 0;
|
|
123
|
+
const endFunc = () => {
|
|
124
|
+
handler.isExecuting = false;
|
|
125
|
+
elem.classList.remove(CommandExecutingCssClassName);
|
|
126
|
+
};
|
|
127
|
+
const asyncContext = {
|
|
128
|
+
target: elem,
|
|
129
|
+
uiElem: this,
|
|
130
|
+
transparent: context.transparent,
|
|
131
|
+
complate: () => {
|
|
132
|
+
clearTimeout(timeoutId);
|
|
133
|
+
endFunc();
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const handlerResult = handler.delegate(asyncContext);
|
|
137
|
+
if (handlerResult && handlerResult instanceof Promise)
|
|
138
|
+
handlerResult.finally(() => asyncContext.complate());
|
|
139
|
+
if (handler.isExecuting && asyncContext.timeout) {
|
|
140
|
+
timeoutId = window.setTimeout(() => {
|
|
141
|
+
if (asyncContext.timeoutCallback)
|
|
142
|
+
asyncContext.timeoutCallback();
|
|
143
|
+
endFunc();
|
|
144
|
+
}, asyncContext.timeout);
|
|
145
|
+
}
|
|
146
|
+
context.transparent = asyncContext.transparent;
|
|
147
|
+
}
|
|
148
|
+
else
|
|
149
|
+
throw new Error("Not set command execute flow.");
|
|
150
|
+
return { result: exports.CommandsExecStatus.Success, context: context };
|
|
151
|
+
}
|
|
152
|
+
verifyCommandName(name) {
|
|
153
|
+
const key = name.toLowerCase();
|
|
154
|
+
if (key in this.__commandHandlers)
|
|
155
|
+
throw new Error(`Command "${name}" already registered.`);
|
|
156
|
+
return key;
|
|
157
|
+
}
|
|
158
|
+
_onRenderElement(_elem) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
_onCanExecCommand(_name, _elem) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
destroy() {
|
|
165
|
+
const elem = this.__element;
|
|
166
|
+
if (!elem)
|
|
167
|
+
return;
|
|
168
|
+
this.__element = null;
|
|
169
|
+
delete elem.dataset[ElemAttributeName];
|
|
170
|
+
delete elem[ElemPropertyName];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
exports.CommandsExecStatus = void 0;
|
|
174
|
+
(function (CommandsExecStatus) {
|
|
175
|
+
CommandsExecStatus[CommandsExecStatus["NotAllow"] = 1] = "NotAllow";
|
|
176
|
+
CommandsExecStatus[CommandsExecStatus["AlreadyExecuting"] = 2] = "AlreadyExecuting";
|
|
177
|
+
CommandsExecStatus[CommandsExecStatus["Success"] = 3] = "Success";
|
|
178
|
+
})(exports.CommandsExecStatus || (exports.CommandsExecStatus = {}));
|
|
179
|
+
const fundUiElementByCommand = (elem, commandName) => {
|
|
180
|
+
while (elem) {
|
|
181
|
+
if (elem.dataset[ElemAttributeName]) {
|
|
182
|
+
const uiElem = elem[ElemPropertyName];
|
|
183
|
+
if (uiElem.hasCommand(commandName))
|
|
184
|
+
return uiElem;
|
|
185
|
+
}
|
|
186
|
+
if (typeof elem.parentElement === "undefined")
|
|
187
|
+
elem = elem.parentNode;
|
|
188
|
+
else if (elem.parentElement)
|
|
189
|
+
elem = elem.parentElement;
|
|
190
|
+
else
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
};
|
|
195
|
+
const commandClickHandler = (e) => {
|
|
196
|
+
let commandElem = e.target;
|
|
197
|
+
while (commandElem) {
|
|
198
|
+
if (commandElem.dataset[CommandAttributeName])
|
|
199
|
+
break;
|
|
200
|
+
if (commandElem === e.currentTarget)
|
|
201
|
+
return;
|
|
202
|
+
commandElem = commandElem.parentElement;
|
|
203
|
+
}
|
|
204
|
+
if (!commandElem)
|
|
205
|
+
return;
|
|
206
|
+
const commandName = commandElem.dataset[CommandAttributeName];
|
|
207
|
+
if (!commandName)
|
|
208
|
+
throw new Error("Command data attribute is not have value.");
|
|
209
|
+
const uiElem = fundUiElementByCommand(commandElem, commandName);
|
|
210
|
+
if (uiElem === null) {
|
|
211
|
+
console.warn(`Not find handler for command "${commandName}".`);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
const commandResult = uiElem.execCommand(commandName, commandElem);
|
|
215
|
+
if (commandResult.context.transparent)
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
e.preventDefault();
|
|
219
|
+
e.stopPropagation();
|
|
220
|
+
e.stopImmediatePropagation();
|
|
221
|
+
};
|
|
222
|
+
window.addEventListener("click", commandClickHandler, false);
|
|
223
|
+
|
|
224
|
+
exports.CommandAttributeName = CommandAttributeName;
|
|
225
|
+
exports.CommandExecutingCssClassName = CommandExecutingCssClassName;
|
|
226
|
+
exports.ElemAttributeName = ElemAttributeName;
|
|
227
|
+
exports.ElemPropertyName = ElemPropertyName;
|
|
228
|
+
exports.UIElement = UIElement;
|
|
229
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
const ElemAttributeName = "uiElement";
|
|
2
|
+
const ElemPropertyName = "brandupUiElement";
|
|
3
|
+
const CommandAttributeName = "command";
|
|
4
|
+
const CommandExecutingCssClassName = "executing";
|
|
5
|
+
class UIElement {
|
|
6
|
+
__element = null;
|
|
7
|
+
__events = {};
|
|
8
|
+
__commandHandlers = {};
|
|
9
|
+
// Element members
|
|
10
|
+
get element() { return this.__element; }
|
|
11
|
+
setElement(elem) {
|
|
12
|
+
if (!elem)
|
|
13
|
+
throw "Not set value elem.";
|
|
14
|
+
if (this.__element || UIElement.hasElement(elem))
|
|
15
|
+
throw "UIElement already defined";
|
|
16
|
+
this.__element = elem;
|
|
17
|
+
this.__element[ElemPropertyName] = this;
|
|
18
|
+
this.__element.dataset[ElemAttributeName] = this.typeName;
|
|
19
|
+
this.defineEvent("command", { cancelable: false, bubbles: true });
|
|
20
|
+
this._onRenderElement(elem);
|
|
21
|
+
}
|
|
22
|
+
// static members
|
|
23
|
+
static hasElement(elem) {
|
|
24
|
+
return !!elem.dataset[ElemAttributeName];
|
|
25
|
+
}
|
|
26
|
+
// HTMLElement event members
|
|
27
|
+
defineEvent(eventName, eventOptions) {
|
|
28
|
+
this.__events[eventName] = eventOptions ? eventOptions : null;
|
|
29
|
+
}
|
|
30
|
+
raiseEvent(eventName, eventArgs) {
|
|
31
|
+
if (!(eventName in this.__events))
|
|
32
|
+
throw new Error(`Not found event "${eventName}".`);
|
|
33
|
+
const eventOptions = this.__events[eventName];
|
|
34
|
+
const eventInit = {};
|
|
35
|
+
if (eventOptions) {
|
|
36
|
+
if (eventOptions.bubbles)
|
|
37
|
+
eventInit.bubbles = eventOptions.bubbles;
|
|
38
|
+
if (eventOptions.cancelable)
|
|
39
|
+
eventInit.cancelable = eventOptions.cancelable;
|
|
40
|
+
if (eventOptions.composed)
|
|
41
|
+
eventInit.composed = eventOptions.composed;
|
|
42
|
+
}
|
|
43
|
+
eventInit.detail = eventArgs ? eventArgs : {};
|
|
44
|
+
const event = new CustomEvent(eventName, eventInit);
|
|
45
|
+
return this.dispatchEvent(event);
|
|
46
|
+
}
|
|
47
|
+
addEventListener(type, listener, options) {
|
|
48
|
+
this.__element?.addEventListener(type, listener, options);
|
|
49
|
+
}
|
|
50
|
+
removeEventListener(type, listener, options) {
|
|
51
|
+
this.__element?.removeEventListener(type, listener, options);
|
|
52
|
+
}
|
|
53
|
+
dispatchEvent(event) {
|
|
54
|
+
if (!this.__element)
|
|
55
|
+
throw new Error("HTMLElement is not defined.");
|
|
56
|
+
return this.__element.dispatchEvent(event);
|
|
57
|
+
}
|
|
58
|
+
// Command members
|
|
59
|
+
registerCommand(name, execute, canExecute) {
|
|
60
|
+
name = this.verifyCommandName(name);
|
|
61
|
+
this.__commandHandlers[name] = {
|
|
62
|
+
name: name,
|
|
63
|
+
execute,
|
|
64
|
+
canExecute,
|
|
65
|
+
isExecuting: false
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
registerAsyncCommand(name, delegate, canExecute) {
|
|
69
|
+
name = this.verifyCommandName(name);
|
|
70
|
+
this.__commandHandlers[name] = {
|
|
71
|
+
name: name,
|
|
72
|
+
delegate,
|
|
73
|
+
canExecute,
|
|
74
|
+
isExecuting: false
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
hasCommand(name) {
|
|
78
|
+
return name.toLowerCase() in this.__commandHandlers;
|
|
79
|
+
}
|
|
80
|
+
execCommand(name, elem) {
|
|
81
|
+
if (!this.__element)
|
|
82
|
+
throw new Error("UIElement is not set HTMLElement.");
|
|
83
|
+
const key = name.toLowerCase();
|
|
84
|
+
if (!(key in this.__commandHandlers))
|
|
85
|
+
throw new Error(`Command "${name}" is not registered.`);
|
|
86
|
+
const context = {
|
|
87
|
+
target: elem,
|
|
88
|
+
uiElem: this,
|
|
89
|
+
transparent: false
|
|
90
|
+
};
|
|
91
|
+
const handler = this.__commandHandlers[key];
|
|
92
|
+
if (handler.isExecuting)
|
|
93
|
+
return { result: CommandsExecStatus.AlreadyExecuting, context };
|
|
94
|
+
handler.isExecuting = true;
|
|
95
|
+
if (!this._onCanExecCommand(name, elem)) {
|
|
96
|
+
handler.isExecuting = false;
|
|
97
|
+
return { result: CommandsExecStatus.NotAllow, context };
|
|
98
|
+
}
|
|
99
|
+
if (handler.canExecute && !handler.canExecute(elem, context)) {
|
|
100
|
+
handler.isExecuting = false;
|
|
101
|
+
return { result: CommandsExecStatus.NotAllow, context };
|
|
102
|
+
}
|
|
103
|
+
this.raiseEvent("command", {
|
|
104
|
+
name: handler.name,
|
|
105
|
+
uiElem: this,
|
|
106
|
+
elem: this.__element
|
|
107
|
+
});
|
|
108
|
+
if (handler.execute) {
|
|
109
|
+
// Если команда синхронная.
|
|
110
|
+
try {
|
|
111
|
+
handler.execute(elem, context);
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
handler.isExecuting = false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else if (handler.delegate) {
|
|
118
|
+
// Если команда асинхронная.
|
|
119
|
+
elem.classList.add(CommandExecutingCssClassName);
|
|
120
|
+
let timeoutId = 0;
|
|
121
|
+
const endFunc = () => {
|
|
122
|
+
handler.isExecuting = false;
|
|
123
|
+
elem.classList.remove(CommandExecutingCssClassName);
|
|
124
|
+
};
|
|
125
|
+
const asyncContext = {
|
|
126
|
+
target: elem,
|
|
127
|
+
uiElem: this,
|
|
128
|
+
transparent: context.transparent,
|
|
129
|
+
complate: () => {
|
|
130
|
+
clearTimeout(timeoutId);
|
|
131
|
+
endFunc();
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const handlerResult = handler.delegate(asyncContext);
|
|
135
|
+
if (handlerResult && handlerResult instanceof Promise)
|
|
136
|
+
handlerResult.finally(() => asyncContext.complate());
|
|
137
|
+
if (handler.isExecuting && asyncContext.timeout) {
|
|
138
|
+
timeoutId = window.setTimeout(() => {
|
|
139
|
+
if (asyncContext.timeoutCallback)
|
|
140
|
+
asyncContext.timeoutCallback();
|
|
141
|
+
endFunc();
|
|
142
|
+
}, asyncContext.timeout);
|
|
143
|
+
}
|
|
144
|
+
context.transparent = asyncContext.transparent;
|
|
145
|
+
}
|
|
146
|
+
else
|
|
147
|
+
throw new Error("Not set command execute flow.");
|
|
148
|
+
return { result: CommandsExecStatus.Success, context: context };
|
|
149
|
+
}
|
|
150
|
+
verifyCommandName(name) {
|
|
151
|
+
const key = name.toLowerCase();
|
|
152
|
+
if (key in this.__commandHandlers)
|
|
153
|
+
throw new Error(`Command "${name}" already registered.`);
|
|
154
|
+
return key;
|
|
155
|
+
}
|
|
156
|
+
_onRenderElement(_elem) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
_onCanExecCommand(_name, _elem) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
destroy() {
|
|
163
|
+
const elem = this.__element;
|
|
164
|
+
if (!elem)
|
|
165
|
+
return;
|
|
166
|
+
this.__element = null;
|
|
167
|
+
delete elem.dataset[ElemAttributeName];
|
|
168
|
+
delete elem[ElemPropertyName];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
var CommandsExecStatus;
|
|
172
|
+
(function (CommandsExecStatus) {
|
|
173
|
+
CommandsExecStatus[CommandsExecStatus["NotAllow"] = 1] = "NotAllow";
|
|
174
|
+
CommandsExecStatus[CommandsExecStatus["AlreadyExecuting"] = 2] = "AlreadyExecuting";
|
|
175
|
+
CommandsExecStatus[CommandsExecStatus["Success"] = 3] = "Success";
|
|
176
|
+
})(CommandsExecStatus || (CommandsExecStatus = {}));
|
|
177
|
+
const fundUiElementByCommand = (elem, commandName) => {
|
|
178
|
+
while (elem) {
|
|
179
|
+
if (elem.dataset[ElemAttributeName]) {
|
|
180
|
+
const uiElem = elem[ElemPropertyName];
|
|
181
|
+
if (uiElem.hasCommand(commandName))
|
|
182
|
+
return uiElem;
|
|
183
|
+
}
|
|
184
|
+
if (typeof elem.parentElement === "undefined")
|
|
185
|
+
elem = elem.parentNode;
|
|
186
|
+
else if (elem.parentElement)
|
|
187
|
+
elem = elem.parentElement;
|
|
188
|
+
else
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
};
|
|
193
|
+
const commandClickHandler = (e) => {
|
|
194
|
+
let commandElem = e.target;
|
|
195
|
+
while (commandElem) {
|
|
196
|
+
if (commandElem.dataset[CommandAttributeName])
|
|
197
|
+
break;
|
|
198
|
+
if (commandElem === e.currentTarget)
|
|
199
|
+
return;
|
|
200
|
+
commandElem = commandElem.parentElement;
|
|
201
|
+
}
|
|
202
|
+
if (!commandElem)
|
|
203
|
+
return;
|
|
204
|
+
const commandName = commandElem.dataset[CommandAttributeName];
|
|
205
|
+
if (!commandName)
|
|
206
|
+
throw new Error("Command data attribute is not have value.");
|
|
207
|
+
const uiElem = fundUiElementByCommand(commandElem, commandName);
|
|
208
|
+
if (uiElem === null) {
|
|
209
|
+
console.warn(`Not find handler for command "${commandName}".`);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
const commandResult = uiElem.execCommand(commandName, commandElem);
|
|
213
|
+
if (commandResult.context.transparent)
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
e.preventDefault();
|
|
217
|
+
e.stopPropagation();
|
|
218
|
+
e.stopImmediatePropagation();
|
|
219
|
+
};
|
|
220
|
+
window.addEventListener("click", commandClickHandler, false);
|
|
221
|
+
|
|
222
|
+
export { CommandAttributeName, CommandExecutingCssClassName, CommandsExecStatus, ElemAttributeName, ElemPropertyName, UIElement };
|
|
223
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
declare const ElemAttributeName = "uiElement";
|
|
2
|
+
declare const ElemPropertyName = "brandupUiElement";
|
|
3
|
+
declare const CommandAttributeName = "command";
|
|
4
|
+
declare const CommandExecutingCssClassName = "executing";
|
|
5
|
+
type CommandDelegate = (elem: HTMLElement, context: CommandContext) => void;
|
|
6
|
+
type CommandCanExecuteDelegate = (elem: HTMLElement, context: CommandContext) => boolean;
|
|
7
|
+
type CommandAsyncDelegate = (context: CommandAsyncContext) => void | Promise<void>;
|
|
8
|
+
declare abstract class UIElement {
|
|
9
|
+
private __element;
|
|
10
|
+
private __events;
|
|
11
|
+
private __commandHandlers;
|
|
12
|
+
abstract typeName: string;
|
|
13
|
+
get element(): HTMLElement | null;
|
|
14
|
+
protected setElement(elem: HTMLElement): void;
|
|
15
|
+
static hasElement(elem: HTMLElement): boolean;
|
|
16
|
+
protected defineEvent(eventName: string, eventOptions?: EventOptions): void;
|
|
17
|
+
protected raiseEvent<T = {}>(eventName: string, eventArgs?: T): boolean;
|
|
18
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
19
|
+
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
|
20
|
+
dispatchEvent(event: Event): boolean;
|
|
21
|
+
registerCommand(name: string, execute: CommandDelegate, canExecute?: CommandCanExecuteDelegate): void;
|
|
22
|
+
registerAsyncCommand(name: string, delegate: CommandAsyncDelegate, canExecute?: CommandCanExecuteDelegate): void;
|
|
23
|
+
hasCommand(name: string): boolean;
|
|
24
|
+
execCommand(name: string, elem: HTMLElement): CommandExecutionResult;
|
|
25
|
+
private verifyCommandName;
|
|
26
|
+
protected _onRenderElement(_elem: HTMLElement): void;
|
|
27
|
+
protected _onCanExecCommand(_name: string, _elem: HTMLElement): boolean;
|
|
28
|
+
destroy(): void;
|
|
29
|
+
}
|
|
30
|
+
interface EventOptions {
|
|
31
|
+
bubbles?: boolean;
|
|
32
|
+
cancelable?: boolean;
|
|
33
|
+
composed?: boolean;
|
|
34
|
+
}
|
|
35
|
+
interface CommandEventArgs {
|
|
36
|
+
name: string;
|
|
37
|
+
uiElem: UIElement;
|
|
38
|
+
elem: HTMLElement;
|
|
39
|
+
}
|
|
40
|
+
interface CommandContext {
|
|
41
|
+
/** Елемент, который принял команду. */
|
|
42
|
+
target: HTMLElement;
|
|
43
|
+
uiElem: UIElement;
|
|
44
|
+
transparent: boolean;
|
|
45
|
+
}
|
|
46
|
+
interface CommandExecutionResult {
|
|
47
|
+
result: CommandsExecStatus;
|
|
48
|
+
context: CommandContext;
|
|
49
|
+
}
|
|
50
|
+
interface CommandAsyncContext extends CommandContext {
|
|
51
|
+
timeout?: number;
|
|
52
|
+
complate: VoidFunction;
|
|
53
|
+
timeoutCallback?: VoidFunction;
|
|
54
|
+
}
|
|
55
|
+
declare enum CommandsExecStatus {
|
|
56
|
+
NotAllow = 1,
|
|
57
|
+
AlreadyExecuting = 2,
|
|
58
|
+
Success = 3
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { type CommandAsyncContext, type CommandAsyncDelegate, CommandAttributeName, type CommandCanExecuteDelegate, type CommandContext, type CommandDelegate, type CommandEventArgs, CommandExecutingCssClassName, type CommandExecutionResult, CommandsExecStatus, ElemAttributeName, ElemPropertyName, type EventOptions, UIElement };
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@brandup/ui",
|
|
3
|
+
"description": "Lightweight UI framework on top of the DOM.",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"brandup",
|
|
6
|
+
"javascript",
|
|
7
|
+
"typescript",
|
|
8
|
+
"dom",
|
|
9
|
+
"ui"
|
|
10
|
+
],
|
|
11
|
+
"author": {
|
|
12
|
+
"name": "Dmitry Kovyazin",
|
|
13
|
+
"email": "it@brandup.online"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/brandup-online/brandup-ui",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/brandup-online/brandup-ui.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/brandup-online/brandup-ui/issues",
|
|
22
|
+
"email": "it@brandup.online"
|
|
23
|
+
},
|
|
24
|
+
"license": "Apache-2.0",
|
|
25
|
+
"version": "1.0.1",
|
|
26
|
+
"main": "dist/cjs/index.js",
|
|
27
|
+
"module": "dist/mjs/index.js",
|
|
28
|
+
"types": "dist/types.d.ts",
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@rollup/plugin-commonjs": "^25.0.8",
|
|
31
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
32
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
33
|
+
"rollup": "^4.19.0",
|
|
34
|
+
"rollup-plugin-dts": "^6.1.1",
|
|
35
|
+
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
36
|
+
"rollup-plugin-typescript2": "^0.36.0",
|
|
37
|
+
"tslib": "^2.6.3",
|
|
38
|
+
"typescript": "^5.5.3"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"README.md"
|
|
43
|
+
],
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "rollup -c --bundleConfigAsCjs",
|
|
46
|
+
"watch": "rollup -c -w --bundleConfigAsCjs"
|
|
47
|
+
}
|
|
48
|
+
}
|