x4js 1.4.13 → 1.4.16

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.
@@ -96,5 +96,6 @@ export declare class Application<P extends ApplicationProps = ApplicationProps,
96
96
  setTitle(title: string): void;
97
97
  disableZoomWheel(): void;
98
98
  enterModal(enter: boolean): void;
99
+ handleTouchEvents(): void;
99
100
  }
100
101
  export {};
@@ -30,6 +30,7 @@
30
30
  Object.defineProperty(exports, "__esModule", { value: true });
31
31
  exports.Application = void 0;
32
32
  const base_component_1 = require("./base_component");
33
+ const component_1 = require("./component");
33
34
  const settings_1 = require("./settings");
34
35
  const tools_1 = require("./tools");
35
36
  /**
@@ -145,6 +146,33 @@ class Application extends base_component_1.BaseComponent {
145
146
  }
146
147
  enterModal(enter) {
147
148
  }
149
+ handleTouchEvents() {
150
+ document.addEventListener('touchstart', (ev) => {
151
+ let me = this;
152
+ let now = new Date().getTime();
153
+ if (!me.__last_touch || (me.__last_touch - now) > 700) {
154
+ me.__touch_cnt = 1;
155
+ }
156
+ else {
157
+ me.__touch_cnt++;
158
+ }
159
+ me.__last_touch = now;
160
+ if (me.__touch_cnt == 2) {
161
+ me.__touch_cnt = 0;
162
+ let fake = {
163
+ type: "dblclick",
164
+ };
165
+ const tch = ev.touches[0];
166
+ for (const n in tch) {
167
+ fake[n] = tch[n];
168
+ }
169
+ // ts-ignore -> private
170
+ component_1.Component._dispatchEvent(fake);
171
+ ev.preventDefault();
172
+ ev.stopPropagation();
173
+ }
174
+ });
175
+ }
148
176
  }
149
177
  exports.Application = Application;
150
178
  ;
@@ -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,10 +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
  });
331
- cols.push(new component_1.Flex({}));
335
+ cols.push(new component_1.Flex({
336
+ ref: 'flex',
337
+ cls: flex ? '@hidden' : ''
338
+ }));
332
339
  // compute full width
333
340
  let full_width = 0;
334
341
  this.m_columns.forEach((col) => {
@@ -358,9 +365,13 @@ class GridView extends layout_1.VLayout {
358
365
  width: col.width
359
366
  }
360
367
  });
368
+ col.$ftr = comp;
361
369
  return comp;
362
370
  });
363
- foots.push(new component_1.Flex({}));
371
+ foots.push(new component_1.Flex({
372
+ ref: 'flex',
373
+ cls: flex ? '@hidden' : ''
374
+ }));
364
375
  this.m_footer = new layout_1.HLayout({
365
376
  cls: '@footer',
366
377
  content: foots,
@@ -380,8 +391,33 @@ class GridView extends layout_1.VLayout {
380
391
  ]);
381
392
  }
382
393
  _on_col_resize(col, width) {
383
- this.m_columns[col].width = width;
384
- 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
+ }
385
421
  this._updateScroll(true);
386
422
  }
387
423
  /**
@@ -393,19 +429,19 @@ class GridView extends layout_1.VLayout {
393
429
  }
394
430
  this.m_columns.forEach((c) => {
395
431
  if (c !== col) {
396
- c.$col.sorted = false;
432
+ c.$hdr.sorted = false;
397
433
  }
398
434
  });
399
- const $col = col.$col;
400
- if ($col.sorted) {
401
- $col.toggleSens();
435
+ const $hdr = col.$hdr;
436
+ if ($hdr.sorted) {
437
+ $hdr.toggleSens();
402
438
  }
403
439
  else {
404
- $col.sorted = true;
440
+ $hdr.sorted = true;
405
441
  }
406
442
  if (this.m_dataview) {
407
443
  this.m_dataview.sort([
408
- { field: col.id, ascending: $col.sens == 'dn' ? false : true }
444
+ { field: col.id, ascending: $hdr.sens == 'dn' ? false : true }
409
445
  ]);
410
446
  }
411
447
  }
@@ -457,7 +493,7 @@ class GridView extends layout_1.VLayout {
457
493
  let cidx = 0;
458
494
  let index = this.m_topIndex;
459
495
  let count = this.m_dataview ? this.m_dataview.count : 0;
460
- let full_width = 0;
496
+ let full_width = 0; // todo: +4 pixel of left border
461
497
  let even = this.m_topIndex & 1 ? true : false;
462
498
  // compute full width
463
499
  this.m_columns.forEach((col) => {
@@ -581,16 +617,16 @@ class GridView extends layout_1.VLayout {
581
617
  }
582
618
  this.m_empty_msg.show(show);
583
619
  if (full_width < rc.width) {
584
- //this.m_header.setStyleValue('width', null);
585
- //this.m_footer?.setStyleValue('width', null);
620
+ this.m_header.setStyleValue('width', null);
621
+ this.m_footer?.setStyleValue('width', null);
586
622
  this.m_container.setStyle({
587
623
  height: count * this.m_itemHeight,
588
624
  width: null
589
625
  });
590
626
  }
591
627
  else {
592
- this.m_header.setStyleValue('width', full_width + 5);
593
- this.m_footer?.setStyleValue('width', full_width + 5);
628
+ this.m_header.setStyleValue('width', full_width + 1000);
629
+ this.m_footer?.setStyleValue('width', full_width + 1000);
594
630
  this.m_container.setStyle({
595
631
  height: count * this.m_itemHeight,
596
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): boolean;
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,52 @@ 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
+ return true;
72
+ }
73
+ else {
74
+ let page = this.m_pages.find(x => x.id === id);
75
+ if (page) {
76
+ this._select(page, notify);
77
+ return true;
78
+ }
79
+ return false;
70
80
  }
71
81
  }
72
- _select(p) {
73
- if (this.dom && this.m_curPage && this.m_curPage.page) {
82
+ _select(p, notify) {
83
+ if (this.m_curPage == p) {
84
+ return;
85
+ }
86
+ if (!this.dom) {
87
+ this.m_props.default = p.id;
88
+ return;
89
+ }
90
+ if (this.m_curPage) {
74
91
  this.m_curPage.btn.removeClass('selected');
75
- this.m_curPage.page.hide();
92
+ if (this.m_curPage.page) {
93
+ this.m_curPage.page.hide();
94
+ }
76
95
  }
77
96
  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) {
97
+ if (notify) {
98
+ this.signal('change', (0, x4_events_1.EvChange)(p ? p.id : null));
99
+ }
100
+ if (this.m_curPage) {
80
101
  this.m_curPage.btn.addClass('selected');
81
- this.m_curPage.page.show();
102
+ if (this.m_curPage.page) {
103
+ this.m_curPage.page.show();
104
+ }
82
105
  }
83
106
  }
107
+ get selection() {
108
+ return this.m_curPage?.page;
109
+ }
84
110
  }
85
111
  exports.TabBar = TabBar;
package/lib/x4.css CHANGED
@@ -306,6 +306,8 @@ textarea::selection {
306
306
  align-items: center;
307
307
  outline: none;
308
308
  cursor: pointer;
309
+ font-family: var(--x4-font);
310
+ font-size: var(--x4-font-size);
309
311
  height: 2rem;
310
312
  padding: 8px;
311
313
  overflow: hidden;
@@ -1124,7 +1126,7 @@ textarea::selection {
1124
1126
  .x-spreadsheet .x-row,
1125
1127
  .x-grid-view .x-row {
1126
1128
  position: absolute;
1127
- width: 100%;
1129
+ width: calc(100% - 4px);
1128
1130
  border-bottom: 1px solid #f0f0f0;
1129
1131
  align-items: center;
1130
1132
  height: 2em;
@@ -1691,3 +1693,21 @@ textarea::selection {
1691
1693
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
1692
1694
  grid-auto-rows: 10px;
1693
1695
  }
1696
+ .x-tab-bar {
1697
+ border-bottom: 1px solid var(--gray-600);
1698
+ background-color: var(--gray-100);
1699
+ }
1700
+ .x-tab-bar > .x-button {
1701
+ border: none;
1702
+ color: var(--gray-600);
1703
+ }
1704
+ .x-tab-bar > .x-button.selected {
1705
+ font-weight: bold;
1706
+ border-bottom: none;
1707
+ color: var(--x4-selection-color);
1708
+ background-color: transparent;
1709
+ }
1710
+ .x-tab-bar > .x-button:focus:not(.selected) {
1711
+ text-decoration: underline;
1712
+ color: black;
1713
+ }
@@ -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.13",
3
+ "version": "1.4.16",
4
4
  "description": "X4js core files",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -194,6 +194,42 @@ export class Application<P extends ApplicationProps = ApplicationProps, E extend
194
194
 
195
195
  public enterModal( enter: boolean ) {
196
196
  }
197
+
198
+ public handleTouchEvents( ) {
199
+ document.addEventListener( 'touchstart', ( ev: TouchEvent ) => {
200
+
201
+ let me = this as any;
202
+ let now = new Date( ).getTime();
203
+
204
+ if( !me.__last_touch || (me.__last_touch-now) > 700 ) {
205
+ me.__touch_cnt = 1;
206
+ }
207
+ else {
208
+ me.__touch_cnt++;
209
+ }
210
+
211
+ me.__last_touch = now;
212
+
213
+ if( me.__touch_cnt==2 ) {
214
+ me.__touch_cnt = 0;
215
+
216
+ let fake = {
217
+ type: "dblclick",
218
+ };
219
+
220
+ const tch = ev.touches[0];
221
+ for( const n in tch ) {
222
+ fake[n] = tch[n];
223
+ }
224
+
225
+ // ts-ignore -> private
226
+ (Component as any)._dispatchEvent( fake );
227
+
228
+ ev.preventDefault( );
229
+ ev.stopPropagation( );
230
+ }
231
+ });
232
+ }
197
233
  };
198
234
 
199
235
 
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
@@ -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,12 +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
 
470
- (cols as any).push( new Flex( {} ) );
471
-
476
+ (cols as any).push( new Flex( {
477
+ ref: 'flex',
478
+ cls: flex ? '@hidden' : ''
479
+ } ) );
480
+
472
481
  // compute full width
473
482
  let full_width = 0;
474
483
  this.m_columns.forEach((col) => {
@@ -481,8 +490,7 @@ export class GridView extends VLayout<GridViewProps, GridViewEventMap> {
481
490
  style: {
482
491
  minWidth: full_width
483
492
  }
484
- });
485
-
493
+ });
486
494
 
487
495
  if( this.m_props.hasFooter ) {
488
496
  let foots = this.m_columns.map((col, index) => {
@@ -506,11 +514,15 @@ export class GridView extends VLayout<GridViewProps, GridViewEventMap> {
506
514
  }
507
515
  });
508
516
 
517
+ (col as GridColumnInternal).$ftr = comp;
509
518
  return comp;
510
519
  });
511
520
 
512
- (foots as any).push( new Flex( {} ) );
513
-
521
+ (foots as any).push( new Flex( {
522
+ ref: 'flex',
523
+ cls: flex ? '@hidden' : ''
524
+ } ) );
525
+
514
526
  this.m_footer = new HLayout({
515
527
  cls: '@footer',
516
528
  content: <any>foots,
@@ -532,9 +544,40 @@ export class GridView extends VLayout<GridViewProps, GridViewEventMap> {
532
544
 
533
545
  }
534
546
 
535
- private _on_col_resize(col, width) {
536
- this.m_columns[col].width = width;
537
- 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
+
538
581
  this._updateScroll(true);
539
582
  }
540
583
 
@@ -550,22 +593,22 @@ export class GridView extends VLayout<GridViewProps, GridViewEventMap> {
550
593
 
551
594
  this.m_columns.forEach((c) => {
552
595
  if (c !== col) {
553
- (c as GridColumnInternal).$col.sorted = false;
596
+ (c as GridColumnInternal).$hdr.sorted = false;
554
597
  }
555
598
  });
556
599
 
557
- const $col = (col as GridColumnInternal).$col;
600
+ const $hdr = (col as GridColumnInternal).$hdr;
558
601
 
559
- if ($col.sorted) {
560
- $col.toggleSens( );
602
+ if ($hdr.sorted) {
603
+ $hdr.toggleSens( );
561
604
  }
562
605
  else {
563
- $col.sorted = true;
606
+ $hdr.sorted = true;
564
607
  }
565
608
 
566
609
  if (this.m_dataview) {
567
610
  this.m_dataview.sort([
568
- { field: col.id, ascending: $col.sens=='dn' ? false : true }
611
+ { field: col.id, ascending: $hdr.sens=='dn' ? false : true }
569
612
  ]);
570
613
  }
571
614
  }
@@ -630,7 +673,8 @@ export class GridView extends VLayout<GridViewProps, GridViewEventMap> {
630
673
  let cidx = 0;
631
674
  let index = this.m_topIndex;
632
675
  let count = this.m_dataview ? this.m_dataview.count : 0;
633
- let full_width = 0;
676
+
677
+ let full_width = 0; // todo: +4 pixel of left border
634
678
  let even = this.m_topIndex & 1 ? true : false;
635
679
 
636
680
  // compute full width
@@ -783,17 +827,16 @@ export class GridView extends VLayout<GridViewProps, GridViewEventMap> {
783
827
  this.m_empty_msg.show(show);
784
828
 
785
829
  if (full_width < rc.width) {
786
- //this.m_header.setStyleValue('width', null);
787
- //this.m_footer?.setStyleValue('width', null);
830
+ this.m_header.setStyleValue('width', null);
831
+ this.m_footer?.setStyleValue('width', null);
788
832
  this.m_container.setStyle({
789
833
  height: count * this.m_itemHeight,
790
834
  width: null
791
835
  });
792
836
  }
793
837
  else {
794
- this.m_header.setStyleValue('width', full_width + 5 );
795
- this.m_footer?.setStyleValue('width', full_width + 5 );
796
-
838
+ this.m_header.setStyleValue('width', full_width + 1000 );
839
+ this.m_footer?.setStyleValue('width', full_width + 1000 );
797
840
  this.m_container.setStyle({
798
841
  height: count * this.m_itemHeight,
799
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,61 @@ 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 ): boolean {
102
+ if( !id ) {
103
+ this._select( null, notify );
104
+ return true;
105
+ }
106
+ else {
107
+ let page = this.m_pages.find( x => x.id===id );
108
+ if( page ) {
109
+ this._select( page, notify );
110
+ return true;
111
+ }
112
+ return false;
102
113
  }
103
114
  }
104
115
 
105
- private _select( p: TabPage ) {
116
+ private _select( p: TabPage, notify: boolean ) {
117
+
118
+ if( this.m_curPage==p ) {
119
+ return;
120
+ }
106
121
 
107
- if( this.dom && this.m_curPage && this.m_curPage.page ) {
122
+ if( !this.dom ) {
123
+ this.m_props.default = p.id;
124
+ return;
125
+ }
126
+
127
+ if( this.m_curPage ) {
108
128
  this.m_curPage.btn.removeClass( 'selected' );
109
- this.m_curPage.page.hide( );
129
+ if( this.m_curPage.page ) {
130
+ this.m_curPage.page.hide( );
131
+ }
110
132
  }
111
133
 
112
134
  this.m_curPage = p;
113
- this.signal( 'change', EvChange(p ? p.id : null) );
114
135
 
115
- if( this.dom && this.m_curPage && this.m_curPage.page ) {
136
+ if( notify ) {
137
+ this.signal( 'change', EvChange(p ? p.id : null) );
138
+ }
139
+
140
+ if( this.m_curPage ) {
116
141
  this.m_curPage.btn.addClass( 'selected' );
117
- this.m_curPage.page.show( );
142
+ if( this.m_curPage.page ) {
143
+ this.m_curPage.page.show( );
144
+ }
118
145
  }
119
146
  }
147
+
148
+ get selection( ) {
149
+ return this.m_curPage?.page;
150
+ }
120
151
  }
package/src/x4.less CHANGED
@@ -386,6 +386,8 @@ textarea {
386
386
  align-items: center;
387
387
  outline: none;
388
388
  cursor: pointer;
389
+ font-family: var( --x4-font );
390
+ font-size: var( --x4-font-size );
389
391
 
390
392
  height: 2rem;
391
393
  padding: 8px;
@@ -1312,12 +1314,13 @@ textarea {
1312
1314
  }
1313
1315
  }
1314
1316
 
1317
+ @bwidth: 4px;
1315
1318
 
1316
1319
  .x-spreadsheet,
1317
1320
  .x-grid-view {
1318
1321
 
1319
1322
  @def-height: 2em;
1320
-
1323
+
1321
1324
  min-height: 0;
1322
1325
  overflow: hidden;
1323
1326
  background-color: white;
@@ -1370,7 +1373,7 @@ textarea {
1370
1373
 
1371
1374
  .x-row {
1372
1375
  position: absolute;
1373
- width: 100%;
1376
+ width: calc( 100% - @bwidth ); // todo: border of 1st col
1374
1377
  border-bottom: 1px solid #f0f0f0;
1375
1378
  align-items: center;
1376
1379
  height: @def-height;
@@ -1400,11 +1403,11 @@ textarea {
1400
1403
  .x-grid-view {
1401
1404
  .x-footer,
1402
1405
  .x-header {
1403
- border-left: 4px solid #f0f0f0;
1406
+ border-left: @bwidth solid #f0f0f0;
1404
1407
  }
1405
1408
 
1406
1409
  .x-row {
1407
- border-left: 4px solid transparent;
1410
+ border-left: @bwidth solid transparent;
1408
1411
 
1409
1412
  &:hover {
1410
1413
  background-color: rgba(0,0,0,0.1);
@@ -2133,4 +2136,27 @@ textarea {
2133
2136
  grid-gap: 10px;
2134
2137
  grid-template-columns: repeat(auto-fill, minmax(250px,1fr));
2135
2138
  grid-auto-rows: 10px;
2139
+ }
2140
+
2141
+ .x-tab-bar {
2142
+ border-bottom: 1px solid var(--gray-600);
2143
+ background-color: var(--gray-100);
2144
+
2145
+ &> .x-button {
2146
+ border: none;
2147
+ color: var( --gray-600 );
2148
+
2149
+ &.selected {
2150
+ font-weight: bold;
2151
+ //border: 1px solid var( --x4-selection-color );
2152
+ border-bottom: none;
2153
+ color: var( --x4-selection-color );
2154
+ background-color: transparent;
2155
+ }
2156
+
2157
+ &:focus:not(.selected) {
2158
+ text-decoration: underline;
2159
+ color: black;;
2160
+ }
2161
+ }
2136
2162
  }
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