x4js 1.4.2

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 (176) hide show
  1. package/lib/application.d.ts +95 -0
  2. package/lib/application.js +137 -0
  3. package/lib/base64.d.ts +31 -0
  4. package/lib/base64.js +135 -0
  5. package/lib/base_component.d.ts +64 -0
  6. package/lib/base_component.js +77 -0
  7. package/lib/button.d.ts +145 -0
  8. package/lib/button.js +235 -0
  9. package/lib/calendar.d.ts +77 -0
  10. package/lib/calendar.js +236 -0
  11. package/lib/canvas.d.ts +88 -0
  12. package/lib/canvas.js +354 -0
  13. package/lib/cardview.d.ts +83 -0
  14. package/lib/cardview.js +152 -0
  15. package/lib/checkbox.d.ts +72 -0
  16. package/lib/checkbox.js +126 -0
  17. package/lib/color.d.ts +144 -0
  18. package/lib/color.js +584 -0
  19. package/lib/colorpicker.d.ts +98 -0
  20. package/lib/colorpicker.js +1457 -0
  21. package/lib/combobox.d.ts +97 -0
  22. package/lib/combobox.js +246 -0
  23. package/lib/component.d.ts +572 -0
  24. package/lib/component.js +1712 -0
  25. package/lib/datastore.d.ts +392 -0
  26. package/lib/datastore.js +986 -0
  27. package/lib/dialog.d.ts +171 -0
  28. package/lib/dialog.js +468 -0
  29. package/lib/dom_events.d.ts +284 -0
  30. package/lib/dom_events.js +13 -0
  31. package/lib/drag_manager.d.ts +26 -0
  32. package/lib/drag_manager.js +118 -0
  33. package/lib/drawtext.d.ts +43 -0
  34. package/lib/drawtext.js +261 -0
  35. package/lib/fileupload.d.ts +60 -0
  36. package/lib/fileupload.js +158 -0
  37. package/lib/form.d.ts +122 -0
  38. package/lib/form.js +293 -0
  39. package/lib/formatters.d.ts +31 -0
  40. package/lib/formatters.js +75 -0
  41. package/lib/gridview.d.ts +171 -0
  42. package/lib/gridview.js +786 -0
  43. package/lib/hosts/host.d.ts +44 -0
  44. package/lib/hosts/host.js +69 -0
  45. package/lib/i18n.d.ts +67 -0
  46. package/lib/i18n.js +169 -0
  47. package/lib/icon.d.ts +56 -0
  48. package/lib/icon.js +173 -0
  49. package/lib/image.d.ts +51 -0
  50. package/lib/image.js +149 -0
  51. package/lib/index.js +1 -0
  52. package/lib/input.d.ts +86 -0
  53. package/lib/input.js +172 -0
  54. package/lib/label.d.ts +54 -0
  55. package/lib/label.js +86 -0
  56. package/lib/layout.d.ts +77 -0
  57. package/lib/layout.js +261 -0
  58. package/lib/link.d.ts +46 -0
  59. package/lib/link.js +55 -0
  60. package/lib/listview.d.ts +173 -0
  61. package/lib/listview.js +532 -0
  62. package/lib/md5.d.ts +56 -0
  63. package/lib/md5.js +397 -0
  64. package/lib/menu.d.ts +122 -0
  65. package/lib/menu.js +276 -0
  66. package/lib/messagebox.d.ts +64 -0
  67. package/lib/messagebox.js +141 -0
  68. package/lib/panel.d.ts +42 -0
  69. package/lib/panel.js +61 -0
  70. package/lib/popup.d.ts +71 -0
  71. package/lib/popup.js +373 -0
  72. package/lib/property_editor.d.ts +67 -0
  73. package/lib/property_editor.js +247 -0
  74. package/lib/radiobtn.d.ts +68 -0
  75. package/lib/radiobtn.js +131 -0
  76. package/lib/rating.d.ts +49 -0
  77. package/lib/rating.js +93 -0
  78. package/lib/request.d.ts +48 -0
  79. package/lib/request.js +220 -0
  80. package/lib/router.d.ts +13 -0
  81. package/lib/router.js +27 -0
  82. package/lib/settings.d.ts +33 -0
  83. package/lib/settings.js +63 -0
  84. package/lib/sidebarview.d.ts +44 -0
  85. package/lib/sidebarview.js +73 -0
  86. package/lib/smartedit.d.ts +103 -0
  87. package/lib/smartedit.js +381 -0
  88. package/lib/spreadsheet.d.ts +214 -0
  89. package/lib/spreadsheet.js +1073 -0
  90. package/lib/styles.d.ts +81 -0
  91. package/lib/styles.js +262 -0
  92. package/lib/svgcomponent.d.ts +165 -0
  93. package/lib/svgcomponent.js +350 -0
  94. package/lib/tabbar.d.ts +41 -0
  95. package/lib/tabbar.js +66 -0
  96. package/lib/tabview.d.ts +45 -0
  97. package/lib/tabview.js +79 -0
  98. package/lib/textarea.d.ts +59 -0
  99. package/lib/textarea.js +119 -0
  100. package/lib/textedit.d.ts +118 -0
  101. package/lib/textedit.js +406 -0
  102. package/lib/texthiliter.d.ts +56 -0
  103. package/lib/texthiliter.js +219 -0
  104. package/lib/toaster.d.ts +38 -0
  105. package/lib/toaster.js +58 -0
  106. package/lib/tools.d.ts +382 -0
  107. package/lib/tools.js +1096 -0
  108. package/lib/tooltips.d.ts +42 -0
  109. package/lib/tooltips.js +148 -0
  110. package/lib/treeview.d.ts +128 -0
  111. package/lib/treeview.js +490 -0
  112. package/lib/x4_events.d.ts +253 -0
  113. package/lib/x4_events.js +363 -0
  114. package/package.json +21 -0
  115. package/src/README.md +2 -0
  116. package/src/application.ts +191 -0
  117. package/src/base64.ts +162 -0
  118. package/src/base_component.ts +118 -0
  119. package/src/button.ts +327 -0
  120. package/src/calendar.ts +312 -0
  121. package/src/canvas.ts +501 -0
  122. package/src/cardview.ts +220 -0
  123. package/src/checkbox.ts +178 -0
  124. package/src/color.ts +748 -0
  125. package/src/colorpicker.ts +1618 -0
  126. package/src/combobox.ts +348 -0
  127. package/src/component.ts +2330 -0
  128. package/src/datastore.ts +1318 -0
  129. package/src/dialog.ts +631 -0
  130. package/src/dom_events.ts +297 -0
  131. package/src/drag_manager.ts +168 -0
  132. package/src/drawtext.ts +342 -0
  133. package/src/fileupload.ts +208 -0
  134. package/src/form.ts +362 -0
  135. package/src/formatters.ts +96 -0
  136. package/src/gridview.ts +1051 -0
  137. package/src/hosts/electron.ts +161 -0
  138. package/src/hosts/host.ts +100 -0
  139. package/src/hosts/nwjs.ts +141 -0
  140. package/src/hosts/nwjs_types.ts +339 -0
  141. package/src/i18n.ts +205 -0
  142. package/src/icon.ts +237 -0
  143. package/src/image.ts +198 -0
  144. package/src/input.ts +236 -0
  145. package/src/label.ts +124 -0
  146. package/src/layout.ts +366 -0
  147. package/src/link.ts +82 -0
  148. package/src/listview.ts +749 -0
  149. package/src/md5.ts +432 -0
  150. package/src/menu.ts +394 -0
  151. package/src/messagebox.ts +199 -0
  152. package/src/panel.ts +81 -0
  153. package/src/popup.ts +488 -0
  154. package/src/property_editor.ts +333 -0
  155. package/src/radiobtn.ts +190 -0
  156. package/src/rating.ts +131 -0
  157. package/src/request.ts +296 -0
  158. package/src/router.ts +43 -0
  159. package/src/settings.ts +75 -0
  160. package/src/sidebarview.ts +97 -0
  161. package/src/smartedit.ts +532 -0
  162. package/src/spreadsheet.ts +1423 -0
  163. package/src/styles.ts +332 -0
  164. package/src/svgcomponent.ts +440 -0
  165. package/src/tabbar.ts +105 -0
  166. package/src/tabview.ts +106 -0
  167. package/src/textarea.ts +183 -0
  168. package/src/textedit.ts +535 -0
  169. package/src/texthiliter.ts +284 -0
  170. package/src/toaster.ts +76 -0
  171. package/src/tools.ts +1391 -0
  172. package/src/tooltips.ts +185 -0
  173. package/src/treeview.ts +670 -0
  174. package/src/x4.less +1940 -0
  175. package/src/x4_events.ts +558 -0
  176. package/tsconfig.json +14 -0
