swing-ui 0.1.25 → 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.
Files changed (116) hide show
  1. package/LICENSE +5 -4
  2. package/README.md +144 -7
  3. package/SwingUI.js +2611 -0
  4. package/classes/Arc.js +185 -0
  5. package/classes/Button.js +55 -0
  6. package/classes/CheckBox.js +33 -0
  7. package/classes/CheckBoxMenuItem.js +24 -0
  8. package/classes/ComboBox.js +329 -0
  9. package/classes/ComponentContainer.js +828 -0
  10. package/classes/ContextMenu.js +141 -0
  11. package/classes/DocumentEditor.js +168 -0
  12. package/classes/Ellipse.js +76 -0
  13. package/classes/InternalWindow.js +397 -0
  14. package/classes/InternalWindowContainer.js +93 -0
  15. package/classes/Label.js +52 -0
  16. package/classes/Line.js +105 -0
  17. package/classes/List.js +452 -0
  18. package/classes/MediaPlayer.js +504 -0
  19. package/classes/Menu.js +74 -0
  20. package/classes/MenuBar.js +56 -0
  21. package/classes/MenuItem.js +92 -0
  22. package/classes/Panel.js +50 -0
  23. package/classes/PasswordField.js +47 -0
  24. package/classes/Polygon.js +236 -0
  25. package/classes/Polyline.js +240 -0
  26. package/classes/ProgressBar.js +62 -0
  27. package/classes/RadioButton.js +110 -0
  28. package/classes/RadioButtonMenuItem.js +106 -0
  29. package/classes/Rectangle.js +199 -0
  30. package/classes/ScrollPane.js +53 -0
  31. package/classes/ScrollableComponent.js +141 -0
  32. package/classes/Separator.js +24 -0
  33. package/classes/Shape.js +584 -0
  34. package/classes/ShapeContainer.js +231 -0
  35. package/classes/Slider.js +172 -0
  36. package/classes/Spinner.js +172 -0
  37. package/classes/SplitView.js +82 -0
  38. package/classes/Stroke.js +115 -0
  39. package/classes/Table.js +982 -0
  40. package/classes/Tabs.js +228 -0
  41. package/classes/TextArea.js +125 -0
  42. package/classes/TextComponent.js +207 -0
  43. package/classes/TextField.js +128 -0
  44. package/classes/TextShape.js +123 -0
  45. package/classes/ToggleButton.js +147 -0
  46. package/classes/ToolBar.js +120 -0
  47. package/classes/TrayIcon.js +54 -0
  48. package/classes/Tree.js +569 -0
  49. package/classes/UIComponent.js +2113 -0
  50. package/classes/Window.js +609 -0
  51. package/enums.js +298 -0
  52. package/fonts.conf +32 -0
  53. package/functions.js +254 -0
  54. package/icon.png +0 -0
  55. package/index.js +109 -4
  56. package/jar/gui-11.jar +0 -0
  57. package/jar/lib/flatlaf.jar +0 -0
  58. package/jar/lib/jna.jar +0 -0
  59. package/package.json +58 -33
  60. package/typedefs.js +973 -0
  61. package/types/SwingUI.d.ts +727 -0
  62. package/types/classes/Arc.d.ts +111 -0
  63. package/types/classes/Button.d.ts +38 -0
  64. package/types/classes/CheckBox.d.ts +22 -0
  65. package/types/classes/CheckBoxMenuItem.d.ts +18 -0
  66. package/types/classes/ComboBox.d.ts +172 -0
  67. package/types/classes/ComponentContainer.d.ts +131 -0
  68. package/types/classes/ContextMenu.d.ts +74 -0
  69. package/types/classes/DocumentEditor.d.ts +95 -0
  70. package/types/classes/Ellipse.d.ts +34 -0
  71. package/types/classes/InternalWindow.d.ts +163 -0
  72. package/types/classes/InternalWindowContainer.d.ts +36 -0
  73. package/types/classes/Label.d.ts +33 -0
  74. package/types/classes/Line.d.ts +48 -0
  75. package/types/classes/List.d.ts +223 -0
  76. package/types/classes/MediaPlayer.d.ts +381 -0
  77. package/types/classes/Menu.d.ts +43 -0
  78. package/types/classes/MenuBar.d.ts +26 -0
  79. package/types/classes/MenuItem.d.ts +57 -0
  80. package/types/classes/Panel.d.ts +28 -0
  81. package/types/classes/PasswordField.d.ts +22 -0
  82. package/types/classes/Polygon.d.ts +93 -0
  83. package/types/classes/Polyline.d.ts +96 -0
  84. package/types/classes/ProgressBar.d.ts +36 -0
  85. package/types/classes/RadioButton.d.ts +70 -0
  86. package/types/classes/RadioButtonMenuItem.d.ts +71 -0
  87. package/types/classes/Rectangle.d.ts +82 -0
  88. package/types/classes/ScrollPane.d.ts +23 -0
  89. package/types/classes/ScrollableComponent.d.ts +81 -0
  90. package/types/classes/Separator.d.ts +14 -0
  91. package/types/classes/Shape.d.ts +223 -0
  92. package/types/classes/ShapeContainer.d.ts +92 -0
  93. package/types/classes/Slider.d.ts +107 -0
  94. package/types/classes/Spinner.d.ts +129 -0
  95. package/types/classes/SplitView.d.ts +47 -0
  96. package/types/classes/Stroke.d.ts +99 -0
  97. package/types/classes/Table.d.ts +652 -0
  98. package/types/classes/Tabs.d.ts +118 -0
  99. package/types/classes/TextArea.d.ts +45 -0
  100. package/types/classes/TextComponent.d.ts +136 -0
  101. package/types/classes/TextField.d.ts +94 -0
  102. package/types/classes/TextShape.d.ts +73 -0
  103. package/types/classes/ToggleButton.d.ts +84 -0
  104. package/types/classes/ToolBar.d.ts +82 -0
  105. package/types/classes/TrayIcon.d.ts +43 -0
  106. package/types/classes/Tree.d.ts +354 -0
  107. package/types/classes/UIComponent.d.ts +945 -0
  108. package/types/classes/Window.d.ts +334 -0
  109. package/types/enums.d.ts +437 -0
  110. package/types/functions.d.ts +109 -0
  111. package/types/index.d.ts +188 -0
  112. package/types/typedefs.d.ts +1626 -0
  113. package/ui-process-manager.js +317 -0
  114. package/video-devices-linux.sh +72 -0
  115. package/video-devices-mac.sh +33 -0
  116. package/CHANGES.md +0 -3
