x4js 1.4.12 → 1.4.15

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.
@@ -107,7 +107,6 @@ export declare class Component<P extends CProps<BaseComponentEventMap> = CProps<
107
107
  private static __privateEvents;
108
108
  private static __sizeObserver;
109
109
  private static __createObserver;
110
- private static __intersectionObserver;
111
110
  private static __capture;
112
111
  private static __capture_mask;
113
112
  private static __css;
package/lib/component.js CHANGED
@@ -89,7 +89,7 @@ class Component extends base_component_1.BaseComponent {
89
89
  static __privateEvents = {};
90
90
  static __sizeObserver; // resize observer
91
91
  static __createObserver; // creation observer
92
- static __intersectionObserver; // visibility observer
92
+ //private static __intersectionObserver: IntersectionObserver; // visibility observer
93
93
  static __capture = null;
94
94
  static __capture_mask = null;
95
95
  static __css = null;
@@ -101,6 +101,10 @@ class Component extends base_component_1.BaseComponent {
101
101
  uid: Component.__comp_guid++,
102
102
  inrender: false,
103
103
  };
104
+ // prepare iprops
105
+ if (this.m_props.cls) {
106
+ this.addClass(this.m_props.cls);
107
+ }
104
108
  }
105
109
  /**
106
110
  *
@@ -1042,7 +1046,8 @@ class Component extends base_component_1.BaseComponent {
1042
1046
  this.addClass('@' + (0, tools_1.pascalCase)(clsname));
1043
1047
  me = Object.getPrototypeOf(me);
1044
1048
  }
1045
- this.addClass(this.m_props.cls);
1049
+ //done in ctor now
1050
+ //this.addClass(this.m_props.cls);
1046
1051
  }
1047
1052
  /**
1048
1053
  * prepend the system class name prefix on a name if needed (if class starts with @)
package/lib/gridview.js CHANGED
@@ -289,6 +289,7 @@ class GridView extends layout_1.VLayout {
289
289
  },
290
290
  content: this.m_container
291
291
  });
292
+ let flex = false;
292
293
  let cols = this.m_columns.map((col, index) => {
293
294
  let cls = '@cell';
294
295
  if (col.cls) {
@@ -325,9 +326,16 @@ class GridView extends layout_1.VLayout {
325
326
  sens: 'right',
326
327
  events: { resize: (e) => resizeCol(e) }
327
328
  });
328
- col.$col = comp;
329
+ if (col.flex) {
330
+ flex = true;
331
+ }
332
+ col.$hdr = comp;
329
333
  return comp;
330
334
  });
335
+ cols.push(new component_1.Flex({
336
+ ref: 'flex',
337
+ cls: flex ? '@hidden' : ''
338
+ }));
331
339
  // compute full width
332
340
  let full_width = 0;
333
341
  this.m_columns.forEach((col) => {
@@ -357,8 +365,13 @@ class GridView extends layout_1.VLayout {
357
365
  width: col.width
358
366
  }
359
367
  });
368
+ col.$ftr = comp;
360
369
  return comp;
361
370
  });
371
+ foots.push(new component_1.Flex({
372
+ ref: 'flex',
373
+ cls: flex ? '@hidden' : ''
374
+ }));
362
375
  this.m_footer = new layout_1.HLayout({
363
376
  cls: '@footer',
364
377
  content: foots,
@@ -378,8 +391,33 @@ class GridView extends layout_1.VLayout {
378
391
  ]);
379
392
  }
380
393
  _on_col_resize(col, width) {
381
- this.m_columns[col].width = width;
382
- this.m_columns[col].flex = undefined;
394
+ const _col = this.m_columns[col];
395
+ let updateFlex = false;
396
+ if (width >= 0) {
397
+ _col.width = width;
398
+ if (_col.flex) {
399
+ _col.$hdr.removeClass('@flex');
400
+ _col.flex = undefined;
401
+ updateFlex = true;
402
+ }
403
+ }
404
+ else if (width < 0 && !_col.flex) {
405
+ _col.$hdr.addClass('@flex');
406
+ _col.flex = 1;
407
+ updateFlex = true;
408
+ }
409
+ if (updateFlex) {
410
+ let flex = false;
411
+ this.m_columns.forEach(c => {
412
+ if (c.flex) {
413
+ flex = true;
414
+ }
415
+ });
416
+ this.m_header.itemWithRef('flex')?.show(flex ? false : true);
417
+ if (this.m_footer) {
418
+ this.m_footer.itemWithRef('flex')?.show(flex ? false : true);
419
+ }
420
+ }
383
421
  this._updateScroll(true);
384
422
  }
385
423
  /**
@@ -391,19 +429,19 @@ class GridView extends layout_1.VLayout {
391
429
  }
392
430
  this.m_columns.forEach((c) => {
393
431
  if (c !== col) {
394
- c.$col.sorted = false;
432
+ c.$hdr.sorted = false;
395
433
  }
396
434
  });
397
- const $col = col.$col;
398
- if ($col.sorted) {
399
- $col.toggleSens();
435
+ const $hdr = col.$hdr;
436
+ if ($hdr.sorted) {
437
+ $hdr.toggleSens();
400
438
  }
401
439
  else {
402
- $col.sorted = true;
440
+ $hdr.sorted = true;
403
441
  }
404
442
  if (this.m_dataview) {
405
443
  this.m_dataview.sort([
406
- { field: col.id, ascending: $col.sens == 'dn' ? false : true }
444
+ { field: col.id, ascending: $hdr.sens == 'dn' ? false : true }
407
445
  ]);
408
446
  }
409
447
  }
@@ -455,7 +493,7 @@ class GridView extends layout_1.VLayout {
455
493
  let cidx = 0;
456
494
  let index = this.m_topIndex;
457
495
  let count = this.m_dataview ? this.m_dataview.count : 0;
458
- let full_width = 0;
496
+ let full_width = 0; // todo: +4 pixel of left border
459
497
  let even = this.m_topIndex & 1 ? true : false;
460
498
  // compute full width
461
499
  this.m_columns.forEach((col) => {
@@ -587,8 +625,8 @@ class GridView extends layout_1.VLayout {
587
625
  });
588
626
  }
589
627
  else {
590
- this.m_header.setStyleValue('width', full_width);
591
- this.m_footer?.setStyleValue('width', full_width);
628
+ this.m_header.setStyleValue('width', full_width + 1000);
629
+ this.m_footer?.setStyleValue('width', full_width + 1000);
592
630
  this.m_container.setStyle({
593
631
  height: count * this.m_itemHeight,
594
632
  width: full_width
package/lib/layout.js CHANGED
@@ -278,10 +278,17 @@ exports.ScrollView = ScrollView;
278
278
  // https://medium.com/@andybarefoot/a-masonry-style-layout-using-css-grid-8c663d355ebb
279
279
  class Masonry extends component_1.Container {
280
280
  constructor(props) {
281
+ const items = props.items;
282
+ props.items = undefined;
281
283
  super(props);
282
284
  this.setDomEvent('sizechange', () => {
283
285
  this.resizeAllItems();
284
286
  });
287
+ if (items) {
288
+ items.forEach(i => {
289
+ this.addItem(i);
290
+ });
291
+ }
285
292
  }
286
293
  resizeItem(item) {
287
294
  const style = this.getComputedStyle();
package/lib/listview.d.ts CHANGED
@@ -83,7 +83,7 @@ export interface ListViewProps<E extends ListViewEventMap = ListViewEventMap> ex
83
83
  /**
84
84
  * Standard listview class
85
85
  */
