x4js 2.0.31 → 2.0.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/package.json +1 -2
  2. package/src/components/base.scss +66 -2
  3. package/src/components/boxes/boxes.module.scss +3 -3
  4. package/src/components/button/button.module.scss +1 -1
  5. package/src/components/calendar/calendar.module.scss +3 -3
  6. package/src/components/checkbox/checkbox.module.scss +1 -1
  7. package/src/components/combobox/combobox.module.scss +6 -6
  8. package/src/components/dialog/dialog.module.scss +3 -2
  9. package/src/components/form/form.module.scss +2 -2
  10. package/src/components/gridview/folder-open.svg +1 -0
  11. package/src/components/gridview/gridview.module.scss +14 -0
  12. package/src/components/gridview/gridview.ts +51 -12
  13. package/src/components/icon/icon.module.scss +1 -1
  14. package/src/components/label/label.module.scss +3 -2
  15. package/src/components/listbox/listbox.module.scss +2 -2
  16. package/src/components/menu/menu.module.scss +3 -3
  17. package/src/components/messages/messages.module.scss +37 -0
  18. package/src/components/messages/messages.ts +46 -3
  19. package/src/components/messages/spinner.svg +1 -0
  20. package/src/components/notification/notification.module.scss +1 -1
  21. package/src/components/panel/panel.module.scss +6 -1
  22. package/src/components/shared.scss +12 -82
  23. package/src/components/sizers/sizer.module.scss +18 -1
  24. package/src/components/sizers/sizer.ts +14 -11
  25. package/src/components/tabs/tabs.module.scss +21 -0
  26. package/src/components/tabs/tabs.ts +10 -5
  27. package/src/components/textarea/textarea.module.scss +1 -1
  28. package/src/components/textedit/textedit.module.scss +5 -5
  29. package/src/components/tooltips/tooltips.scss +1 -1
  30. package/src/components/treeview/treeview.module.scss +1 -1
  31. package/src/components/viewport/viewport.module.scss +1 -1
  32. package/src/core/core_application.ts +1 -1
  33. package/src/x4.scss +3 -3
  34. package/ai-comments.txt +0 -97
  35. package/lib/types/x4js.d.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x4js",
3
- "version": "2.0.31",
3
+ "version": "2.0.33",
4
4
  "type": "module",
5
5
  "main": "src/x4.ts",
6
6
  "module": "src/x4.ts",
@@ -12,7 +12,6 @@
12
12
  "build": "x4build --watch --hmr --serve",
13
13
  "build-release": "x4build --release"
14
14
  },