@@ -0,0 +1,2113 @@
1
+ import {ShortcutScope} from "swing-ui/enums.js";
2
+
3
+ /**
4
+ * Base component class that other UI components extend. Not to be instantiated by itself.
5
+ * Most methods in this class are used by other classes that extend this class.
6
+ * @class
7
+ * @global
8
+ */
9
+ class UIComponent
10
+ {
11
+ #id = null; // keep it private
12
+ #token = null;
13
+ parent_id = null;
14
+ #context_menu = null;
15
+ #prop = {
16
+ class: null, // instantiated class prototype
17
+ type: "UIComponent",
18
+ leaf: true, // true if the class is not extended by another class
19
+ visible: !(this instanceof Window), // true, unless it's a Window
20
+ size: {},
21
+ location: {x: 0, y: 0},
22
+ dragAction: DragDropAction.None,
23
+ dropAction: DragDropAction.None,
24
+ container: null // default NULL until container() getter is called.
25
+ };
26
+ callback = {
27
+ "onError": [e => ui.callback.onError.call(this, e)], // Default error event callback. Can be changed by .onError(callback)
28
+ "onRectangleUpdate": [e =>
29
+ {
30
+ if(e.type === "resize")
31
+ {
32
+ this.#prop.size.width = e.width ?? this.#prop.size.width;
33
+ this.#prop.size.height = e.height ?? this.#prop.size.height;
34
+ }
35
+ if(e.type === "move")
36
+ {
37
+ this.#prop.location.x = e.x ?? this.#prop.location.x;
38
+ this.#prop.location.y = e.y ?? this.#prop.location.y;
39
+ }
40
+ }]
41
+ };
42
+
43
+ static #controls_id = 0;
44
+ static #_freed_ids = [];
45
+ static #_components = []; // all components
46
+
47
+ /**
48
+ * This is used to be notified when the component loses all references to it, to notify SwingUI to destroy the component for garbage collection
49
+ * @private
50
+ */
51
+ static #refRegistry = new FinalizationRegistry(id => { UIComponent.destroy(id); });
52
+
53
+ /**
54
+ * Makes one class implements methods, setters, and getters of another class.
55
+ * @param toClass
56
+ * @param fromClass
57
+ * @throws {Error} When one or more parameters are `null`.
58
+ * @internal
59
+ * @ignore
60
+ */
61
+ static implementClass(toClass, fromClass)
62
+ {
63
+ if(toClass == null || fromClass == null) throw new Error(`Invalid class parameters provided to implementClass(toClass, fromClass)`);
64
+
65
+ Object.getOwnPropertyNames(fromClass.prototype).forEach(key =>
66
+ {
67
+ if(key !== 'constructor')
68
+ {
69
+ const desc = Object.getOwnPropertyDescriptor(fromClass.prototype, key);
70
+ desc.configurable = false;
71
+ if(typeof desc.value === "function") desc.writable = false;
72
+ Object.defineProperty(toClass.prototype, key, desc);
73
+ }
74
+ });
75
+ }
76
+
77
+ /**
78
+ * Removes references to UI component with provided ID. Make sure to destroy all other references to the component to truly destroy it to free RAM.
79
+ *
80
+ * @internal
81
+ * @ignore
82
+ * @private
83
+ * @param {number} id - ID of a component to be destroyed
84
+ * @return {void} Doesn't return a value
85
+ */
86
+ static destroy(id)
87
+ {
88
+ // makes sure that component is garbage collected and new one is not created with same ID yet before this one is not garbage collected
89
+ // this._components[id] should have a WeakReference object, but deref() should be undefined.
90
+ if(UIComponent.#_components[id] === undefined) return;
91
+
92
+ const component = UIComponent.#_components[id].deref();
93
+ if(component !== undefined) component.emit("onDispose", {type: "dispose"});
94
+
95
+ // if this was initiated by FinalizationRegistry for GC, then remove weak reference
96
+ UIComponent.#_components[id] = undefined;
97
+ delete UIComponent.#_components[id];
98
+
99
+ // send "delete" command to GUI
100
+ ui.sendToGUI({control: id, delete: true});
101
+
102
+ // recycle component's ID at a delay, to prevent events from destroyed component reach a new component with recycled ID
103
+ setTimeout(() => {UIComponent.#_freed_ids.push(id);}, 5000);
104
+ }
105
+
106
+ /**
107
+ * Get UI component by ID.
108
+ *
109
+ * @param {number} id - ID of requested component
110
+ * @return {any|undefined} Returns UI component or `undefined` if there is no valid component with specified ID.
111
+ */
112
+ static get(id)
113
+ {
114
+ return UIComponent.#_components[id]?.deref();
115
+ }
116
+
117
+ /**
118
+ * Returns last visible non-PopUp Window or null if no Window component is not found.
119
+ *
120
+ * @internal
121
+ * @static
122
+ * @readonly
123
+ * @returns {Window|null} Returns Window component or null.
124
+ */
125
+ static getVisibleWindow()
126
+ {
127
+ const _ids = Object.keys(UIComponent.#_components);
128
+
129
+ for(let i = _ids.length - 1; i >= 0; i--)
130
+ {
131
+ const c = UIComponent.#_components[_ids[i]].deref();
132
+ if(c instanceof Window && c.visible() && c.type !== Window.Type.Popup) return c;
133
+ }
134
+
135
+ return null;
136
+ }
137
+
138
+ /**
139
+ * Not to be instantiated by itself. This call is extended dby other UI components.
140
+ *
141
+ * @param {string} type - Component type.
142
+ * @param {boolean} leaf - `true` if the UI component was not extended by a custom class.
143
+ * @param {...any} [data] - Additional component init data (used by some UI components).
144
+ * @internal
145
+ * @ignore
146
+ */
147
+ constructor(type, leaf, ...data)
148
+ {
149
+ this.#prop.class = new.target;
150
+ this.#prop.type = type;
151
+ this.#prop.leaf = leaf;
152
+
153
+ if(this.#prop.class === UIComponent) throw new Error(`Class UIComponent cannot not to be instantiated. It must be extended by one of swing-ui components.`);
154
+
155
+ // reuse freed IDs or create a new one
156
+ this.#id = (UIComponent.#_freed_ids.length > 0) ? UIComponent.#_freed_ids.shift() : ++UIComponent.#controls_id;
157
+
158
+ // register it in array of components
159
+ UIComponent.#_components[this.#id] = new WeakRef(this);
160
+
161
+ // Create a token to be able to unregister this component when it's manually destroyed
162
+ this.#token = {id: this.#id};
163
+
164
+ // register this component for deletion when all references to it are lost (garbage collection).
165
+ UIComponent.#refRegistry.register(this, this.#id, this.#token); // token must be a Symbol, Number doesn't work.
166
+
167
+ // tell GUI about the new component
168
+ let msg = {control: this.#id, create: this.#prop.type};
169
+ if(data.length > 0) msg.data = data[0];
170
+ ui.sendToGUI(msg);
171
+ }
172
+
173
+ /**
174
+ * Locks methods from being overwritten or deleted.
175
+ * @param {Function} prototype_class
176
+ * @ignore
177
+ * @internal
178
+ */
179
+ finalize(..._classes)
180
+ {
181
+ _classes.push(UIComponent); // UIComponent must be last in array
182
+
183
+ for(const c of _classes)
184
+ {
185
+ Object.getOwnPropertyNames(c.prototype).forEach(key=>
186
+ {
187
+ if(key === "constructor" || key === "finalize" || key === "isFinalized") return;
188
+ if(this.isFinalized(key)) return; // skip already locked method/setter/getter
189
+
190
+ const desc = Object.getOwnPropertyDescriptor(c.prototype, key);
191
+
192
+ if(typeof desc.value === "function") // lock function
193
+ {
194
+
195
+ Object.defineProperty(this, key, {
196
+ value: desc.value.bind(this),
197
+ writable: false,
198
+ configurable: false,
199
+ enumerable: desc.enumerable
200
+ });
201
+ }
202
+ else if((desc.get || desc.set) && !this.isFinalized(key)) // lock getter and setter
203
+ {
204
+ Object.defineProperty(this, key, {
205
+ get: desc.get?.bind(this),
206
+ set: desc.set?.bind(this),
207
+ configurable: false,
208
+ enumerable: desc.enumerable
209
+ });
210
+ }
211
+ });
212
+ }
213
+
214
+ // these are no longer needed
215
+ this.finalize = null;
216
+ this.isFinalized = null;
217
+ }
218
+
219
+ /**
220
+ * @internal
221
+ * @ignore
222
+ */
223
+ isFinalized(methodName)
224
+ {
225
+ const desc = Object.getOwnPropertyDescriptor(this, methodName);
226
+ if(!desc) return false; // not directly defined on the object
227
+ return desc.writable === false && desc.configurable === false;
228
+ }
229
+
230
+ /**
231
+ * @internal
232
+ * @readonly
233
+ * @ignore
234
+ * @type {object}
235
+ */
236
+ get prop()
237
+ {
238
+ return this.#prop;
239
+ }
240
+
241
+ //region Property setters/getters
242
+
243
+ /**
244
+ * Returns a unique ID of the component. ID is reusable, so one component may have an ID of a previously destroyed component.
245
+ * @readonly
246
+ * @type {number}
247
+ */
248
+ get id()
249
+ {
250
+ return this.#id;
251
+ }
252
+
253
+ /**
254
+ * Gets a parent Object. If this component wasn't added to any container component, then `null` is returned.
255
+ * @readonly
256
+ * @type {Object|undefined}
257
+ */
258
+ get parent()
259
+ {
260
+ this.verifyComponent();
261
+
262
+ if(this.parent_id == null) return null;
263
+ return UIComponent.get(this.parent_id);
264
+ }
265
+
266
+ /**
267
+ * Gets a Window component containing this component. Returns `null` if no Window contain this component.
268
+ * @readonly
269
+ * @type {Window|null}
270
+ */
271
+ get window()
272
+ {
273
+ if(this instanceof Window) return this;
274
+ return this.parent?.window ?? null;
275
+ }
276
+
277
+ /**
278
+ * Returns `true` if the component is a container (other components can be added to it), otherwise returns false.
279
+ * @readonly
280
+ * @type {boolean}
281
+ */
282
+ get isContainer()
283
+ {
284
+ if(this.#prop.container == null) this.#prop.container = this instanceof ComponentContainer; // cache answer
285
+ return this.#prop.container;
286
+ }
287
+
288
+ /**
289
+ * Gets a position of this component relative to primary screen. Returns `null` if the component wasn't added to a container that is visible, or of this component is a Window and it was never visible.
290
+ *
291
+ * @readonly
292
+ * @type {Point|null}
293
+ * @see Point
294
+ * @see ui#screens
295
+ */
296
+ get screenPosition()
297
+ {
298
+ return this.property("screenLocation", []);
299
+ }
300
+
301
+ /**
302
+ * Adds this component to a parent container component. An optional index specifies the Z-order position at which to insert the component. It calls {@link ComponentContainer#addChild}.
303
+ *
304
+ * @param {ComponentContainer} parent - The container component to which this component will be added.
305
+ * @param {number|object} [data_or_index] - An parameter to pass some extra data (such as Tab Name to add Panel to Tabs), or index at which to insert the child.
306
+ * @returns {this} Returns an instance of this component for chaining.
307
+ * @see ComponentContainer#addChild
308
+ * @example <h2>Adding a component at specified index</h2>
309
+ * let checkbox = new CheckBox("Enabled checkbox").addTo(panel, 5).position(10, 15); // adds to panel and positions at (10,15) coordinates
310
+ *
311
+ * @example <h2>Adding a component at specified index</h2>
312
+ * let panel = new Panel().addTo(tabs, {text: "Registration", enabled: true, tooltip: "Create new account", icon: "images/user.png"});
313
+ */
314
+ addTo(parent, ...args)
315
+ {
316
+ // making sure that parent is instance of UIComponent
317
+ if(typeof parent !== "object" || !(parent instanceof UIComponent)) throw Error(`'parent' component must be derived from UIComponent of swing-ui module. ${ui._msg[2]}`);
318
+
319
+ parent.addChild(this, ...args);
320
+ return this;
321
+ }
322
+
323
+ /**
324
+ * Removes this component from its parent.
325
+ *
326
+ * @param {boolean} [recursive] - Boolean indicates if all descendents of this child should also be removed.
327
+ * @returns {this} Returns this component for method chaining
328
+ * @example
329
+ * child.remove(); // Removes child from it's parent container
330
+ * child.remove(true); // Removes child component from it's parent and all its descendants recursively
331
+ */
332
+ remove()
333
+ {
334
+ let recursive = false;
335
+
336
+ if(arguments.length > 0)
337
+ {
338
+ if(typeof arguments[0] !== "boolean") throw new Error(`Invalid argument passed to remove(recursive). Boolean is expected, but received ${typeof recursive}.`);
339
+ recursive = arguments[0];
340
+ }
341
+
342
+ if(this instanceof Window) // don't remove window, but only it's children, if recursive
343
+ {
344
+ // remove recursively, from back
345
+ if(recursive) for(let c = this.childCount - 1; c >= 0; c--) this.removeChild(c, true);
346
+ }
347
+ else
348
+ {
349
+ if(this.parent != null && this.parent instanceof UIComponent) this.parent.removeChild(this, recursive);
350
+ }
351
+
352
+ return this;
353
+ }
354
+
355
+ /**
356
+ * Removes the component from its container, destroys event callbacks, unregisters the component, recycles the component ID. Also prepares this component to be garbage-collected in JavaScript and GUI to free RAM.
357
+ *
358
+ * @param {boolean} [recursive=false] - Set to true to also destroy all of its descendants recursively
359
+ * @returns {void}
360
+ */
361
+ destroy(...args)
362
+ {
363
+ if(this.destroyed) return;
364
+
365
+ UIComponent.#refRegistry.unregister(this.#token);
366
+
367
+ try
368
+ {
369
+ this.remove(); // remove this element from its parent
370
+ }catch(ex)
371
+ {
372
+ console.error(ex);
373
+ }
374
+
375
+ // remove context menu
376
+ if(this.#context_menu != null) this.contextMenu(null);
377
+
378
+ // remove references
379
+ UIComponent.destroy(this.#id);
380
+
381
+ // remove all properties
382
+ for(let prop of Object.keys(this))
383
+ {
384
+ if(prop !== "verifyComponent" && typeof this[prop] !== "function") delete this[prop];
385
+ }
386
+
387
+ this.destroyed = true;
388
+ }
389
+
390
+ /**
391
+ * This function may be called to make sure the component wasn't destroyed yet before performing another operation with the component. If it's already destroyed, Exception is thrown.
392
+ *
393
+ * @return {this} Returns this component.
394
+ */
395
+ verifyComponent()
396
+ {
397
+ if(this.destroyed ?? false)
398
+ {
399
+ throw new Error(ui._msg[0] + " (" + this.constructor.name + ")");
400
+ }
401
+
402
+ return this;
403
+ }
404
+
405
+ /**
406
+ * Checks if this component is inside specified ancestor component (at any level, not just direct child).
407
+ * @param {Object} ancestor - Ancestor component to test against
408
+ * @return {boolean}
409
+ * @see ComponentContainer#contains
410
+ */
411
+ insideOf(ancestor)
412
+ {
413
+ if(!(ancestor instanceof ComponentContainer)) return false;
414
+ return ancestor.contains(this);
415
+ }
416
+
417
+ /**
418
+ * Sets or gets component's visibility.
419
+ * NOTE: Window and InternalWindow components visibility is set to `false` by default. Use .visible(true) or .show() to make it visible.
420
+ *
421
+ * @param {boolean} [visible] - `true` to make it visible, `false` to hide it.
422
+ * @return {boolean|this} If called with an argument, then `this` component instance is returned, otherwise `boolean` value is returned.
423
+ */
424
+ visible(...args)
425
+ {
426
+ this.verifyComponent();
427
+
428
+ if(args.length === 0)
429
+ {
430
+ return this.#prop.visible;
431
+ }
432
+ else
433
+ {
434
+ if(args[0] == null) return this; // ignore null or undefined
435
+ this.#prop.visible = (args[0] === true);
436
+ }
437
+ return this.property("visible", [this.#prop.visible]);
438
+ }
439
+
440
+ /**
441
+ * Convenience function that calls this.visible(true)
442
+ * @return {this}
443
+ */
444
+ show()
445
+ {
446
+ return this.visible(true);
447
+ }
448
+
449
+ /**
450
+ * Convenience function that calls this.visible(false)
451
+ * @return {this}
452
+ */
453
+ hide()
454
+ {
455
+ return this.visible(false);
456
+ }
457
+
458
+ /**
459
+ * Enables or disables the component.
460
+ *
461
+ * @param {boolean} [enabled] `true` to enable, `false` to disable the component. Omit to get current value.
462
+ * @return {boolean|this} If called with an argument, then `this` component instance is returned, otherwise `boolean` value is returned.
463
+ */
464
+ enabled(...args)
465
+ {
466
+ return this.property("enabled", args);
467
+ }
468
+
469
+ /**
470
+ * Sets or gets the size of the component.
471
+ *
472
+ * For {@link Window}, the returned and assigned size represents the entire window, including the title bar and borders. The actual area available for placing child components is smaller. Use {@link Window#innerSize} when you need to specify the content area size instead.
473
+ *
474
+ * @param {number|Size} [width_or_size] - Number as width, or `Size` formatted object.
475
+ * @param {number} [height] - Height of component. Required if first argument is `number`.
476
+ * @return {Size|this} Component size object if no arguments are set, otherwise returns `this` component.
477
+ * @see Size
478
+ * @example
479
+ * // Usage examples:
480
+ * button.size(100, 25);
481
+ * button.size({width: 100, height: 25});
482
+ * let size = button.size();
483
+ */
484
+ size(...args)
485
+ {
486
+ if(args.length > 0)
487
+ {
488
+ const size = (args.length > 1) ? {
489
+ width: args[0],
490
+ height: args[1]
491
+ } : (args[0] != null && typeof args[0] === "object") ? {
492
+ width: args[0].width ?? this.#prop.size.width,
493
+ height: args[0].height ?? this.#prop.size.height
494
+ } : null;
495
+
496
+ if(size == null)
497
+ {
498
+ this.#prop.size = {}; // reset, to be retrieved from GUI on next request
499
+ return this;
500
+ }
501
+ if(this.parent instanceof Tabs || this.parent instanceof ScrollPane) return this;
502
+ if(size.width == null || (typeof size.width !== "string" && isNaN(size.width))) size.width = this.#prop.size.width ?? this.width();
503
+ if(size.height == null || (typeof size.height !== "string" && isNaN(size.height))) size.height = this.#prop.size.height ?? this.height();
504
+
505
+ // check if width is in %
506
+ if(typeof size.width === "string" && size.width.indexOf("%") > 0)
507
+ {
508
+ const m = parseFloat(size.width.replace("%", "")) / 100;
509
+ size.width = (this.parent != null) ? (("innerSize" in this.parent) ? this.parent.innerSize().width : this.parent.width()) * m : this.width();
510
+ }
511
+
512
+ // check if height is in %
513
+ if(typeof size.height === "string" && size.height.indexOf("%") > 0)
514
+ {
515
+ const m = parseFloat(size.height.replace("%", "")) / 100;
516
+ size.height = (this.parent != null) ? (("innerSize" in this.parent) ? this.parent.innerSize().height : this.parent.height()) * m : this.height();
517
+ }
518
+
519
+ // if size changed, then send to GUI
520
+ if(this.#prop.size.width !== size.width || this.#prop.size.height !== size.height)
521
+ {
522
+ this.#prop.size = size;
523
+ return this.property("size", [this.#prop.size]);
524
+ }
525
+ else
526
+ {
527
+ return this;
528
+ }
529
+ }
530
+ else
531
+ {
532
+ this.verifyComponent();
533
+
534
+ // if size isn't known yet, retrieve it from GUI
535
+ if(!("width" in this.#prop.size && "height" in this.#prop.size))
536
+ {
537
+ const size = this.property("size", []);
538
+ Object.assign(this.#prop.size, size);
539
+ }
540
+
541
+ return {...this.#prop.size};
542
+ }
543
+ }
544
+
545
+ /**
546
+ * Convenience method to set or get the width of component.
547
+ *
548
+ * @param {number} [width] - New width of the component.
549
+ * @return {number|UIComponent} Returns width if no argument is set, otherwise returns `this` component.
550
+ * @see UIComponent#size
551
+ */
552
+ width(...args)
553
+ {
554
+ const size = this.size();
555
+
556
+ if(args.length === 0)
557
+ {
558
+ return size.width;
559
+ }
560
+ else
561
+ {
562
+ return this.size(parseInt(args[0]), size.height);
563
+ }
564
+ }
565
+
566
+ /**
567
+ * Convenience method to set or get the height of component.
568
+ *
569
+ * @param {number} [height] - New height of the component.
570
+ * @return {number|UIComponent} Returns height if no argument is set, otherwise returns `this` component.
571
+ * @see UIComponent#size
572
+ */
573
+ height(...args)
574
+ {
575
+ if(args.length === 0)
576
+ {
577
+ return this.size().height;
578
+ }
579
+ else
580
+ {
581
+ return this.size(this.size().width, parseInt(args[0]));
582
+ }
583
+ }
584
+
585
+ /**
586
+ * Sets or gets text content of the component. Even though some components don't show text, you can still set text to it.
587
+ *
588
+ * @param {string} [text=undefined] - Can be plain text or HTML. HTML is only supported in some components (e.g. Label, Button, TextDocument, ...) and should be surrounded with HTML tags. Check example below.
589
+ * @param {TextAction} [action=TextAction.Set] - Specific action to perform with text. Replace, append, prepend, or insert. Applies only to some components. Do not use this argument for HTML content.
590
+ * @param {number} [insert_position=undefined] - Position where to insert text. Applies only if `action` argument is TextAction.Insert.
591
+ * @return {string|this} Returns text value if no argument is set, otherwise returns `this` component.
592
+ * @example
593
+ * const txt = textfield.text(); // gets text value
594
+ * textfield.text("New Text"); // sets new text value
595
+ * textfield.text("Appended Text", TextAction.Append); // Appends new text to the end of current text
596
+ * textfield.text("Inserted Text", TextAction.Insert); // Inserts at current caret position
597
+ * textfield.text("Inserted Text", TextAction.Insert, textfield.caretPosition() - 50); // Inserts at specified position
598
+ * textfield.text("New Text", TextAction.ReplaceSelection); // Replaces selected text or inserts at current cursor position if no text is selected (applies only to text editing components, like TextField, TextArea, ...).
599
+ */
600
+ text(...args)
601
+ {
602
+ if(args.length > 0)
603
+ {
604
+ ui.sendToGUI({
605
+ control: this.#id,
606
+ set: "text",
607
+ value: String(args[0]),
608
+ data: {
609
+ action: args.length > 1 ? args[1] : TextAction.Set,
610
+ index: args.length > 2 && args[1] === TextAction.Insert ? parseInt(args[2]) : undefined
611
+ }
612
+ });
613
+ return this;
614
+ }
615
+ else
616
+ {
617
+ return ui.sendToGUI({control: this.#id, get: "text"});
618
+ }
619
+ }
620
+
621
+ /**
622
+ * @internal
623
+ * @ignore
624
+ * @private
625
+ * @param {string} property
626
+ * @param {any} value
627
+ * @return {any}
628
+ */
629
+ property(property, ...args)
630
+ {
631
+ if(args.length > 0) args = args[0];
632
+
633
+ this.verifyComponent();
634
+
635
+ if(args.length > 0)
636
+ {
637
+ ui.sendToGUI({control: this.#id, set: property, value: args[0]});
638
+ return this;
639
+ }
640
+ else
641
+ {
642
+ return ui.sendToGUI({control: this.#id, get: property});
643
+ }
644
+ }
645
+
646
+ /**
647
+ * Registers, unregisters, and gets an event handler callback function for a specified events.
648
+ *
649
+ * - component.event(name, function, true); // unregisters one callback function (even if the same callback was registered multiple times)
650
+ * - component.event(name, function); // registers callback to be called when the event is emitted
651
+ * - component.event(name); // returns all callback functions for specified event
652
+ *
653
+ * @param {string} name - Name of event
654
+ * @param {function(Object):any|null} [callback=undefined] - Callback function. Pass `null` to unregister all handler functions.
655
+ * @param {boolean} [remove=false] - Set to true to unregister the callback function.
656
+ * @return {Function[]|this} If only the `name` argument is provided, returns an array of registered callback functions. Otherwise, returns `this` component.
657
+ * @internal
658
+ * @ignore
659
+ * @private
660
+ * @example
661
+ * const _callbacks_array = button.event("onAction"); // gets an array of previously registered handler functions
662
+ * button.event("onAction", _callbacks_array[0], true); // unregisters callback function, same as button.onAction(_callbacks_array[0], true);
663
+ * button.event("onAction", e => {console.debug(e);}); // registers new event handler function, same as button.onAction(e => {console.debug(e);});
664
+ */
665
+ event(name, callback = undefined, remove = false)
666
+ {
667
+ if(this.callback === undefined) return this; // in case the component is disposed already
668
+
669
+ // return callbacks
670
+ if(callback === undefined) return Array.isArray(this.callback[name]) ? [...this.callback[name]] : [];
671
+
672
+ // remove callback
673
+ if(remove === true || callback === null) // removes one or all callbacks
674
+ {
675
+ if(!Array.isArray(this.callback[name])) return this;
676
+
677
+ if(callback === null)
678
+ {
679
+ this.callback[name].length = 0;
680
+ }
681
+ else
682
+ {
683
+ const i = this.callback[name].indexOf(callback); // find callback
684
+ if(i === -1) return this; // return if not found
685
+ this.callback[name].splice(i, 1); // remove it
686
+ }
687
+
688
+ if(this.callback[name].length === 0)
689
+ {
690
+ delete this.callback[name]; // delete array
691
+ this.property(name, [false]); // notify GUI
692
+ }
693
+ }
694
+ // add callback
695
+ else
696
+ {
697
+ // make sure callback is function
698
+ if(typeof callback !== "function") throw new Error(`"callback" argument must be of type "function".`);
699
+
700
+ if(!Array.isArray(this.callback[name]))
701
+ {
702
+ this.callback[name] = []; // create array
703
+ this.property(name, [true]); // notify GUI
704
+ }
705
+
706
+ this.callback[name].push(callback); // add callback (don't bind to `this` here, it's done when called)
707
+ }
708
+
709
+ return this;
710
+ }
711
+
712
+ /**
713
+ * Sets or gets the background color of the component. <br />
714
+ * <br />
715
+ * Accepts three formats: <br />
716
+ * - RGB hex (e.g. `"#FF0000"`) <br />
717
+ * - RGBA hex with alpha (e.g. `"#FF000050"`) <br />
718
+ * - The string `"transparent"` (equivalent to `"#00000000"`)
719
+ *
720
+ * @param {string} [value] - Background color in supported formats. Panel and Window components also accept gradient, image position and other info like in CSS. If omitted, returns the current value.
721
+ * @param {boolean} [update] - For Panel or Window only. Specifies whether to update specified background attributes instead of replacing all previously set background attributes.
722
+ * @returns {this|string} Returns `this` when setting value, or the current background color string when getting.
723
+ *
724
+ * @example
725
+ * component.background("#FF0000"); // Red background
726
+ * component.background("#00FF0080"); // Semi-transparent green
727
+ * component.background("transparent"); // Fully transparent
728
+ *
729
+ * // For Panel and Window components the background value can be a string that contains a mixture of background color,
730
+ * // image, linear-gradient, radial-gradient, like in CSS in web browsers. Some features may not be supported though.
731
+ * panel.background("#FF000055 url(images/bg.png) center no-repeat opacity=0.6 10px 15px linear-gradient(#DDDDDD55, #F8F8F855 10%) / auto);
732
+ * panel.background("url(data:image/png;base64,As8fE4fdt1r.....)", true); // supports base64 encoded image string
733
+ */
734
+ background(...args)
735
+ {
736
+ if(args.length > 1 && args[1] === true && (this instanceof Panel || this instanceof Window))
737
+ {
738
+ args[0] = {
739
+ value: args[0],
740
+ update: true
741
+ };
742
+ }
743
+
744
+ return this.property("background", args);
745
+ }
746
+
747
+ /**
748
+ * Sets or gets a foreground color of component (usually text color).
749
+ *
750
+ * @param {string} value Three formats are acceptable, regular RGB "#FF0000", with alpha RGBA "#FF000050", and "transparent" string, which is equivalent to "#00000000".
751
+ * @return {string|this} Returns color value if no argument is set, otherwise returns `this` component.
752
+ */
753
+ color(...args)
754
+ {
755
+ return this.property("color", args);
756
+ }
757
+
758
+ /**
759
+ * Sets or gets the position of the component relative to its parent component's top-let corner (or to the screen's top-left corner when component is Window).
760
+ *
761
+ * @param {number|Point} [x_or_position] - X position. Alternatively, provide a single `Point` object parameter to specify the position.
762
+ * @param {number} [y] - Y position.
763
+ * @param {number} [right] - Resizes the component so that its right edge is this many pixels from the right edge of the parent component or screen.
764
+ * @param {number} [bottom] - Resizes the component so that its bottom edge is this many pixels from the bottom edge of the parent component or screen.
765
+ * @return {Point|this} If no arguments are provided, returns a {x: number, y: number} position object. Otherwise, returns `this` component.
766
+ * @see Point
767
+ * @example
768
+ * panel.size(50, 100); // sets position
769
+ * panel.size({x: 50, y: 100}); // sets position
770
+ * panel.size(50, 100, 20); // sets position of left, top and right sides relative to sides of parent component.
771
+ * const window = new Window("New Window").size(50, 100, 20, 20); // sets position of each side relative to sides of screen.
772
+ */
773
+ position(...args)
774
+ {
775
+ if(args.length > 0)
776
+ {
777
+ delete this.#prop.location.right;
778
+ delete this.#prop.location.bottom;
779
+ let changed = false;
780
+
781
+ try
782
+ {
783
+ if(args[0] == null)
784
+ {
785
+ this.#prop.location = {}; // reset position to make it load from GUI on next request
786
+ return this;
787
+ }
788
+
789
+ if(this.parent instanceof Tabs || this.parent instanceof ScrollPane) return this; // ignore position for tabs' or scroll pane's direct children
790
+
791
+ const position = (typeof args[0] === "object") ? {x: args[0].x, y: args[0].y} : {
792
+ x: args[0],
793
+ y: args[1]
794
+ };
795
+
796
+ if(Number.isFinite(position.x)) position.x = Number.parseInt(position.x);
797
+ if(Number.isFinite(position.y)) position.y = Number.parseInt(position.y);
798
+
799
+ if(isNaN(position.x) || isNaN(position.y)) throw new TypeError(`Invalid arguments for position():\n\n${stringify(args)}`);
800
+
801
+ // check if the size changed
802
+ if(position.x !== this.#prop.location.x || position.y !== this.#prop.location.y)
803
+ {
804
+ this.#prop.location = position;
805
+ this.property("location", [this.#prop.location]); // make sure this position is set before size is changed below
806
+ }
807
+
808
+ if(args.length > 2 && Number.isFinite(args[2])) args[2] = Number.parseInt(args[2]); // fix right
809
+ if(args.length > 3 && Number.isFinite(args[3])) args[3] = Number.parseInt(args[3]); // fix bottom
810
+
811
+ // resize to right edge
812
+ if(args.length > 2 && !isNaN(args[2]))
813
+ {
814
+ const parent = this.parent;
815
+
816
+ if(parent != null || this instanceof Window)
817
+ {
818
+ // get parent's or screen size
819
+ const containerSize = (parent != null) ? ((parent instanceof Window || parent instanceof InternalWindow) ? parent.innerSize() : parent.size()) : this.screenSize;
820
+
821
+ const w = containerSize.width - this.#prop.location.x - args[2];
822
+
823
+ // resize to bottom edge
824
+ if(args.length > 3 && !isNaN(args[3]))
825
+ {
826
+ const h = containerSize.height - this.#prop.location.y - args[3];
827
+ this.size(w, h);
828
+ }
829
+ else
830
+ {
831
+ this.width(w);
832
+ }
833
+ }
834
+ else
835
+ {
836
+ // save for later to be applied when component gets added to a parent
837
+ this.#prop.size.right = args[2];
838
+ if(args.length > 3 && !isNaN(args[3])) this.#prop.size.bottom = args[3];
839
+ }
840
+ }
841
+ }catch(ex)
842
+ {
843
+ throw new TypeError(ex.message + ".\n\nArguments must be one of following:\n position(number x, number y)\n position(number x, number y, number right, number bottom)\n position(number x, number y, number right)\n position({x: number, y: number})\n");
844
+ }
845
+
846
+ return this;
847
+ }
848
+ else
849
+ {
850
+ this.verifyComponent();
851
+
852
+ // if location isn't known, retrieve it from GUI
853
+ if(!("x" in this.#prop.location || "y" in this.#prop.location))
854
+ {
855
+ ({x: this.#prop.location.x, y: this.#prop.location.y} = this.property("location", []));
856
+ }
857
+
858
+ return {...this.#prop.location};
859
+ }
860
+ }
861
+
862
+ /**
863
+ * Convenience method to set or get left position of component.
864
+ *
865
+ * @param {number} [value]
866
+ * @return {number} Returns left position if no argument is set, otherwise returns `this` component.
867
+ * @see UIComponent#position
868
+ */
869
+ x(...args)
870
+ {
871
+ if(args.length > 0)
872
+ {
873
+ if(Number.isFinite(args[0])) args[0] = Number.parseInt(args[0]);
874
+ if(this.parent instanceof Tabs) return this; // can't set position for tabs direct children
875
+ if(isNaN(args[0])) throw new TypeError(`Invalid argument type for x(). Argument is '${args[0]}' of type '${typeof args[0]}'. Must be a number.`);
876
+
877
+ delete this.#prop.location.right; // in case it was set
878
+ if(this.#prop.location.x !== args[0]) // check if changed
879
+ {
880
+ this.#prop.location.x = args[0];
881
+ return this.property("location", [this.#prop.location]);
882
+ }
883
+ return this;
884
+ }
885
+ else
886
+ {
887
+ this.verifyComponent();
888
+ return this.#prop.location.x;
889
+ }
890
+ }
891
+
892
+ /**
893
+ * Convenience method to set or get top position of component.
894
+ *
895
+ * @param {number} [value]
896
+ * @return {number} Returns top position if no argument is set, otherwise returns `this` component.
897
+ * @see UIComponent#position
898
+ */
899
+ y(...args)
900
+ {
901
+ if(args.length > 0)
902
+ {
903
+ if(Number.isFinite(args[0])) args[0] = Number.parseInt(args[0]);
904
+ if(this.parent instanceof Tabs) return this; // can't set position for tabs direct children
905
+ if(isNaN(args[0])) throw new TypeError(`Invalid argument type for y(). Argument is '${args[0]}' of type '${typeof args[0]}'. Must be a number.`);
906
+
907
+ delete this.#prop.location.bottom; // in case it was set
908
+ args[0] = parseInt(args[0]);
909
+ if(this.#prop.location.y !== args[0]) // check if changed
910
+ {
911
+ this.#prop.location.y = args[0];
912
+ return this.property("location", [this.#prop.location]);
913
+ }
914
+ return this;
915
+ }
916
+ else
917
+ {
918
+ this.verifyComponent();
919
+ return this.#prop.location.y;
920
+ }
921
+ }
922
+
923
+ /**
924
+ * Convenience method to get or set the distance between the right edge of this component and the right edge of its parent.
925
+ *
926
+ * @param {number} [value] - Distance from the parent's right edge.
927
+ * @param {boolean} [reize] - `True` to resize the width to accommodate for specified `right` position instead of moving the object. `false` to move the component.
928
+ * @return {number|this} Returns the current right distance if no argument is provided; otherwise, returns `this` component.
929
+ */
930
+ right(...args)
931
+ {
932
+ const parent = this.parent;
933
+
934
+ if(args.length > 0)
935
+ {
936
+ if(Number.isFinite(args[0])) args[0] = Number.parseInt(args[0]);
937
+
938
+ if(isNaN(args[0])) throw new Error(`Invalid argument for right() method. Argument is '${args[0]}' of type '${typeof args[0]}'. Must be a number.`);
939
+
940
+ if(parent !== null)
941
+ {
942
+ if(parent instanceof Window || parent instanceof InternalWindow)
943
+ {
944
+ if(args[1] === true)
945
+ {
946
+ this.width(Math.max(0, this.parent.innerSize().width - this.x() - args[0]));
947
+ }
948
+ else
949
+ {
950
+ this.x(this.parent.innerSize().width - this.width() - args[0]);
951
+ }
952
+ }
953
+ else
954
+ {
955
+ if(args[1] === true)
956
+ {
957
+ this.width(Math.max(0, this.parent.width() - this.x() - args[0]));
958
+ }
959
+ else
960
+ {
961
+ this.x(this.parent.width() - this.width() - args[0]);
962
+ }
963
+ }
964
+ }
965
+ else
966
+ {
967
+ if(args[1] !== true) this.#prop.location.right = args[0]; // set to be applied when added to component
968
+ }
969
+ return this;
970
+ }
971
+ else
972
+ {
973
+ if(parent === null) return NaN;
974
+
975
+ if(parent instanceof Window || parent instanceof InternalWindow)
976
+ {
977
+ return this.parent.innerSize().width - (this.x() + this.width()); // calculate
978
+ }
979
+ else
980
+ {
981
+ return this.parent.width() - (this.x() + this.width()); // calculate
982
+ }
983
+ }
984
+ }
985
+
986
+ /**
987
+ * Convenience method to get or set the distance between the bottom edge of this component and the bottom edge of its parent.
988
+ *
989
+ * @param {number} [value] - Distance from the parent's bottom edge to this component's bottom edge.
990
+ * @param {boolean} [reize] - `True` to resize the height to accommodate for specified `bottom` position instead of moving the object. `false` to move the component.
991
+ * @return {number|this} Returns the current bottom distance if no argument is provided; otherwise, returns `this` component.
992
+ */
993
+ bottom(...args)
994
+ {
995
+ if(args.length > 0)
996
+ {
997
+ if(Number.isFinite(args[0])) args[0] = Number.parseInt(args[0]);
998
+
999
+ if(isNaN(args[0])) throw new Error(`Invalid argument for bottom() function. Argument is '${args[0]}' of type '${typeof args[0]}'. Must be a number.`);
1000
+
1001
+ if(this.parent !== null)
1002
+ {
1003
+ if(this.parent instanceof Window || this.parent instanceof InternalWindow)
1004
+ {
1005
+ if(args[1] === true)
1006
+ {
1007
+ this.height(Math.max(0, this.parent.innerSize().height - this.y() - args[0]));
1008
+ }
1009
+ else
1010
+ {
1011
+ this.y(this.parent.innerSize().height - this.height() - args[0]);
1012
+ }
1013
+ }
1014
+ else
1015
+ {
1016
+ if(args[1] === true)
1017
+ {
1018
+ this.height(Math.max(0, this.parent.height() - this.y() - args[0]));
1019
+ }
1020
+ else
1021
+ {
1022
+ this.y(this.parent.height() - this.height() - args[0]);
1023
+ }
1024
+ }
1025
+ }
1026
+ else
1027
+ {
1028
+ if(args[1] !== true) this.#prop.location.bottom = args[0]; // set to be applied when added to component
1029
+ }
1030
+ return this;
1031
+ }
1032
+ else
1033
+ {
1034
+ if(this.parent === null) return NaN;
1035
+
1036
+ if(this.parent instanceof Window || this.parent instanceof InternalWindow)
1037
+ {
1038
+ return this.parent.innerSize().height - (this.y() + this.height()); // calculate
1039
+ }
1040
+ else
1041
+ {
1042
+ return this.parent.height() - (this.y() + this.height()); // calculate
1043
+ }
1044
+ }
1045
+ }
1046
+
1047
+ /**
1048
+ * Moves the component by adding the specified x and y values to its current position.
1049
+ * The function shifts the element's position relative to current position.
1050
+ *
1051
+ * @param {number} [dx=0] - Delta x (can be positive or negative number)
1052
+ * @param {number} [dy=0] - Delta y (can be positive or negative number)
1053
+ * @return {this} Returns `this` component.
1054
+ */
1055
+ move(dx = 0, dy = 0)
1056
+ {
1057
+ if(Number.isFinite(dx)) dx = Number.parseInt(dx);
1058
+ if(Number.isFinite(dy)) dy = Number.parseInt(dy);
1059
+
1060
+ if((!isNaN(dx) && !isNaN(dy)) && !(dx === 0 && dy === 0))
1061
+ {
1062
+ const _args = [];
1063
+ const p = this.#prop.location;
1064
+ _args.push(p.x + dx);
1065
+ _args.push(p.y + dy);
1066
+
1067
+ // update possible `right` and `bottom` in location
1068
+ if("right" in p) _args.push(p.right - dx);
1069
+ if("bottom" in p) _args.push(p.bottom - dy);
1070
+
1071
+ return this.position.apply(this, _args);
1072
+ }
1073
+
1074
+ return this;
1075
+ }
1076
+
1077
+ /**
1078
+ * When called without arguments and repainting is not disabled, it flushes message queue, revalidates and repaints the component.
1079
+ * When the first argument is `true`, it enables repainting (enabled by default) and flushes the message queue.
1080
+ * When the first argument is `false`, it disables repainting.
1081
+ *
1082
+ * @param {boolean} repaint_enabled - `false` to pause repainting this element. `true` to resume repainting
1083
+ * @return {this} Returns `this` component.
1084
+ */
1085
+ update(...args)
1086
+ {
1087
+ if(args.length === 0)
1088
+ {
1089
+ if(this.#prop.update !== false)
1090
+ {
1091
+ ui.sendToGUI({control: this.#id, set: "update", value: true});
1092
+ ui.sendMessageQueue();
1093
+ }
1094
+ }
1095
+ else if((args[0] === true) !== (this.#prop.update !== false))
1096
+ {
1097
+ this.#prop.update = (args[0] === true);
1098
+ ui.sendToGUI({control: this.#id, set: "update", value: this.#prop.update});
1099
+ ui.sendMessageQueue();
1100
+ }
1101
+
1102
+ return this;
1103
+ }
1104
+
1105
+ /**
1106
+ * Sets or gets context menu component for this UI component.
1107
+ *
1108
+ * @param {ContextMenu} [menu] - ContextMenu component to be assigned to this component. Set to `null` to remove ContextMenu.
1109
+ * @return {ContextMenu|null|this} Returns the current context menu if no argument is provided; otherwise, returns `this` component.
1110
+ */
1111
+ contextMenu(...args)
1112
+ {
1113
+ if(args.length === 0) return this.#context_menu;
1114
+
1115
+ let menu = args[0];
1116
+
1117
+ if(menu === null) // remove context menu
1118
+ {
1119
+ if(this.#context_menu != null && typeof this.#context_menu === "object" && this.#context_menu instanceof globalThis.ContextMenu && this.#context_menu.destroyed !== true)
1120
+ {
1121
+ let i = this.#context_menu._parents.indexOf(this);
1122
+
1123
+ if(i > -1)
1124
+ {
1125
+ this.#context_menu._parents.splice(i, 1);
1126
+ ui.sendToGUI({control: this.#context_menu.id, set: "removeFrom", value: this.#id});
1127
+ }
1128
+ }
1129
+
1130
+ this.#context_menu = null;
1131
+ }
1132
+ else // add context menu
1133
+ {
1134
+ if(this === menu || typeof menu !== "object" || !(menu instanceof globalThis.ContextMenu)) // can't use !(menu instanceof ContextMenu) because ContextMenu imports this class, thus would create circular import.
1135
+ {
1136
+ throw new Error(ui._msg[10]);
1137
+ return this;
1138
+ }
1139
+
1140
+ if(this.#context_menu != null) this.contextMenu(null); // remove existing menu before setting a new one
1141
+
1142
+ this.#context_menu = menu;
1143
+ menu._parents.push(this);
1144
+
1145
+ const resp = ui.sendToGUI({control: menu.id, get: "parent", data: {parent: this.#id}});
1146
+ }
1147
+
1148
+ return this;
1149
+ }
1150
+
1151
+ /**
1152
+ * Sets or gets component `focusable` property whether component can retain focus.
1153
+ *
1154
+ * @param {boolean} [value]
1155
+ * @return {boolean|this} If called with an argument, then `this` component instance is returned, otherwise `boolean` value is returned.
1156
+ */
1157
+ focusable(...args)
1158
+ {
1159
+ return this.property("focusable", args);
1160
+ }
1161
+
1162
+ /**
1163
+ * If value is passed, it makes component to get or loose focus, otherwise returns `boolean` indicating if it has focus or not.
1164
+ *
1165
+ * @param {boolean} [value] - `true` to get focus, `false` to attempt to shift focus to next component (if it has focus), usually by switching focus to next component.
1166
+ * @returns {boolean|this} If called with an argument, then `this` component instance is returned, otherwise `boolean` value is returned.
1167
+ */
1168
+ focused(...args)
1169
+ {
1170
+ return this.property("focused", args);
1171
+ }
1172
+
1173
+ /**
1174
+ * Sets or gets an index (z-order) of this component if it is added to a container, otherwise returns null.
1175
+ * Larger value brings component forward, smaller to the back, like z-index in CSS.
1176
+ *
1177
+ * @param {number} [index]
1178
+ * @returns {this|null|number} If called with an argument, then `this` component instance is returned, if component has no parent then `null` is returned, otherwise index is returned.
1179
+ */
1180
+ index(...args)
1181
+ {
1182
+ if(this.parent == null) return args.length === 0 ? null : this; // if no parent
1183
+
1184
+ return this.parent.childIndex(this, ...args);
1185
+ }
1186
+
1187
+ /**
1188
+ * Sets or gets a layer at which this component resides in Panel container. This is separate from z-index.
1189
+ * This applies only to components that are added to Panel or Window components.
1190
+ * For example components in layer 2 are always on top of components in layer 1, regardless on their z-index.
1191
+ * Larger value brings component forward, smaller to the back, like width z-index.
1192
+ *
1193
+ * @param {number} layer_index
1194
+ * @returns {number|this} If called with an argument, then `this` component instance is returned, otherwise `number` value is returned.
1195
+ */
1196
+ layer(...args)
1197
+ {
1198
+ if(args.length > 0) args[0] = Math.max(0, parseInt(args[0]));
1199
+ return this.property("layer", args);
1200
+ }
1201
+
1202
+ /**
1203
+ * Gets or sets the font used for the component's text. See the example for font object format.
1204
+ *
1205
+ * @param {Font} [font] - Font formatted object.
1206
+ * @param {boolean} [update] - If `true`, only the specified font properties will be updated instead of replacing the entire font.
1207
+ * @return {this|Font}
1208
+ * @see Font
1209
+ * @example <h2>Full list of supported font properties:</h2>
1210
+ * Font {
1211
+ * name: string, // Font name or family name from {@link FontFamily} enum (e.g., "sans-serif")
1212
+ * size: number, // Font size in pixels
1213
+ * bold: boolean, // Whether the text is bold
1214
+ * italic: boolean, // Whether the text is italic
1215
+ * underlined: boolean, // Whether the text is underlined
1216
+ * strikethrough: boolean, // Whether the text has a strikethrough
1217
+ * direction: string, // Text direction: "ltr" or "rtl"
1218
+ * kerning: boolean, // Enable or disable kerning
1219
+ * ligatures: boolean, // Enable or disable font ligatures
1220
+ * tracking: number // Letter spacing adjustment: -1, 0 (default), or 1
1221
+ * };
1222
+ * @example <h2>Usage examples:</h2>
1223
+ * // Sets new font
1224
+ * textarea.font({name: "sans-serif", size: 14, italic: true});
1225
+ *
1226
+ * // Set a new font; previously set italic style will be removed
1227
+ * textarea.font({name: "sans-serif", size: 14});
1228
+ *
1229
+ * // Update only the font size, preserving other properties
1230
+ * textarea.font({size: 20}, true);
1231
+ *
1232
+ * // get component's font
1233
+ * const font = textarea.font();
1234
+ * @see Font
1235
+ * @see FontFamily
1236
+ */
1237
+ font(...args)
1238
+ {
1239
+ if(args.length > 0)
1240
+ {
1241
+ if(typeof args[0] !== "object") throw new Error("Font argument must be an Object");
1242
+ const msg = {control: this.id, set: "font", value: args[0]};
1243
+ if(args[1] === true) msg.update = true;
1244
+ ui.sendToGUI(msg);
1245
+ return this;
1246
+ }
1247
+ else
1248
+ {
1249
+ return this.property("font", []);
1250
+ }
1251
+ }
1252
+
1253
+ /**
1254
+ * Sets or gets an icon image of this component. You can set icon to any component even though not all components display the icon.
1255
+ * When a component is added to Tabs, that component's icon is used as a Tab icon by default.
1256
+ * Following image types are supported: ICO, PNG, JPG, JPEG, GIF, BMP, WBMP, TIFF/TIF, and base64 encoded images of such types.
1257
+ * If FFMPEG with support of these image types is installed, the following image types are also supported from local relative or absolute paths: WEBP, HEIC, HEIF, AVIF.
1258
+ *
1259
+ * @param {string|null} image_path - Relative or absolute path to image file, base64 encoded image string, or NULL to remove existing icon.
1260
+ * @returns {this|string|null} Returns `this` component if no arguments are set, a string of a previously set icon, or `null` if icon hasn't been set.
1261
+ */
1262
+ icon(...args)
1263
+ {
1264
+ return this.property("icon", args);
1265
+ }
1266
+
1267
+ /**
1268
+ * Sets an cursor to the control. Passing NULL value removes custom cursor.
1269
+ *
1270
+ * @param {number|null|string} cursor - A value from Cursor enum, or a relative path to image file for custom cursor icon.
1271
+ * @param {number} x - Used with custom image cursor to set pointer point on the image.
1272
+ * @param {number} y - Used with custom image cursor to set pointer point on the image.
1273
+ * @returns {this} Returns `this` component.
1274
+ */
1275
+ cursor(cursor, ...point)
1276
+ {
1277
+ const msg = {control: this.#id, set: "cursor", value: cursor};
1278
+
1279
+ if(point.length > 1)
1280
+ {
1281
+ msg.x = parseInt(point[0]);
1282
+ msg.y = parseInt(point[1]);
1283
+ }
1284
+
1285
+ ui.sendToGUI(msg);
1286
+ return this;
1287
+ }
1288
+
1289
+ /**
1290
+ * Sets or gets the drag action for this component (e.g., `Copy`, `Move`), and enables or disables dragging accordingly.
1291
+ * Passing `None` disables dragging; all other values enable it.
1292
+ *
1293
+ * @param {DragDropAction} [action] - One of enum values: DragDropAction.[None|Copy|Move|CopyOrMove|Link]. `None` is default.
1294
+ * @returns {string|this} Returns `this` component if argument is provided, otherwise returns `string` value as one of the DragDropAction enum values.
1295
+ */
1296
+ dragAction(...args)
1297
+ {
1298
+ if(args.length > 0)
1299
+ {
1300
+ if(!DragDropAction.hasOwnProperty(args[0])) return this; // validate argument
1301
+ this.#prop.dragAction = args[0];
1302
+ }
1303
+ return this.property("dragAction", args);
1304
+ }
1305
+
1306
+ /**
1307
+ * Sets or gets which type of drop action is accepted by the component (e.g., `Copy`, `Move`). Setting any action other
1308
+ * than `None` enables dropping on the component. Action `None` disables dropping.
1309
+ *
1310
+ * @param {DragDropAction} [action] - One of enum values: DragDropAction.[None|Copy|Move|CopyOrMove|Link]. `None` is default.
1311
+ * @returns {string|this} Returns `this` component if argument is provided, otherwise returns `string` value as one of the DragDropAction enum values.
1312
+ */
1313
+ dropAction(...args)
1314
+ {
1315
+ if(args.length > 0)
1316
+ {
1317
+ if(!DragDropAction.hasOwnProperty(args[0])) return this; // validate argument
1318
+ this.#prop.dropAction = args[0];
1319
+ }
1320
+ return this.property("dropAction", args);
1321
+ }
1322
+
1323
+ /**
1324
+ * Sets or gets the types of data that should be transffered during drag operation. The data itself can be set using dragData(...) method.
1325
+ * More than one type can be specified by setting multiple arguments or passing an array of values.
1326
+ *
1327
+ * @param {...DataType|DataType[]} data_type - DataType.[None|Text|Image|...]
1328
+ * @returns {string[]|this}
1329
+ * @example
1330
+ * // Usage examples:
1331
+ * label.dragDataType(DataType.Text, DataType.HTML); // setting by multiple arguments
1332
+ * label.dragDataType([DataType.Text, DataType.HTML]); // setting by an array of values
1333
+ * const _types = label.dragDataType(); // getting an array of data types
1334
+ */
1335
+ dragDataType(...args)
1336
+ {
1337
+ if(Array.isArray(args[0])) args = args[0];
1338
+ let _args = [];
1339
+ for(let v of args) if(DataType.hasOwnProperty(v) && _args.indexOf(v) === -1) _args.push(v); // validate argument and remove duplicates
1340
+
1341
+ return this.property("dragDataType", _args.length > 0 ? [_args] : []);
1342
+ }
1343
+
1344
+ /**
1345
+ * Sets custom data that should be passed when dragging this component instead of passing component's data, such as text or icon.
1346
+ * Without setting this data a default component data will be dragged, such as text, list item, icon, what what ever applies to this component.
1347
+ *
1348
+ * @arg {string|string[]} data - String data or array of strings. For a list of files or URLs use array or separate them by new line character \n.
1349
+ * @return {this} Returns `this` component.
1350
+ */
1351
+ dragData(...args)
1352
+ {
1353
+ if(args.length === 0) return this.property("dragDataType", []);
1354
+
1355
+ if(Array.isArray(args[0])) args = args[0];
1356
+ let _args = [];
1357
+ for(let v of args)
1358
+ {
1359
+ if(!(typeof v === "string")) continue;
1360
+ if((DataType.hasOwnProperty(v) || v.startsWith("component/")) && _args.indexOf(v) === -1) _args.push(v);// validate argument and remove duplicates
1361
+ }
1362
+
1363
+ return this.property("dragDataType", [_args]);
1364
+ }
1365
+
1366
+ /**
1367
+ * Sets acceptable data types that may be dropped on this component during drag and drop operation.
1368
+ *
1369
+ * @param {...DataType|DataType[]} data_type - DataType.[None|All|Text|Image|...]. Use `All` to accept all data types.
1370
+ * @return {string[]|this} Returns `this` component is argument is provided, otherwise returns `string[]` value.
1371
+ * @example
1372
+ * // Usage examples:
1373
+ * label.dropDataType(DataType.All); // Setting to accept all data types
1374
+ * label.dropDataType(.DataType.Text, DataType.HTML); // setting by multiple arguments
1375
+ * label.dropDataType([DataType.Text, DataType.HTML]); // setting by an array of values
1376
+ * const _types = label.dropDataType(); // getting an array of data types
1377
+ */
1378
+ dropDataType(...args)
1379
+ {
1380
+ if(args.length === 0) return this.property("dropDataType", []);
1381
+
1382
+ if(Array.isArray(args[0])) args = args[0];
1383
+ let _args = [];
1384
+ for(let v of args)
1385
+ {
1386
+ if(!(typeof v === "string")) continue;
1387
+ if((DataType.hasOwnProperty(v) || v.startsWith("component/")) && _args.indexOf(v) === -1) _args.push(v);// validate argument and remove duplicates
1388
+ }
1389
+
1390
+ return this.property("dropDataType", [_args]);
1391
+ }
1392
+
1393
+ /**
1394
+ * Sets a drop mode for components: Table, List, Tree.
1395
+ *
1396
+ * @param {DropMode} [mode] - Enum value: DropMode.[On|Insert|OnOrInsert]. On - allows data to be dropped on list items. Insert - allows to drop data between list items.
1397
+ * @returns {DropMode|this} Returns `this` component if an argument is provided; otherwise, returns the current drop mode as one of the DropMode enum values.
1398
+ * @see DropMode
1399
+ */
1400
+ dropMode(...args)
1401
+ {
1402
+ if(args.length > 0) args[0] = String(args[0]);
1403
+ return this.property("dropMode", args);
1404
+ }
1405
+
1406
+ /**
1407
+ * Sets, updates, or gets component border object. Border object should contain only properties that you want to set or update.
1408
+ *
1409
+ * @param {Border} [border] - Border object.
1410
+ * @param {boolean} [update] - Set to `true` to only change specified border properties instead of replacing existing border.
1411
+ * @return {this|Border|null} - If called without arguments a `Border` object is returned or `null` if the component's border wasn't set. Otherwise and instance of the component is returned.
1412
+ * @see UIComponent#border
1413
+ */
1414
+ border(...args)
1415
+ {
1416
+ if(args.length > 0)
1417
+ {
1418
+ if(typeof args[0] !== "object") throw new Error("Border argument must be an Object");
1419
+ const msg = {control: this.id, set: "border", value: args[0]};
1420
+ if(args[1] === true) msg.update = true;
1421
+ ui.sendToGUI(msg);
1422
+ return this;
1423
+ }
1424
+ else
1425
+ {
1426
+ return this.property("border", []);
1427
+ }
1428
+ }
1429
+
1430
+ /**
1431
+ * Sets or gets an alignment of icon relative to text of a Label, Button, or items of a List.
1432
+ *
1433
+ * @param {string} [alignment] - A combination of one or more words: "top right bottom left center leading trailing"
1434
+ * @return {this|string} Returns `this` component if an argument is provided, otherwise returns `string` value.
1435
+ */
1436
+ iconAlignment(...args)
1437
+ {
1438
+ return this.property("iconAlignment", args);
1439
+ }
1440
+
1441
+ /**
1442
+ * Sets or gets an alignment of the component's content relative to the component, such as Label, Button, CheckBox, RadioBox, or items of a List.
1443
+ *
1444
+ * @param {string} [alignment] - A combination of one or more words: "top bottom right left center leading trailing"
1445
+ * @return {this|string} Returns `this` component if an argument is provided, otherwise returns `string` value.
1446
+ */
1447
+ alignment()
1448
+ {
1449
+ return this.property("alignment", arguments);
1450
+ }
1451
+
1452
+ /**
1453
+ * Sets or get a gap in pixel between icon and text of a List item, Button, Label, ComboBox, CheckBox, and RadioBox.
1454
+ *
1455
+ * @param {number} [gap] - The gap in pixels (must be an integer ≥ 0). If omitted, returns the current gap.
1456
+ * @return {this|number} Returns `number` if argument is omitted, otherwise returns `this` component instance.
1457
+ */
1458
+ iconTextGap(...args)
1459
+ {
1460
+ return this.property("iconTextGap", args);
1461
+ }
1462
+
1463
+ /**
1464
+ * Captures an image of the component and all subcomponents. Returns the image as a base 64 encoded string in format "data:image/FORMAT;base64,As8fE4fdt1r.....".
1465
+ *
1466
+ * @param {string} [format="png"] - Image format: `png`, `jpg`, `jpeg`, `bmp`, `gif`.
1467
+ * @param {Object} [crop] - Object that holds position and size of the cropped area. Format: {x: number, y: number, width: number, height: number}.
1468
+ * @return {string} Returns base 64 encoded string representation of the image. To get its binary data you must extract the base 64 string, then decode it from base 64 to bytes.
1469
+ */
1470
+ snapshot(format = "png", crop = null)
1471
+ {
1472
+ return ui.sendToGUI({control: this.#id, get: "snapshot", data: {format: format.toUpperCase(), crop: crop}});
1473
+ }
1474
+
1475
+ /**
1476
+ * Sets or gets the anchor style of the component that indicates how to resize this component based on the resizing parent component.
1477
+ * Anchor defines which sides (top, right, bottom, left) of the component stay attached to the parent's edge when parent gets resized.
1478
+ * Set anchors after the component and its ancestors are added to a container to ensure expected behaviour.
1479
+ *
1480
+ * @param {string} [anchors] - Space-separated list of sides to anchor (e.g., `"top left"`). If omitted, returns the current anchor value.
1481
+ * @returns {this|string} Returns `this` component when setting, or the current anchor `string` when getting value.
1482
+ * @example
1483
+ * const txt1 = new TextArea().anchor("top left right"); // Horizontally resizable
1484
+ * const txt2 = new TextArea().anchor("top left bottom"); // Vertically resizable
1485
+ */
1486
+ anchor(...args)
1487
+ {
1488
+ if(args.length > 0 && typeof args[0] !== "string") return this; // enforce string
1489
+ return this.property("anchor", args);
1490
+ }
1491
+
1492
+ /**
1493
+ * Sets or gets tooltip text for the component. It's the text that pops up when cursor hovers the component.
1494
+ *
1495
+ * @param {string} text - Can be plain text or HTML. HTML string should be surrounded with HTML tags. See example.
1496
+ * @return {string|this} Returns `this` component if argument is set, otherwise returns `string` value.
1497
+ * @example
1498
+ * button.tooltip("<html>This is line 1<br />This is line 2</html>"); // Setting HTML string with 2 lines of text
1499
+ */
1500
+ tooltip(...args)
1501
+ {
1502
+ return this.property("tooltip", args);
1503
+ }
1504
+
1505
+ /**
1506
+ * Indicates the minimum size the component can be resized to.
1507
+ *
1508
+ * @param {number|Size} width_or_size - Minimum width, or a Size formatted object as a sole parameter.
1509
+ * @param {number} height - Minimum height of the component.
1510
+ * @returns {Size|this|null} If called without parameters, current minimum Size is returned or `null` if it wasn't previously set; otherwise returns `this` component instance for chaining.
1511
+ * @see Size
1512
+ */
1513
+ minSize(...args)
1514
+ {
1515
+ if(args.length > 1) // convert (width, height) params to object
1516
+ {
1517
+ args = [{width: parseInt(args[0]), height: parseInt(args[1])}];
1518
+ }
1519
+ else if(args.length === 1 && typeof args[0] !== "object") // if only 1 arg and it's not an object, then ignore
1520
+ {
1521
+ return this;
1522
+ }
1523
+
1524
+ return this.property("minSize", args);
1525
+ }
1526
+
1527
+ /**
1528
+ * Indicates the maximum size the component can be resized to.
1529
+ *
1530
+ * @param {number|Size} width_or_size - Maximum width, or a Size formatted object as a sole parameter.
1531
+ * @param {number} height - Maximum height of the component.
1532
+ * @returns {Size|this|null} If called without parameters, current maximum Size is returned or `null` if it wasn't previously set; otherwise returns `this` component instance for chaining.
1533
+ * @see Size
1534
+ */
1535
+ maxSize(...args)
1536
+ {
1537
+ if(args.length > 1) // convert (width, height) params to object
1538
+ {
1539
+ args = [{width: parseInt(args[0]), height: parseInt(args[1])}];
1540
+ }
1541
+ else if(args.length > 0 && typeof args[0] !== "object") // if only 1 arg and it's not an object, then ignore
1542
+ {
1543
+ return this;
1544
+ }
1545
+
1546
+ return this.property("maxSize", args);
1547
+ }
1548
+
1549
+ /**
1550
+ * Positions the component in the center of it's parent component, owner Window (if Window has an owner Window), or screen (Window only).
1551
+ * Applies only to components that have position() property (not like menu, menu item, tray icon, etc.)
1552
+ *
1553
+ * @arg {Object} [component] - If provided, `this` component will be positioned in the center of the provided component.
1554
+ * @return {this} Returns `this` component.
1555
+ */
1556
+ center(component = null)
1557
+ {
1558
+ let parentSize = null;
1559
+ let offset = {x: 0, y: 0};
1560
+
1561
+ if(component !== null) // if relative component was specified
1562
+ {
1563
+ if(!(component instanceof UIComponent)) throw new Error(`"component" argument must be of type derived from UIComponent that have size and position properties.`);
1564
+ if(this === component) throw new Error(`"component" should not be same object as you are trying to center.`);
1565
+
1566
+ parentSize = component.size();
1567
+ if(component !== this.parent) offset = component.position();
1568
+ }
1569
+ else if(this.parent !== null) // this has parent, then center to it
1570
+ {
1571
+ parentSize = (this.parent instanceof Window || this.parent instanceof InternalWindow) ? this.parent.innerSize() : this.parent.size();
1572
+ }
1573
+ else if(this instanceof Window) // if this is a Window
1574
+ {
1575
+ if(this.owner !== null) // if this Window has owner, then center to it
1576
+ {
1577
+ parentSize = this.owner.size();
1578
+ offset = this.owner.position();
1579
+ }
1580
+ else
1581
+ {
1582
+ parentSize = this.screenSize; // center to screen
1583
+ }
1584
+ }
1585
+
1586
+ if(parentSize !== null) this.position(offset.x + ((parentSize.width - this.width()) / 2), offset.y + ((parentSize.height - this.height()) / 2));
1587
+
1588
+ return this;
1589
+ }
1590
+
1591
+ /**
1592
+ * Enables/disables onKey() events to be emitted for focus traversal key (usually Tab key). By default onKey() event doesn't emit for such keys.
1593
+ *
1594
+ * @param {boolean} [enable] - `true` - enabled. `false` - disabled.
1595
+ * @return {this|boolean} Returns `this` component if argumen is provided, otherwise returns `boolean` value.
1596
+ */
1597
+ emitFocusKeyEvents(...args)
1598
+ {
1599
+ if(args.length > 0) args[0] = (args[0] === true);
1600
+ return this.property("emitFocusKeyEvents", args);
1601
+ }
1602
+
1603
+ /**
1604
+ * Finds and returns first ancestor component of specified class. e.g. panel.getAncestor(Window) will return Window component that panel is in, or null if panel doesn't have a parent or ancestor with specified class.
1605
+ *
1606
+ * @param {Function} ancestor_class - UI Component class that comes with `swing-ui` module.
1607
+ * @return {Object|null}
1608
+ */
1609
+ getAncestor(ancestor_class)
1610
+ {
1611
+ if(this.parent == null) return null;
1612
+ if(this.parent instanceof ancestor_class) return this.parent;
1613
+ return this.parent.getAncestor(ancestor_class);
1614
+ }
1615
+
1616
+ /**
1617
+ * Emits a specified event with a custom event object for this component. This event will be sent to all event handlers registered with the specified event name.
1618
+ *
1619
+ * @param {string} event_name - Name of the event is same as name of an event handler registering function, such as `onAction` or `onChange`.
1620
+ * @param {object} event - Custom event object
1621
+ * @returns {this} Returns an instance of this component.
1622
+ * @example
1623
+ * button.emit("onAction", {type: "action", command: "Click Me"}); // emits a custom event without user clicking on the button
1624
+ */
1625
+ emit(event_name, event = {type: "custom"})
1626
+ {
1627
+ this.verifyComponent();
1628
+ if(!Array.isArray(this.callback[event_name])) return this;
1629
+ if(typeof event !== "object") throw new Error(`"properties" argument must be an object.`);
1630
+
1631
+ event.event = event_name;
1632
+ event.target = this;
1633
+
1634
+ for(const callback of this.callback[event_name])
1635
+ {
1636
+ try
1637
+ {
1638
+ callback.call(this, event);
1639
+ }catch(ex)
1640
+ {
1641
+ error(`${ui._msg[6]}${this.constructor.name}: ${event} \n\n${ex.stack}\n\nEvent ${stringify(event)}`, false);
1642
+ }
1643
+ }
1644
+
1645
+ return this;
1646
+ }
1647
+
1648
+ /**
1649
+ * Gets or sets the minimum size of the icon's largest side (in pixels).
1650
+ * If the icon's largest side is smaller than the specified minimum, it will be scaled up proportionally.
1651
+ * Applies to Button, Label, List, and ComboBox.
1652
+ *
1653
+ * @param {number|null} [min_size] - Sets the minimum size of a larger side of icon, or resets to no minimum when `null` is passed.
1654
+ * @return {number|null|Object} If called without arguments, returns `number` value, otherwise returns `this` component instance.
1655
+ */
1656
+ minIconSize()
1657
+ {
1658
+ return this.property("minIconSize", arguments);
1659
+ }
1660
+
1661
+ /**
1662
+ * Gets or sets the maximum size of the icon's largest side (in pixels).
1663
+ * If the icon's largest side is larger than the specified maximum, it will be scaled down proportionally.
1664
+ * Applies to Button, Label, List, and ComboBox.
1665
+ *
1666
+ * @param {number|null} [max_size] - Sets the maximum size of a larger side of icon, or resets to no maximum when `null` is passed.
1667
+ * @return {number|null|Object} If called without arguments, returns `number` value, otherwise returns `this` component instance.
1668
+ */
1669
+ maxIconSize()
1670
+ {
1671
+ return this.property("maxIconSize", arguments);
1672
+ }
1673
+
1674
+ /**
1675
+ * Sets or gets an array of keyboard shortcuts handled by {@link UIComponent#onShortcut|onShortcut()} event handler.
1676
+ *
1677
+ * @param {object[]|string[]} [shortcuts] - An array of shortcut objects {shortcut:{string}, scope: {ShortcutScope}}, or an Array of String shortcuts (for strings a default {@link ShortcutScope} will be assigned).
1678
+ * @returns {this|object[]} If called without arguments, then current shortcuts array is returned, otherwise an instance of `this` component is returned.
1679
+ * @see ShortcutScope
1680
+ * @example <h2>Setting shortcuts as strings</h2>
1681
+ * window.shortcuts(["f1", "ctrl+s"]);
1682
+ * @example <h2>Setting shortcuts as objects</h2>
1683
+ * window.shortcuts([
1684
+ * {
1685
+ * key: "control+s",
1686
+ * scope: ShortcutScope.Window, // optional
1687
+ * action: "save_file" // optional
1688
+ * }
1689
+ * ]);
1690
+ * @example <h2>Clearing all shortcuts</h2>
1691
+ * window.shortcuts([]); // pass an empty array
1692
+ * @example <h2>Retrieving current shortcuts</h2>
1693
+ * const shortcuts = window.shortcuts(); // returns an Array of objects with `key` and `scope` properties.
1694
+ */
1695
+ shortcuts()
1696
+ {
1697
+ if(arguments.length > 0)
1698
+ {
1699
+ if(!Array.isArray(arguments[0])) throw new TypeError(`First argument to shortcuts() method must be an array, not '${typeof arguments[0]}'`);
1700
+
1701
+ for(let i = 0; i < arguments[0].length; i++)
1702
+ {
1703
+ if(typeof arguments[0][i] === "string")
1704
+ {
1705
+ arguments[0][i] = {key: arguments[0][i]};
1706
+ }
1707
+ else if(typeof arguments[0][i] !== "object" || typeof arguments[0][i].key !== "string")
1708
+ {
1709
+ arguments[0].splice(i--, 1); // remove invalid type
1710
+ continue;
1711
+ }
1712
+
1713
+ if(typeof arguments[0][i].scope !== "string")
1714
+ {
1715
+ arguments[0][i].scope = this instanceof Window ? ShortcutScope.Window : (this instanceof ComponentContainer ? ShortcutScope.Ancestor : ShortcutScope.Focused) // set default scope
1716
+ }
1717
+
1718
+ if(typeof arguments[0][i].action !== "string") arguments[0][i].action = arguments[0][i].key; // default action is the key value
1719
+ }
1720
+ }
1721
+
1722
+ return this.property("shortcuts", arguments);
1723
+ }
1724
+
1725
+ //endregion
1726
+
1727
+ //region Event Handlers
1728
+
1729
+ /**
1730
+ * Overrides current error handler.
1731
+ *
1732
+ * @param {function(ErrorEvent): any} [callback=undefined] - The event handler function receiving an {@link ErrorEvent} object. Pass `null` to unregister all handler functions.
1733
+ * @return {this}
1734
+ * @see ErrorEvent
1735
+ */
1736
+ onError(callback)
1737
+ {
1738
+ if(typeof callback === "function") this.callback.onError = [callback];
1739
+ return this;
1740
+ }
1741
+
1742
+ /**
1743
+ * Registers or unregisters a handler function for an event when the component gets disposed, either by calling destroy() method or loosing all references to the component.
1744
+ *
1745
+ * @param {function(DisposeEvent): any} [callback=undefined] - The event handler function receiving {@link DisposeEvent} object. Pass `null` to unregister all handler functions.
1746
+ * @param {boolean} [remove=false] - Set to `true` to remove this event handler
1747
+ * @return {this}
1748
+ * @see DisposeEvent
1749
+ */
1750
+ onDispose(callback, remove = false)
1751
+ {
1752
+ const name = "onDispose";
1753
+
1754
+ // return callbacks
1755
+ if(callback === null)
1756
+ {
1757
+ return Array.isArray(this.callback[name]) ?? [];
1758
+ }
1759
+ // remove callback
1760
+ else if(remove === true)
1761
+ {
1762
+ if(Array.isArray(this.callback[name]))
1763
+ {
1764
+ const i = this.callback[name].indexOf(callback); // find callback
1765
+ if(i > -1) this.callback[name].splice(i, 1); // remove it
1766
+ if(this.callback[name].length === 0) delete this.callback[name]; // delete array
1767
+ }
1768
+ }
1769
+ // add callback
1770
+ else if(typeof callback === "function")
1771
+ {
1772
+ if(!Array.isArray(this.callback[name])) this.callback[name] = []; // create array
1773
+ this.callback[name].push(callback); // add callback (don't bind to this here, do it at call)
1774
+ }
1775
+
1776
+ return this;
1777
+ }
1778
+
1779
+ /**
1780
+ * Gets, registers or unregisters a handler function for component's primary action event.
1781
+ *
1782
+ * @param {function(ActionEvent): any} [callback=undefined] - The event handler function that receives a {@link ActionEvent} object. Pass `null` to unregister all handler functions.
1783
+ * @param {boolean} [remove=false] - Set to `true` to unregister the event handler.
1784
+ * @returns {this|function[]} An array of handler functions if no arguments are passed, otherwise `this` component for method chaining.
1785
+ * @see ActionEvent
1786
+ * @example
1787
+ * // Example event object:
1788
+ * const e = {
1789
+ * target: object, // The component that triggered the event.
1790
+ * command: string, // Button text or a specified command using .command("") method
1791
+ * modifiers: number, // Keyboard modifiers (bitmask or flags).
1792
+ * alt: boolean, // Indicates if ALT key was pressed.
1793
+ * shift: boolean, // Indicates if SHIFT key was pressed.
1794
+ * ctrl: boolean, // Indicates if CTRL key was pressed.
1795
+ * type: string, // `action` or `index_change`. Present when applicable.
1796
+ * x: number, // Mouse x position relative to component
1797
+ * y: number, // Mouse y position relative to component
1798
+ * screenX: number, // Mouse x position relative to default screen
1799
+ * screenY: number, // Mouse y position relative to default screen
1800
+ * interactive: boolean, // Indicates if the action was triggered by user's interaction (not programmatically). Present when applicable.
1801
+ * selected: false // Present when applicable.
1802
+ * };
1803
+ */
1804
+ onAction(...args)
1805
+ {
1806
+ if(args.length === 0) return this.event("onAction");
1807
+ return this.event("onAction", args[0], args[1] ?? false);
1808
+ }
1809
+
1810
+ /**
1811
+ * Gets, registers or unregisters a mouse buttons event handler function.
1812
+ *
1813
+ * @param {function(MouseEvent): any} [callback=undefined] - The event handler function that receives a {@link MouseEvent} object. Pass `null` to unregister all handler functions.
1814
+ * @param {boolean} [remove=false] - Set to `true` to unregister the event handler.
1815
+ * @returns {this|function[]} An array of handler functions if no arguments are passed, otherwise `this` component for method chaining.
1816
+ * @see MouseEvent
1817
+ * @example
1818
+ * // Example event object:
1819
+ * const e = {
1820
+ * target: object, // The component that triggered the event.
1821
+ * type: string, // `click`, `down`, `up`, `enter`, or `leave`.
1822
+ * button: number, // Button number. One of MouseButton enum values.
1823
+ * clicks: number, // Amount of time the click happened. Present when applicable.
1824
+ * modifiers: number, // Keyboard modifiers (bitmask or flags).
1825
+ * alt: boolean, // Indicates if ALT key was pressed.
1826
+ * shift: boolean, // Indicates if SHIFT key was pressed.
1827
+ * ctrl: boolean, // Indicates if CTRL key was pressed.
1828
+ * x: number, // Present when applicable.
1829
+ * y: number, // Present when applicable.
1830
+ * screenX: number, // Present when applicable.
1831
+ * screenY: number, // Present when applicable.
1832
+ * };
1833
+ */
1834
+ onMouseEvents(...args)
1835
+ {
1836
+ if(args.length === 0) return this.event("onMouseEvents");
1837
+ return this.event("onMouseEvents", args[0], args[1] ?? false);
1838
+ }
1839
+
1840
+ /**
1841
+ * Gets, registers or unregisters a mouse motion event handler function.
1842
+ *
1843
+ * @param {function(MouseMotionEvent): any} [callback=undefined] - The event handler function that receives a {@link MouseMotionEvent} object. Pass `null` to unregister all handler functions.
1844
+ * @param {boolean} [remove=false] - Set to `true` to unregister the event handler.
1845
+ * @returns {this|function[]} An array of handler functions if no arguments are passed, otherwise `this` component for method chaining.
1846
+ * @see MouseMotionEvent
1847
+ * @example
1848
+ * // Example event object:
1849
+ * const e = {
1850
+ * target: object, // The component that triggered the event.
1851
+ * type: string, // `move` or `drag`.
1852
+ * button: number, // Button number. One of MouseButton enum values. Present when applicable.
1853
+ * modifiers: number, // Keyboard modifiers (bitmask or flags).
1854
+ * x: number, // Mouse x position relative to component
1855
+ * y: number, // Mouse y position relative to component
1856
+ * screenX: number, // Mouse x position relative to default screen
1857
+ * screenY: number, // Mouse y position relative to default screen
1858
+ * };
1859
+ */
1860
+ onMouseMotion(...args)
1861
+ {
1862
+ if(args.length === 0) return this.event("onMouseMotion");
1863
+ return this.event("onMouseMotion", args[0], args[1] ?? false);
1864
+ }
1865
+
1866
+ /**
1867
+ * Gets, registers or unregisters a mouse wheel event handler function.
1868
+ *
1869
+ * @param {function(MouseWheelEvent): any} [callback=undefined] - The event handler function that receives a {@link MouseWheelEvent} object. Pass `null` to unregister all handler functions.
1870
+ * @param {boolean} [remove=false] - Set to `true` to unregister the event handler.
1871
+ * @returns {this|function[]} An array of handler functions if no arguments are passed, otherwise `this` component for method chaining.
1872
+ * @see MouseWheelEvent
1873
+ * @example
1874
+ * // Example event object:
1875
+ * const e = {
1876
+ * target: object, // The component that triggered the event.
1877
+ * type: string, // `wheel`.
1878
+ * button: number, // Button number. One of MouseButton enum values. Present when applicable.
1879
+ * scrollAmount: number, // Returns the number of units that should be scrolled per click of mouse wheel rotation.
1880
+ * rotation: number, // Indicates the number of "clicks" the mouse wheel was rotated.
1881
+ * // A partial rotation may occur if the mouse supports a high-resolution wheel.
1882
+ * // In this case, the number is zero until a full "click" has been accumulated.
1883
+ * modifiers: number, // Keyboard modifiers (bitmask or flags).
1884
+ * alt: boolean, // Indicates if ALT key was pressed.
1885
+ * shift: boolean, // Indicates if SHIFT key was pressed.
1886
+ * ctrl: boolean, // Indicates if CTRL key was pressed.
1887
+ * x: number, // Mouse x position relative to component
1888
+ * y: number, // Mouse y position relative to component
1889
+ * screenX: number, // Mouse x position relative to default screen
1890
+ * screenY: number, // Mouse y position relative to default screen
1891
+ * };
1892
+ */
1893
+ onMouseWheel(...args)
1894
+ {
1895
+ if(args.length === 0) return this.event("onMouseWheel");
1896
+ return this.event("onMouseWheel", args[0], args[1] ?? false);
1897
+ }
1898
+
1899
+ /**
1900
+ * Gets, registers or unregisters a keyboard keys' event handler function.
1901
+ *
1902
+ * @param {function(KeyEvent): any} [callback=undefined] - The event handler function that receives a {@link KeyEvent} object. Pass `null` to unregister all handler functions.
1903
+ * @param {boolean} [remove=false] - Set to `true` to unregister the event handler.
1904
+ * @returns {this|function[]} An array of handler functions if no arguments are passed, otherwise `this` component for method chaining.
1905
+ * @see KeyEvent
1906
+ * @example
1907
+ * // Example event object:
1908
+ * const e = {
1909
+ * target: object, // The component that triggered the event.
1910
+ * type: string, // `up`, `down`, `type`.
1911
+ * keyCode: number // Numeric code associated with the key
1912
+ * extendedKeyCode: number // The extended key code is a unique id assigned to a key on the keyboard just like keyCode. However, unlike keyCode, this value depends on the current keyboard layout.
1913
+ * keyChar: string // Key character
1914
+ * modifiers: number, // Keyboard modifiers (bitmask or flags).
1915
+ * modifiersText: string // Modifiers representation as a string.
1916
+ * alt: boolean, // Indicates if ALT key was pressed.
1917
+ * shift: boolean, // Indicates if SHIFT key was pressed.
1918
+ * ctrl: boolean, // Indicates if CTRL key was pressed.
1919
+ * keyLocation: string // `standard`, `numpad`, `left`, `right`. Location of the key on keyboard. Only present when type is `up` or `down`.
1920
+ * };
1921
+ */
1922
+ onKey(...args)
1923
+ {
1924
+ if(args.length === 0) return this.event("onKey");
1925
+ return this.event("onKey", args[0], args[1] ?? false);
1926
+ }
1927
+
1928
+ /**
1929
+ * Gets, registers or unregisters a focus event handler function.
1930
+ *
1931
+ * @param {function(FocusEvent): any} [callback=undefined] - The event handler function that receives a {@link FocusEvent} object. Pass `null` to unregister all handler functions.
1932
+ * @param {boolean} [remove=false] - Set to `true` to unregister the event handler.
1933
+ * @returns {this|function[]} An array of handler functions if no arguments are passed, otherwise `this` component for method chaining.
1934
+ * @see FocusEvent
1935
+ * @example
1936
+ * // Example event object:
1937
+ * const e = {
1938
+ * target: object, // The component that triggered the event.
1939
+ * type: string, // `focus`, `blur`.
1940
+ * cause: string // `UNKNOWN`, `ACTIVATION`, `TRAVERSAL_FORWARD`, `TRAVERSAL_BACKWARD`.
1941
+ * temporary: boolean, // Identifies the focus change event as temporary or permanent
1942
+ * };
1943
+ */
1944
+ onFocusEvents(...args)
1945
+ {
1946
+ if(args.length === 0) return this.event("onFocusEvents");
1947
+ return this.event("onFocusEvents", args[0], args[1] ?? false);
1948
+ }
1949
+
1950
+ /**
1951
+ * Gets, registers or unregisters an event handler function for events: resize, move, show, hide.
1952
+ *
1953
+ * @param {function(ComponentEvent): any} [callback=undefined] - The event handler function that receives a {@link ComponentEvent} object. Pass `null` to unregister all handler functions.
1954
+ * @param {boolean} [remove=false] - Set to `true` to unregister the event handler.
1955
+ * @returns {this|function[]} An array of handler functions if no arguments are passed, otherwise `this` component for method chaining.
1956
+ * @see ComponentEvent
1957
+ * @example
1958
+ * // Example event object:
1959
+ * const e = {
1960
+ * target: object, // The component that triggered the event.
1961
+ * type: string, // `resize`, `move`, `show`, `hide`.
1962
+ * interactive: boolean, // Indicates if the event was triggered by user's interaction (not programmatically). Present when e.type is `move` or `resize`.
1963
+ * x: number, // New x position of component.
1964
+ * y: number, // New y position of component.
1965
+ * width: number, // New width of component. Present when e.type is `resize`.
1966
+ * height: number, // New height of component. Present when e.type is `resize`.
1967
+ * innerWidth: number, // New width of content area of the component. Present when e.type is `resize` and component is Window or InternalWindow.
1968
+ * innerHeight: number, // New height of content area of the component. Present when e.type is `resize` and component is Window or InternalWindow.
1969
+ *};
1970
+ */
1971
+ onComponentEvents(...args)
1972
+ {
1973
+ if(args.length === 0) return this.event("onComponentEvents");
1974
+ return this.event("onComponentEvents", args[0], args[0] ?? false);
1975
+ }
1976
+
1977
+ /**
1978
+ * Gets, registers or unregisters an event handler that listens for changes to this component's properties.
1979
+ *
1980
+ * @param {function(PropertyChangeEvent): any} [callback=undefined] - The event handler function that receives a {@link PropertyChangeEvent} object. Pass `null` to unregister all handler functions.
1981
+ * @param {boolean} [remove=false] - Set to `true` to unregister the event handler.
1982
+ * @returns {this|function[]} An array of handler functions if no arguments are passed, otherwise `this` component for method chaining.
1983
+ * @see PropertyChangeEvent
1984
+ * @example
1985
+ * // Example event object:
1986
+ * const e = {
1987
+ * target: object, // The container component that triggered the event.
1988
+ * type: string, // `change`.
1989
+ * property: string, // The property that was changed
1990
+ * oldValue: any, // Previous value of the changed property
1991
+ * newValue: any // New value of the changed property
1992
+ * };
1993
+ */
1994
+ onPropertyChange(...args)
1995
+ {
1996
+ if(args.length === 0) return this.event("onPropertyChange");
1997
+ return this.event("onPropertyChange", args[0], args[1] ?? false);
1998
+ }
1999
+
2000
+ /**
2001
+ * Gets, registers or unregisters a handler function for drag related events. Call without parameters to get an array of already registered handler functions.
2002
+ *
2003
+ * @param {function(DragEvent): any} [callback=undefined] - The event handler function that receives a {@link DragEvent} object. Pass `null` to unregister all handler functions.
2004
+ * @param {boolean} [remove=false] - Set to `true` to unregister the event handler.
2005
+ * @returns {this|function[]} An array of handler functions if no arguments are passed, otherwise `this` component for method chaining.
2006
+ * @see DragEvent
2007
+ *
2008
+ * @example
2009
+ * // Example event object:
2010
+ * const e = {
2011
+ * target: object, // The component that triggered the event.
2012
+ * type: string, // `dragstart`, `dragend`, `cancel`.
2013
+ * action: string // One of DragDropAction enum values. Present when applicable.
2014
+ * x: number, // X position of a pointer relative to this component. Present when e.type is `dragstart` or `dragend`.
2015
+ * y: number, // Y position of a pointer relative to this component. Present when e.type is `dragstart` or `dragend`.
2016
+ * screenX: number, // X position of a pointer relative to default screen. Present when e.type is `dragstart` or `dragend`.
2017
+ * screenY: number, // Y position of a pointer relative to default screen. Present when e.type is `dragstart` or `dragend`.
2018
+ * button: number, // Indicates a pointing device button involved in dragging. Value of MouseButton enum.
2019
+ * modifiers: number, // Keyboard modifiers (bitmask or flags). Present when e.type is `dragstart`.
2020
+ * modifiersText: string, // Keyboard modifiers. Present when e.type is `dragstart`.
2021
+ * alt: boolean, // Indicates if ALT key was pressed. Present when e.type is `dragstart`.
2022
+ * shift: boolean, // Indicates if SHIFT key was pressed. Present when e.type is `dragstart`.
2023
+ * ctrl: boolean, // Indicates if CTRL key was pressed. Present when e.type is `dragstart`.
2024
+ * selected: number[] // Array of selected indexes of a List component whose items are being dragged. Present when List items are being dragged.
2025
+ * };
2026
+ */
2027
+ onDragEvents(...args)
2028
+ {
2029
+ if(args.length === 0) return this.event("onDragEvents");
2030
+ return this.event("onDragEvents", args[0], args[1] ?? false);
2031
+ }
2032
+
2033
+ /**
2034
+ * Gets, registers or unregisters a handler function for drop related events. Call without parameters to get an array of already registered handler functions.
2035
+ *
2036
+ * @param {function(DropEvent): any} [callback=undefined] - The event handler function that receives a {@link DropEvent} object. Pass `null` to unregister all handler functions.
2037
+ * @param {boolean} [remove=false] - Set to `true` to unregister the event handler.
2038
+ * @returns {this|function[]} An array of handler functions if no arguments are passed, otherwise `this` component for method chaining.
2039
+ * @see DropEvent
2040
+ * @example <h2>{@link DropEvent} object format:</h2>
2041
+ * const e = {
2042
+ * target: object, // The component that triggered the event.
2043
+ * type: string, // `dragenter`, `dragover`, `dragleave`, `drop`.
2044
+ * action: string // One of the values of enum DragDropAction. Present when applicable.
2045
+ * data: object // An object that contains the data that is being dropped. Present only when e.type is `drop` or `dragenter`.
2046
+ * source: object, // A component that is being dragged or dropped onto this component.
2047
+ * // Present only when a another UI component is dragged onto this component and `DataType.Component` is of the types set by dragDataType().
2048
+ * x: number, // X position of a pointer relative to this component. Present when e.type is `dragstart` or `drop`.
2049
+ * y: number, // Y position of a pointer relative to this component. Present when e.type is `dragstart` or `drop`.
2050
+ * screenX: number, // X position of a pointer relative to default screen. Present when e.type is `dragstart` or `drop`.
2051
+ * screenY: number, // Y position of a pointer relative to default screen. Present when e.type is `dragstart` or `drop`.
2052
+ * selected: number[] // Array of selected indexes of a List component whose items are being dragged. Present when List items are being dragged.
2053
+ * };
2054
+ */
2055
+ onDropEvents(...args)
2056
+ {
2057
+ if(args.length === 0) return this.event("onDropEvents");
2058
+
2059
+ return this.event("onDropEvents", args[0], args[1] ?? false);
2060
+ }
2061
+
2062
+ /**
2063
+ * Gets, registers or unregisters a handler function for component's text value change event. Applied to components where text can be changed by user, such as TextField, PasswordField, TextArea, DocumentEditor, ComboBox, Spinner, etc.
2064
+ *
2065
+ * @param {function(ChangeEvent): any} [callback=undefined] - The event handler function that receives a {@link ChangeEvent} object. Pass `null` to unregister all handler functions.
2066
+ * @param {boolean} [remove=false] - Set to `true` to unregister the event handler.
2067
+ * @returns {this|function[]} An array of handler functions if no arguments are passed, otherwise `this` component for method chaining.
2068
+ * @see ChangeEvent
2069
+ */
2070
+ onChange(...args)
2071
+ {
2072
+ if(args.length === 0) return this.event("onChange");
2073
+ return this.event("onChange", args[0], args[1] ?? false);
2074
+ }
2075
+
2076
+ /**
2077
+ * Gets, registers or unregisters a handler function for component's event when ancestor is added, removed, or moved.
2078
+ *
2079
+ * @param {function(AncestorEvent): any} [callback=undefined] - The event handler function that receives a {@link AncestorEvent} object. Pass `null` to unregister all handler functions.
2080
+ * @param {boolean} [remove=false] - Set to `true` to unregister the event handler.
2081
+ * @returns {this|function[]} An array of handler functions if no arguments are passed, otherwise `this` component for method chaining.
2082
+ * @see AncestorEvent
2083
+ * @example
2084
+ * // Example event object:
2085
+ * const e = {
2086
+ * target: object, // The component that triggered the event.
2087
+ * type: string, // `add`, `remove`, `move`.
2088
+ * ancestor: object, // The ancestor component.
2089
+ * };
2090
+ */
2091
+ onAncestorEvents(...args)
2092
+ {
2093
+ if(args.length === 0) return this.event("onAncestorEvents");
2094
+ return this.event("onAncestorEvents", args[0], args[1] ?? false);
2095
+ }
2096
+
2097
+ /**
2098
+ * Gets, registers or unregisters a handler function for component's {@link ShortcutEvent} when a shortcut is invoked.
2099
+ *
2100
+ * @param {function(ShortcutEvent): any} [callback=undefined] - The event handler function that receives a {@link ShortcutEvent} object. Pass `null` to unregister all handler functions.
2101
+ * @param {boolean} [remove=false] - Set to `true` to unregister the event handler.
2102
+ * @returns {Function[]|UIComponent}
2103
+ */
2104
+ onShortcut(...args)
2105
+ {
2106
+ if(args.length === 0) return this.event("onShortcut");
2107
+ return this.event("onShortcut", args[0], args[1] ?? false);
2108
+ }
2109
+
2110
+ //endregion
2111
+ }
2112
+
2113
+ export {UIComponent};