86
- export declare class ListView<T extends ListViewProps = ListViewProps, E extends ListViewEventMap = ListViewEventMap> extends VLayout<T, E> {
86
+ export declare class ListView extends VLayout<ListViewProps, ListViewEventMap> {
87
87
  protected m_selection: {
88
88
  item: ListViewItem;
89
89
  citem: Component;
@@ -94,7 +94,7 @@ export declare class ListView<T extends ListViewProps = ListViewProps, E extends
94
94
  protected m_topIndex: number;
95
95
  protected m_itemHeight: number;
96
96
  protected m_cache: Map<number, Component>;
97
- constructor(props: T);
97
+ constructor(props: ListViewProps);
98
98
  componentCreated(): void;
99
99
  render(props: ListViewProps): void;
100
100
  /**
package/lib/router.d.ts CHANGED
@@ -26,9 +26,17 @@
26
26
  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27
27
  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
28
  **/
29
- export declare class Router {
29
+ import { EventSource, EvError, EventMap } from "./x4_events";
30
+ declare type RouteHandler = (params: any, path: string) => void;
31
+ interface RouterEventMap extends EventMap {
32
+ error: EvError;
33
+ }
34
+ export declare class Router extends EventSource<RouterEventMap> {
30
35
  private routes;
31
36
  constructor();
32
- get(uri: any, callback: any): void;
37
+ get(uri: string | RegExp, handler: RouteHandler): void;
33
38
  init(): void;
39
+ navigate(uri: string, notify?: boolean): void;
40
+ private _find;
34
41
  }
42
+ export {};
package/lib/router.js CHANGED
@@ -29,31 +29,111 @@
29
29
  **/
30
30
  Object.defineProperty(exports, "__esModule", { value: true });
31
31
  exports.Router = void 0;
32
- class Router {
32
+ const x4_events_1 = require("./x4_events");
33
+ function parseRoute(str, loose = false) {
34
+ if (str instanceof RegExp) {
35
+ return {
36
+ keys: null,
37
+ pattern: str
38
+ };
39
+ }
40
+ const arr = str.split('/');
41
+ let keys = [];
42
+ let pattern = '';
43
+ if (arr[0] == '') {
44
+ arr.shift();
45
+ }
46
+ for (const tmp of arr) {
47
+ const c = tmp[0];
48
+ if (c === '*') {
49
+ keys.push('wild');
50
+ pattern += '/(.*)';
51
+ }
52
+ else if (c === ':') {
53
+ const o = tmp.indexOf('?', 1);
54
+ const ext = tmp.indexOf('.', 1);
55
+ keys.push(tmp.substring(1, o >= 0 ? o : ext >= 0 ? ext : tmp.length));
56
+ pattern += o < 0 && ext < 0 ? '(?:/([^/]+?))?' : '/([^/]+?)';
57
+ if (ext >= 0) {
58
+ pattern += (o >= 0 ? '?' : '') + '\\' + tmp.substring(ext);
59
+ }
60
+ }
61
+ else {
62
+ pattern += '/' + tmp;
63
+ }
64
+ }
65
+ return {
66
+ keys,
67
+ pattern: new RegExp(`^${pattern}${loose ? '(?=$|\/)' : '\/?$'}`, 'i')
68
+ };
69
+ }
70
+ class Router extends x4_events_1.EventSource {
33
71
  routes;
34
72
  constructor() {
73
+ super();
35
74
  this.routes = [];
36
- }
37
- get(uri, callback) {
38
- // throw an error if the route uri already exists to avoid confilicting routes
39
- this.routes.forEach(route => {
40
- if (route.uri === uri) {
41
- throw new Error(`the uri ${route.uri} already exists`);
42
- }
43
- });
44
- this.routes.push({
45
- uri,
46
- callback
75
+ window.addEventListener('popstate', (event) => {
76
+ const url = document.location.pathname;
77
+ const found = this._find(url);
78
+ found.handlers.forEach(h => {
79
+ h(found.params, url);
80
+ });
47
81
  });
48
82
  }
83
+ get(uri, handler) {
84
+ let { keys, pattern } = parseRoute(uri);
85
+ this.routes.push({ keys, pattern, handler });
86
+ }
49
87
  init() {
50
- this.routes.some(route => {
51
- let regEx = new RegExp(`^${route.uri}$`);
52
- let path = window.location.pathname;
53
- if (path.match(regEx)) {
54
- return route.callback({ path });
88
+ this.navigate(window.location.pathname);
89
+ }
90
+ navigate(uri, notify = true) {
91
+ const found = this._find(uri);
92
+ if (!found || found.handlers.length == 0) {
93
+ //window.history.pushState({}, '', 'error')
94
+ console.log('route not found: ' + uri);
95
+ this.signal("error", (0, x4_events_1.EvError)(404, "route not found"));
96
+ return;
97
+ }
98
+ window.history.pushState({}, '', uri);
99
+ if (notify) {
100
+ found.handlers.forEach(h => {
101
+ h(found.params, uri);
102
+ });
103
+ }
104
+ }
105
+ _find(url) {
106
+ let matches = [];
107
+ let params = {};
108
+ let handlers = [];
109
+ for (const tmp of this.routes) {
110
+ if (!tmp.keys) {
111
+ matches = tmp.pattern.exec(url);
112
+ if (!matches) {
113
+ continue;
114
+ }
115
+ if (matches['groups']) {
116
+ for (const k in matches['groups']) {
117
+ params[k] = matches['groups'][k];
118
+ }
119
+ }
120
+ handlers = [...handlers, tmp.handler];
55
121
  }
56
- });
122
+ else if (tmp.keys.length > 0) {
123
+ matches = tmp.pattern.exec(url);
124
+ if (matches === null) {
125
+ continue;
126
+ }
127
+ for (let j = 0; j < tmp.keys.length;) {
128
+ params[tmp.keys[j]] = matches[++j];
129
+ }
130
+ handlers = [...handlers, tmp.handler];
131
+ }
132
+ else if (tmp.pattern.test(url)) {
133
+ handlers = [...handlers, tmp.handler];
134
+ }
135
+ }
136
+ return { params, handlers };
57
137
  }
58
138
  }
59
139
  exports.Router = Router;
package/lib/tabbar.d.ts CHANGED
@@ -48,9 +48,11 @@ export declare class TabBar extends Container<TabBarProps, TabBarEventMap> {
48
48
  private m_pages;
49
49
  private m_curPage;
50
50
  constructor(props: TabBarProps);
51
+ componentCreated(): void;
51
52
  addPage(page: ITabPage): void;
52
53
  render(): void;
53
- select(id: string): void;
54
+ select(id: string | null, notify?: boolean): void;
54
55
  private _select;
56
+ get selection(): Component<CProps<import("./base_component").BaseComponentEventMap>, import("./base_component").BaseComponentEventMap>;
55
57
  }
56
58
  export {};
package/lib/tabbar.js CHANGED
@@ -47,8 +47,10 @@ class TabBar extends component_1.Container {
47
47
  this.addClass('@hlayout');
48
48
  }
49
49
  this.m_props.pages?.forEach(p => this.addPage(p));
50
+ }
51
+ componentCreated() {
50
52
  if (this.m_props.default) {
51
- this.select(this.m_props.default);
53
+ this.select(this.m_props.default, true);
52
54
  }
53
55
  }
54
56
  addPage(page) {
@@ -58,28 +60,45 @@ class TabBar extends component_1.Container {
58
60
  render() {
59
61
  let buttons = [];
60
62
  this.m_pages.forEach(p => {
61
- p.btn = new button_1.Button({ cls: p === this.m_curPage ? 'selected' : '', text: p.title, icon: p.icon, click: () => this._select(p) });
63
+ p.btn = new button_1.Button({ cls: p === this.m_curPage ? 'selected' : '', text: p.title, icon: p.icon, click: () => this._select(p, true) });
62
64
  buttons.push(p.btn);
63
65
  });
64
66
  this.setContent(buttons);
65
67
  }
66
- select(id) {
67
- let page = this.m_pages.find(x => x.id === id);
68
- if (page) {
69
- this._select(page);
68
+ select(id, notify = false) {
69
+ if (!id) {
70
+ this._select(null, notify);
71
+ }
72
+ else {
73
+ let page = this.m_pages.find(x => x.id === id);
74
+ if (page) {
75
+ this._select(page, notify);
76
+ }
70
77
  }
71
78
  }
72
- _select(p) {
73
- if (this.dom && this.m_curPage && this.m_curPage.page) {
79
+ _select(p, notify) {
80
+ if (this.m_curPage == p) {
81
+ return;
82
+ }
83
+ if (this.dom && this.m_curPage) {
74
84
  this.m_curPage.btn.removeClass('selected');
75
- this.m_curPage.page.hide();
85
+ if (this.m_curPage.page) {
86
+ this.m_curPage.page.hide();
87
+ }
76
88
  }
77
89
  this.m_curPage = p;
78
- this.signal('change', (0, x4_events_1.EvChange)(p ? p.id : null));
79
- if (this.dom && this.m_curPage && this.m_curPage.page) {
90
+ if (notify) {
91
+ this.signal('change', (0, x4_events_1.EvChange)(p ? p.id : null));
92
+ }
93
+ if (this.dom && this.m_curPage) {
80
94
  this.m_curPage.btn.addClass('selected');
81
- this.m_curPage.page.show();
95
+ if (this.m_curPage.page) {
96
+ this.m_curPage.page.show();
97
+ }
82
98
  }
83
99
  }
100
+ get selection() {
101
+ return this.m_curPage?.page;
102
+ }
84
103
  }
85
104
  exports.TabBar = TabBar;
package/lib/x4.css CHANGED
@@ -1124,7 +1124,7 @@ textarea::selection {
1124
1124
  .x-spreadsheet .x-row,
1125
1125
  .x-grid-view .x-row {
1126
1126
  position: absolute;
1127
- width: 100%;
1127
+ width: calc(100% - 4px);
1128
1128
  border-bottom: 1px solid #f0f0f0;
1129
1129
  align-items: center;
1130
1130
  height: 2em;
@@ -106,6 +106,14 @@ export interface EvDrag extends BasicEvent {
106
106
  data: any;
107
107
  }
108
108
  export declare function EvDrag(element: unknown, data: any, ctx: any): EvDrag;
109
+ /**
110
+ * Errors
111
+ */
112
+ export interface EvError extends BasicEvent {
113
+ code: number;
114
+ message: string;
115
+ }
116
+ export declare function EvError(code: number, message: string): EvError;
109
117
  /**
110
118
  * this Base interface is used to describe available events & their types
111
119
  *
package/lib/x4_events.js CHANGED
@@ -28,7 +28,7 @@
28
28
  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29
29
  **/
30
30
  Object.defineProperty(exports, "__esModule", { value: true });
31
- exports.EventSource = exports.EvDrag = exports.EvMessage = exports.EvTimer = exports.EvContextMenu = exports.EvSelectionChange = exports.EvChange = exports.EvClick = exports.BasicEvent = void 0;
31
+ exports.EventSource = exports.EvError = exports.EvDrag = exports.EvMessage = exports.EvTimer = exports.EvContextMenu = exports.EvSelectionChange = exports.EvChange = exports.EvClick = exports.BasicEvent = void 0;
32
32
  // default stopPropagation implementation for Events
33
33
  const stopPropagation = function () {
34
34
  this.propagationStopped = true;
@@ -81,6 +81,10 @@ function EvDrag(element, data, ctx) {
81
81
  return BasicEvent({ element, data, context: ctx });
82
82
  }
83
83
  exports.EvDrag = EvDrag;
84
+ function EvError(code, message) {
85
+ return BasicEvent({ code, message });
86
+ }
87
+ exports.EvError = EvError;
84
88
  /**
85
89
  * Event emitter class
86
90
  * this class allow you to emit and handle events
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x4js",
3
- "version": "1.4.12",
3
+ "version": "1.4.15",
4
4
  "description": "X4js core files",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
package/src/component.ts CHANGED
@@ -188,7 +188,7 @@ export class Component<P extends CProps<BaseComponentEventMap> = CProps<BaseComp
188
188
  private static __privateEvents: any = {};
189
189
  private static __sizeObserver: ResizeObserver; // resize observer
190
190
  private static __createObserver: MutationObserver; // creation observer
191
- private static __intersectionObserver: IntersectionObserver; // visibility observer
191
+ //private static __intersectionObserver: IntersectionObserver; // visibility observer
192
192
 
193
193
  private static __capture: ICaptureInfo = null;
194
194
  private static __capture_mask = null;
@@ -202,6 +202,11 @@ export class Component<P extends CProps<BaseComponentEventMap> = CProps<BaseComp
202
202
  dom_events: {},
203
203
  uid: Component.__comp_guid++,
204
204
  inrender: false,
205
+ };
206
+
207
+ // prepare iprops
208
+ if( this.m_props.cls ) {
209
+ this.addClass( this.m_props.cls );
205
210
  }
206
211
  }
207
212
 
@@ -1389,7 +1394,8 @@ export class Component<P extends CProps<BaseComponentEventMap> = CProps<BaseComp
1389
1394
  me = Object.getPrototypeOf(me);
1390
1395
  }
1391
1396
 
1392
- this.addClass(this.m_props.cls);
1397
+ //done in ctor now
1398
+ //this.addClass(this.m_props.cls);
1393
1399
  }
1394
1400
 
1395
1401
  /**
package/src/gridview.ts CHANGED
@@ -36,7 +36,7 @@ const T_UPDATE = Symbol('update');
36
36
 
37
37
 
38
38
  import { HLayout, VLayout } from './layout'
39
- import { Component, ContainerEventMap, EvSize, EvDblClick, CProps, flyWrap, html, HtmlString, SizerOverlay } from './component'
39
+ import { Component, ContainerEventMap, EvSize, EvDblClick, CProps, flyWrap, html, HtmlString, SizerOverlay, Flex } from './component'
40
40
  import { Label } from './label'
41
41
  import { _tr } from './i18n'
42
42
  import * as Formatters from './formatters'
@@ -76,7 +76,8 @@ export interface GridColumn {
76
76
  }
77
77
 
78
78
  interface GridColumnInternal extends GridColumn {
79
- $col: ColHeader;
79
+ $hdr: ColHeader;
80
+ $ftr: Component;
80
81
  }
81
82
 
82
83
 
@@ -421,6 +422,7 @@ export class GridView extends VLayout<GridViewProps, GridViewEventMap> {
421
422
  content: this.m_container
422
423
  });
423
424
 
425
+ let flex = false;
424
426
  let cols = this.m_columns.map((col, index) => {
425
427
 
426
428
  let cls = '@cell';
@@ -463,10 +465,19 @@ export class GridView extends VLayout<GridViewProps, GridViewEventMap> {
463
465
  events: {resize: ( e ) => resizeCol(e )}
464
466
  });
465
467
 
466
- (<any>col).$col = comp;
468
+ if( col.flex ) {
469
+ flex = true;
470
+ }
471
+
472
+ (<any>col).$hdr = comp;
467
473
  return comp;
468
474
  });
469
475
 
476
+ (cols as any).push( new Flex( {
477
+ ref: 'flex',
478
+ cls: flex ? '@hidden' : ''
479
+ } ) );
480
+
470
481
  // compute full width
471
482
  let full_width = 0;
472
483
  this.m_columns.forEach((col) => {
@@ -479,8 +490,7 @@ export class GridView extends VLayout<GridViewProps, GridViewEventMap> {
479
490
  style: {
480
491
  minWidth: full_width
481
492
  }
482
- });
483
-
493
+ });
484
494
 
485
495
  if( this.m_props.hasFooter ) {
486
496
  let foots = this.m_columns.map((col, index) => {
@@ -504,9 +514,15 @@ export class GridView extends VLayout<GridViewProps, GridViewEventMap> {
504
514
  }
505
515
  });
506
516
 
517
+ (col as GridColumnInternal).$ftr = comp;
507
518
  return comp;
508
519
  });
509
520
 
521
+ (foots as any).push( new Flex( {
522
+ ref: 'flex',
523
+ cls: flex ? '@hidden' : ''
524
+ } ) );
525
+
510
526
  this.m_footer = new HLayout({
511
527
  cls: '@footer',
512
528
  content: <any>foots,
@@ -528,9 +544,40 @@ export class GridView extends VLayout<GridViewProps, GridViewEventMap> {
528
544
 
529
545
  }
530
546
 
531
- private _on_col_resize(col, width) {
532
- this.m_columns[col].width = width;
533
- this.m_columns[col].flex = undefined;
547
+ private _on_col_resize(col: number, width: number) {
548
+
549
+ const _col = this.m_columns[col] as GridColumnInternal;
550
+
551
+ let updateFlex = false;
552
+
553
+ if( width>=0 ) {
554
+ _col.width = width;
555
+ if( _col.flex ) {
556
+ _col.$hdr.removeClass( '@flex' );
557
+ _col.flex = undefined;
558
+ updateFlex = true;
559
+ }
560
+ }
561
+ else if( width<0 && !_col.flex ) {
562
+ _col.$hdr.addClass( '@flex' );
563
+ _col.flex = 1;
564
+ updateFlex = true;
565
+ }
566
+
567
+ if( updateFlex ) {
568
+ let flex = false;
569
+ this.m_columns.forEach( c => {
570
+ if( c.flex ) {
571
+ flex = true;
572
+ }
573
+ });
574
+
575
+ this.m_header.itemWithRef( 'flex' )?.show( flex ? false : true );
576
+ if( this.m_footer ) {
577
+ this.m_footer.itemWithRef( 'flex' )?.show( flex ? false : true );
578
+ }
579
+ }
580
+
534
581
  this._updateScroll(true);
535
582
  }
536
583
 
@@ -546,22 +593,22 @@ export class GridView extends VLayout<GridViewProps, GridViewEventMap> {
546
593
 
547
594
  this.m_columns.forEach((c) => {
548
595
  if (c !== col) {
549
- (c as GridColumnInternal).$col.sorted = false;
596
+ (c as GridColumnInternal).$hdr.sorted = false;
550
597
  }
551
598
  });
552
599
 
553
- const $col = (col as GridColumnInternal).$col;
600
+ const $hdr = (col as GridColumnInternal).$hdr;
554
601
 
555
- if ($col.sorted) {
556
- $col.toggleSens( );
602
+ if ($hdr.sorted) {
603
+ $hdr.toggleSens( );
557
604
  }
558
605
  else {
559
- $col.sorted = true;
606
+ $hdr.sorted = true;
560
607
  }
561
608
 
562
609
  if (this.m_dataview) {
563
610
  this.m_dataview.sort([
564
- { field: col.id, ascending: $col.sens=='dn' ? false : true }
611
+ { field: col.id, ascending: $hdr.sens=='dn' ? false : true }
565
612
  ]);
566
613
  }
567
614
  }
@@ -626,7 +673,8 @@ export class GridView extends VLayout<GridViewProps, GridViewEventMap> {
626
673
  let cidx = 0;
627
674
  let index = this.m_topIndex;
628
675
  let count = this.m_dataview ? this.m_dataview.count : 0;
629
- let full_width = 0;
676
+
677
+ let full_width = 0; // todo: +4 pixel of left border
630
678
  let even = this.m_topIndex & 1 ? true : false;
631
679
 
632
680
  // compute full width
@@ -787,9 +835,8 @@ export class GridView extends VLayout<GridViewProps, GridViewEventMap> {
787
835
  });
788
836
  }
789
837
  else {
790
- this.m_header.setStyleValue('width', full_width);
791
- this.m_footer?.setStyleValue('width', full_width);
792
-
838
+ this.m_header.setStyleValue('width', full_width + 1000 );
839
+ this.m_footer?.setStyleValue('width', full_width + 1000 );
793
840
  this.m_container.setStyle({
794
841
  height: count * this.m_itemHeight,
795
842
  width: full_width
package/src/layout.ts CHANGED
@@ -378,11 +378,20 @@ export class ScrollView extends Component<ScrollViewProps> {
378
378
  export class Masonry extends Container {
379
379
 
380
380
  constructor(props) {
381
+ const items = props.items;
382
+ props.items = undefined;
383
+
381
384
  super(props);
382
385
 
383
386
  this.setDomEvent('sizechange', () => {
384
387
  this.resizeAllItems( );
385
388
  });
389
+
390
+ if( items ) {
391
+ items.forEach( i => {
392
+ this.addItem( i );
393
+ });
394
+ }
386
395
  }
387
396
 
388
397
  resizeItem(item: Component) {
package/src/listview.ts CHANGED
@@ -100,7 +100,7 @@ export interface ListViewProps<E extends ListViewEventMap = ListViewEventMap> ex
100
100
  * Standard listview class
101
101
  */
102
102
 
103
- export class ListView<T extends ListViewProps = ListViewProps, E extends ListViewEventMap = ListViewEventMap> extends VLayout<T,E> {
103
+ export class ListView extends VLayout<ListViewProps,ListViewEventMap> {
104
104
 
105
105
  protected m_selection: {
106
106
  item: ListViewItem;
@@ -117,7 +117,7 @@ export class ListView<T extends ListViewProps = ListViewProps, E extends ListVie
117
117
 
118
118
  protected m_cache: Map<number, Component>; // recycling elements
119
119
 
120
- constructor(props: T) {
120
+ constructor(props: ListViewProps) {
121
121
  super(props);
122
122
 
123
123
  this.setDomEvent('keydown', (e) => this._handleKey(e));
package/src/router.ts CHANGED
@@ -27,45 +27,157 @@
27
27
  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
28
  **/
29
29
 
30
- type Callback = (request: { path: string }) => void;
30
+ import { EventSource, EvError, EventMap } from "./x4_events"
31
+
32
+ type RouteHandler = ( params: any, path: string ) => void;
33
+
34
+ interface Segment {
35
+ keys: string[],
36
+ pattern: RegExp;
37
+ }
31
38
 
32
39
  interface Route {
33
- uri: string;
34
- callback: Callback;
40
+ keys: string[],
41
+ pattern: RegExp;
42
+ handler: RouteHandler;
35
43
  }
36
44
 
37
- export class Router {
45
+ function parseRoute(str: string | RegExp, loose = false): Segment {
38
46
 
39
- private routes: Route[];
47
+ if (str instanceof RegExp) {
48
+ return {
49
+ keys: null,
50
+ pattern: str
51
+ };
52
+ }
40
53
 
41
- constructor() {
42
- this.routes = [];
54
+ const arr = str.split('/');
55
+
56
+ let keys = [];
57
+ let pattern = '';
58
+
59
+ if( arr[0]=='' ) {
60
+ arr.shift();
43
61
  }
44
62
 
45
- get(uri, callback) {
63
+ for (const tmp of arr) {
64
+ const c = tmp[0];
46
65
 
47
- // throw an error if the route uri already exists to avoid confilicting routes
48
- this.routes.forEach(route => {
49
- if (route.uri === uri) {
50
- throw new Error(`the uri ${route.uri} already exists`);
66
+ if (c === '*') {
67
+ keys.push('wild');
68
+ pattern += '/(.*)';
69
+ }
70
+ else if (c === ':') {
71
+ const o = tmp.indexOf('?', 1);
72
+ const ext = tmp.indexOf('.', 1);
73
+
74
+ keys.push(tmp.substring(1, o >= 0 ? o : ext >= 0 ? ext : tmp.length));
75
+ pattern += o < 0 && ext < 0 ? '(?:/([^/]+?))?' : '/([^/]+?)';
76
+ if (ext >= 0) {
77
+ pattern += (o >= 0 ? '?' : '') + '\\' + tmp.substring(ext);
51
78
  }
52
- });
79
+ }
80
+ else {
81
+ pattern += '/' + tmp;
82
+ }
83
+ }
84
+
85
+ return {
86
+ keys,
87
+ pattern: new RegExp( `^${pattern}${loose ? '(?=$|\/)' : '\/?$'}`, 'i' )
88
+ };
89
+ }
90
+
91
+ interface RouterEventMap extends EventMap {
92
+ error: EvError;
93
+ }
94
+
95
+ export class Router extends EventSource< RouterEventMap > {
96
+
97
+ private routes: Route[];
53
98
 
54
- this.routes.push({
55
- uri,
56
- callback
99
+ constructor() {
100
+ super( );
101
+
102
+ this.routes = [];
103
+
104
+ window.addEventListener('popstate', (event) => {
105
+ const url = document.location.pathname;
106
+ const found = this._find(url);
107
+
108
+ found.handlers.forEach(h => {
109
+ h(found.params,url);
110
+ });
57
111
  });
58
112
  }
59
113
 
114
+ get(uri: string | RegExp, handler: RouteHandler ) {
115
+ let { keys, pattern } = parseRoute(uri);
116
+ this.routes.push({ keys, pattern, handler });
117
+ }
118
+
60
119
  init() {
61
- this.routes.some(route => {
120
+ this.navigate( window.location.pathname );
121
+ }
122
+
123
+ navigate( uri: string, notify = true ) {
124
+
125
+ const found = this._find( uri );
62
126
 
63
- let regEx = new RegExp(`^${route.uri}$`);
64
- let path = window.location.pathname;
127
+ if( !found || found.handlers.length==0 ) {
128
+ //window.history.pushState({}, '', 'error')
129
+ console.log( 'route not found: '+uri );
130
+ this.signal( "error", EvError( 404, "route not found" ) );
131
+ return;
132
+ }
65
133
 
66
- if (path.match(regEx)) {
67
- return route.callback({ path });
134
+ window.history.pushState({}, '', uri )
135
+
136
+ if( notify ) {
137
+ found.handlers.forEach( h => {
138
+ h( found.params, uri );
139
+ } );
140
+ }
141
+ }
142
+
143
+ private _find( url: string ): { params: any, handlers: RouteHandler[] } {
144
+
145
+ let matches = [];
146
+ let params = {};
147
+ let handlers = [];
148
+
149
+ for (const tmp of this.routes ) {
150
+ if (!tmp.keys ) {
151
+ matches = tmp.pattern.exec(url);
152
+ if (!matches) {
153
+ continue;
154
+ }
155
+
156
+ if (matches['groups']) {
157
+ for (const k in matches['groups']) {
158
+ params[k] = matches['groups'][k];
159
+ }
160
+ }
161
+
162
+ handlers = [...handlers, tmp.handler];
163
+ }
164
+ else if (tmp.keys.length > 0) {
165
+ matches = tmp.pattern.exec(url);
166
+ if (matches === null) {
167
+ continue;
168
+ }
169
+
170
+ for ( let j = 0; j < tmp.keys.length;) {
171
+ params[tmp.keys[j]] = matches[++j];
172
+ }
173
+
174
+ handlers = [...handlers, tmp.handler];
175
+ }
176
+ else if (tmp.pattern.test(url)) {
177
+ handlers = [...handlers, tmp.handler];
68
178
  }
69
- })
179
+ }
180
+
181
+ return { params, handlers };
70
182
  }
71
183
  }
package/src/tabbar.ts CHANGED
@@ -75,9 +75,12 @@ export class TabBar extends Container<TabBarProps,TabBarEventMap> {
75
75
  }
76
76
 
77
77
  this.m_props.pages?.forEach( p => this.addPage(p) );
78
+ }
79
+
80
+ componentCreated(): void {
78
81
  if( this.m_props.default ) {
79
- this.select( this.m_props.default );
80
- }
82
+ this.select( this.m_props.default, true );
83
+ }
81
84
  }
82
85
 
83
86
  addPage( page: ITabPage ) {
@@ -88,33 +91,53 @@ export class TabBar extends Container<TabBarProps,TabBarEventMap> {
88
91
  render( ) {
89
92
  let buttons = [];
90
93
  this.m_pages.forEach( p => {
91
- p.btn = new Button( { cls: p===this.m_curPage ? 'selected' : '', text: p.title, icon: p.icon, click: () => this._select(p) } );
94
+ p.btn = new Button( { cls: p===this.m_curPage ? 'selected' : '', text: p.title, icon: p.icon, click: () => this._select(p,true) } );
92
95
  buttons.push( p.btn );
93
96
  });
94
97
 
95
98
  this.setContent( buttons );
96
99
  }
97
100
 
98
- select( id: string ) {
99
- let page = this.m_pages.find( x => x.id===id );
100
- if( page ) {
101
- this._select( page );
101
+ select( id: string | null, notify = false ) {
102
+ if( !id ) {
103
+ this._select( null, notify );
104
+ }
105
+ else {
106
+ let page = this.m_pages.find( x => x.id===id );
107
+ if( page ) {
108
+ this._select( page, notify );
109
+ }
102
110
  }
103
111
  }
104
112
 
105
- private _select( p: TabPage ) {
113
+ private _select( p: TabPage, notify: boolean ) {
106
114
 
107
- if( this.dom && this.m_curPage && this.m_curPage.page ) {
115
+ if( this.m_curPage==p ) {
116
+ return;
117
+ }
118
+
119
+ if( this.dom && this.m_curPage ) {
108
120
  this.m_curPage.btn.removeClass( 'selected' );
109
- this.m_curPage.page.hide( );
121
+ if( this.m_curPage.page ) {
122
+ this.m_curPage.page.hide( );
123
+ }
110
124
  }
111
125
 
112
126
  this.m_curPage = p;
113
- this.signal( 'change', EvChange(p ? p.id : null) );
114
127
 
115
- if( this.dom && this.m_curPage && this.m_curPage.page ) {
128
+ if( notify ) {
129
+ this.signal( 'change', EvChange(p ? p.id : null) );
130
+ }
131
+
132
+ if( this.dom && this.m_curPage ) {
116
133
  this.m_curPage.btn.addClass( 'selected' );
117
- this.m_curPage.page.show( );
134
+ if( this.m_curPage.page ) {
135
+ this.m_curPage.page.show( );
136
+ }
118
137
  }
119
138
  }
139
+
140
+ get selection( ) {
141
+ return this.m_curPage?.page;
142
+ }
120
143
  }
package/src/x4.less CHANGED
@@ -1312,12 +1312,13 @@ textarea {
1312
1312
  }
1313
1313
  }
1314
1314
 
1315
+ @bwidth: 4px;
1315
1316
 
1316
1317
  .x-spreadsheet,
1317
1318
  .x-grid-view {
1318
1319
 
1319
1320
  @def-height: 2em;
1320
-
1321
+
1321
1322
  min-height: 0;
1322
1323
  overflow: hidden;
1323
1324
  background-color: white;
@@ -1370,7 +1371,7 @@ textarea {
1370
1371
 
1371
1372
  .x-row {
1372
1373
  position: absolute;
1373
- width: 100%;
1374
+ width: calc( 100% - @bwidth ); // todo: border of 1st col
1374
1375
  border-bottom: 1px solid #f0f0f0;
1375
1376
  align-items: center;
1376
1377
  height: @def-height;
@@ -1400,11 +1401,11 @@ textarea {
1400
1401
  .x-grid-view {
1401
1402
  .x-footer,
1402
1403
  .x-header {
1403
- border-left: 4px solid #f0f0f0;
1404
+ border-left: @bwidth solid #f0f0f0;
1404
1405
  }
1405
1406
 
1406
1407
  .x-row {
1407
- border-left: 4px solid transparent;
1408
+ border-left: @bwidth solid transparent;
1408
1409
 
1409
1410
  &:hover {
1410
1411
  background-color: rgba(0,0,0,0.1);
package/src/x4_events.ts CHANGED
@@ -168,6 +168,19 @@ export function EvDrag(element: unknown, data: any, ctx: any ) {
168
168
  return BasicEvent<EvDrag>({ element, data, context: ctx });
169
169
  }
170
170
 
171
+ /**
172
+ * Errors
173
+ */
174
+
175
+ export interface EvError extends BasicEvent {
176
+ code: number;
177
+ message: string;
178
+ }
179
+
180
+ export function EvError( code: number, message: string ) : EvError {
181
+ return BasicEvent<EvError>( {code, message} );
182
+ }
183
+
171
184
 
172
185
  /**
173
186
  * this Base interface is used to describe available events & their types