@brandup/ui 1.0.1 → 1.0.3

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