x4js 1.4.14 → 1.4.17
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.
- package/lib/application.d.ts +3 -0
- package/lib/application.js +30 -0
- package/lib/component.d.ts +0 -1
- package/lib/component.js +7 -2
- package/lib/layout.js +7 -0
- package/lib/listview.d.ts +2 -2
- package/lib/router.d.ts +10 -2
- package/lib/router.js +98 -18
- package/lib/tabbar.d.ts +3 -1
- package/lib/tabbar.js +38 -12
- package/lib/x4.css +20 -0
- package/lib/x4_events.d.ts +8 -0
- package/lib/x4_events.js +5 -1
- package/package.json +1 -1
- package/src/application.ts +38 -0
- package/src/component.ts +8 -2
- package/src/layout.ts +9 -0
- package/src/listview.ts +2 -2
- package/src/router.ts +134 -22
- package/src/tabbar.ts +44 -13
- package/src/x4.less +25 -0
- package/src/x4_events.ts +13 -0
package/lib/application.d.ts
CHANGED
|
@@ -75,6 +75,8 @@ export declare class Application<P extends ApplicationProps = ApplicationProps,
|
|
|
75
75
|
private m_app_uid;
|
|
76
76
|
private m_local_storage;
|
|
77
77
|
private m_user_data;
|
|
78
|
+
private m_touch_time;
|
|
79
|
+
private m_touch_count;
|
|
78
80
|
constructor(props: P);
|
|
79
81
|
ApplicationCreated(): void;
|
|
80
82
|
get app_name(): string;
|
|
@@ -96,5 +98,6 @@ export declare class Application<P extends ApplicationProps = ApplicationProps,
|
|
|
96
98
|
setTitle(title: string): void;
|
|
97
99
|
disableZoomWheel(): void;
|
|
98
100
|
enterModal(enter: boolean): void;
|
|
101
|
+
enableTouchDblClick(): void;
|
|
99
102
|
}
|
|
100
103
|
export {};
|
package/lib/application.js
CHANGED
|
@@ -30,8 +30,10 @@
|
|
|
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");
|
|
36
|
+
const _x4_touch_time = Symbol();
|
|
35
37
|
/**
|
|
36
38
|
* Represents an x4 application, which is typically a single page app.
|
|
37
39
|
* You should inherit Application to define yours.
|
|
@@ -65,6 +67,8 @@ class Application extends base_component_1.BaseComponent {
|
|
|
65
67
|
m_app_uid;
|
|
66
68
|
m_local_storage;
|
|
67
69
|
m_user_data;
|
|
70
|
+
m_touch_time;
|
|
71
|
+
m_touch_count;
|
|
68
72
|
constructor(props) {
|
|
69
73
|
console.assert(Application.self === null, 'application is a singleton');
|
|
70
74
|
super(props);
|
|
@@ -74,6 +78,8 @@ class Application extends base_component_1.BaseComponent {
|
|
|
74
78
|
let settings_name = `${this.m_app_name}.${this.m_app_version}.settings`;
|
|
75
79
|
this.m_local_storage = new settings_1.Settings(settings_name);
|
|
76
80
|
this.m_user_data = {};
|
|
81
|
+
this.m_touch_time = 0;
|
|
82
|
+
this.m_touch_count = 0;
|
|
77
83
|
Application.self = this;
|
|
78
84
|
if ('onload' in globalThis) {
|
|
79
85
|
globalThis.addEventListener('load', () => {
|
|
@@ -145,6 +151,30 @@ class Application extends base_component_1.BaseComponent {
|
|
|
145
151
|
}
|
|
146
152
|
enterModal(enter) {
|
|
147
153
|
}
|
|
154
|
+
enableTouchDblClick() {
|
|
155
|
+
document.addEventListener('touchstart', (ev) => {
|
|
156
|
+
let now = Date.now();
|
|
157
|
+
if ((now - this.m_touch_time) > 700) {
|
|
158
|
+
this.m_touch_count = 1;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
this.m_touch_count++;
|
|
162
|
+
}
|
|
163
|
+
this.m_touch_time = now;
|
|
164
|
+
if (this.m_touch_count == 2) {
|
|
165
|
+
this.m_touch_count = 0;
|
|
166
|
+
// dirty fake dblclick event
|
|
167
|
+
const tch = ev.touches[0];
|
|
168
|
+
let fake = { type: "dblclick" };
|
|
169
|
+
for (const n in tch) {
|
|
170
|
+
fake[n] = tch[n];
|
|
171
|
+
}
|
|
172
|
+
// ignore -> private: dirty x2
|
|
173
|
+
component_1.Component._dispatchEvent(fake);
|
|
174
|
+
ev.stopPropagation();
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
148
178
|
}
|
|
149
179
|
exports.Application = Application;
|
|
150
180
|
;
|
package/lib/component.d.ts
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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/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
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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):
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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.
|
|
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
|
|
92
|
+
if (this.m_curPage.page) {
|
|
93
|
+
this.m_curPage.page.hide();
|
|
94
|
+
}
|
|
76
95
|
}
|
|
77
96
|
this.m_curPage = p;
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
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;
|
|
@@ -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
|
+
}
|
package/lib/x4_events.d.ts
CHANGED
|
@@ -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
package/src/application.ts
CHANGED
|
@@ -34,6 +34,8 @@ import { Settings } from './settings'
|
|
|
34
34
|
import { deferCall } from './tools'
|
|
35
35
|
import { _tr } from './i18n'
|
|
36
36
|
|
|
37
|
+
const _x4_touch_time = Symbol( );
|
|
38
|
+
|
|
37
39
|
|
|
38
40
|
interface ApplicationEventMap extends BaseComponentEventMap {
|
|
39
41
|
message: EvMessage;
|
|
@@ -92,6 +94,9 @@ export class Application<P extends ApplicationProps = ApplicationProps, E extend
|
|
|
92
94
|
|
|
93
95
|
private m_local_storage: Settings;
|
|
94
96
|
private m_user_data: any;
|
|
97
|
+
|
|
98
|
+
private m_touch_time: number;
|
|
99
|
+
private m_touch_count: number;
|
|
95
100
|
|
|
96
101
|
constructor( props : P ) {
|
|
97
102
|
console.assert( Application.self===null, 'application is a singleton' );
|
|
@@ -104,6 +109,9 @@ export class Application<P extends ApplicationProps = ApplicationProps, E extend
|
|
|
104
109
|
let settings_name = `${this.m_app_name}.${this.m_app_version}.settings`;
|
|
105
110
|
this.m_local_storage = new Settings( settings_name );
|
|
106
111
|
this.m_user_data = {};
|
|
112
|
+
|
|
113
|
+
this.m_touch_time = 0;
|
|
114
|
+
this.m_touch_count = 0;
|
|
107
115
|
|
|
108
116
|
(Application.self as any) = this;
|
|
109
117
|
|
|
@@ -194,6 +202,36 @@ export class Application<P extends ApplicationProps = ApplicationProps, E extend
|
|
|
194
202
|
|
|
195
203
|
public enterModal( enter: boolean ) {
|
|
196
204
|
}
|
|
205
|
+
|
|
206
|
+
public enableTouchDblClick( ) {
|
|
207
|
+
document.addEventListener( 'touchstart', ( ev: TouchEvent ) => {
|
|
208
|
+
|
|
209
|
+
let now = Date.now( );
|
|
210
|
+
if( (now-this.m_touch_time) > 700 ) {
|
|
211
|
+
this.m_touch_count = 1;
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
this.m_touch_count++;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
this.m_touch_time = now;
|
|
218
|
+
|
|
219
|
+
if( this.m_touch_count==2 ) {
|
|
220
|
+
this.m_touch_count = 0;
|
|
221
|
+
|
|
222
|
+
// dirty fake dblclick event
|
|
223
|
+
const tch = ev.touches[0];
|
|
224
|
+
let fake: any = {type: "dblclick" };
|
|
225
|
+
for( const n in tch ) {
|
|
226
|
+
fake[n] = tch[n];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ignore -> private: dirty x2
|
|
230
|
+
(Component as any)._dispatchEvent( fake );
|
|
231
|
+
ev.stopPropagation( );
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
197
235
|
};
|
|
198
236
|
|
|
199
237
|
|
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
|
-
|
|
1397
|
+
//done in ctor now
|
|
1398
|
+
//this.addClass(this.m_props.cls);
|
|
1393
1399
|
}
|
|
1394
1400
|
|
|
1395
1401
|
/**
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
40
|
+
keys: string[],
|
|
41
|
+
pattern: RegExp;
|
|
42
|
+
handler: RouteHandler;
|
|
35
43
|
}
|
|
36
44
|
|
|
37
|
-
|
|
45
|
+
function parseRoute(str: string | RegExp, loose = false): Segment {
|
|
38
46
|
|
|
39
|
-
|
|
47
|
+
if (str instanceof RegExp) {
|
|
48
|
+
return {
|
|
49
|
+
keys: null,
|
|
50
|
+
pattern: str
|
|
51
|
+
};
|
|
52
|
+
}
|
|
40
53
|
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
63
|
+
for (const tmp of arr) {
|
|
64
|
+
const c = tmp[0];
|
|
46
65
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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.
|
|
120
|
+
this.navigate( window.location.pathname );
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
navigate( uri: string, notify = true ) {
|
|
124
|
+
|
|
125
|
+
const found = this._find( uri );
|
|
62
126
|
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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;
|
|
@@ -2134,4 +2136,27 @@ textarea {
|
|
|
2134
2136
|
grid-gap: 10px;
|
|
2135
2137
|
grid-template-columns: repeat(auto-fill, minmax(250px,1fr));
|
|
2136
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
|
+
}
|
|
2137
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
|