15
-
16
15
  "repository": {
17
16
  "type": "git",
18
17
  "url": "git+https://github.com/rlibre/x4.git"
@@ -17,9 +17,73 @@
17
17
  @use "./shared.scss";
18
18
 
19
19
  .x4box {
20
- @extend .box;
20
+ @extend %box;
21
21
  }
22
22
 
23
23
  .x4flex {
24
- @extend .flex;
24
+ @extend %flex;
25
25
  }
26
+
27
+ .fit {
28
+ @extend %fit;
29
+ }
30
+
31
+ @keyframes rotating {
32
+ from {
33
+ transform: rotate(0deg);
34
+ }
35
+ to {
36
+ transform: rotate(360deg);
37
+ }
38
+ }
39
+
40
+ @keyframes shaking {
41
+ 0% {
42
+ transform: rotate(-15deg)
43
+ }
44
+
45
+ 4% {
46
+ transform: rotate(15deg)
47
+ }
48
+
49
+ 8%,24% {
50
+ transform: rotate(-18deg)
51
+ }
52
+
53
+ 12%,28% {
54
+ transform: rotate(18deg)
55
+ }
56
+
57
+ 16% {
58
+ transform: rotate(-22deg)
59
+ }
60
+
61
+ 20% {
62
+ transform: rotate(22deg)
63
+ }
64
+
65
+ 32% {
66
+ transform: rotate(-12deg)
67
+ }
68
+
69
+ 36% {
70
+ transform: rotate(12deg)
71
+ }
72
+
73
+ 40% {
74
+ transform: rotate(0deg)
75
+ }
76
+ }
77
+
78
+
79
+ .x4rotate {
80
+ animation: rotating 2s linear infinite;
81
+ }
82
+
83
+ .x4shake {
84
+ animation-name: shaking;
85
+ animation-duration: 5s;
86
+ animation-iteration-count: infinite;
87
+ animation-timing-function: linear;
88
+ animation-direction: reverse;
89
+ }
@@ -17,21 +17,21 @@
17
17
  @use "../shared.scss";
18
18
 
19
19
  .x4hbox {
20
- @extend .hbox;
20
+ @extend %hbox;
21
21
  &.align-start {
22
22
  align-items: start;
23
23
  }
24
24
  }
25
25
 
26
26
  .x4vbox {
27
- @extend .vbox;
27
+ @extend %vbox;
28
28
  }
29
29
 
30
30
  .x4stackbox {
31
31
  display: flex;
32
32
 
33
33
  &>* {
34
- @extend .fit;
34
+ @extend %fit;
35
35
  position: relative !important;
36
36
  }
37
37
 
@@ -51,7 +51,7 @@
51
51
  }
52
52
 
53
53
  .x4button {
54
- @extend .hbox;
54
+ @extend %hbox;
55
55
 
56
56
  margin: 5px;
57
57
  gap: 4px;
@@ -81,12 +81,12 @@
81
81
  }
82
82
 
83
83
  .week {
84
- @extend .flex;
84
+ @extend %flex;
85
85
  align-items: center;
86
86
  padding: 2px;
87
87
 
88
88
  .cell {
89
- @extend .flex;
89
+ @extend %flex;
90
90
  min-width: 28px;
91
91
  min-height: 28px;
92
92
 
@@ -94,7 +94,7 @@
94
94
  text-align: center;
95
95
 
96
96
  .text {
97
- @extend .hbox;
97
+ @extend %hbox;
98
98
 
99
99
  transition: background-color 0.3s, color 0.3s;
100
100
  justify-content: center;
@@ -27,7 +27,7 @@
27
27
  }
28
28
 
29
29
  .x4checkbox {
30
- @extend .hbox;
30
+ @extend %hbox;
31
31
 
32
32
  display: flex;
33
33
  flex-direction: row;
@@ -31,8 +31,8 @@
31
31
  }
32
32
 
33
33
  .x4dropdownlist {
34
- @extend .shadow-xl;
35
- @extend .hbox;
34
+ @extend %shadow-xl;
35
+ @extend %hbox;
36
36
 
37
37
  max-height: 250px;
38
38
  max-width: 50vw;
@@ -42,7 +42,7 @@
42
42
  border: 1px solid var( --dropdown-border );
43
43
 
44
44
  .x4listbox {
45
- @extend .flex;
45
+ @extend %flex;
46
46
 
47
47
  border: none;
48
48
  margin: 0;
@@ -65,7 +65,7 @@
65
65
  }
66
66
 
67
67
  .x4combobox {
68
- @extend .hbox;
68
+ @extend %hbox;
69
69
  margin: 5px;
70
70
  gap: 6px;
71
71
 
@@ -88,14 +88,14 @@
88
88
  }
89
89
 
90
90
  &>#edit {
91
- @extend .flex;
91
+ @extend %flex;
92
92
  border-bottom: 1px solid var( --combobox-border );
93
93
  &:focus-within {
94
94
  border-bottom-color: var( --combobox-border-focus );
95
95
  }
96
96
 
97
97
  .x4input {
98
- @extend .flex;
98
+ @extend %flex;
99
99
 
100
100
  &[readonly] {
101
101
  cursor: pointer;
@@ -22,7 +22,8 @@
22
22
  }
23
23
 
24
24
  .x4dialog {
25
- @extend .shadow-xl, .vbox;
25
+ @extend %shadow-xl;
26
+ @extend %vbox;
26
27
 
27
28
  position: absolute !important;
28
29
  background-color: white;
@@ -33,7 +34,7 @@
33
34
  background-color: var(--color-dialog-caption);
34
35
 
35
36
  & > #title {
36
- @extend .flex;
37
+ @extend %flex;
37
38
  color: var(--color-dialog-caption-text);
38
39
  font-weight: bold;
39
40
  font-size: 120%;
@@ -21,8 +21,8 @@
21
21
  }
22
22
 
23
23
  .x4form {
24
- @extend .vbox;
25
- @extend .flex;
24
+ @extend %vbox;
25
+ @extend %flex;
26
26
 
27
27
  padding: 16px;
28
28
  background-color: var( --color-form );
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Pro 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2026 Fonticons, Inc.--><path opacity=".4" d="M32 96l0 200.6 21.7-54.3C65.9 211.9 95.3 192 128 192l320 0 0-32c0-17.7-14.3-32-32-32l-117.5 0c-25.5 0-49.9-10.1-67.9-28.1L204.1 73.4c-6-6-14.1-9.4-22.6-9.4L64 64C46.3 64 32 78.3 32 96zM49.1 426.1c-2 4.9-1.4 10.5 1.6 14.9s7.9 7 13.2 7l320 0 80 0c6.5 0 12.4-4 14.9-10.1l64-160c2-4.9 1.4-10.5-1.6-14.9s-7.9-7-13.2-7l-400 0c-6.5 0-12.4 4-14.9 10.1l-64 160z"/><path d="M448 160l0 32 32 0 0-32c0-35.3-28.7-64-64-64L298.5 96c-17 0-33.3-6.7-45.3-18.7L226.7 50.7c-12-12-28.3-18.7-45.3-18.7L64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l320 0 80 0c19.6 0 37.3-11.9 44.6-30.2l64-160c5.9-14.8 4.1-31.5-4.8-44.7S543.9 224 528 224l-400 0c-19.6 0-37.3 11.9-44.6 30.2L32 382.8 32 96c0-17.7 14.3-32 32-32l117.5 0c8.5 0 16.6 3.4 22.6 9.4l22.6-22.6L204.1 73.4l26.5 26.5c18 18 42.4 28.1 67.9 28.1L416 128c17.7 0 32 14.3 32 32zM384 448L64 448c-5.3 0-10.3-2.6-13.2-7s-3.6-10-1.6-14.9l64-160c2.4-6.1 8.3-10.1 14.9-10.1l400 0c5.3 0 10.3 2.6 13.2 7s3.6 10 1.6 14.9l-64 160C476.4 444 470.5 448 464 448l-80 0z"/></svg>
@@ -109,7 +109,21 @@
109
109
  bottom: var( --scrollbar-size );
110
110
  left: 0;
111
111
  top: 0;
112
+
113
+ .empty {
114
+ position: relative;
115
+ left: 50%;
116
+ top: 20%;
117
+ width: fit-content;
118
+
119
+ #icon {
120
+ height: 64px;
121
+ fill: var( --accent-background );
122
+ opacity: 50%;
123
+ }
124
+ }
112
125
  }
126
+
113
127
  .row {
114
128
  position: absolute;
115
129
  left: 0;
@@ -15,7 +15,7 @@
15
15
  **/
16
16
 
17
17
 
18
- import { Component, ComponentContent, ComponentEvents, ComponentProps, EvClick, EvContextMenu, EvDblClick, EvSelectionChange, componentFromDOM } from '../../core/component';
18
+ import { Component, ComponentContent, ComponentEvents, ComponentProps, EvClick, EvContextMenu, EvDblClick, EvSelectionChange } from '../../core/component';
19
19
  import { class_ns, isNumber, isString, setWaitCursor, UnsafeHtml } from '../../core/core_tools';
20
20
  import { DataModel, DataStore, DataView, DataRecord, EvViewChange } from '../../core/core_data';
21
21
  import { EventCallback } from '../../core/core_events';
@@ -26,9 +26,13 @@ import { Image } from '../image/image'
26
26
  import { Box } from '../boxes/boxes';
27
27
  import { CSizer } from '../sizers/sizer'
28
28
  import { Viewport } from '../viewport/viewport';
29
- import { SimpleText } from '../label/label';
29
+ import { Label, SimpleText } from '../label/label';
30
+
31
+ import { _tr } from '../../core/core_i18n';
30
32
 
31
33
  import check_icon from "../checkbox/check.svg";
34
+ import empty_icon from "./folder-open.svg";
35
+
32
36
  import "./gridview.module.scss"
33
37
 
34
38
  export type CellRenderer = (rec: DataRecord) => Component;
@@ -75,11 +79,16 @@ export interface GridviewProps extends ComponentProps {
75
79
  footer?: boolean;
76
80
  store: DataStore;
77
81
  columns: GridColumn[];
82
+ emptyMsg?: string;
78
83
 
79
84
  click?: EventCallback<EvClick>;
80
85
  dblClick?: EventCallback<EvDblClick>;
81
86
  contextMenu?: EventCallback<EvContextMenu>;
82
87
  selectionChange?: EventCallback<EvSelectionChange>;
88
+ sort?: {
89
+ id: string;
90
+ asc?: boolean;
91
+ }
83
92
  }
84
93
 
85
94
  /**
@@ -121,6 +130,7 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
121
130
  private _end: number;
122
131
 
123
132
  private _selection: Set<number>;
133
+
124
134
 
125
135
  // TODO: that
126
136
  private _num_fmt = new Intl.NumberFormat('fr-FR');
@@ -172,8 +182,11 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
172
182
 
173
183
  if (props.store) {
174
184
  this.setStore(props.store);
175
- }
176
185
 
186
+ if( props.sort ) {
187
+ this.sortCol( props.sort.id, props.sort.asc===true );
188
+ }
189
+ }
177
190
  }
178
191
 
179
192
  /**
@@ -240,7 +253,7 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
240
253
 
241
254
  if( sens==kbNav.first || sens==kbNav.last ) {
242
255
  let nel = sens==kbNav.first ? 0 : this._dataview.getCount()-1;
243
- this._clearSelection();
256
+ this._clearSelection( false );
244
257
  this._addSelection( nel );
245
258
  this._scrollToIndex( nel );
246
259
  return true;
@@ -249,7 +262,7 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
249
262
  const fsel = this._selection.values().next().value;
250
263
  let nel = sens==kbNav.next ? fsel+1 : fsel-1;
251
264
  if( nel>=0 && nel<this._dataview.getCount() ) {
252
- this._clearSelection();
265
+ this._clearSelection( false );
253
266
  this._addSelection( nel );
254
267
  this._scrollToIndex( nel );
255
268
  return true;
@@ -271,7 +284,7 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
271
284
  }
272
285
 
273
286
  if( nel!=fsel ) {
274
- this._clearSelection();
287
+ this._clearSelection( false );
275
288
  this._addSelection( nel );
276
289
 
277
290
  if (this._dataview.getCount() < SCROLL_LIMIT) {
@@ -785,6 +798,13 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
785
798
  rowel.addClass("selected");
786
799
  }
787
800
 
801
+ if( rowid&1 ) {
802
+ rowel.addClass( "even" );
803
+ }
804
+ else {
805
+ rowel.addClass( "odd" );
806
+ }
807
+
788
808
  return rowel;
789
809
  }
790
810
 
@@ -922,7 +942,7 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
922
942
  }
923
943
 
924
944
  this.setStyleVariable("--fixed-width", maxfw + "px");
925
- this._body.setStyleValue("height", maxh + "px");
945
+ this._body.setStyleValue("height", maxh==0 ? "100%" : maxh + "px");
926
946
  this._body.setStyleValue("width", maxw + "px");
927
947
  this._vheader.setStyleValue("height", maxh + "px");
928
948
  }
@@ -998,10 +1018,13 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
998
1018
  if (row!==undefined ) {
999
1019
  //TODO: multiselection
1000
1020
  if( !this._selection.has(row) ) {
1001
- this._clearSelection();
1021
+ this._clearSelection( false );
1002
1022
  this._addSelection(row);
1003
1023
  }
1004
1024
  }
1025
+ else {
1026
+ this._clearSelection( true );
1027
+ }
1005
1028
  });
1006
1029
 
1007
1030
  // DBLCLICK
@@ -1010,7 +1033,7 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
1010
1033
  if (row!==undefined ) {
1011
1034
  //TODO: multiselection
1012
1035
  if( !this._selection.has(row) ) {
1013
- this._clearSelection();
1036
+ this._clearSelection( false );
1014
1037
  this._addSelection(row);
1015
1038
  }
1016
1039
 
@@ -1027,7 +1050,7 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
1027
1050
  if (row!==undefined ) {
1028
1051
  //TODO: multiselection
1029
1052
  if( !this._selection.has(row) ) {
1030
- this._clearSelection();
1053
+ this._clearSelection( false );
1031
1054
  this._addSelection(row);
1032
1055
  }
1033
1056
 
@@ -1114,6 +1137,14 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
1114
1137
 
1115
1138
  // rows
1116
1139
  const rowc = this._dataview ? this._dataview.getCount() : 0;
1140
+
1141
+ // empty ?
1142
+ if( rowc==0 ) {
1143
+ this._body.setContent( new Label( { cls: "empty vertical", icon: empty_icon, text: this.props.emptyMsg ?? _tr.global.empty_list } ) );
1144
+ return;
1145
+ }
1146
+
1147
+
1117
1148
  const mul = rowc < SCROLL_LIMIT ? this._row_height : 1;
1118
1149
 
1119
1150
  const start = Math.floor(this._top / mul);
@@ -1190,10 +1221,14 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
1190
1221
  */
1191
1222
 
1192
1223
  clearSelection( ) {
1193
- this._clearSelection( );
1224
+ this._clearSelection( false );
1194
1225
  }
1195
1226
 
1196
- private _clearSelection() {
1227
+ private _clearSelection( notify: boolean ) {
1228
+ if( !this._selection.size ) {
1229
+ return;
1230
+ }
1231
+
1197
1232
  for (const ref of this._selection.keys()) {
1198
1233
  const els = this.queryAll(`.row[data-row="${ref}"]`)
1199
1234
  els.forEach(el => {
@@ -1202,6 +1237,9 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
1202
1237
  }
1203
1238
 
1204
1239
  this._selection.clear();
1240
+ if( notify ) {
1241
+ this.fire("selectionChange", { selection: [], empty: true } );
1242
+ }
1205
1243
  }
1206
1244
 
1207
1245
  /**
@@ -1274,3 +1312,4 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
1274
1312
  }
1275
1313
  }
1276
1314
 
1315
+
@@ -17,7 +17,7 @@
17
17
  @use "../shared.scss";
18
18
 
19
19
  .x4icon {
20
- @extend .hbox;
20
+ @extend %hbox;
21
21
 
22
22
  flex-shrink: 0;
23
23
  min-width: 10px;
@@ -24,7 +24,7 @@
24
24
  }
25
25
 
26
26
  .x4label {
27
- @extend .hbox;
27
+ @extend %hbox;
28
28
 
29
29
  color: var( --label-color );
30
30
  background-color: var( --label-background );
@@ -72,11 +72,12 @@
72
72
  }
73
73
 
74
74
  &.vertical {
75
- @extend .vbox;
75
+ @extend %vbox;
76
76
  }
77
77
  }
78
78
 
79
79
  .x4simpletext {
80
+ color: var(--label-color);
80
81
  text-align: left;
81
82
  &.al-center { text-align: center; }
82
83
  &.al-right { text-align: right; }
@@ -33,7 +33,7 @@
33
33
  }
34
34
 
35
35
  .x4listbox {
36
- @extend .vbox;
36
+ @extend %vbox;
37
37
 
38
38
  margin: 5px;
39
39
  border: 1px solid var( --listbox-border );
@@ -66,7 +66,7 @@
66
66
  }
67
67
 
68
68
  .x4item {
69
- @extend .flex;
69
+ @extend %flex;
70
70
 
71
71
  padding: 4px;
72
72
  background-color: transparent;
@@ -28,7 +28,7 @@
28
28
  }
29
29
 
30
30
  .x4menu {
31
- @extend .shadow-xxl;
31
+ @extend %shadow-xxl;
32
32
 
33
33
  position: absolute;
34
34
  overflow-y: auto;
@@ -44,7 +44,7 @@
44
44
  max-height: calc( 100vh - 32px );
45
45
 
46
46
  .x4cmenuitem {
47
- @extend .hbox;
47
+ @extend %hbox;
48
48
 
49
49
  transition: background-color 0.3s, color 0.3s;
50
50
 
@@ -82,7 +82,7 @@
82
82
  }
83
83
 
84
84
  #text {
85
- @extend .flex;
85
+ @extend %flex;
86
86
  }
87
87
 
88
88
  &.title {
@@ -90,3 +90,40 @@
90
90
 
91
91
  }
92
92
 
93
+
94
+ .x4progressionbox {
95
+ width: 640px;
96
+ height: 200px;
97
+ &> .x4form {
98
+ min-height: unset;
99
+ }
100
+
101
+ & > .caption,
102
+ & > #btnbar {
103
+ display: none;
104
+ }
105
+
106
+ & .x4form {
107
+ min-width: 400px;
108
+ .x4icon {
109
+ height: 64px;
110
+ animation: rotating 3s linear infinite;
111
+ fill: var(--accent-background);
112
+ }
113
+
114
+ .x4label,
115
+ .x4simpletext {
116
+ padding: 16px 16px 0 16px;
117
+ line-height: 1.8em;
118
+ }
119
+
120
+ .x4progress {
121
+ margin-left: 16px;
122
+ }
123
+
124
+ #sub-text {
125
+ max-height: 140px;
126
+ overflow-y: auto;
127
+ }
128
+ }
129
+ }
@@ -2,21 +2,23 @@
2
2
  // :: MESSAGEBOX ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
3
3
 
4
4
  import { _tr } from '../../core/core_i18n';
5
- import { asap, class_ns, UnsafeHtml } from '../../core/core_tools';
5
+ import { asap, class_ns, unsafeHtml, UnsafeHtml } from '../../core/core_tools';
6
6
 
7
7
  import { HBox, VBox } from '../boxes/boxes';
8
8
  import { Icon } from '../icon/icon';
9
- import { Label } from '../label/label';
9
+ import { Label, SimpleText } from '../label/label';
10
10
  import { Dialog, DialogProps } from "../dialog/dialog"
11
11
  import { Form } from '../form/form';
12
12
  import { BtnGroupItem } from '../btngroup/btngroup';
13
13
  import { Input } from '../input/input';
14
+ import { Component } from '../../core/component';
15
+ import { Progress } from '../components.js';
14
16
 
15
17
  import "./messages.module.scss";
16
18
 
17
19
  import error_icon from "./circle-exclamation.svg";
18
20
  import pen_icon from "./pen-field.svg";
19
- import { Component } from '../../core/component';
21
+ import spinner from "./spinner.svg"
20
22
 
21
23
  export interface MessageBoxProps extends DialogProps {
22
24
  message: string;
@@ -235,3 +237,44 @@ export class PromptBox extends Dialog<DialogProps>
235
237
  }
236
238
  }
237
239
 
240
+ @class_ns( "x4" )
241
+ export class ProgressionBox extends Dialog {
242
+
243
+ #log: (string | UnsafeHtml)[];
244
+
245
+ constructor( title: string ) {
246
+ super( {
247
+ modal: true,
248
+ title: null,
249
+ movable: true,
250
+ form: new Form( {
251
+ content: [
252
+ new HBox( {
253
+ content: [
254
+ new Icon( { iconId: spinner }),
255
+ new VBox( { flex: 1, cls: "right", content: [
256
+ new SimpleText( { id: "title", text: title } ),
257
+ new Progress( { id:"prog", min: 0, max: 100, value: 0 } ),
258
+ new SimpleText( { id: "sub-text", text: "" } ),
259
+ ]})
260
+ ]
261
+ }),
262
+ ]
263
+ }),
264
+ buttons: [ "ok.outline.default","cancel.outline" ]
265
+ });
266
+
267
+ this.#log = [];
268
+ }
269
+
270
+ setText( text: string | UnsafeHtml, perc: number ) {
271
+ this.#log.push( text );
272
+ this.query<Label>( "#sub-text").setText( unsafeHtml(this.#log.join("<br/>")) );
273
+ this.query<Progress>( "#prog").setValue( perc );
274
+ }
275
+
276
+ clearText( ) {
277
+ this.#log = [];
278
+ this.query<Label>( "#sub-text").setText( '' );
279
+ }
280
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Pro 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2026 Fonticons, Inc.--><path d="M352 96C352 78.3 337.7 64 320 64C302.3 64 288 78.3 288 96C288 113.7 302.3 128 320 128C337.7 128 352 113.7 352 96zM352 544C352 526.3 337.7 512 320 512C302.3 512 288 526.3 288 544C288 561.7 302.3 576 320 576C337.7 576 352 561.7 352 544zM512 320C512 337.7 526.3 352 544 352C561.7 352 576 337.7 576 320C576 302.3 561.7 288 544 288C526.3 288 512 302.3 512 320zM96 352C113.7 352 128 337.7 128 320C128 302.3 113.7 288 96 288C78.3 288 64 302.3 64 320C64 337.7 78.3 352 96 352zM139 501C146.9 509.8 159 513.4 170.5 510.6C181.9 507.7 190.9 498.7 193.8 487.3C196.6 475.8 193 463.7 184.2 455.8C176.3 447 164.2 443.4 152.7 446.2C141.3 449.1 132.3 458.1 129.4 469.5C126.6 481 130.2 493.1 139 501zM455.8 501C463.7 509.8 475.8 513.4 487.3 510.6C498.7 507.7 507.7 498.7 510.6 487.3C513.4 475.8 509.8 463.7 501 455.8C493.1 447 481 443.4 469.5 446.2C458.1 449.1 449.1 458.1 446.2 469.5C443.4 481 447 493.1 455.8 501zM139 139C130.2 146.9 126.6 159 129.4 170.5C132.3 181.9 141.3 190.9 152.7 193.8C164.2 196.6 176.3 193 184.2 184.2C193 176.3 196.6 164.2 193.8 152.7C190.9 141.3 181.9 132.3 170.5 129.4C159 126.6 146.9 130.2 139 139z"/></svg>
@@ -26,7 +26,7 @@
26
26
  }
27
27
 
28
28
  .x4notification {
29
- @extend .shadow-xl;
29
+ @extend %shadow-xl;
30
30
 
31
31
  padding: 8px;
32
32
  border: 1px solid var(--notification-border);
@@ -40,7 +40,12 @@
40
40
  }
41
41
 
42
42
  & > .body {
43
- @extend .rel-fit;
43
+ position: relative;
44
+ left: 0;
45
+ top: 0;
46
+ right: 0;
47
+ bottom: 0;
48
+
44
49
  overflow: hidden;
45
50
  justify-content: start;
46
51
  padding: 8px;
@@ -14,25 +14,25 @@
14
14
  * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
15
  **/
16
16
 
17
- .box {
17
+ %box {
18
18
  position: relative;
19
19
  }
20
20
 
21
- .hbox {
22
- @extend .box;
21
+ %hbox {
22
+ @extend %box;
23
23
  display: flex;
24
24
  flex-direction: row;
25
25
  align-items: center;
26
26
  }
27
27
 
28
- .vbox {
29
- @extend .box;
28
+ %vbox {
29
+ @extend %box;
30
30
  display: flex;
31
31
  flex-direction: column;
32
32
  }
33
33
 
34
34
 
35
- .fit {
35
+ %fit {
36
36
  position: absolute;
37
37
  left: 0;
38
38
  top: 0;
@@ -40,16 +40,7 @@
40
40
  bottom: 0;
41
41
  }
42
42
 
43
-
44
- .rel-fit {
45
- position: relative;
46
- left: 0;
47
- top: 0;
48
- right: 0;
49
- bottom: 0;
50
- }
51
-
52
- .flex {
43
+ %flex {
53
44
  flex-grow: 1;
54
45
  flex-shrink: 0;
55
46
  flex-basis: 0;
@@ -59,83 +50,22 @@
59
50
 
60
51
  // from tailwind
61
52
 
62
- .shadow-sm {
53
+ %shadow-sm {
63
54
  box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
64
55
  }
65
56
 
66
- .shadow-md {
57
+ %shadow-md {
67
58
  box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
68
59
  }
69
60
 
70
- .shadow-lg {
61
+ %shadow-lg {
71
62
  box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
72
63
  }
73
64
 
74
- .shadow-xl {
65
+ %shadow-xl {
75
66
  box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
76
67
  }
77
68
 
78
- .shadow-xxl {
69
+ %shadow-xxl {
79
70
  box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.3), 0 8px 10px -6px rgb(0 0 0 / 0.3);
80
71
  }
81
-
82
-
83
- @keyframes rotating {
84
- from {
85
- transform: rotate(0deg);
86
- }
87
- to {
88
- transform: rotate(360deg);
89
- }
90
- }
91
-
92
- @keyframes shaking {
93
- 0% {
94
- transform: rotate(-15deg)
95
- }
96
-
97
- 4% {
98
- transform: rotate(15deg)
99
- }
100
-
101
- 8%,24% {
102
- transform: rotate(-18deg)
103
- }
104
-
105
- 12%,28% {
106
- transform: rotate(18deg)
107
- }
108
-
109
- 16% {
110
- transform: rotate(-22deg)
111
- }
112
-
113
- 20% {
114
- transform: rotate(22deg)
115
- }
116
-
117
- 32% {
118
- transform: rotate(-12deg)
119
- }
120
-
121
- 36% {
122
- transform: rotate(12deg)
123
- }
124
-
125
- 40% {
126
- transform: rotate(0deg)
127
- }
128
- }
129
-
130
-
131
- .x4rotate {
132
- animation: rotating 2s linear infinite;
133
- }
134
-
135
- .x4shake {
136
- animation-name: shaking;
137
- animation-duration: 5s;
138
- animation-iteration-count: infinite;
139
- animation-timing-function: linear;
140
- animation-direction: reverse;
141
- }
@@ -26,6 +26,17 @@
26
26
  cursor: ns-resize;
27
27
  }
28
28
 
29
+ &.inplace {
30
+ position: relative;
31
+ align-self: stretch;
32
+ min-width: 4px;
33
+ min-height: 4px;
34
+
35
+ &:hover {
36
+ background-color: var( --border );
37
+ }
38
+ }
39
+
29
40
  &.top {
30
41
  @include horz;
31
42
  top: 0;
@@ -87,4 +98,10 @@
87
98
  bottom: 0;
88
99
  cursor: nw-resize;
89
100
  }
90
- }
101
+ }
102
+
103
+ .x4hbox {
104
+ &>.x4csizer {
105
+ cursor: ew-resize;
106
+ }
107
+ }
@@ -33,7 +33,7 @@ interface CSizerEvent extends ComponentEvents {
33
33
  stop: ComponentEvent;
34
34
  }
35
35
 
36
- type SizerType = "left" | "top" | "right" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right" ;
36
+ type SizerType = "left" | "top" | "right" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right" | "inplace";
37
37
 
38
38
  /**
39
39
  *
@@ -52,15 +52,21 @@ export class CSizer extends Component<ComponentProps,CSizerEvent> {
52
52
 
53
53
  this._type = type;
54
54
  this.addClass( type );
55
-
55
+
56
56
  this.addDOMEvent( "pointerdown", ( e: PointerEvent ) => {
57
57
  this.setCapture( e.pointerId );
58
- this._ref = target ?? componentFromDOM( this.dom.parentElement );
58
+
59
+ let targ = target;
60
+ if( !targ && type=='inplace' ) {
61
+ targ = this.nextElement( );
62
+ }
63
+
64
+ this._ref = targ ?? componentFromDOM( this.dom.parentElement );
59
65
 
60
66
  this._delta = {x:0,y:0};
61
67
  const rc = this._ref.getBoundingRect();
62
68
 
63
- if( this._type.includes("left") ) {
69
+ if( this._type=="inplace" || this._type.includes("left") ) {
64
70
  this._delta.x = e.pageX-rc.left;
65
71
  }
66
72
  else {
@@ -104,21 +110,18 @@ export class CSizer extends Component<ComponentProps,CSizerEvent> {
104
110
  nr.height = (rc.top+rc.height)-pt.y;
105
111
  horz = false;
106
112
  }
107
-
108
- if( this._type.includes("bottom") ) {
113
+ else if( this._type.includes("bottom") ) {
109
114
  //nr.top = rc.top;
110
115
  nr.height = (pt.y-rc.top);
111
116
  horz = false;
112
117
  }
113
-
114
- if( this._type.includes("left") ) {
118
+ else if( this._type=="inplace" || this._type.includes("left") ) {
115
119
  nr.left = pt.x;
116
120
  nr.width = ((rc.left+rc.width)-pt.x);
117
121
  }
118
-
119
- if( this._type.includes("right") ) {
122
+ else if( this._type.includes("right") ) {
120
123
  nr.width = (pt.x-rc.left);
121
- }
124
+ }
122
125
 
123
126
  this._ref.setStyle( nr );
124
127
  //this._ref.setStyleValue( "flexGrow", 0 );
@@ -6,7 +6,14 @@
6
6
  }
7
7
 
8
8
  .x4tabs {
9
+ display: grid;
10
+ grid-template-rows: auto 1fr;
11
+
12
+
9
13
  .x4ctablist {
14
+ display: flex;
15
+ flex-direction: row;
16
+
10
17
  gap: 4px;
11
18
  padding: 5px 5px 0 5px;
12
19
 
@@ -34,9 +41,23 @@
34
41
  //color: var( --color-60 );
35
42
  //}
36
43
  }
44
+ }
37
45
 
46
+ &.vertical {
47
+ display: grid;
48
+ grid-template-rows: unset;
49
+ grid-template-columns: auto 1fr;
50
+
51
+ .x4ctablist {
52
+ align-items: start;
53
+ flex-direction: column;
54
+ border-top: 1px solid var(--border);
55
+ border-bottom: 1px solid var(--border);
56
+ border-left: 1px solid var(--border);
57
+ }
38
58
  }
39
59
 
60
+
40
61
  &> .body {
41
62
  padding: 8px;
42
63
  border: 1px solid var( --border );
@@ -17,7 +17,7 @@ import { Component, ComponentEvents, ComponentProps, EvClick } from '../../core/
17
17
  import { CoreEvent } from '../../core/core_events';
18
18
 
19
19
  import { Button, ButtonProps } from '../button/button';
20
- import { HBox, VBox, StackBox } from '../boxes/boxes';
20
+ import { HBox, VBox, StackBox, Box } from '../boxes/boxes';
21
21
 
22
22
  import "./tabs.module.scss"
23
23
  import { class_ns } from '../../core/core_tools';
@@ -26,12 +26,13 @@ import { class_ns } from '../../core/core_tools';
26
26
  *
27
27
  */
28
28
 
29
+ type callback = ( ) => Component;
29
30
 
30
31
  export interface TabItem {
31
32
  name: string;
32
33
  title: string;
33
34
  icon?: string;
34
- content: Component;
35
+ content: Component | callback;
35
36
  cls?: string; // button class
36
37
  }
37
38
 
@@ -64,6 +65,7 @@ interface TablistClickEvent extends CoreEvent {
64
65
  }
65
66
 
66
67
  interface TablistProps extends ComponentProps {
68
+ vertical?: boolean;
67
69
  click: ( ev: TablistClickEvent ) => void;
68
70
  }
69
71
 
@@ -133,7 +135,8 @@ class CTabList extends HBox<TablistProps,TablistEvents> {
133
135
 
134
136
  interface TabsProps extends Omit<ComponentProps,"content"> {
135
137
  default: string;
136
- items: TabItem[]
138
+ items: TabItem[];
139
+ vertical?: boolean;
137
140
  }
138
141
 
139
142
  /**
@@ -141,7 +144,7 @@ interface TabsProps extends Omit<ComponentProps,"content"> {
141
144
  */
142
145
 
143
146
  @class_ns( "x4" )
144
- export class Tabs extends VBox<TabsProps> {
147
+ export class Tabs extends Box<TabsProps> {
145
148
 
146
149
  private _list: CTabList;
147
150
  private _stack: StackBox;
@@ -150,6 +153,8 @@ export class Tabs extends VBox<TabsProps> {
150
153
  constructor( props: TabsProps ) {
151
154
  super( props );
152
155
 
156
+ this.setClass( "vertical", props.vertical );
157
+
153
158
  const pages = props.items?.map( x => {
154
159
  return {
155
160
  name: x.name,
@@ -160,7 +165,7 @@ export class Tabs extends VBox<TabsProps> {
160
165
  this.setContent( [
161
166
  this._list = new CTabList( {
162
167
  click: ( ev ) => this._onclick( ev ) },
163
- props.items
168
+ props.items
164
169
  ),
165
170
  this._stack = new StackBox( {
166
171
  cls: "body x4flex",
@@ -39,7 +39,7 @@
39
39
  }
40
40
 
41
41
  textarea {
42
- @extend .flex;
42
+ @extend %flex;
43
43
 
44
44
  padding: 4px;
45
45
  outline: none;
@@ -29,7 +29,7 @@
29
29
  }
30
30
 
31
31
  .x4textedit {
32
- @extend .hbox;
32
+ @extend %hbox;
33
33
  margin: 5px;
34
34
  gap: 6px;
35
35
 
@@ -38,7 +38,7 @@
38
38
  justify-content: end;
39
39
 
40
40
  &> .x4label {
41
- @extend .hbox;
41
+ @extend %hbox;
42
42
  height: 100%;
43
43
  padding: 0;
44
44
  font-weight: 500;
@@ -51,12 +51,12 @@
51
51
  }
52
52
 
53
53
  &> #edit {
54
- @extend .hbox;
55
- @extend .flex;
54
+ @extend %hbox;
55
+ @extend %flex;
56
56
  border-bottom: 1px solid var( --textedit-border );
57
57
 
58
58
  .x4input {
59
- @extend .flex;
59
+ @extend %flex;
60
60
  outline: none;
61
61
  margin: 0;
62
62
  }
@@ -29,7 +29,7 @@
29
29
  }
30
30
 
31
31
  .x4tooltip {
32
- @extend .shadow-xl;
32
+ @extend %shadow-xl;
33
33
 
34
34
  display: flex;
35
35
  flex-direction: row;
@@ -34,7 +34,7 @@
34
34
  }
35
35
 
36
36
  .x4treeview {
37
- @extend .vbox;
37
+ @extend %vbox;
38
38
 
39
39
  overflow-y: auto;
40
40
  height: 100%;
@@ -17,7 +17,7 @@
17
17
  @use "../shared.scss";
18
18
 
19
19
  .x4scrollview {
20
- @extend .flex;
20
+ @extend %flex;
21
21
 
22
22
  position: absolute;
23
23
  height: 100%;
@@ -304,7 +304,7 @@ export class Application<E extends ApplicationEvents = ApplicationEvents> extend
304
304
  msg_socket = null;
305
305
 
306
306
  if( opened ) {
307
- looseCallback( );
307
+ looseCallback?.( );
308
308
  opened = 0;
309
309
  }
310
310
  }
package/src/x4.scss CHANGED
@@ -14,6 +14,6 @@
14
14
  * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
15
  **/
16
16
 
17
- @use "./components/normalize.scss";
18
- @use "./components/themes.scss";
19
- @use "./components/base.scss"
17
+ @import "./components/normalize.scss";
18
+ @import "./components/themes.scss";
19
+ @import "./components/base.scss";
package/ai-comments.txt DELETED
@@ -1,97 +0,0 @@
1
- x4 se distingue des approches réactives conventionnelles.
2
- Il offre une expérience de développement qui met l'accent sur la simplicité, la performance et un contrôle affiné sur la manière dont l'interface utilisateur est construite et mise à jour.
3
-
4
- Voici les avantages qui découlent de cette philosophie :
5
-
6
- - Abstraction du DOM, mais avec une philosophie distincte :
7
- Contrairement aux frameworks réactifs qui utilisent un Virtual DOM et des mécanismes de réconciliation
8
- pour abstraire les manipulations du DOM, x4 propose une abstraction différente.
9
- L'objectif n'est pas d'imiter le fonctionnement des frameworks populaires, mais de fournir une manière plus directe, plus légère et plus performante
10
- d'interagir avec les concepts de l'interface utilisateur. Cette abstraction est conçue pour être moins "magique" et plus prévisible.
11
-
12
- - Performance optimisée :
13
- En concevant son propre modèle d'abstraction et de mise à jour du DOM, x4 vise à atteindre une performance brute supérieure.
14
- Il peut s'affranchir de certaines complexités inhérentes aux approches réactives pour se concentrer sur l'essentiel,
15
- offrant ainsi une expérience utilisateur fluide même dans des applications exigeantes.
16
-
17
- - Un écosystème TypeScript pur :
18
- Pour garantir une robustesse et une maintenabilité maximales, ce framework élimine tout mélange de syntaxe déclarative comme JSX/TSX avec du code JavaScript.
19
- L'intégralité de la création d'interfaces et de la logique applicative est réalisée en TypeScript natif.
20
- Cela permet de bénéficier pleinement des avantages du typage fort, de la vérification à la compilation, et d'une expérience de développement cohérente et sécurisée.
21
-
22
- - Gestion de l'état et des références simplifiée :
23
- Ce framework s'affranchit des concepts comme useRef ou des "hooks" tels qu'on les trouve dans d'autres écosystèmes.
24
- La gestion des états et des références est intégrée de manière plus directe et idiomatique à sa propre architecture, favorisant une logique plus linéaire et plus facile à suivre.
25
-
26
- - Conçu pour les applications complètes :
27
- Plutôt que de se concentrer uniquement sur des composants UI isolés, ce framework est orienté vers la construction d'applications complètes.
28
- Il offre les outils et la structure nécessaires pour gérer des architectures plus vastes, de la gestion des routes à l'état global.
29
-
30
- - Compatibilité avec les Web Workers :
31
- Afin d'optimiser les performances et de permettre des traitements asynchrones intensifs sans bloquer le thread principal,
32
- ce framework est conçu pour être compatible avec les Web Workers. Cela ouvre la voie à des applications plus réactives et performantes, en déportant les tâches lourdes.
33
-
34
- - Environnement de compilation et outils spécifiques :
35
- Le développement avec ce framework est accéléré par un environnement de compilation ultra-rapide, basé sur esbuild.
36
- De plus, il est livré avec un outils spécifiques X4Build qui améliore l'expérience développeur.
37
- ainsi qu'une expérience de Hot Module Replacement (HMR) fluide et efficace pour des itérations rapides.
38
-
39
- - Modèle de développement éprouvé :
40
- L'architecture et les pratiques recommandées par ce framework constituent un modèle de développement éprouvé.
41
- Il facilite notamment des scénarios avancés comme les evenements distribués via WebSocket directement intégré (si besoin) dans l'application,
42
-
43
- - Création de composants intuitive :
44
- La manière dont x4 abstrait le DOM et gère la réactivité rend la création de composants d'une simplicité déconcertante.
45
- Vous pouvez construire des interfaces modulaires et réutilisables avec une facilité remarquable, en vous concentrant sur la logique métier plutôt
46
- que sur la gestion complexe des interactions DOM.
47
-
48
- - Accélération Drastique du Développement :
49
- Avec plus de 50 composants prêts à l'emploi, les développeurs peuvent assembler des interfaces utilisateur complexes et fonctionnelles
50
- à une vitesse remarquable. Plutôt que de passer du temps à concevoir et implémenter des éléments de base comme des buttons, des inputs, des checkboxes ou des sliders,
51
- ils peuvent les utiliser directement. Ceci libère un temps précieux pour se concentrer sur la logique métier spécifique de l'application.
52
-
53
- Le développement "from scratch" de tels composants est coûteux en temps et en ressources. La richesse de cette bibliothèque élimine ce fardeau,
54
- réduisant ainsi les efforts de conception, de codage, de test et de maintenance de ces éléments d'interface fondamentaux.
55
- Cela signifie moins de code à écrire, moins de bugs potentiels et une meilleure productivité globale.
56
-
57
- - Richesse et Diversité des Interfaces Utilisateur :
58
- La large gamme de composants permet de créer des expériences utilisateur très riches et interactives sans avoir à intégrer de bibliothèques
59
- tierces supplémentaires pour des fonctionnalités de base. Des composants sophistiqués comme gridview (pour l'affichage de données tabulaires), spreadsheet (pour des fonctionnalités de tableur complètes),
60
- calendar, colorpicker, dialog, treeview ou progress sont inclus, offrant une boîte à outils complète pour des applications métier complexes ou des tableaux de bord interactifs.
61
-
62
- - Cohérence et Qualité :
63
- L'utilisation de composants d'un même framework assure une cohérence visuelle et comportementale à travers toute l'application.
64
- Ces composants sont testés et optimisés, garantissant une meilleure qualité et une expérience utilisateur plus fiable dès le départ.
65
- En somme, cette collection de composants est un avantage concurrentiel énorme.
66
-
67
-
68
-
69
-
70
-
71
-
72
- You are a TypeScript/JavaScript documentation assistant.
73
- Your task is to generate **JSDoc comments** for public APIs in the provided code.
74
- Follow these rules strictly:
75
-
76
- ### Rules:
77
- 1. **Only document public APIs**:
78
- - Public classes, methods, properties, and events.
79
- - Ignore private/protected/internal members (e.g., `_privateField`, `#privateMethod`).
80
-
81
- 2. **Output format**:
82
- - Generate **only JSDoc blocks** (no code repetition).
83
- - Use **inline comments** (`/** ... */`) for brevity.
84
- - Do not include `@class` or `@extends` (JSDoc infers these).
85
- - Document in english
86
-
87
- 3. **Content guidelines**:
88
- - Describe **purpose, behavior, and usage** (not implementation).
89
- - Mention auto-generated CSS classes (e.g., `Button → x4button`).
90
- - Include `@example` where helpful.
91
- - Be concise, clear, and professional.
92
-
93
- Avoid:
94
-
95
- Implementation details.
96
- Private/protected members.
97
- Redundant or repetitive descriptions.
File without changes