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,749 @@
1
+ /**
2
+ * ___ ___ __
3
+ * \ \_/ / / _
4
+ * \ / /_| |_
5
+ * / _ \____ _|
6
+ * /__/ \__\ |_|
7
+ *
8
+ * @file listview.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
+ import { Container, Component, CProps, ContainerEventMap, EvDblClick } from './component'
27
+ import { IconID } from './icon';
28
+ import { HLayout, VLayout } from './layout'
29
+ import { Popup, PopupEventMap, PopupProps } from './popup';
30
+ import { HtmlString, isFunction } from './tools';
31
+ import { Menu, MenuItem } from "./menu";
32
+
33
+ import { EvContextMenu, EvSelectionChange, EvClick, EventCallback, BasicEvent, EvChange } from "./x4_events";
34
+
35
+ /**
36
+ * item definition
37
+ */
38
+
39
+ export class ListViewItem {
40
+ id: any;
41
+ text?: string | HtmlString; // if you need pure text
42
+ html?: boolean; // if text is html
43
+ icon?: IconID;
44
+ data?: any;
45
+ };
46
+
47
+ /**
48
+ * callback to render item
49
+ */
50
+
51
+ export interface RenderListItem {
52
+ (item: ListViewItem): any;
53
+ }
54
+
55
+ /**
56
+ * callback to fill the list
57
+ */
58
+ export interface PopulateItems {
59
+ (): ListViewItem[];
60
+ }
61
+
62
+ /**
63
+ * listview can generate these events
64
+ */
65
+
66
+ export interface ListViewEventMap extends ContainerEventMap {
67
+ click?: EvClick;
68
+ dblClick?: EvDblClick;
69
+ contextMenu?: EvContextMenu;
70
+ selectionChange?: EvSelectionChange;
71
+ cancel?: EvCancel;
72
+ }
73
+
74
+
75
+ /**
76
+ * listview properties
77
+ */
78
+ export interface ListViewProps<E extends ListViewEventMap = ListViewEventMap> extends CProps<E> {
79
+ items?: ListViewItem[];
80
+ populate?: PopulateItems;
81
+ gadgets?: Component[]; // gadgets to instert at bottom
82
+
83
+ virtual?: boolean; // if true, items will be rendered on demand
84
+ itemHeight?: number; // in case of a virtual list, item height
85
+
86
+ renderItem?: RenderListItem;
87
+
88
+ click?: EventCallback<EvClick>; // shortcut to events: { click: ... }
89
+ dblClick?: EventCallback<EvDblClick>;// shortcut to events: { dblClick: ... }
90
+ contextMenu?: EventCallback<EvContextMenu>;// shortcut to events: { contentMenu: ... }
91
+ selectionChange?: EventCallback<EvSelectionChange>;// shortcut to events: { selectionChange: ... }
92
+ cancel?: EventCallback<EvCancel>; // shortcut to events: { cancel: ... }
93
+ }
94
+
95
+ /**
96
+ * Standard listview class
97
+ */
98
+
99
+ export class ListView<T extends ListViewProps = ListViewProps, E extends ListViewEventMap = ListViewEventMap> extends VLayout<T,E> {
100
+
101
+ protected m_selection: {
102
+ item: ListViewItem;
103
+ citem: Component;
104
+ }
105
+
106
+ protected m_defer_sel: any;
107
+
108
+ protected m_container: Container;
109
+ protected m_view: Container;
110
+
111
+ protected m_topIndex: number;
112
+ protected m_itemHeight: number;
113
+
114
+ protected m_cache: Map<number, Component>; // recycling elements
115
+
116
+ constructor(props: T) {
117
+ super(props);
118
+
119
+ this.setDomEvent('keydown', (e) => this._handleKey(e));
120
+ this.setDomEvent('click', (e) => this._handleClick(e));
121
+ this.setDomEvent('dblclick', (e) => this._handleClick(e));
122
+ this.setDomEvent('contextmenu', (e) => this._handleCtxMenu(e));
123
+
124
+ this._setTabIndex(props.tabIndex, 0);
125
+ this.mapPropEvents(props, 'click', 'dblClick', 'contextMenu', 'selectionChange', 'cancel' );
126
+ }
127
+
128
+ componentCreated() {
129
+
130
+ if (this.m_props.virtual) {
131
+ this._buildItems();
132
+ }
133
+ else if( this.m_props.populate ) {
134
+ this.items = this.m_props.populate( );
135
+ }
136
+ }
137
+
138
+ render(props: ListViewProps) {
139
+
140
+ props.items = props.items || [];
141
+ props.gadgets = props.gadgets;
142
+ props.renderItem = props.renderItem;
143
+ props.virtual = props.virtual ?? false;
144
+
145
+ this.m_topIndex = 0;
146
+
147
+ if (props.virtual) {
148
+ console.assert(props.itemHeight !== undefined);
149
+ this.m_itemHeight = props.itemHeight;
150
+ this.m_cache = new Map<number, Component>();
151
+ this.addClass('virtual');
152
+ }
153
+ else {
154
+ this.m_itemHeight = undefined;
155
+ this.m_cache = undefined;
156
+ }
157
+
158
+ this._buildContent();
159
+ }
160
+
161
+ /**
162
+ * change the list of item displayed
163
+ * @param items - new array of items
164
+ * @deprecated
165
+ */
166
+
167
+ public set items(items: ListViewItem[]) {
168
+ this.setItems( items );
169
+ }
170
+
171
+ public get items( ) {
172
+ return this.m_props.items;
173
+ }
174
+
175
+ /**
176
+ * change the list of item displayed
177
+ * @param items - new array of items
178
+ */
179
+
180
+ public setItems( items: ListViewItem[], keepSel = true ) {
181
+ this.m_props.items = items;
182
+
183
+ if( !keepSel ) {
184
+ this.m_selection = null;
185
+ }
186
+
187
+ if( !this.m_container ) {
188
+ this._buildContent( );
189
+ }
190
+ else {
191
+ this._buildItems( );
192
+ }
193
+ }
194
+
195
+ private _handleKey(ev: KeyboardEvent) {
196
+
197
+ let moveSel = (sens) => {
198
+
199
+ let items: ListViewItem[];
200
+ if (isFunction(this.m_props.items)) {
201
+ items = this.m_props.items();
202
+ this.m_props.items = items;
203
+ }
204
+ else {
205
+ items = this.m_props.items;
206
+ }
207
+
208
+ let newsel: ListViewItem;
209
+ if (!this.m_selection) {
210
+ if (items) {
211
+ newsel = items[0];
212
+ }
213
+ }
214
+ else {
215
+ let index = items.findIndex((item) => item === this.m_selection.item);
216
+ if (sens > 0 && index < (items.length - 1)) {
217
+ newsel = items[index + 1];
218
+ }
219
+ else if (sens < 0 && index > 0) {
220
+ newsel = items[index - 1];
221
+ }
222
+ else {
223
+ newsel = this.selection
224
+ }
225
+ }
226
+
227
+ let citem = this._findItemWithId(newsel?.id);
228
+ this._selectItem(newsel, citem, true);
229
+ }
230
+
231
+ switch (ev.key) {
232
+ case 'ArrowDown': {
233
+ moveSel(1);
234
+ ev.stopPropagation();
235
+ break;
236
+ }
237
+
238
+ case 'ArrowUp': {
239
+ moveSel(-1);
240
+ ev.stopPropagation();
241
+ break;
242
+ }
243
+ }
244
+ }
245
+
246
+ /** @ignore */
247
+ private _buildContent() {
248
+
249
+ let props = this.m_props;
250
+
251
+ if (props.virtual) {
252
+ this.m_container = new Container({
253
+ cls: '@scroll-container',
254
+ content: []
255
+ });
256
+
257
+ this.m_view = new Container({
258
+ cls: '@scroll-view',
259
+ flex: 1,
260
+ content: this.m_container,
261
+ dom_events: {
262
+ sizechange: () => this._updateScroll(true),
263
+ scroll: () => this._updateScroll(false),
264
+ }
265
+ });
266
+
267
+ this.setContent(
268
+ [
269
+ this.m_view,
270
+ props.gadgets ? new HLayout({
271
+ cls: 'gadgets',
272
+ content: props.gadgets
273
+ }) : null,
274
+ ]
275
+ );
276
+ }
277
+ else {
278
+ this.m_view = undefined;
279
+ this.m_container = new VLayout({
280
+ cls: '@scroll-container',
281
+ content: []
282
+ });
283
+
284
+ this.addClass('@scroll-view');
285
+ this.setContent(this.m_container, false);
286
+ }
287
+
288
+ if (props.virtual) {
289
+ this.m_container.setStyleValue('height', props.items.length * this.m_itemHeight);
290
+ }
291
+
292
+ if (this.dom || !props.virtual ) {
293
+ this._buildItems();
294
+ }
295
+ }
296
+
297
+ /**
298
+ *
299
+ */
300
+
301
+ private _updateScroll(forceUpdate: boolean) {
302
+
303
+ const update = () => {
304
+ let newTop = Math.floor(this.m_view.dom.scrollTop / this.m_itemHeight);
305
+
306
+ if (newTop != this.m_topIndex || forceUpdate) {
307
+ this.m_topIndex = newTop;
308
+ this._buildItems();
309
+ }
310
+ }
311
+
312
+ if (forceUpdate) {
313
+ this.startTimer('scroll', 10, false, update);
314
+ }
315
+ else {
316
+ update();
317
+ }
318
+ }
319
+
320
+ private async _buildItems() {
321
+ let props = this.m_props;
322
+ let items: Component[] = [];
323
+
324
+ let list_items = props.items;
325
+ if (isFunction(list_items)) {
326
+ list_items = list_items();
327
+ }
328
+
329
+ let selId = this.m_selection?.item.id;
330
+ let selFnd = false;
331
+
332
+ if (props.virtual) {
333
+ let rc = this.getBoundingRect();
334
+ let limit = 100;
335
+
336
+ let y = 0;
337
+ let top = this.m_topIndex * this.m_itemHeight;
338
+ let index = this.m_topIndex;
339
+ let height = rc.height;
340
+ let count = props.items.length;
341
+
342
+ let newels = [];
343
+
344
+ let cache = this.m_cache;
345
+ this.m_cache = new Map<number, Component>();
346
+
347
+ while (y < height && index < count && --limit > 0) {
348
+
349
+ let it = props.items[index];
350
+ let itm: Component;
351
+
352
+ if (cache.has(it.id)) {
353
+ itm = cache.get(it.id); // reuse it
354
+ cache.delete(it.id); // cache will contain only elements to remove
355
+ }
356
+ else {
357
+ itm = this._renderItem(it);
358
+ newels.push(itm);
359
+ }
360
+
361
+ if (selId == it.id) {
362
+ itm.addClass('@selected');
363
+ selFnd = true;
364
+ }
365
+
366
+ itm.setStyleValue('top', top + y);
367
+ items.push(itm);
368
+
369
+ this.m_cache.set(it.id, itm); // keep it for next time
370
+
371
+ y += this.m_itemHeight;
372
+ index++;
373
+ }
374
+
375
+ // all element remaining here are to remove
376
+ cache.forEach((c) => {
377
+ c.dispose();
378
+ });
379
+
380
+ // append new elements
381
+ this.m_container.appendChild(newels);
382
+ this.m_container.setStyleValue( 'height', count*this.m_itemHeight );
383
+
384
+ // check that it's still existing
385
+ if( !selFnd ) {
386
+ if( !list_items.some( it => selId == it.id ) ) {
387
+ this.m_selection = null;
388
+ }
389
+ }
390
+
391
+ }
392
+ else {
393
+ list_items.forEach((it) => {
394
+ let itm = this._renderItem(it);
395
+ if (selId == it.id) {
396
+ itm.addClass('@selected');
397
+ selFnd = true;
398
+ }
399
+
400
+ items.push(itm);
401
+ });
402
+
403
+ this.m_container.setContent(items, false);
404
+
405
+ if( !selFnd ) {
406
+ this.m_selection = null;
407
+ }
408
+ }
409
+
410
+ if (this.m_defer_sel) {
411
+ let t = this.m_defer_sel;
412
+ this.m_defer_sel = undefined;
413
+ this.selection = t;
414
+ }
415
+ }
416
+
417
+ /** @ignore
418
+ * default rendering of an item
419
+ */
420
+
421
+ private _renderItem(item: ListViewItem): Component {
422
+
423
+ const newItem = this.onRenderItem( item );
424
+
425
+ newItem.setAttribute( 'data-id', item.id );
426
+ newItem.addClass('@list-item');
427
+ newItem.setData('item-id', item.id);
428
+
429
+ return newItem;
430
+ }
431
+
432
+ onRenderItem( item: ListViewItem): Component {
433
+ if (this.m_props.renderItem) {
434
+ return this.m_props.renderItem(item);
435
+ }
436
+ else {
437
+ return new HLayout({ content: item.text });
438
+ }
439
+ }
440
+
441
+ /** @ignore */
442
+ private _handleClick(e: MouseEvent) {
443
+
444
+ let dom = e.target as HTMLElement,
445
+ self = this.dom,
446
+ list_items = this.m_props.items as ListViewItem[]; // already created by build
447
+
448
+ // go up until we find something interesting
449
+ while (dom && dom != self) {
450
+ let itm = Component.getElement(dom),
451
+ id = itm?.getData('item-id');
452
+
453
+ if (id!==undefined) {
454
+ // find the element
455
+ let item = list_items.find((item) => item.id == id);
456
+ if (item) {
457
+ let event: BasicEvent;
458
+ if (e.type == 'click') {
459
+ event = EvClick(item);
460
+ this.emit('click', event);
461
+ }
462
+ else {
463
+ event = EvDblClick(item);
464
+ this.emit('dblClick', event);
465
+ }
466
+
467
+ if (!event.defaultPrevented) {
468
+ this._selectItem(item, itm);
469
+ }
470
+ }
471
+ else {
472
+ this._selectItem(null, null);
473
+ }
474
+
475
+ return;
476
+ }
477
+
478
+ dom = dom.parentElement;
479
+ }
480
+
481
+ this._selectItem(null, null);
482
+ }
483
+
484
+ /** @ignore */
485
+ private _handleCtxMenu(e: MouseEvent) {
486
+
487
+ e.preventDefault();
488
+
489
+ let dom = e.target as HTMLElement,
490
+ self = this.dom,
491
+ list_items = this.m_props.items as ListViewItem[]; // already created by build;
492
+
493
+ while (dom && dom != self) {
494
+ let itm = Component.getElement(dom),
495
+ id = itm?.getData('item-id');
496
+
497
+ if (id) {
498
+
499
+ // find the element
500
+ let item = list_items.find((item) => item.id == id);
501
+ if (item) {
502
+ this._selectItem(item, itm);
503
+ this.emit('contextMenu', EvContextMenu(e, item));
504
+ }
505
+
506
+ return;
507
+ }
508
+
509
+ dom = dom.parentElement;
510
+ }
511
+
512
+ this.emit('contextMenu', EvContextMenu(e, null));
513
+ }
514
+
515
+ /**
516
+ * @ignore
517
+ * called when an item is selected by mouse
518
+ */
519
+
520
+ protected _selectItem(item: ListViewItem, citem: Component, notify = true) {
521
+
522
+ if (this.m_selection && this.m_selection.citem) {
523
+ this.m_selection.citem.removeClass('@selected');
524
+ }
525
+
526
+ this.m_selection = item ? {
527
+ item: item,
528
+ citem: citem
529
+ } : null;
530
+
531
+ if (this.m_selection && this.m_selection.citem) {
532
+ this.m_selection.citem.addClass('@selected');
533
+ }
534
+
535
+ if (notify) {
536
+ this.emit('selectionChange', EvSelectionChange(item));
537
+ }
538
+ }
539
+
540
+ /**
541
+ * return the current selection or null
542
+ */
543
+
544
+ public get selection() {
545
+ return this.m_selection ? this.m_selection.item : null;
546
+ }
547
+
548
+ public set selection(id) {
549
+ if (id === null || id === undefined) {
550
+ this._selectItem(null, null);
551
+ }
552
+ else {
553
+ if (isFunction(this.m_props.items)) {
554
+ this.m_defer_sel = id;
555
+ }
556
+ else {
557
+ let item = this.m_props.items.find((item) => item.id == id);
558
+ let citem = this._findItemWithId(item.id);
559
+ this._selectItem(item, citem, false);
560
+ }
561
+ }
562
+ }
563
+
564
+ private _findItemWithId(id: any) {
565
+ let citem: Component = null;
566
+
567
+ if (this.dom) {
568
+ // make the element visible to user
569
+ // todo: problem with virtual listview
570
+ this.m_container.enumChilds((c: Component) => {
571
+ if (c.getData('item-id') == id) {
572
+ c.scrollIntoView();
573
+ citem = c;
574
+ return true;
575
+ }
576
+ })
577
+ }
578
+
579
+ return citem;
580
+ }
581
+
582
+ /**
583
+ * append or prepend a new item
584
+ * @param item
585
+ * @param prepend
586
+ * @param select
587
+ */
588
+
589
+ appendItem( item: ListViewItem, prepend = false, select = true ) {
590
+
591
+ if( prepend ) {
592
+ this.m_props.items.unshift( item );
593
+ }
594
+ else {
595
+ this.m_props.items.push( item );
596
+ }
597
+
598
+ if( select ) {
599
+ this.selection = null;
600
+ }
601
+
602
+ if( !this.m_container ) {
603
+ this._buildContent();
604
+ }
605
+ else {
606
+ this._buildItems();
607
+ }
608
+
609
+ if( select ) {
610
+ this.selection = item.id;
611
+ }
612
+ }
613
+
614
+ /**
615
+ * update an item
616
+ */
617
+
618
+ updateItem( id: any, item: ListViewItem ) {
619
+
620
+ // find item
621
+ const idx = this.m_props.items.findIndex( itm => itm.id === id );
622
+ if( idx<0 ) {
623
+ return;
624
+ }
625
+
626
+ // take care of selection
627
+ let was_sel = false;
628
+ if( this.m_selection && this.m_selection.item===this.m_props.items[idx] ) {
629
+ was_sel = true;
630
+ }
631
+
632
+ // replace it in the list
633
+ this.m_props.items[idx] = item;
634
+
635
+ // rebuild & replace it's line
636
+ const oldDOM = this.queryItem( `[data-id="${item.id}"]` )?.dom;
637
+ if( oldDOM ) {
638
+ const _new = this._renderItem( item );
639
+
640
+ if( was_sel ) {
641
+ _new.addClass( '@selected' );
642
+ this.m_selection.citem = _new;
643
+ this.m_selection.item = item;
644
+ }
645
+
646
+ const newDOM = _new._build( );
647
+ this.m_container.dom.replaceChild( newDOM, oldDOM );
648
+ }
649
+ }
650
+
651
+
652
+ }
653
+
654
+
655
+
656
+ /**
657
+ * Cancel Event
658
+ */
659
+
660
+ export interface EvCancel extends BasicEvent {
661
+ }
662
+
663
+ export function EvCancel( context = null ) {
664
+ return BasicEvent<EvCancel>({ context });
665
+ }
666
+
667
+ interface PopupListViewEventMap extends PopupEventMap {
668
+ cancel: EvCancel;
669
+ }
670
+
671
+ interface PopupListViewProps extends PopupProps<PopupListViewEventMap> {
672
+
673
+ }
674
+
675
+
676
+ /**
677
+ *
678
+ */
679
+
680
+ export class PopupListView extends Popup<PopupListViewProps,PopupListViewEventMap> {
681
+
682
+ m_list: ListView;
683
+
684
+ constructor(props: ListViewProps) {
685
+ super({ tabIndex: false });
686
+
687
+ this.enableMask(false);
688
+
689
+ props.tabIndex = false;
690
+ this.m_list = new ListView(props);
691
+ //this.m_list.addClass( '@fit' );
692
+
693
+ this.setContent(this.m_list);
694
+ this.mapPropEvents( props, 'cancel' );
695
+ }
696
+
697
+ set items( items: ListViewItem[] ) {
698
+ this.m_list.items = items;
699
+ }
700
+
701
+ // @override
702
+ // todo: move into popup
703
+ private _handleClick = (e: MouseEvent) => {
704
+ if (!this.dom) {
705
+ return;
706
+ }
707
+
708
+ let newfocus = <HTMLElement>e.target;
709
+
710
+ // child of this: ok
711
+ if (this.dom.contains(newfocus)) {
712
+ return;
713
+ }
714
+
715
+ // menu: ok
716
+ let dest = Component.getElement(newfocus, MenuItem);
717
+ if (dest) {
718
+ return;
719
+ }
720
+
721
+ this.signal( 'cancel', EvCancel() );
722
+ this.close();
723
+ }
724
+
725
+ // todo: move into popup
726
+ show(modal?: boolean) {
727
+ document.addEventListener('mousedown', this._handleClick);
728
+ super.show(modal);
729
+ }
730
+
731
+ hide( ) {
732
+ document.removeEventListener('mousedown', this._handleClick);
733
+ super.hide( );
734
+ }
735
+
736
+ // todo: move into popup
737
+ close() {
738
+ document.removeEventListener('mousedown', this._handleClick);
739
+ super.close();
740
+ }
741
+
742
+ get selection(): any {
743
+ return this.m_list.selection;
744
+ }
745
+
746
+ set selection(itemId: any) {
747
+ this.m_list.selection = itemId;
748
+ }
749
+ }