@@ -0,0 +1,2330 @@
1
+ /**
2
+ * ___ ___ __
3
+ * \ \/ / / _
4
+ * \ / /_| |_
5
+ * / \____ _|
6
+ * /__/\__\ |_|
7
+ *
8
+ * @file components.ts
9
+ * @author Etienne Cochard
10
+ * @license
11
+ * Copyright (c) 2019-2021 R-libre ingenierie
12
+ *
13
+ * This program is free software; you can redistribute it and/or modify
14
+ * it under the terms of the GNU General Public License as published by
15
+ * the Free Software Foundation; either version 3 of the License, or
16
+ * (at your option) any later version.
17
+ *
18
+ * This program is distributed in the hope that it will be useful,
19
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
+ * GNU General Public License for more details.
22
+ *
23
+ * You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>..
24
+ */
25
+
26
+
27
+ /**
28
+ * @todo
29
+ * create Container class
30
+ */
31
+
32
+ import { pascalCase, Rect, isString, isArray, Size, Point, isNumber, asap, HtmlString, isHtmlString, Constructor, getMousePos } from './tools';
33
+ import { Stylesheet, ComputedStyle } from './styles';
34
+ import { _tr } from './i18n';
35
+ import { BasicEvent, EventCallback } from './x4_events';
36
+ import { BaseComponent, BaseComponentProps, BaseComponentEventMap } from './base_component';
37
+ import { IDOMEvents, X4ElementEventMap } from './dom_events';
38
+
39
+ export { HtmlString, isHtmlString, html } from './tools'
40
+
41
+ export interface EventHandler<T> {
42
+ (ev: T): any;
43
+ }
44
+
45
+ export interface ICaptureInfo {
46
+ initiator: Component;
47
+ handler: EventHandler<UIEvent>;
48
+ iframes: NodeListOf<HTMLIFrameElement>;
49
+ }
50
+
51
+ /** @ignore classname prefix for system classes */
52
+ const _x4_ns_prefix = 'x-';
53
+
54
+ // -- elements -----------
55
+
56
+ /** @ignore where event handlers are stored */
57
+ const _x4_el_store = Symbol();
58
+
59
+ /** @ignore where Component is stored in dom */
60
+ const _x4_el_sym = Symbol();
61
+
62
+ /** @ignore properties without 'px' unit */
63
+ const _x4_unitless = {
64
+ animationIterationCount: 1, borderImageOutset: 1, borderImageSlice: 1, borderImageWidth: 1, boxFlex: 1, boxFlexGroup: 1,
65
+ boxOrdinalGroup: 1, columnCount: 1, flex: 1, flexGrow: 1, flexPositive: 1, flexShrink: 1, flexNegative: 1, flexOrder: 1,
66
+ gridRow: 1, gridColumn: 1, fontWeight: 1, lineClamp: 1, lineHeight: 1, opacity: 1, order: 1, orphans: 1, tabSize: 1, widows: 1,
67
+ zIndex: 1, zoom: 1,
68
+
69
+ // SVG-related _properties
70
+ fillOpacity: 1, floodOpacity: 1, stopOpacity: 1, strokeDasharray: 1, strokeDashoffset: 1, strokeMiterlimit: 1, strokeOpacity: 1,
71
+ strokeWidth: 1,
72
+ };
73
+
74
+ /** @ignore this events must be defined on domNode (do not bubble) */
75
+ const unbubbleEvents = {
76
+ mouseleave: 1, mouseenter: 1, load: 1, unload: 1, scroll: 1, focus: 1, blur: 1, rowexit: 1, beforeunload: 1, stop: 1,
77
+ dragdrop: 1, dragenter: 1, dragexit: 1, draggesture: 1, dragover: 1, contextmenu: 1, create: 2, sizechange: 2
78
+ };
79
+
80
+ /** @ignore */
81
+ const passiveEvents = {
82
+ touchstart: 1, touchmove: 1, touchend: 1,
83
+ //pointerdown: 1, pointermove: 1, pointerup: 1,
84
+ };
85
+
86
+ /** ignore */
87
+ const reNumber = /^-?\d+(\.\d+)?$/;
88
+
89
+
90
+ /**
91
+ *
92
+ */
93
+
94
+ export type VoidCallback = () => void;
95
+
96
+ export type ComponentOrString = Component | string | HtmlString;
97
+ export type ComponentContent = ComponentOrString | ComponentOrString[];
98
+
99
+ interface IMap<T> {
100
+ [key: string]: T;
101
+ }
102
+
103
+ /**
104
+ * DblClick Event
105
+ * double click event
106
+ */
107
+
108
+ export interface EvDblClick extends BasicEvent {
109
+ }
110
+
111
+ export function EvDblClick(context = null) {
112
+ return BasicEvent<EvDblClick>({ context });
113
+ }
114
+
115
+ /**
116
+ * DblFocus Event
117
+ * double click event
118
+ */
119
+
120
+ export interface EvFocus extends BasicEvent {
121
+ readonly focus: boolean;
122
+ }
123
+
124
+ export function EvFocus( focus = true, context = null) {
125
+ return BasicEvent<EvFocus>({ focus, context });
126
+ }
127
+
128
+ /**
129
+ * basic event map of Component
130
+ */
131
+
132
+ export interface CEventMap extends BaseComponentEventMap {
133
+ }
134
+
135
+
136
+
137
+
138
+ /**
139
+ * Basic properties of every Component
140
+ */
141
+
142
+ export interface CProps<T extends CEventMap = CEventMap> extends BaseComponentProps<T> {
143
+ tag?: string; // dom Tag <div> if not specified
144
+ ns?: string; // namespace (usefull for svg)
145
+ cls?: string; // elements classes (space separated), prefix class name with @ to make it system wide
146
+ id?: string; // element id
147
+ style?: IMap<string | number>; // element style
148
+ attrs?: object; // element attributes
149
+
150
+ dom_events?: IDOMEvents; // DOM event handlers
151
+ data?: any; // element user data (you can store everything you need here)
152
+ content?: ComponentContent; // array of sub components
153
+ tooltip?: string; // tooltip text
154
+ ref?: string; // internal reference for itemWithRef
155
+
156
+ // shortcuts for element style
157
+ left?: number;
158
+ top?: number;
159
+ width?: number | string;
160
+ height?: number | string;
161
+ tabIndex?: number | boolean;
162
+
163
+ flex?: number | string; // add @flex class to the element
164
+ enabled?: boolean; // add @disabled to the element if false
165
+ }
166
+
167
+ interface CInternalProps {
168
+ dom_events: any;
169
+ classes: IMap<boolean>;
170
+ uid: number;
171
+ inrender: boolean;
172
+ }
173
+
174
+ /**
175
+ *
176
+ */
177
+
178
+ export class Component<P extends CProps<BaseComponentEventMap> = CProps<BaseComponentEventMap>, E extends BaseComponentEventMap = BaseComponentEventMap> extends BaseComponent<P, E> {
179
+ private m_dom: HTMLElement;
180
+ private m_iprops: CInternalProps;
181
+
182
+ private static __sb_width; // scrollbar width
183
+ private static __comp_guid = 1000; // component global unique id
184
+ private static __privateEvents: any = {};
185
+ private static __sizeObserver: ResizeObserver; // resize observer
186
+ private static __createObserver: MutationObserver; // creation observer
187
+ private static __intersectionObserver: IntersectionObserver; // visibility observer
188
+
189
+ private static __capture: ICaptureInfo = null;
190
+ private static __capture_mask = null;
191
+ private static __css = null;
192
+
193
+ constructor(props: P = null ) {
194
+ super(props ?? {} as P );
195
+
196
+ this.m_iprops = {
197
+ classes: {},
198
+ dom_events: {},
199
+ uid: Component.__comp_guid++,
200
+ inrender: false,
201
+ }
202
+ }
203
+
204
+ /**
205
+ *
206
+ */
207
+
208
+ get uid() {
209
+ return this.m_iprops.uid;
210
+ }
211
+
212
+ /**
213
+ * change the component content
214
+ * @param content new content or null
215
+ */
216
+
217
+ setContent(content: ComponentContent, refreshAll = false ) {
218
+ this.m_props.content = content ?? [];
219
+
220
+ if( this.m_iprops.inrender || !this.m_dom ) {
221
+ return;
222
+ }
223
+
224
+ if (refreshAll) {
225
+ this.update();
226
+ }
227
+ else {
228
+ this._updateContent();
229
+ }
230
+ }
231
+
232
+
233
+ /**
234
+ * add a new child to the component content
235
+ * @param content
236
+ */
237
+
238
+ appendChild(content: ComponentContent) {
239
+
240
+ if (!content) {
241
+ return;
242
+ }
243
+
244
+ const append = (c) => {
245
+
246
+ if (!this.m_props.content) {
247
+ this.m_props.content = [];
248
+ }
249
+ else if (!isArray(this.m_props.content)) {
250
+ this.m_props.content = [this.m_props.content];
251
+ }
252
+
253
+ this.m_props.content.push(c);
254
+ if (this.m_dom) {
255
+ this._appendChild(c);
256
+ }
257
+ }
258
+
259
+ if (isArray(content)) {
260
+ content.forEach(append);
261
+ }
262
+ else {
263
+ append(content);
264
+ }
265
+ }
266
+
267
+ /**
268
+ * get the Component value
269
+ * @param name name to get
270
+ */
271
+
272
+ getProp(name: string): any {
273
+ return this.m_props[name];
274
+ }
275
+
276
+ /**
277
+ * change a Component value
278
+ * @param name name to set
279
+ * @param value new value
280
+ */
281
+
282
+ setProp(name: string, value?: any) {
283
+ this.m_props[name] = value;
284
+ }
285
+
286
+ /**
287
+ * get the Component data value
288
+ * @param name name to get
289
+ */
290
+
291
+ getData(name: string | Symbol): any {
292
+ if (this.m_props.data !== undefined) {
293
+ return this.m_props.data[name.toString()];
294
+ }
295
+
296
+ return undefined;
297
+ }
298
+
299
+ /**
300
+ * set the Component data value
301
+ * @param name name to get
302
+ * @param value
303
+ */
304
+
305
+ setData(name: string | Symbol , value: any) {
306
+
307
+ let data = this.m_props.data;
308
+ if (data === undefined) {
309
+ data = this.m_props.data = {};
310
+ }
311
+
312
+ data[name.toString()] = value;
313
+ }
314
+
315
+
316
+ /**
317
+ * return the DOM associated with the Component (if any)
318
+ */
319
+
320
+ get dom(): HTMLElement {
321
+ return this.m_dom;
322
+ }
323
+
324
+ /**
325
+ * shows the element
326
+ * @param show
327
+ */
328
+
329
+ show(show?: boolean) {
330
+ if (show === undefined || show === true) {
331
+ this.removeClass('@hidden');
332
+ }
333
+ else {
334
+ this.addClass('@hidden');
335
+ }
336
+ }
337
+
338
+ /**
339
+ * hides the element
340
+ */
341
+ hide() {
342
+ this.addClass('@hidden');
343
+ }
344
+
345
+ /**
346
+ * enable or disable the element
347
+ * @param enable
348
+ */
349
+
350
+ enable(enable?: boolean) {
351
+ if (enable === undefined || enable === true) {
352
+ this.removeClass('@disable');
353
+ this.removeAttribute('disabled');
354
+ }
355
+ else {
356
+ this.disable();
357
+ }
358
+ }
359
+
360
+ /**
361
+ * disable the element
362
+ */
363
+
364
+ disable() {
365
+ this.addClass('@disable');
366
+ this.setAttribute('disabled', '');
367
+ }
368
+
369
+ /**
370
+ * set the focus on the element
371
+ */
372
+
373
+ focus() {
374
+ console.assert(!!this.m_dom);
375
+ this.m_dom.focus();
376
+ }
377
+
378
+ /**
379
+ * change the object style
380
+ * @param style - style to add
381
+ * @example ```typescript
382
+ * el.setStyle( {left:100} );
383
+ */
384
+
385
+ public setStyle(style: object) {
386
+ for (let s in style) {
387
+ this.setStyleValue(s, style[s]);
388
+ }
389
+ }
390
+
391
+ /**
392
+ * change a style value
393
+ * @param name string style nale
394
+ * @param value any style value or null to remove style
395
+ */
396
+
397
+ setStyleValue(name: string, value: any) {
398
+ let style = this.m_props.style;
399
+ if (!style) {
400
+ style = this.m_props.style = {};
401
+ }
402
+
403
+ style[name] = value;
404
+ this._setDomStyleValue(name, value);
405
+ }
406
+
407
+ private _setDomStyleValue(name: string, value: string | number) {
408
+ if (this.m_dom) {
409
+
410
+ if (value === undefined) {
411
+ value = null;
412
+ }
413
+ else if (!_x4_unitless[name] && (isNumber(value) || reNumber.test(value))) {
414
+ value = value + 'px';
415
+ }
416
+
417
+ this.m_dom.style[name] = value;
418
+ }
419
+ }
420
+
421
+ /**
422
+ * compute the element style
423
+ * @return all styles computed
424
+ */
425
+
426
+ public getComputedStyle(pseudoElt?: string): ComputedStyle {
427
+ if (this.dom) {
428
+ return new ComputedStyle(getComputedStyle(this.dom, pseudoElt ?? null));
429
+ }
430
+
431
+ return new ComputedStyle(this.m_props.style as any);
432
+ }
433
+
434
+ /**
435
+ * return a single stype value
436
+ * @param name - value to get
437
+ */
438
+
439
+ public getStyleValue(name: string) {
440
+ return this.getComputedStyle()[name];
441
+ }
442
+
443
+ /**
444
+ * define the elements attributes
445
+ * @param attrs
446
+ */
447
+
448
+ public setAttributes(attrs: any) {
449
+ for (let a in attrs) {
450
+ this.setAttribute(a, attrs[a]);
451
+ }
452
+ }
453
+
454
+ /**
455
+ * change a single attribute
456
+ * @param name attribute name
457
+ * @param value new value
458
+ */
459
+
460
+ public setAttribute(name: string, value: any) {
461
+
462
+ if (value === false || value === undefined) {
463
+ this.removeAttribute(name);
464
+ }
465
+ else {
466
+ if (value === true) {
467
+ value = '';
468
+ }
469
+ else if (isNumber(value)) {
470
+ value = '' + value;
471
+ }
472
+
473
+ let attrs = this.m_props.attrs;
474
+ if (!attrs) {
475
+ attrs = this.m_props.attrs = {};
476
+ }
477
+
478
+ attrs[name] = value;
479
+ this._setDomAttribute(name, value);
480
+ }
481
+ }
482
+
483
+ private _setDomAttribute(name: string, value: string) {
484
+ if (this.m_dom) {
485
+ this.m_dom.setAttribute(name, value);
486
+ }
487
+ }
488
+
489
+ /**
490
+ * remove an atrribute
491
+ * @param name name of the attribute
492
+ */
493
+ public removeAttribute(name: string) {
494
+ let attrs = this.m_props.attrs;
495
+ if (!attrs) {
496
+ return;
497
+ }
498
+
499
+ delete attrs[name];
500
+
501
+ if (this.m_dom) {
502
+ this.m_dom.removeAttribute(name);
503
+ }
504
+ }
505
+
506
+ /**
507
+ * get an attribute value
508
+ * @param {string} name - attribute name
509
+ * @return {string} attribute value
510
+ * @example ```typescript
511
+ * let chk = el.getAttribute( 'checked' );
512
+ * @review double cache
513
+ */
514
+
515
+ public getAttribute(name: string): string {
516
+ if (this.m_dom) {
517
+ return this.m_dom.getAttribute(name);
518
+ }
519
+ else {
520
+ if (!this.m_props.attrs) {
521
+ return undefined;
522
+ }
523
+
524
+ return this.m_props.attrs[name];
525
+ }
526
+ }
527
+
528
+ /**
529
+ * check if the element has an attribute
530
+ * @param name attribute name
531
+ * @return true is attribute is present
532
+ * @example ```typescript
533
+ * if( el.hasAttribute('checked') ) {
534
+ * }
535
+ */
536
+
537
+ public hasAttribute(name: string): boolean {
538
+ if (this.m_dom) {
539
+ return this.m_dom.hasAttribute(name);
540
+ }
541
+ else {
542
+ return this.m_props.attrs.hasOwnProperty(name);
543
+ }
544
+ }
545
+
546
+
547
+ /**
548
+ * a some classnames to the component
549
+ * classes can be separated by a space
550
+ * @param cls class to add
551
+ * @example ```typescript
552
+ * addClass( 'my class name @flex' );
553
+ */
554
+
555
+ public addClass(name: string) {
556
+
557
+ if (name === null || name === undefined) {
558
+ return;
559
+ }
560
+
561
+ name = name.trim();
562
+ if (name === '') {
563
+ return;
564
+ }
565
+
566
+ let add = (c) => {
567
+
568
+ if (c === undefined || c === null || c === '') {
569
+ return;
570
+ }
571
+
572
+ c = this._makeCls(c);
573
+
574
+ // update vdom
575
+ classes[c] = true;
576
+
577
+ // update dom
578
+ if (this.m_dom) {
579
+ this.m_dom.classList.add(c);
580
+ }
581
+ }
582
+
583
+ let classes = this.m_iprops.classes;
584
+ if (name.indexOf(' ') < 0) {
585
+ add(name);
586
+ }
587
+ else {
588
+ let names = name.split(' ');
589
+ names.forEach((n) => add(n));
590
+ }
591
+ }
592
+
593
+ /**
594
+ * Remove a class from the element
595
+ * @param {string|array} name - classes in string form can be space separated
596
+ *
597
+ * @example ```typescript
598
+ * el.removeClass( 'myclass' );
599
+ * el.removeClass( 'myclass1 myclass2' );
600
+ */
601
+
602
+ public removeClass(name: string): void {
603
+
604
+ if (name === undefined) {
605
+ return;
606
+ }
607
+
608
+ let remove = (c) => {
609
+ if (c === undefined || c === null || c === '') {
610
+ return;
611
+ }
612
+
613
+ c = this._makeCls(c);
614
+
615
+ delete this.m_iprops.classes[c];
616
+ if (this.m_dom) {
617
+ this.m_dom.classList.remove(c);
618
+ }
619
+ }
620
+
621
+ // faster
622
+ if (name.indexOf(' ') < 0) {
623
+ remove(name);
624
+ }
625
+ else {
626
+ // build class list
627
+ let classes = name.trim().split(' ');
628
+ for (let c of classes) {
629
+ if (c !== undefined && c !== null && c !== '') {
630
+ remove(c);
631
+ }
632
+ }
633
+ }
634
+ }
635
+
636
+ /**
637
+ *
638
+ * @param cls
639
+ * @param set
640
+ */
641
+
642
+ public setClass(cls: string, set: boolean) {
643
+ if (set) { this.addClass(cls); }
644
+ else { this.removeClass(cls); }
645
+ return this;
646
+ }
647
+
648
+ /**
649
+ * Toggle a class from the element (if present remove, if absent add)
650
+ * @param {string|string[]} name - classes in string form can be space separated
651
+ * @example ```typescript
652
+ * el.toggleClass( 'myclass' );
653
+ * el.toggleClass( 'myclass1 myclass2');
654
+ * el.toggleClass( ['myclass1','myclass2']);
655
+ */
656
+
657
+ public toggleClass(name: string): void {
658
+
659
+ let toggle = (c) => {
660
+ if (c === undefined && c === null && c === '') {
661
+ return;
662
+ }
663
+
664
+ c = this._makeCls(c);
665
+ if (this.m_iprops.classes[c]) {
666
+ delete this.m_iprops.classes[c]
667
+ }
668
+ else {
669
+ this.m_iprops.classes[c] = true;
670
+ }
671
+
672
+ if (this.m_dom) {
673
+ this.m_dom.classList.toggle(c);
674
+ }
675
+ }
676
+
677
+ // faster
678
+ if (name.indexOf(' ') < 0) {
679
+ toggle(name);
680
+ }
681
+ else {
682
+
683
+ // build class list
684
+ let classes = name.trim().split(' ');
685
+ for (let c of classes) {
686
+ toggle(c);
687
+ }
688
+ }
689
+ }
690
+
691
+ /**
692
+ * check if the object has the class
693
+ * @param cls
694
+ */
695
+
696
+ public hasClass(cls: string): boolean {
697
+
698
+ let c = this._makeCls(cls);
699
+ if (this.m_dom) {
700
+ return this.dom.classList.contains(c);
701
+ }
702
+ else {
703
+ return !!this.m_iprops.classes[c];
704
+ }
705
+ }
706
+
707
+ /**
708
+ * remove all classes from the object
709
+ * this is usefull for component recycling & reusing
710
+ */
711
+
712
+ public clearClasses() {
713
+ this.m_iprops.classes = {};
714
+ if (this.m_dom) {
715
+ return this.m_dom.classList.value = '';
716
+ }
717
+ }
718
+
719
+ ///@deprecated
720
+ //private build(): void {}
721
+ /**
722
+ * @deprecated
723
+ */
724
+
725
+ private Build(): void { }
726
+
727
+ public _build(): HTMLElement {
728
+ if (this.m_dom) {
729
+ return this.m_dom;
730
+ }
731
+
732
+ this._createDOM();
733
+
734
+ return this.m_dom;
735
+ }
736
+
737
+ public render(props: P) {
738
+ if( this.m_props.tag=='footer') {
739
+ debugger;
740
+ }
741
+ }
742
+
743
+ public _createDOM(): HTMLElement {
744
+
745
+ if (this.m_dom) {
746
+ return this.m_dom;
747
+ }
748
+
749
+ // setup props
750
+ const props = this.m_props;
751
+
752
+ if( props.tabIndex!==undefined ) {
753
+ this._setTabIndex( props.tabIndex );
754
+ }
755
+ this.render(props);
756
+
757
+ // shortcuts ---------
758
+ if (props.left !== undefined) { this.setStyleValue('left', props.left); }
759
+ if (props.top !== undefined) { this.setStyleValue('top', props.top); }
760
+ if (props.width !== undefined) { this.setStyleValue('width', props.width); }
761
+ if (props.height !== undefined) { this.setStyleValue('height', props.height); }
762
+
763
+ if (props.flex !== undefined) {
764
+ this.addClass('@flex');
765
+ if (props.flex != 1) {
766
+ this.setStyleValue('flex', props.flex);
767
+ }
768
+ }
769
+
770
+ if (props.enabled === false) {
771
+ this.disable();
772
+ }
773
+
774
+ // shortcut: tip
775
+ if (props.tooltip !== undefined) {
776
+ this.setAttribute('tip', props.tooltip.replace(/\n/gi, '<br/>'));
777
+ }
778
+
779
+
780
+ // prepare iprops
781
+ if (props.dom_events) {
782
+ for (let ename in props.dom_events) {
783
+ this._setDomEvent(ename, props.dom_events[ename]);
784
+ }
785
+ }
786
+
787
+ this._genClassName();
788
+ this.m_props.cls = undefined; // now classes are tranfered to m_iprops
789
+
790
+ // create self
791
+ let vdom = this.m_iprops;
792
+
793
+ if (props.ns) {
794
+ this.m_dom = <HTMLElement>document.createElementNS(props.ns, props.tag ?? 'div');
795
+ }
796
+ else {
797
+ this.m_dom = document.createElement(props.tag ?? 'div');
798
+ }
799
+
800
+ this.m_dom[_x4_el_sym] = this;
801
+
802
+ //let me = Object.getPrototypeOf(this);
803
+ //console.log( 'create', this.m_iprops.uid, me.constructor.name );
804
+
805
+ // classes
806
+ this.m_dom.classList.add(...Object.keys(vdom.classes));
807
+
808
+ // styles
809
+ let sty = props.style;
810
+ if (sty) {
811
+ for (let s in sty) {
812
+ this._setDomStyleValue(s, sty[s]);
813
+ }
814
+ }
815
+
816
+ // attributes
817
+ let att = props.attrs;
818
+ if (att) {
819
+ for (let a in att) {
820
+ const attr = att[a];
821
+ if( attr!==false && attr!==undefined ) {
822
+ this._setDomAttribute(a, att[a]);
823
+ }
824
+ }
825
+ }
826
+
827
+ // special properties
828
+ if (this.m_props.id) {
829
+ this._setDomAttribute('id', this.m_props.id);
830
+ }
831
+
832
+ // events
833
+ let evt = this.m_iprops.dom_events;
834
+ if (evt) {
835
+ for (let e in evt) {
836
+ let handlers = evt[e];
837
+ for (let h of handlers) {
838
+ this.createEvent(e, h);
839
+ }
840
+ }
841
+ }
842
+
843
+ // create children
844
+ let content = props.content;
845
+ if (content) {
846
+
847
+ if (!isArray(content)) {
848
+ content = [content];
849
+ }
850
+
851
+ content.forEach((el) => {
852
+ if (!el) {
853
+ return;
854
+ }
855
+
856
+ if (isString(el)) {
857
+ this.m_dom.insertAdjacentText('beforeend', el);
858
+ }
859
+ else if (isHtmlString(el)) {
860
+ this.m_dom.insertAdjacentHTML('beforeend', el as string);
861
+ }
862
+ else if (el instanceof Component) {
863
+ this.m_dom.append(el._build());
864
+ }
865
+ else {
866
+ console.log( 'unknown element type: ', el );
867
+ }
868
+ });
869
+ }
870
+
871
+ // wait for dom insertion inside document.body
872
+ if (!Component.__createObserver) {
873
+ Component.__createObserver = new MutationObserver(Component._observeCreation);
874
+ Component.__createObserver.observe(document.body, { childList: true, subtree: true });
875
+ }
876
+
877
+ return this.m_dom;
878
+ }
879
+
880
+ protected _setTabIndex(tabIndex: number | boolean, defValue = 0) {
881
+
882
+ if (tabIndex === true) {
883
+ tabIndex = 0;
884
+ }
885
+ else if (tabIndex === undefined) {
886
+ tabIndex = defValue;
887
+ }
888
+
889
+ if (tabIndex !== false && tabIndex !== undefined) {
890
+ this.setAttribute('tabindex', tabIndex);
891
+ }
892
+
893
+ this.m_props.tabIndex = tabIndex;
894
+ }
895
+
896
+ private static _observeCreation(mutations: MutationRecord[]) {
897
+
898
+ // notify descendants that we have been created (dom exists)
899
+
900
+ for (let mutation of mutations) {
901
+
902
+ if (mutation.type == 'childList') {
903
+
904
+ for (let i = 0, n = mutation.addedNodes.length; i < n; i++) {
905
+
906
+ let add = mutation.addedNodes[i] as HTMLElement;
907
+ let el = add[_x4_el_sym] as Component;
908
+
909
+ if (el) {
910
+ el.enumChilds((c: Component) => {
911
+
912
+ if (c.dom && c.m_iprops.dom_events && c.m_iprops.dom_events.create) {
913
+ c.dom.dispatchEvent(new Event('create'));
914
+ }
915
+
916
+ c.componentCreated();
917
+
918
+ }, true);
919
+
920
+ if (el.m_iprops.dom_events && el.m_iprops.dom_events.create) {
921
+ el.dom.dispatchEvent(new Event('create'));
922
+ }
923
+
924
+ el.componentCreated();
925
+ }
926
+ }
927
+ }
928
+ }
929
+ }
930
+
931
+ public dispose() {
932
+ if (this.m_dom) {
933
+ this._dispose(true,true);
934
+ }
935
+ }
936
+
937
+ protected _dispose(with_dom: boolean, timers: boolean ) {
938
+
939
+ let _dom = this.m_dom;
940
+
941
+ // free attached resources
942
+ delete _dom[_x4_el_sym];
943
+ delete _dom[_x4_el_store];
944
+
945
+ //
946
+ if (with_dom) {
947
+ _dom.remove();
948
+ }
949
+
950
+ // notify every child that they will be removed
951
+ this.enumChilds((c: Component) => {
952
+ c._dispose(false,true);
953
+ });
954
+
955
+ this.m_dom = null;
956
+
957
+ if( timers ) {
958
+ this.disposeTimers();
959
+ }
960
+
961
+ this.componentDisposed();
962
+ // todo: pb on update this.removeAllListeners( null );
963
+ }
964
+
965
+ componentDisposed() {
966
+ }
967
+
968
+ componentCreated() {
969
+ }
970
+
971
+
972
+
973
+
974
+
975
+
976
+
977
+ /**
978
+ *
979
+ */
980
+
981
+ public update(delay = 0) {
982
+
983
+ if (this.m_dom) {
984
+
985
+ const _update = () => {
986
+ let oldDOM = this.m_dom;
987
+ this._dispose(false,false);
988
+
989
+ let newDOM = this._build();
990
+ console.assert( !!oldDOM.parentNode, 'update in componentCreated is not allowed, use updateContent' );
991
+
992
+ oldDOM.parentNode.replaceChild(newDOM, oldDOM);
993
+ }
994
+
995
+ if (delay) {
996
+ this.singleShot(_update, delay);
997
+ }
998
+ else {
999
+ _update();
1000
+ }
1001
+ }
1002
+ }
1003
+
1004
+ /**
1005
+ * empty the node
1006
+ */
1007
+ public _empty( ) {
1008
+ //this.m_dom.innerHTML = '';
1009
+
1010
+ const el = this.m_dom;
1011
+ if( !el ) {
1012
+ return;
1013
+ }
1014
+
1015
+ while (el.firstChild) {
1016
+ el.removeChild(el.firstChild);
1017
+ }
1018
+ }
1019
+
1020
+ public _updateContent() {
1021
+
1022
+ if (!this.m_dom) {
1023
+ return;
1024
+ }
1025
+
1026
+ this._empty( );
1027
+
1028
+ let content = this.m_props.content;
1029
+
1030
+ // create children
1031
+ if (content) {
1032
+
1033
+ if (!isArray(content)) {
1034
+ content = [content];
1035
+ }
1036
+
1037
+ content.forEach((el) => {
1038
+ if (!el) {
1039
+ return;
1040
+ }
1041
+
1042
+ if (isHtmlString(el)) {
1043
+ this.m_dom.insertAdjacentHTML('beforeend', el as string);
1044
+ }
1045
+ else if (el instanceof Component) {
1046
+ this.m_dom.append(el._build());
1047
+ }
1048
+ else {
1049
+ this.m_dom.insertAdjacentText('beforeend', el + '');
1050
+ }
1051
+ });
1052
+ }
1053
+
1054
+ }
1055
+
1056
+ /**
1057
+ * @return the bounding rectangle
1058
+ * @example ```typescript
1059
+ * let rc = el.getBoundingRect( );
1060
+ * console.log( rc.left, rc.top, rc.right, rc.bottom );
1061
+ */
1062
+
1063
+ public getBoundingRect(withMargins = false): Rect {
1064
+ console.assert(this.dom != null, 'cannot get bounding rect of an non DOM element');
1065
+ let r = this.dom.getBoundingClientRect();
1066
+
1067
+ let rc = new Rect(r.left, r.top, r.width, r.height);
1068
+
1069
+ if (withMargins) {
1070
+
1071
+ let st = this.getComputedStyle();
1072
+
1073
+ let tm = st.parse('marginTop'),
1074
+ bm = st.parse('marginBottom'),
1075
+ lm = st.parse('marginLeft'),
1076
+ rm = st.parse('marginRight');
1077
+
1078
+ rc.left -= lm;
1079
+ rc.width += lm + rm;
1080
+
1081
+ rc.top -= tm;
1082
+ rc.height += tm + bm;
1083
+ }
1084
+
1085
+ return rc;
1086
+ }
1087
+
1088
+ /**
1089
+ * append a new dom event handler
1090
+ * @param name - you can specify multiple names separated by a space
1091
+ * @param handler
1092
+ * @example
1093
+ *
1094
+ * this.setDomEvent( 'drag drop', this._handleDrag, this );
1095
+ * this.setDomEvent( 'dblclick', this._handleDblClick, this );
1096
+ */
1097
+
1098
+ public setDomEvent<K extends keyof X4ElementEventMap>(type: K, listener: (this: HTMLDivElement, ev: X4ElementEventMap[K]) => void) {
1099
+ let _listener = listener as EventListener;
1100
+ this._setDomEvent(type as string, _listener);
1101
+ }
1102
+
1103
+ private _setDomEvent(type: string, listener: EventListener) {
1104
+
1105
+ // add event to the vdom
1106
+ if (!this.m_iprops.dom_events) {
1107
+ this.m_iprops.dom_events = {};
1108
+ }
1109
+
1110
+ let listeners = this.m_iprops.dom_events[type];
1111
+ if (!listeners) {
1112
+ listeners = this.m_iprops.dom_events[type] = [listener];
1113
+ }
1114
+ else {
1115
+ listeners.push(listener);
1116
+ }
1117
+
1118
+ if (this.m_dom) {
1119
+ //this.m_dom.addEventListener(type, listener);
1120
+ this.createEvent(type, listener);
1121
+ }
1122
+ }
1123
+
1124
+ /**
1125
+ *
1126
+ */
1127
+
1128
+ public clearDomEvent<K extends keyof X4ElementEventMap>(type: K) {
1129
+ if (!this.m_iprops.dom_events) {
1130
+ return;
1131
+ }
1132
+
1133
+ delete this.m_iprops.dom_events[type];
1134
+
1135
+ let _dom = this.m_dom;
1136
+ if (_dom) {
1137
+ let store = _dom[_x4_el_store];
1138
+ if (store) {
1139
+ delete store[type];
1140
+ }
1141
+ }
1142
+ }
1143
+
1144
+ public mapPropEvents<N extends keyof E>(props: P, ...elements: N[] ) {
1145
+ elements.forEach( name => {
1146
+ const n = name as string;
1147
+ if (props[n]) {
1148
+ this._on(n, props[n]);
1149
+ }
1150
+ })
1151
+ }
1152
+
1153
+ /**
1154
+ *
1155
+ * @param name
1156
+ * @param handler
1157
+ */
1158
+
1159
+ public createEvent(name: string, handler: Function) {
1160
+
1161
+ let _dom = this.m_dom;
1162
+ let store = _dom[_x4_el_store];
1163
+
1164
+ if (!store) {
1165
+ store = _dom[_x4_el_store] = {};
1166
+ }
1167
+
1168
+ if (!store[name]) {
1169
+ // no handler for this event...
1170
+ store[name] = [handler];
1171
+ }
1172
+ else {
1173
+ // append the handler
1174
+ store[name].push(handler);
1175
+ }
1176
+
1177
+ if (unbubbleEvents[name] === 1) {
1178
+ _dom['on' + name] = Component._dispatchUnbubbleEvent;
1179
+ }
1180
+ else if (!Component.__privateEvents[name]) {
1181
+ Component.__privateEvents[name] = true; // todo count it
1182
+
1183
+ if (passiveEvents[name]) {
1184
+ document.addEventListener(name, Component._dispatchEvent, { passive: false, capture: true });
1185
+ }
1186
+ else {
1187
+ document.addEventListener(name, Component._dispatchEvent, true);
1188
+ }
1189
+ }
1190
+
1191
+ if (name === 'sizechange') {
1192
+ if (!Component.__sizeObserver) {
1193
+ Component.__sizeObserver = new ResizeObserver(Component._observeSize);
1194
+ }
1195
+
1196
+ Component.__sizeObserver.observe(this.m_dom);
1197
+ }
1198
+ }
1199
+
1200
+ /**
1201
+ * dispatch a dom event to the appropriated component
1202
+ * called by the system
1203
+ */
1204
+
1205
+ private static _dispatchEvent(ev: any) {
1206
+
1207
+ let target = ev.target,
1208
+ noup = unbubbleEvents[ev.type] === 2;
1209
+
1210
+ while (target) {
1211
+ if (target[_x4_el_store]) {
1212
+ let store = target[_x4_el_store][ev.type];
1213
+ if (store) {
1214
+ let el = target[_x4_el_sym];
1215
+ let root = el?.root ?? null;
1216
+
1217
+ if (store instanceof Array) {
1218
+ store.some((fn) => {
1219
+ fn(ev, root);
1220
+ if (!el.dom) {
1221
+ return true;
1222
+ }
1223
+ });
1224
+ }
1225
+ else {
1226
+ store(ev, root);
1227
+ }
1228
+
1229
+ if (ev.cancelBubble || ev.defaultPrevented || noup) {
1230
+ break;
1231
+ }
1232
+ }
1233
+ }
1234
+
1235
+ target = target.parentNode;
1236
+
1237
+ // no need to go above
1238
+ if (target == document) {
1239
+ break;
1240
+ }
1241
+ }
1242
+ }
1243
+
1244
+ /**
1245
+ * dispatch a dom event to the appropriated component
1246
+ * called by the system
1247
+ */
1248
+
1249
+ private static _dispatchUnbubbleEvent(ev: any) {
1250
+
1251
+ let target = ev.currentTarget || ev.target,
1252
+ eventType = ev.type;
1253
+
1254
+ let eventStore = target[_x4_el_store],
1255
+ store = eventStore && eventStore[eventType];
1256
+
1257
+ if (store) {
1258
+
1259
+ let el = target[_x4_el_sym];
1260
+ let root = el?.root ?? null;
1261
+
1262
+ if (store instanceof Array) {
1263
+ store.forEach((fn) => {
1264
+ fn(ev, root);
1265
+ });
1266
+ }
1267
+ else {
1268
+ store(ev, root);
1269
+ }
1270
+ }
1271
+ }
1272
+
1273
+ /**
1274
+ * called when a size change on an observed component
1275
+ */
1276
+
1277
+ private static _observeSize(entries: ResizeObserverEntry[]) {
1278
+
1279
+ entries.forEach((entry) => {
1280
+ let dom = entry.target as HTMLElement;
1281
+ if (dom.offsetParent !== null) {
1282
+ dom.dispatchEvent(new Event('sizechange'));
1283
+ }
1284
+ });
1285
+ }
1286
+
1287
+ /**
1288
+ * enum all children recursively
1289
+ * @param recursive - if true do a full sub-child search
1290
+ * @param cb - callback
1291
+ * return true to stop enumeration
1292
+ */
1293
+
1294
+ enumChilds(cb: (child: Component) => boolean | void, recursive = false): boolean {
1295
+
1296
+ // use dom if available
1297
+ if (this.m_dom) {
1298
+
1299
+ let el = this.m_dom.firstChild;
1300
+
1301
+ while (el) {
1302
+ // get component (if any)
1303
+ let cel = el[_x4_el_sym];
1304
+ if (cel) {
1305
+ cb(cel);
1306
+
1307
+ if (recursive && cel.enumChilds(cb, true) === true) {
1308
+ return true;
1309
+ }
1310
+ }
1311
+
1312
+ el = el.nextSibling;
1313
+ }
1314
+ }
1315
+ else {
1316
+ let content = this.m_props.content;
1317
+ if (!content) {
1318
+ return;
1319
+ }
1320
+
1321
+ if (!isArray(content)) {
1322
+ content = [content];
1323
+ }
1324
+
1325
+ content.some((el) => {
1326
+ if (!el || isString(el) || isHtmlString(el)) {
1327
+ return;
1328
+ }
1329
+
1330
+ if (cb(el)) {
1331
+ return true;
1332
+ }
1333
+
1334
+ if (recursive && el.enumChilds(cb, true) === true) {
1335
+ return true;
1336
+ }
1337
+ });
1338
+ }
1339
+
1340
+ return false;
1341
+ }
1342
+
1343
+ /**
1344
+ * apprend a child to the DOM
1345
+ * @param props child to append (or string)
1346
+ */
1347
+
1348
+ private _appendChild(el: ComponentOrString) {
1349
+
1350
+ if (isString(el)) {
1351
+ this.m_dom.insertAdjacentText('beforeend', el);
1352
+ }
1353
+ else if (isHtmlString(el)) {
1354
+ this.m_dom.insertAdjacentHTML('beforeend', el as string);
1355
+ }
1356
+ else {
1357
+ let component: Component = el;
1358
+
1359
+ try {
1360
+ component._build();
1361
+ this.m_dom.appendChild(component.m_dom);
1362
+ }
1363
+ catch (e) {
1364
+ console.error(e);
1365
+ }
1366
+ }
1367
+ }
1368
+
1369
+ /**
1370
+ * generate classes from the component inheritance
1371
+ * @example
1372
+ * Button extends Component will give
1373
+ * x-comp x-button
1374
+ */
1375
+
1376
+ private _genClassName() {
1377
+
1378
+ this.addClass('@comp');
1379
+
1380
+ let me = Object.getPrototypeOf(this);
1381
+ while (me && me.constructor !== Component) {
1382
+ let clsname = me.constructor.name;
1383
+ this.addClass('@' + pascalCase(clsname));
1384
+
1385
+ me = Object.getPrototypeOf(me);
1386
+ }
1387
+
1388
+ this.addClass(this.m_props.cls);
1389
+ }
1390
+
1391
+ /**
1392
+ * prepend the system class name prefix on a name if needed (if class starts with @)
1393
+ */
1394
+
1395
+ private _makeCls(cls: string): string {
1396
+ if (cls[0] == '@') {
1397
+ return cls = _x4_ns_prefix + cls.substring(1);
1398
+ }
1399
+ else {
1400
+ return cls;
1401
+ }
1402
+ }
1403
+
1404
+ /**
1405
+ *
1406
+ */
1407
+
1408
+ private static dispatchCaptures(event: UIEvent) {
1409
+ Component.__capture.handler(event);
1410
+ }
1411
+
1412
+ /**
1413
+ * capture mouse events
1414
+ * @param capture name of the current capture
1415
+ * @param callback funciton to call on captured mouse events
1416
+ *
1417
+ * @example
1418
+ * Component.setCapture( this, ( ev: MouseEvent, initiator: Component ) => {
1419
+ * if( ev.type=='mousemove' ) {
1420
+ * this.setStyle( {
1421
+ * left: ev.clientX,
1422
+ * top: ev.clientY
1423
+ * } );
1424
+ * }
1425
+ * else if( ev.type=='mouseup' ) {
1426
+ * Component.releaseCapture( );
1427
+ * }
1428
+ * } );
1429
+ */
1430
+
1431
+ protected static setCapture(initiator: Component, listener: EventHandler<UIEvent>) {
1432
+
1433
+ console.assert(!Component.__capture);
1434
+ if (Component.__capture) {
1435
+ debugger;
1436
+ }
1437
+
1438
+ // todo: review that
1439
+
1440
+ let iframes = document.querySelectorAll("iframe");
1441
+ iframes.forEach( f => {
1442
+ flyWrap(f).setStyleValue( 'pointer-events', 'none' );
1443
+ });
1444
+
1445
+ let overs = document.querySelectorAll(":hover");
1446
+
1447
+ let cursor = null;
1448
+ if (overs.length) {
1449
+ let elementOver = <HTMLElement>overs[overs.length - 1];
1450
+ let style = window.getComputedStyle(elementOver);
1451
+ cursor = style.cursor;
1452
+ }
1453
+
1454
+ Component.__capture_mask = document.createElement('div');
1455
+ let mask = flyWrap(Component.__capture_mask);
1456
+ mask.addClass('@capture-mask');
1457
+
1458
+ if (cursor) {
1459
+ mask.setStyleValue('cursor', cursor);
1460
+ }
1461
+
1462
+ document.body.appendChild(mask.dom);
1463
+
1464
+ document.addEventListener('mousedown', Component.dispatchCaptures);
1465
+ document.addEventListener('mousemove', Component.dispatchCaptures);
1466
+ document.addEventListener('mouseup', Component.dispatchCaptures);
1467
+
1468
+ document.addEventListener('touchstart', Component.dispatchCaptures);
1469
+ document.addEventListener('touchmove', Component.dispatchCaptures);
1470
+ document.addEventListener('touchend', Component.dispatchCaptures);
1471
+
1472
+ Component.__capture = {
1473
+ initiator,
1474
+ handler: listener,
1475
+ iframes
1476
+ };
1477
+ }
1478
+
1479
+ protected static releaseCapture() {
1480
+
1481
+ console.assert(!!Component.__capture);
1482
+
1483
+ document.removeEventListener('touchstart', Component.dispatchCaptures);
1484
+ document.removeEventListener('touchmove', Component.dispatchCaptures);
1485
+ document.removeEventListener('touchend', Component.dispatchCaptures);
1486
+
1487
+ document.removeEventListener('mousedown', Component.dispatchCaptures);
1488
+ document.removeEventListener('mousemove', Component.dispatchCaptures);
1489
+ document.removeEventListener('mouseup', Component.dispatchCaptures);
1490
+
1491
+ Component.__capture.iframes.forEach( f => {
1492
+ flyWrap(f).setStyleValue( 'pointer-events', null );
1493
+ })
1494
+
1495
+ Component.__capture = null;
1496
+ if (Component.__capture_mask) {
1497
+ document.body.removeChild(Component.__capture_mask);
1498
+ Component.__capture_mask = null;
1499
+ }
1500
+ }
1501
+
1502
+ /**
1503
+ * ensure the component is visible
1504
+ * @param: alignToTop
1505
+ */
1506
+
1507
+ public scrollIntoView(arg?: boolean | ScrollIntoViewOptions) {
1508
+ if (this.m_dom) {
1509
+
1510
+ const rel = new Rect( this.dom.getBoundingClientRect( ) );
1511
+
1512
+ let top = undefined;
1513
+ let bot = undefined;
1514
+ let left = undefined;
1515
+ let right = undefined;
1516
+
1517
+ let pn = this.dom.parentElement;
1518
+ while( pn && pn!=document.body ) {
1519
+
1520
+ const pr = pn.getBoundingClientRect( );
1521
+
1522
+ if( top===undefined || top<pr.top ) {
1523
+ top = pr.top;
1524
+ }
1525
+
1526
+ if( bot===undefined || bot>pr.bottom ) {
1527
+ bot = pr.bottom;
1528
+ }
1529
+
1530
+ if( left===undefined || left<pr.left ) {
1531
+ left = pr.left;
1532
+ }
1533
+
1534
+ if( right===undefined || right>pr.right ) {
1535
+ right = pr.right;
1536
+ }
1537
+
1538
+ pn = pn.parentElement;
1539
+ }
1540
+
1541
+ if( top===undefined || rel.top<top || rel.bottom>bot || rel.left<left || rel.right>right ) {
1542
+ //this.m_dom.scrollIntoView( true );
1543
+ this.m_dom.scrollIntoView({ behavior: 'auto', block: 'nearest', inline: 'start' });
1544
+ }
1545
+
1546
+ //this.m_dom.scrollIntoView(arg);
1547
+ }
1548
+ }
1549
+
1550
+ /**
1551
+ * search for a given css selector
1552
+ * @param selector
1553
+ * @returns child or null
1554
+ */
1555
+
1556
+ public queryItem<T extends Component>(selector: string): T {
1557
+ let result = <HTMLElement>this.dom.querySelector(selector);
1558
+ return result ? Component.getElement<T>(result) : null;
1559
+ }
1560
+
1561
+ public queryAll(selector: string, cb?: (el: Component) => void) {
1562
+ const elements = this.m_dom.querySelectorAll(selector);
1563
+ if( cb ) {
1564
+ elements.forEach((el) => {
1565
+ cb(flyWrap(el as HTMLElement));
1566
+ });
1567
+ }
1568
+
1569
+ return elements;
1570
+ }
1571
+
1572
+ /**
1573
+ * find a child with the given ID
1574
+ * @param id id (without '#')
1575
+ * @returns child or null
1576
+ *
1577
+ * @example
1578
+ * let btn = this.childWithId<Button>( 'myButtonId' );
1579
+ */
1580
+ public itemWithId<T extends Component>(id: string): T {
1581
+ let result = <HTMLElement>this.dom.querySelector('#' + id);
1582
+ return result ? Component.getElement<T>(result) : null;
1583
+ }
1584
+
1585
+ /**
1586
+ * find a child with given ref
1587
+ * @param ref
1588
+ * @return found child or null
1589
+ */
1590
+
1591
+ public itemWithRef<T = Component>(ref: string): T {
1592
+
1593
+ let result = null;
1594
+ this.enumChilds((c: Component) => {
1595
+ if (c.m_props.ref === ref) {
1596
+ result = c;
1597
+ return true;
1598
+ }
1599
+ }, true);
1600
+
1601
+ return result as T;
1602
+ }
1603
+
1604
+ /**
1605
+ *
1606
+ */
1607
+
1608
+ get ref() {
1609
+ return this.m_props.ref;
1610
+ }
1611
+
1612
+ /**
1613
+ *
1614
+ */
1615
+
1616
+ static getCss(): Stylesheet {
1617
+ if (!Component.__css) {
1618
+ Component.__css = new Stylesheet();
1619
+ }
1620
+
1621
+ return Component.__css;
1622
+ }
1623
+
1624
+ /**
1625
+ * return the parent element
1626
+ * care, object must have been created (dom!=null)
1627
+ */
1628
+
1629
+ public getParent(): Component {
1630
+ console.assert(!!this.m_dom);
1631
+
1632
+ let elParent = this.dom.parentNode;
1633
+ return Component.getElement(<HTMLElement>elParent);
1634
+ }
1635
+
1636
+ /**
1637
+ * get a component from a DOM element
1638
+ * move up to the hierarchy to find the request class type.
1639
+ * @param dom
1640
+ * @param classname
1641
+ * @returns
1642
+ *
1643
+ * @example
1644
+ *
1645
+ * with a DOM like that:
1646
+ * Button
1647
+ * Label
1648
+ * Icon <- the DOM you have (dom-icon)
1649
+ *
1650
+ * let btn = Component.getElement( dom-icon, Button );
1651
+ */
1652
+
1653
+ static getElement<T extends Component>(dom: HTMLElement | Element, classname?: Constructor<T> | string ): T {
1654
+
1655
+ if (classname) {
1656
+
1657
+ const srhCls = isString(classname);
1658
+
1659
+ while (dom) {
1660
+ let el: Component = dom[_x4_el_sym];
1661
+
1662
+ if( srhCls ) {
1663
+ if( el && el.hasClass(classname) ) {
1664
+ return el as T;
1665
+ }
1666
+ }
1667
+ else if (el instanceof classname) {
1668
+ return el;
1669
+ }
1670
+
1671
+ dom = dom.parentElement;
1672
+ }
1673
+
1674
+ return null;
1675
+ }
1676
+ else {
1677
+ return dom ? dom[_x4_el_sym] : null;
1678
+ }
1679
+ }
1680
+
1681
+ /**
1682
+ * compute the scrollbar size ( width = height)
1683
+ */
1684
+
1685
+ static getScrollbarSize() {
1686
+
1687
+ if (Component.__sb_width === undefined) {
1688
+ let outerDiv = document.createElement('div');
1689
+ outerDiv.style.cssText = 'overflow:auto;position:absolute;top:0;width:100px;height:100px';
1690
+
1691
+ let innerDiv = document.createElement('div');
1692
+ innerDiv.style.width = '200px';
1693
+ innerDiv.style.height = '200px';
1694
+
1695
+ outerDiv.appendChild(innerDiv);
1696
+ document.body.appendChild(outerDiv);
1697
+
1698
+ Component.__sb_width = outerDiv.offsetWidth - outerDiv.clientWidth;
1699
+ document.body.removeChild(outerDiv);
1700
+ }
1701
+
1702
+ return Component.__sb_width;
1703
+ }
1704
+
1705
+ /**
1706
+ * check if the Component is visible to the user
1707
+ */
1708
+
1709
+ isUserVisible(): boolean {
1710
+ if (!this.m_dom) {
1711
+ return false;
1712
+ }
1713
+
1714
+ return (this.m_dom.offsetParent !== null);
1715
+ }
1716
+ }
1717
+
1718
+ /** @ignore */
1719
+ let fly_element: Component = null;
1720
+
1721
+ /**
1722
+ * warp <b>temporarily</b> a DOM element to be able to acces to exact API
1723
+ * @param dom dom element to wrap
1724
+ * @review qui libere le fly_element ? -> timeout
1725
+ */
1726
+
1727
+ export function flyWrap<T extends Component>(dom: HTMLElement | EventTarget): T {
1728
+
1729
+ if (dom[_x4_el_sym]) {
1730
+ return dom[_x4_el_sym];
1731
+ }
1732
+
1733
+ let f = fly_element;
1734
+ if (!f) { f = fly_element = new Component({}); }
1735
+ (<any>f).m_dom = dom;
1736
+
1737
+ return f as T;
1738
+ }
1739
+
1740
+
1741
+
1742
+
1743
+
1744
+
1745
+
1746
+
1747
+ /**
1748
+ * simple flex spacer
1749
+ */
1750
+
1751
+ export class Flex extends Component {
1752
+
1753
+ constructor(props: CProps = {}) {
1754
+ if (!props.flex) {
1755
+ props.flex = 1;
1756
+ }
1757
+
1758
+ super(props);
1759
+ }
1760
+ }
1761
+
1762
+ /**
1763
+ * simple space between 2 elements
1764
+ */
1765
+
1766
+ export class Space extends Component {
1767
+
1768
+ m_size: number | string;
1769
+
1770
+ constructor(size: number | string) {
1771
+ super({});
1772
+
1773
+ this.m_size = size;
1774
+ }
1775
+
1776
+ componentCreated() {
1777
+
1778
+ // try to find if we are in a hz / vt / abs container
1779
+ let dom = this.dom;
1780
+ let style = null;
1781
+
1782
+ while (dom) {
1783
+ let el: Component = dom[_x4_el_sym];
1784
+ if (el.hasClass('@hlayout')) {
1785
+ style = { width: this.m_size };
1786
+ break;
1787
+ }
1788
+ else if (el.hasClass('@vlayout')) {
1789
+ style = { height: this.m_size };
1790
+ break;
1791
+ }
1792
+
1793
+ dom = dom.parentElement;
1794
+ }
1795
+
1796
+ if (!style) {
1797
+ style = { width: this.m_size, height: this.m_size };
1798
+ }
1799
+
1800
+ this.setStyle(style);
1801
+ }
1802
+ }
1803
+
1804
+ /**
1805
+ * sizable separator
1806
+ */
1807
+
1808
+ type SizeMode = null | 'minimize' | 'maximize' | 'restore';
1809
+
1810
+ export interface EvSize extends BasicEvent {
1811
+ readonly size: Size;
1812
+ readonly mode: SizeMode;
1813
+ }
1814
+
1815
+ export function EvSize(size: Size, mode: SizeMode = null, context = null ): EvSize {
1816
+ return BasicEvent<EvSize>({ size, mode, context });
1817
+ }
1818
+
1819
+ interface SeparatorEventMap extends CEventMap {
1820
+ resize?: EvSize;
1821
+ }
1822
+
1823
+ interface SeparatorProps extends CProps<SeparatorEventMap> {
1824
+ readonly orientation: 'vertical' | 'horizontal'; // vertical means vertical sizer so it resize horizontally
1825
+ readonly sizing: 'before' | 'after';
1826
+ readonly collapsible?: boolean;
1827
+ }
1828
+
1829
+ export class Separator extends Component<SeparatorProps, SeparatorEventMap> {
1830
+
1831
+ m_irect: Rect;
1832
+ m_delta: number;
1833
+ m_target: Component;
1834
+
1835
+ constructor(props: SeparatorProps) {
1836
+ super(props);
1837
+
1838
+ this.setDomEvent('mousedown', (e) => this._mousedown(e));
1839
+ this.setDomEvent('touchstart', (e) => this._mousedown(e));
1840
+ this.setDomEvent('dblclick', (e) => this._collapse(e) );
1841
+ }
1842
+
1843
+ render() {
1844
+ this.addClass(this.m_props.orientation);
1845
+ }
1846
+
1847
+ private _collapse( ev: UIEvent ) {
1848
+ if( this.m_props.collapsible ) {
1849
+ this._findTarget();
1850
+ if( this.m_target ) {
1851
+ this.m_target.toggleClass( '@collapsed' );
1852
+ }
1853
+ }
1854
+ }
1855
+
1856
+ private _mousedown(ev: UIEvent) {
1857
+
1858
+ if (ev.type == 'touchstart') {
1859
+ let te = ev as TouchEvent;
1860
+ if (te.touches.length == 1) {
1861
+ this._startMoving(te.touches[0].pageX, te.touches[0].pageY, ev);
1862
+ }
1863
+ }
1864
+ else {
1865
+ let me = ev as MouseEvent;
1866
+ this._startMoving(me.pageX, me.pageY, ev);
1867
+ }
1868
+ }
1869
+
1870
+ _startMoving(x: number, y: number, ev: UIEvent) {
1871
+ //if( this.m_props.callback ) {
1872
+ // this.m_props.callback( ev, this );
1873
+ //}
1874
+ //else
1875
+ {
1876
+ this._findTarget();
1877
+
1878
+ if (this.m_target) {
1879
+
1880
+ if (this.m_props.orientation == 'horizontal') {
1881
+ if (this.m_props.sizing == 'before') {
1882
+ this.m_delta = x - this.m_irect.right;
1883
+ }
1884
+ else {
1885
+ this.m_delta = x - this.m_irect.left;
1886
+ }
1887
+ }
1888
+ else {
1889
+ if (this.m_props.sizing == 'before') {
1890
+ this.m_delta = y - this.m_irect.bottom;
1891
+ }
1892
+ else {
1893
+ this.m_delta = y - this.m_irect.top;
1894
+ }
1895
+ }
1896
+
1897
+ ev.preventDefault();
1898
+ ev.stopPropagation();
1899
+
1900
+ this.m_target.addClass('sizing');
1901
+
1902
+ Component.setCapture(this, (e) => this._pointerMoved(e));
1903
+ }
1904
+ }
1905
+ }
1906
+
1907
+ private _pointerMoved(ev: UIEvent) {
1908
+
1909
+ let __move = (ex, ey) => {
1910
+
1911
+ if (this.m_props.orientation == 'horizontal') {
1912
+
1913
+ let width;
1914
+ if (this.m_props.sizing == 'after') {
1915
+ width = this.m_irect.right - (ex - this.m_delta);
1916
+ }
1917
+ else {
1918
+ width = (ex - this.m_delta) - this.m_irect.left
1919
+ }
1920
+
1921
+ if (width > 0) {
1922
+ let size = new Size(width, 0);
1923
+ this.emit('resize', EvSize(size));
1924
+
1925
+ this.m_target.setStyleValue('width', size.width);
1926
+ this.m_target.setStyleValue('flex', null); // for flex>1
1927
+ this.m_target.removeClass('@flex');
1928
+ }
1929
+ }
1930
+ else {
1931
+
1932
+ let height;
1933
+ if (this.m_props.sizing == 'after') {
1934
+ height = this.m_irect.bottom - (ey - this.m_delta);
1935
+ }
1936
+ else {
1937
+ height = (ey - this.m_delta) - this.m_irect.top;
1938
+ }
1939
+
1940
+ if (height > 0) {
1941
+ let size = new Size(0, height);
1942
+ this.emit('resize', EvSize(size));
1943
+
1944
+ this.m_target.setStyleValue('height', size.height);
1945
+ this.m_target.setStyleValue('flex', null); // for flex>1
1946
+ this.m_target.removeClass('@flex');
1947
+ }
1948
+ }
1949
+ }
1950
+
1951
+ if (ev.type == 'mousemove') {
1952
+
1953
+ let mev = ev as MouseEvent;
1954
+ __move(mev.pageX, mev.pageY);
1955
+
1956
+ ev.preventDefault();
1957
+ ev.stopPropagation();
1958
+ }
1959
+ else if (ev.type == 'touchmove') {
1960
+
1961
+ let tev = ev as TouchEvent;
1962
+ __move(tev.touches[0].pageX, tev.touches[0].pageY);
1963
+
1964
+ ev.preventDefault();
1965
+ ev.stopPropagation();
1966
+ }
1967
+ else if (ev.type == 'mouseup' || ev.type == 'touchend') {
1968
+ this.m_target.removeClass('sizing');
1969
+
1970
+ Component.releaseCapture();
1971
+ ev.preventDefault();
1972
+ ev.stopPropagation();
1973
+ }
1974
+ }
1975
+
1976
+ private _findTarget() {
1977
+
1978
+ if (!this.m_target) {
1979
+
1980
+ if (this.m_props.sizing == 'before') {
1981
+ let prevDom = this.dom.previousElementSibling;
1982
+ let prevEl = prevDom ? Component.getElement(prevDom as HTMLElement) : null;
1983
+ this.m_target = prevEl;
1984
+ }
1985
+ else {
1986
+ let nextDom = this.dom.nextElementSibling;
1987
+ let nextEl = nextDom ? Component.getElement(nextDom as HTMLElement) : null;
1988
+ this.m_target = nextEl;
1989
+ }
1990
+ }
1991
+
1992
+ if (this.m_target) {
1993
+ this.m_irect = this.m_target.getBoundingRect();
1994
+ }
1995
+ else {
1996
+ this.m_irect = null;
1997
+ }
1998
+ }
1999
+ }
2000
+
2001
+
2002
+
2003
+
2004
+ // :: SIZERBAR ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
2005
+
2006
+ /**
2007
+ * properties
2008
+ */
2009
+
2010
+ type SizerOverlaySens = 'left' | 'top' | 'right' | 'bottom' | 'topleft' | 'topright' | 'bottomleft' | 'bottomright';
2011
+
2012
+ export interface EvOverlayResize extends BasicEvent {
2013
+ ui_event: UIEvent;
2014
+ sens: SizerOverlaySens;
2015
+ }
2016
+
2017
+ export function EvOverlayResize(ui_event: UIEvent, sens: SizerOverlaySens, context: any = null) {
2018
+ return BasicEvent<EvOverlayResize>({ ui_event, sens, context });
2019
+ }
2020
+
2021
+ interface SizerOverlayEventMap extends CEventMap {
2022
+ resize: EvSize;
2023
+ rawresize: EvOverlayResize;
2024
+ }
2025
+
2026
+
2027
+ export interface SizerOverlayProps extends CProps<SizerOverlayEventMap> {
2028
+ sens: SizerOverlaySens;
2029
+ target: Component;
2030
+
2031
+ resize?: EventCallback<EvSize>; // shortcut to events: { size: ... }
2032
+ }
2033
+
2034
+ export class SizerOverlay extends Component<SizerOverlayProps, SizerOverlayEventMap> {
2035
+
2036
+ private m_delta: number;
2037
+ private m_irect: Rect;
2038
+
2039
+ constructor(props: SizerOverlayProps) {
2040
+ super(props);
2041
+
2042
+ this.addClass(props.sens);
2043
+ this.setDomEvent('mousedown', (e) => this._mousedown(e));
2044
+ this.setDomEvent('touchstart', (e) => this._mousedown(e));
2045
+ this.setDomEvent('dblclick', (e) => this.resetflex(e)); // todo: add option for that
2046
+
2047
+ props.target.appendChild(this);
2048
+
2049
+ if( props.resize ) {
2050
+ this.on( 'resize', this.m_props.resize );
2051
+ }
2052
+ }
2053
+
2054
+ resetflex(event: UIEvent) {
2055
+ this.m_props.target.addClass('@flex');
2056
+ this.emit('resize', EvSize( { width: -1, height: 0})); // todo: see that
2057
+ event.preventDefault();
2058
+ event.stopPropagation();
2059
+ }
2060
+
2061
+ // @review move that in component
2062
+
2063
+ _mousedown(ev: UIEvent) {
2064
+
2065
+ ev.preventDefault();
2066
+ ev.stopPropagation();
2067
+
2068
+ let eev = EvOverlayResize(ev, this.m_props.sens);
2069
+ this.emit('rawresize', eev);
2070
+ if (eev.defaultPrevented) {
2071
+ return;
2072
+ }
2073
+
2074
+ let pos = getMousePos(ev, true);
2075
+ this.m_irect = this.m_props.target.getBoundingRect();
2076
+
2077
+ if (this.m_props.sens == 'right') {
2078
+ this.m_delta = pos.x - this.m_irect.right;
2079
+ }
2080
+ else if (this.m_props.sens == 'left') {
2081
+ this.m_delta = pos.x - this.m_irect.left;
2082
+ }
2083
+ else if (this.m_props.sens == 'bottom') {
2084
+ this.m_delta = pos.y - this.m_irect.bottom;
2085
+ }
2086
+ else if (this.m_props.sens == 'top') {
2087
+ this.m_delta = pos.y - this.m_irect.top;
2088
+ }
2089
+
2090
+ this.m_props.target.addClass('sizing');
2091
+ Component.setCapture(this, (e) => this._handle_mouse(e));
2092
+ }
2093
+
2094
+ private _is_horz() {
2095
+ return this.m_props.sens == 'left' || this.m_props.sens == 'right';
2096
+ }
2097
+
2098
+ public get sens() {
2099
+ return this.m_props.sens;
2100
+ }
2101
+
2102
+ private _handle_mouse(ev: UIEvent) {
2103
+ let __move = (ex, ey) => {
2104
+ if (this._is_horz()) {
2105
+
2106
+ let width;
2107
+ if (this.m_props.sens == 'left') {
2108
+ width = this.m_irect.right - (ex - this.m_delta);
2109
+ }
2110
+ else {
2111
+ width = (ex - this.m_delta) - this.m_irect.left
2112
+ }
2113
+
2114
+ if (width > 0) {
2115
+ let size = {
2116
+ width,
2117
+ height: undefined
2118
+ };
2119
+
2120
+ this.emit('resize', EvSize(size));
2121
+
2122
+ this.m_props.target.setStyleValue('width', size.width);
2123
+ this.m_props.target.setStyleValue('flex', null); // for flex>1
2124
+ this.m_props.target.removeClass('@flex');
2125
+ }
2126
+ }
2127
+ else {
2128
+
2129
+ let height;
2130
+ if (this.m_props.sens == 'top') {
2131
+ height = this.m_irect.bottom - (ey - this.m_delta);
2132
+ }
2133
+ else {
2134
+ height = (ey - this.m_delta) - this.m_irect.top;
2135
+ }
2136
+
2137
+ if (height > 0) {
2138
+ let size = new Size(0, height);
2139
+ this.emit('resize', EvSize(size));
2140
+
2141
+ this.m_props.target.setStyleValue('height', size.height);
2142
+ this.m_props.target.setStyleValue('flex', null); // for flex>1
2143
+ this.m_props.target.removeClass('@flex');
2144
+ }
2145
+ }
2146
+ }
2147
+
2148
+ if (ev.type == 'mousemove') {
2149
+
2150
+ let mev = ev as MouseEvent;
2151
+ __move(mev.pageX, mev.pageY);
2152
+
2153
+ ev.preventDefault();
2154
+ ev.stopPropagation();
2155
+ }
2156
+ else if (ev.type == 'touchmove') {
2157
+
2158
+ let tev = ev as TouchEvent;
2159
+ __move(tev.touches[0].pageX, tev.touches[0].pageY);
2160
+
2161
+ ev.preventDefault();
2162
+ ev.stopPropagation();
2163
+ }
2164
+ else if (ev.type == 'mouseup' || ev.type == 'touchend') {
2165
+ this.m_props.target.removeClass('sizing');
2166
+
2167
+ Component.releaseCapture();
2168
+ ev.preventDefault();
2169
+ ev.stopPropagation();
2170
+ }
2171
+ }
2172
+ }
2173
+
2174
+ /**
2175
+ * sequence: Shift+Ctrl+Alt+A
2176
+ */
2177
+
2178
+ export interface Shortcut {
2179
+ sequence: string;
2180
+ name: string;
2181
+ immediate: boolean;
2182
+ callback?: (domTarget) => void;
2183
+ }
2184
+
2185
+ interface EvShortcut extends BasicEvent {
2186
+ name: string;
2187
+ }
2188
+
2189
+ function EvShortcut(name: string) {
2190
+ return BasicEvent<EvShortcut>({ name });
2191
+ }
2192
+
2193
+ export interface ContainerEventMap extends CEventMap {
2194
+ shortcut: EvShortcut;
2195
+ }
2196
+
2197
+ export interface ContainerProps<E extends ContainerEventMap = ContainerEventMap> extends CProps<E> {
2198
+ }
2199
+
2200
+ /**
2201
+ * you can construct a Container as usual with it's properties but also directly with it's children array
2202
+ *
2203
+ * @example
2204
+ * new Container( [
2205
+ * child1,
2206
+ * child2
2207
+ * ])
2208
+ */
2209
+
2210
+ export class Container<P extends ContainerProps = ContainerProps, E extends ContainerEventMap = ContainerEventMap> extends Component<P, E> {
2211
+
2212
+ private m_shortcuts: Shortcut[];
2213
+
2214
+ constructor( props: P | ComponentOrString[] ) {
2215
+ if( isArray(props) ) {
2216
+ super( {content: props} as P );
2217
+ }
2218
+ else {
2219
+ super( props );
2220
+ }
2221
+ }
2222
+
2223
+ /**
2224
+ * add an application shortcut
2225
+ * @param sequence key sequence Shift+Ctrl+Alt+K
2226
+ * @param callback callback to call
2227
+ */
2228
+
2229
+ public addShortcut(sequence: string | string[], name: string, callback: EventHandler<KeyboardEvent> = null, immediate = false) {
2230
+
2231
+ // first time
2232
+ if (!this.m_shortcuts) {
2233
+ this.m_shortcuts = [];
2234
+ this.setDomEvent('keydown', (e) => this._handleKeydown(e));
2235
+ }
2236
+
2237
+ if (!isArray(sequence)) {
2238
+ sequence = [sequence];
2239
+ }
2240
+
2241
+ sequence.forEach((seq: string) => {
2242
+ let reseq = '';
2243
+
2244
+ let shift = seq.match(/SHIFT/i);
2245
+ if (shift) {
2246
+ seq = seq.replace(/SHIFT/i, '');
2247
+ reseq += 'shift+';
2248
+ }
2249
+
2250
+ let ctrl = seq.match(/CTRL/i);
2251
+ if (ctrl) {
2252
+ seq = seq.replace(/CTRL/i, '');
2253
+ reseq += 'ctrl+';
2254
+ }
2255
+
2256
+ let alt = seq.match(/ALT/i);
2257
+ if (alt) {
2258
+ seq = seq.replace(/ALT/i, '');
2259
+ reseq += 'alt+';
2260
+ }
2261
+
2262
+ reseq += seq.replace('+', '').toLowerCase();
2263
+
2264
+ this.m_shortcuts.push({
2265
+ sequence: reseq,
2266
+ name,
2267
+ immediate,
2268
+ callback
2269
+ });
2270
+ });
2271
+ }
2272
+
2273
+ /**
2274
+ * remove all shortcuts for a target
2275
+ */
2276
+
2277
+ removeShortcuts() {
2278
+ if (this.m_shortcuts) {
2279
+ this.m_shortcuts = [];
2280
+ }
2281
+ }
2282
+
2283
+ /** @ignore this function is binded */
2284
+ private _handleKeydown(e: KeyboardEvent) {
2285
+
2286
+ if (!this.m_shortcuts) {
2287
+ return;
2288
+ }
2289
+
2290
+ let seq = '';
2291
+
2292
+ if (e.shiftKey) {
2293
+ seq += 'shift+';
2294
+ }
2295
+
2296
+ if (e.ctrlKey) {
2297
+ seq += 'ctrl+';
2298
+ }
2299
+
2300
+ if (e.altKey) {
2301
+ seq += 'alt+';
2302
+ }
2303
+
2304
+ seq += e.key.toLowerCase();
2305
+ //console.log( seq );
2306
+
2307
+ this.m_shortcuts.some((sk) => {
2308
+ if (sk.sequence == seq) {
2309
+
2310
+ if (sk.callback) {
2311
+ if (sk.immediate) {
2312
+ sk.callback(e);
2313
+ }
2314
+ else {
2315
+ asap(() => { sk.callback(e); });
2316
+ }
2317
+ }
2318
+ else {
2319
+ this.emit('shortcut', EvShortcut(sk.name));
2320
+ }
2321
+
2322
+ e.preventDefault();
2323
+ e.stopPropagation();
2324
+ return true;
2325
+ }
2326
+ });
2327
+ }
2328
+ }
2329
+
2330
+ export type ComponentConstructor<T> = new (props: CProps) => T;