x4js 2.2.45 → 2.2.47
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/package.json +2 -4
- package/src/components/button/button.ts +14 -11
- package/src/components/listbox/listbox.module.scss +16 -7
- package/src/components/listbox/listbox.ts +2 -2
- package/src/components/menu/menu.ts +12 -2
- package/src/components/notification/notification.module.scss +10 -3
- package/src/components/notification/notification.ts +26 -4
- package/src/components/popup/popup.ts +4 -1
- package/src/components/tag/tag.module.scss +15 -5
- package/src/components/textarea/textarea.module.scss +22 -18
- package/src/components/textarea/textarea.ts +10 -1
- package/src/components/treeview/treeview.ts +2 -4
- package/src/core/core_state.ts +7 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "x4js",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.47",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/x4.ts",
|
|
6
6
|
"module": "src/x4.ts",
|
|
@@ -33,9 +33,7 @@
|
|
|
33
33
|
"application"
|
|
34
34
|
],
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"typescript": "^5.8.3"
|
|
37
|
-
},
|
|
38
|
-
"dependencies": {
|
|
36
|
+
"typescript": "^5.8.3",
|
|
39
37
|
"typedoc": "^0.28.19",
|
|
40
38
|
"typedoc-plugin-markdown": "^4.12.0"
|
|
41
39
|
}
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
* that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
|
|
15
15
|
**/
|
|
16
16
|
|
|
17
|
+
import { Menu, MenuElement, MenuProps } from 'x4js';
|
|
17
18
|
import { Component, ComponentEvents, ComponentProps, EvClick } from "../../core/component"
|
|
18
19
|
import { EventCallback } from '../../core/core_events';
|
|
19
|
-
import { class_ns, UnsafeHtml } from '../../core/core_tools';
|
|
20
|
+
import { class_ns, isFunction, UnsafeHtml } from '../../core/core_tools';
|
|
20
21
|
|
|
21
22
|
import { Icon } from "../icon/icon"
|
|
22
23
|
|
|
@@ -68,6 +69,8 @@ export interface ButtonProps extends ComponentProps {
|
|
|
68
69
|
* cf. ButtonEvents
|
|
69
70
|
*/
|
|
70
71
|
click?: EventCallback<EvClick>;
|
|
72
|
+
|
|
73
|
+
menu?: MenuElement[] | (( ) => MenuElement[]);
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
/**
|
|
@@ -131,17 +134,17 @@ export class Button extends Component<ButtonProps,ButtonEvents> {
|
|
|
131
134
|
|
|
132
135
|
protected _on_click( ev: MouseEvent ) {
|
|
133
136
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
137
|
+
if (this.props.menu) {
|
|
138
|
+
let menu = new Menu({
|
|
139
|
+
items: isFunction(this.props.menu) ? this.props.menu() : this.props.menu
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
let rc = this.getBoundingRect();
|
|
143
|
+
menu.displayNear(rc, 'top-left', 'bottom-left' );
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
143
146
|
this.fire('click', {} );
|
|
144
|
-
|
|
147
|
+
}
|
|
145
148
|
|
|
146
149
|
ev.preventDefault();
|
|
147
150
|
ev.stopPropagation();
|
|
@@ -17,16 +17,17 @@
|
|
|
17
17
|
@use "../shared.scss";
|
|
18
18
|
|
|
19
19
|
:root {
|
|
20
|
-
--listbox-item-
|
|
20
|
+
--listbox-item-color: var( --text-primary );
|
|
21
|
+
--listbox-icon-color: var( --text-ternary );
|
|
22
|
+
--listbox-item-color-disabled: var( --disabled-color-dark );
|
|
23
|
+
|
|
21
24
|
--listbox-item-color-sel: var( --accent-color );
|
|
25
|
+
--listbox-item-background-sel: var( --accent-background );
|
|
22
26
|
--listbox-item-color-sel-disabled: var( --disabled-background );
|
|
23
27
|
|
|
24
|
-
--listbox-item-color: var( --text-primary )
|
|
25
|
-
--listbox-item-color-disabled: var( --disabled-color-dark );
|
|
28
|
+
--listbox-item-color-hover: var( --text-primary );;
|
|
26
29
|
--listbox-item-background-hover: var( --background-secondary );
|
|
27
30
|
|
|
28
|
-
--listbox-icon-color: var( --text-ternary );
|
|
29
|
-
|
|
30
31
|
--listbox-background: white;
|
|
31
32
|
--listbox-border: var( --border );
|
|
32
33
|
--listbox-border-focus: var( --accent-background-focus );
|
|
@@ -79,6 +80,7 @@
|
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
}
|
|
83
|
+
}
|
|
82
84
|
|
|
83
85
|
.x4item {
|
|
84
86
|
@extend %flex;
|
|
@@ -93,9 +95,10 @@
|
|
|
93
95
|
padding: 4px 0;
|
|
94
96
|
}
|
|
95
97
|
|
|
98
|
+
--label-color: var( --listbox-item-color );
|
|
99
|
+
|
|
96
100
|
.x4label,
|
|
97
101
|
.x4simpletext {
|
|
98
|
-
color: inherit;
|
|
99
102
|
padding: 2px 6px;
|
|
100
103
|
|
|
101
104
|
#icon {
|
|
@@ -106,12 +109,15 @@
|
|
|
106
109
|
|
|
107
110
|
&:hover {
|
|
108
111
|
background-color: var( --listbox-item-background-hover );
|
|
112
|
+
--label-color: var( --listbox-item-color-hover );
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
&.selected {
|
|
112
116
|
background-color: var( --listbox-item-background-sel );
|
|
113
117
|
color: var( --listbox-item-color-sel );
|
|
114
118
|
|
|
119
|
+
--label-color: var( --listbox-item-color-sel );
|
|
120
|
+
|
|
115
121
|
&> .x4label {
|
|
116
122
|
#icon {
|
|
117
123
|
color: var( --listbox-item-color-sel );
|
|
@@ -133,7 +139,10 @@
|
|
|
133
139
|
//color: var(--color-0);
|
|
134
140
|
//}
|
|
135
141
|
|
|
136
|
-
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
&.empty .body .x4viewport {
|
|
145
|
+
height: 100%;
|
|
137
146
|
}
|
|
138
147
|
|
|
139
148
|
&> #footer {
|
|
@@ -125,9 +125,7 @@ export class Listbox extends Component<ListboxProps,ListboxEvents> {
|
|
|
125
125
|
contextmenu: (e) => this._on_ctx_menu(e),
|
|
126
126
|
} );
|
|
127
127
|
|
|
128
|
-
asap( ( ) => {
|
|
129
128
|
this.setItems( props.items, false );
|
|
130
|
-
} );
|
|
131
129
|
}
|
|
132
130
|
|
|
133
131
|
/**
|
|
@@ -446,6 +444,7 @@ export class Listbox extends Component<ListboxProps,ListboxEvents> {
|
|
|
446
444
|
let update_sel = false;
|
|
447
445
|
|
|
448
446
|
if( this._items.length ) {
|
|
447
|
+
this.removeClass( "empty" );
|
|
449
448
|
const content = items.map( x => this.renderItem(x) );
|
|
450
449
|
this._view.setContent( content );
|
|
451
450
|
|
|
@@ -454,6 +453,7 @@ export class Listbox extends Component<ListboxProps,ListboxEvents> {
|
|
|
454
453
|
}
|
|
455
454
|
}
|
|
456
455
|
else {
|
|
456
|
+
this.addClass( "empty" );
|
|
457
457
|
update_sel = oldSel.length>0;
|
|
458
458
|
this._view.setContent( new Label( { cls: "empty vertical", icon: icons.empty, text: this.props.emptyMsg ?? _tr.global.empty_list } ) );
|
|
459
459
|
}
|
|
@@ -16,11 +16,12 @@
|
|
|
16
16
|
|
|
17
17
|
import { Component } from "../../core/component"
|
|
18
18
|
import { DOMEventHandler } from '../../core/core_dom';
|
|
19
|
-
import { Timer, UnsafeHtml, class_ns, isString } from '../../core/core_tools';
|
|
19
|
+
import { Timer, UnsafeHtml, class_ns, isFunction, isString } from '../../core/core_tools';
|
|
20
20
|
|
|
21
21
|
import { Popup, PopupProps } from '../popup/popup';
|
|
22
22
|
import { Icon } from '../icon/icon';
|
|
23
23
|
|
|
24
|
+
import icons from "../assets/icons";
|
|
24
25
|
import "./menu.module.scss"
|
|
25
26
|
|
|
26
27
|
const OPEN_DELAY = 400;
|
|
@@ -35,6 +36,7 @@ export interface MenuItem {
|
|
|
35
36
|
text: string | UnsafeHtml;
|
|
36
37
|
menu?: Menu;
|
|
37
38
|
disabled?: boolean;
|
|
39
|
+
checked?: boolean | ( () => boolean);
|
|
38
40
|
click?: DOMEventHandler;
|
|
39
41
|
}
|
|
40
42
|
|
|
@@ -74,8 +76,16 @@ class CMenuItem extends Component {
|
|
|
74
76
|
this.addClass( "popup" );
|
|
75
77
|
}
|
|
76
78
|
|
|
79
|
+
let iconId = itm.icon;
|
|
80
|
+
if( !iconId && itm.checked ) {
|
|
81
|
+
const chk = isFunction(itm.checked) ? itm.checked() : itm.checked;
|
|
82
|
+
if( chk ) {
|
|
83
|
+
iconId = icons.check;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
77
87
|
this.setContent( [
|
|
78
|
-
new Icon({ id:"icon",iconId
|
|
88
|
+
new Icon({ id:"icon", iconId }),
|
|
79
89
|
new Component( { id: "text", content: itm.text } )
|
|
80
90
|
]);
|
|
81
91
|
|
|
@@ -28,10 +28,17 @@
|
|
|
28
28
|
.x4notification {
|
|
29
29
|
@extend %shadow-xl;
|
|
30
30
|
|
|
31
|
-
padding: 8px;
|
|
31
|
+
padding: 4px 8px;
|
|
32
32
|
border: 1px solid var(--notification-border);
|
|
33
33
|
border-left: 3px solid var( --accent-background );
|
|
34
34
|
background-color: var( --notification-background );
|
|
35
|
+
min-width: 250px;
|
|
36
|
+
max-width: 30vw;
|
|
37
|
+
position: absolute;
|
|
38
|
+
|
|
39
|
+
&.smooth {
|
|
40
|
+
transition: top .4s ease;
|
|
41
|
+
}
|
|
35
42
|
|
|
36
43
|
&> .x4hbox {
|
|
37
44
|
gap: 8px;
|
|
@@ -77,8 +84,8 @@
|
|
|
77
84
|
color: var( --color-gray-7 );
|
|
78
85
|
border: none;
|
|
79
86
|
|
|
80
|
-
|
|
87
|
+
//&:hover {
|
|
81
88
|
//background-color: var( -- );;
|
|
82
|
-
}
|
|
89
|
+
//}
|
|
83
90
|
}
|
|
84
91
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
**/
|
|
16
16
|
|
|
17
17
|
import { ComponentProps } from '../../core/component';
|
|
18
|
-
import { class_ns, Rect, UnsafeHtml } from '../../core/core_tools';
|
|
18
|
+
import { asap, class_ns, Rect, UnsafeHtml } from '../../core/core_tools';
|
|
19
19
|
|
|
20
20
|
import { Popup } from '../popup/popup';
|
|
21
21
|
import { HBox, VBox } from '../boxes/boxes';
|
|
@@ -47,6 +47,8 @@ interface NotificationProps extends ComponentProps {
|
|
|
47
47
|
|
|
48
48
|
@class_ns( "x4" )
|
|
49
49
|
export class Notification extends Popup {
|
|
50
|
+
private static list = new Set<Notification>( );
|
|
51
|
+
|
|
50
52
|
constructor( props: NotificationProps ) {
|
|
51
53
|
super( { } );
|
|
52
54
|
|
|
@@ -85,19 +87,39 @@ export class Notification extends Popup {
|
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
close( ) {
|
|
90
|
+
Notification.list.delete( this );
|
|
91
|
+
asap( ( ) => Notification.updatePositions( ) )
|
|
92
|
+
|
|
88
93
|
this.clearTimeout( "close" );
|
|
89
94
|
super.close( );
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
display( time_in_s = 0 ) {
|
|
93
|
-
|
|
94
|
-
this.displayNear( r, "bottom right", "bottom right", { x: -20, y: -10 } );
|
|
95
|
-
|
|
98
|
+
Notification.list.add( this );
|
|
96
99
|
if( time_in_s ) {
|
|
97
100
|
this.setTimeout( "close", time_in_s*1000, ( ) => {
|
|
98
101
|
this.close()
|
|
99
102
|
} );
|
|
100
103
|
}
|
|
104
|
+
|
|
105
|
+
//this.setStyleValue( "top", "9999px" );
|
|
106
|
+
|
|
107
|
+
asap( ( ) => Notification.updatePositions( ) )
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static updatePositions( ) {
|
|
111
|
+
const r = new Rect( 0, 0, window.innerWidth, window.innerHeight );
|
|
112
|
+
|
|
113
|
+
for( const n of this.list ) {
|
|
114
|
+
n.addClass( "smooth" );
|
|
115
|
+
n.displayNear( r, "bottom right", "bottom right", { x: -20, y: -10 }, true );
|
|
116
|
+
const nr = n.getBoundingRect( );
|
|
117
|
+
r.height -= nr.height+10;
|
|
118
|
+
if( r.height<10 ) {
|
|
119
|
+
r.height = 10;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
101
123
|
}
|
|
102
124
|
}
|
|
103
125
|
|
|
@@ -79,9 +79,12 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
|
|
|
79
79
|
*
|
|
80
80
|
*/
|
|
81
81
|
|
|
82
|
-
displayNear( rc: Rect, dst = "top left", src = "top left", offset = {x:0,y:0} ) {
|
|
82
|
+
displayNear( rc: Rect, dst = "top left", src = "top left", offset = {x:0,y:0}, keep_pos = false ) {
|
|
83
83
|
|
|
84
|
+
if( !keep_pos ) {
|
|
84
85
|
this.setStyle( { left: "0px", top: "0px" } ); // avoid scrollbar
|
|
86
|
+
}
|
|
87
|
+
|
|
85
88
|
this._do_show( ); // to compute size
|
|
86
89
|
|
|
87
90
|
let rm = this.getBoundingRect();
|
|
@@ -4,27 +4,37 @@
|
|
|
4
4
|
* @copyright (c) 2026 R-libre ingenierie, all rights reserved.
|
|
5
5
|
**/
|
|
6
6
|
|
|
7
|
+
:root {
|
|
8
|
+
--tag-border: var( --border );
|
|
9
|
+
--tag-border-focus: var( --border-focus );
|
|
10
|
+
--tag-selection: var( --accent-background );
|
|
11
|
+
--tag-color: black;
|
|
12
|
+
--tag-icon-color: var( --tag-color );
|
|
13
|
+
}
|
|
14
|
+
|
|
7
15
|
.x4tag {
|
|
8
|
-
color: black;
|
|
9
16
|
border-radius: 16px;
|
|
10
|
-
border: 1px solid var( --border );
|
|
17
|
+
border: 1px solid var( --tag-border );
|
|
11
18
|
padding: 2px 8px;
|
|
12
19
|
gap: 4px;
|
|
13
20
|
font-weight: normal;
|
|
21
|
+
--label-color: var( --tag-color );
|
|
14
22
|
|
|
15
23
|
.x4icon:empty {
|
|
16
24
|
display: none;
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
.x4icon {
|
|
28
|
+
height: 1em;
|
|
29
|
+
color: var( --tag-icon-color );
|
|
20
30
|
}
|
|
21
31
|
|
|
22
|
-
.x4simpletext {
|
|
23
|
-
|
|
32
|
+
&> .x4simpletext {
|
|
33
|
+
padding: 0;
|
|
24
34
|
}
|
|
25
35
|
|
|
26
36
|
&.alert {
|
|
27
|
-
color: var( --alert-color );
|
|
37
|
+
--tag-color: var( --alert-color );
|
|
28
38
|
background-color: var( --alert-background );
|
|
29
39
|
}
|
|
30
40
|
}
|
|
@@ -22,24 +22,7 @@
|
|
|
22
22
|
--textarea-selection: var( --accent-background );
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
.
|
|
26
|
-
margin: 5px;
|
|
27
|
-
|
|
28
|
-
.x4label{
|
|
29
|
-
gap: 0;
|
|
30
|
-
padding: 4px 0;
|
|
31
|
-
|
|
32
|
-
&::after {
|
|
33
|
-
content: ":"
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.x4label:has(.empty) {
|
|
38
|
-
display: none;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
textarea {
|
|
42
|
-
@extend %flex;
|
|
25
|
+
.x4simpletextarea {
|
|
43
26
|
|
|
44
27
|
padding: 4px;
|
|
45
28
|
outline: none;
|
|
@@ -60,4 +43,25 @@
|
|
|
60
43
|
resize: none;
|
|
61
44
|
}
|
|
62
45
|
}
|
|
46
|
+
|
|
47
|
+
.x4textarea {
|
|
48
|
+
margin: 5px;
|
|
49
|
+
|
|
50
|
+
.x4label{
|
|
51
|
+
gap: 0;
|
|
52
|
+
padding: 4px 0;
|
|
53
|
+
|
|
54
|
+
&::after {
|
|
55
|
+
content: ":"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.x4label:has(.empty) {
|
|
60
|
+
display: none;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.x4simpletextarea {
|
|
64
|
+
@extend %flex;
|
|
65
|
+
|
|
66
|
+
}
|
|
63
67
|
}
|
|
@@ -42,7 +42,8 @@ interface TextAreaProps extends BaseProps {
|
|
|
42
42
|
*
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
@class_ns( "x4" )
|
|
46
|
+
export class SimpleTextArea extends Component<TextAreaProps> {
|
|
46
47
|
|
|
47
48
|
constructor( props: TextAreaProps ) {
|
|
48
49
|
super( { ...props, tag: "textarea" } );
|
|
@@ -72,6 +73,10 @@ class SimpleTextArea extends Component<TextAreaProps> {
|
|
|
72
73
|
return text;
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
scrollToBottom( ) {
|
|
77
|
+
this.dom.scrollTop = this.dom.scrollHeight;
|
|
78
|
+
}
|
|
79
|
+
|
|
75
80
|
queryInterface<T>(name: string): T {
|
|
76
81
|
if( name=="form-element" ) {
|
|
77
82
|
const i: IFormElement = {
|
|
@@ -115,6 +120,10 @@ export class TextArea extends VBox {
|
|
|
115
120
|
return this._input.getText( );
|
|
116
121
|
}
|
|
117
122
|
|
|
123
|
+
scrollToBottom( ) {
|
|
124
|
+
this._input.scrollToBottom( );
|
|
125
|
+
}
|
|
126
|
+
|
|
118
127
|
queryInterface<T>(name: string): T {
|
|
119
128
|
if( name=="form-element" ) {
|
|
120
129
|
const i: IFormElement = {
|
|
@@ -206,9 +206,7 @@ export class Treeview extends Component<TreeviewProps,TreeviewEvents> {
|
|
|
206
206
|
keydown: ( ev ) => this._onkey( ev ),
|
|
207
207
|
});
|
|
208
208
|
|
|
209
|
-
asap( ( ) => {
|
|
210
209
|
this.setItems( props.items );
|
|
211
|
-
} );
|
|
212
210
|
}
|
|
213
211
|
|
|
214
212
|
/**
|
|
@@ -478,8 +476,8 @@ export class Treeview extends Component<TreeviewProps,TreeviewEvents> {
|
|
|
478
476
|
this._selitem = undefined;
|
|
479
477
|
}
|
|
480
478
|
|
|
481
|
-
|
|
482
|
-
|
|
479
|
+
this._selection = undefined;
|
|
480
|
+
this.fire( "selectionChange", { selection: [], empty: true } );
|
|
483
481
|
}
|
|
484
482
|
|
|
485
483
|
/**
|
package/src/core/core_state.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
**/
|
|
6
6
|
|
|
7
7
|
import { CoreEvent, EventMap, EventSource } from './core_events.js';
|
|
8
|
-
import { isPlainObject } from './core_tools.js';
|
|
8
|
+
import { getMemberValue, isPlainObject } from './core_tools.js';
|
|
9
9
|
|
|
10
10
|
type StateData = boolean | number | string | Date | unknown;
|
|
11
11
|
type State = Record<string, StateData>;
|
|
@@ -210,6 +210,11 @@ export class StateManager<T extends State> extends EventSource<StateEvents> {
|
|
|
210
210
|
if( _path_matches( e.path, path ) ) {
|
|
211
211
|
cb( e );
|
|
212
212
|
}
|
|
213
|
+
else if( _path_matches( path, e.path ) ) {
|
|
214
|
+
const ee = { ...e };
|
|
215
|
+
ee.value = getMemberValue(this._proxy,path );
|
|
216
|
+
cb( ee );
|
|
217
|
+
}
|
|
213
218
|
});
|
|
214
219
|
}
|
|
215
220
|
}
|
|
@@ -224,7 +229,7 @@ export class StateManager<T extends State> extends EventSource<StateEvents> {
|
|
|
224
229
|
* state.items.push( 4 ); // "items.3 = 4"
|
|
225
230
|
*/
|
|
226
231
|
|
|
227
|
-
export function makeState<T extends
|
|
232
|
+
export function makeState<T extends Record<keyof T, StateData>>( initialState: T ): StateProxy<T> {
|
|
228
233
|
return new StateManager( initialState ).proxify( );
|
|
229
234
|
}
|
|
230
235
|
|