x4js 2.1.0-manual.2 → 2.2.0

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 (36) hide show
  1. package/package.json +9 -13
  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/button/button.ts +4 -4
  6. package/src/components/calendar/calendar.module.scss +3 -3
  7. package/src/components/checkbox/checkbox.module.scss +1 -1
  8. package/src/components/combobox/combobox.module.scss +6 -6
  9. package/src/components/dialog/dialog.module.scss +4 -2
  10. package/src/components/form/form.module.scss +2 -2
  11. package/src/components/gridview/folder-open.svg +1 -0
  12. package/src/components/gridview/gridview.module.scss +14 -0
  13. package/src/components/gridview/gridview.ts +55 -15
  14. package/src/components/icon/icon.module.scss +1 -1
  15. package/src/components/label/label.module.scss +3 -2
  16. package/src/components/listbox/listbox.module.scss +2 -2
  17. package/src/components/menu/menu.module.scss +3 -3
  18. package/src/components/messages/messages.module.scss +54 -0
  19. package/src/components/messages/messages.ts +69 -3
  20. package/src/components/messages/spinner.svg +1 -0
  21. package/src/components/notification/notification.module.scss +1 -1
  22. package/src/components/panel/panel.module.scss +6 -1
  23. package/src/components/shared.scss +12 -82
  24. package/src/components/sizers/sizer.module.scss +23 -1
  25. package/src/components/sizers/sizer.ts +36 -12
  26. package/src/components/tabs/tabs.module.scss +21 -0
  27. package/src/components/tabs/tabs.ts +10 -5
  28. package/src/components/textarea/textarea.module.scss +1 -1
  29. package/src/components/textedit/textedit.module.scss +5 -5
  30. package/src/components/tooltips/tooltips.scss +1 -1
  31. package/src/components/treeview/treeview.module.scss +1 -1
  32. package/src/components/viewport/viewport.module.scss +1 -1
  33. package/src/core/core_application.ts +1 -1
  34. package/src/core/core_data.ts +20 -7
  35. package/src/x4.d.ts +10 -0
  36. package/src/x4.scss +3 -3
@@ -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,67 @@ export class PromptBox extends Dialog<DialogProps>
235
237
  }
236
238
  }
237
239
 
240
+ @class_ns( "x4" )
241
+ export class ProgressionBox extends Dialog {
242
+
243
+ #has_errors = false;
244
+
245
+ constructor( title: string ) {
246
+ super( {
247
+ modal: true,
248
+ title: null,
249
+ sizable: true,
250
+ movable: true,
251
+ form: new Form( {
252
+ content: [
253
+ new HBox( {
254
+ content: [
255
+ new Icon( { iconId: spinner }),
256
+ new VBox( { flex: 1, cls: "right", content: [
257
+ new SimpleText( { id: "title", text: title } ),
258
+ new Progress( { id:"prog", min: 0, max: 100, value: 0 } ),
259
+ new VBox( { id: "sub-text" } ),
260
+ ]})
261
+ ]
262
+ }),
263
+ ]
264
+ }),
265
+ buttons: [ "ok.outline.default" ]
266
+ });
267
+
268
+ this.query("#btnbar").show( false );
269
+
270
+ this.on("btnclick", ( ) => this.show( false ) );
271
+ }
272
+
273
+ addText( text: string | UnsafeHtml, perc: number ) {
274
+ this.query<Label>( "#sub-text").appendContent( new SimpleText( { text } ) );
275
+ this.query<Progress>( "#prog").setValue( perc );
276
+ }
277
+
278
+ addError( text: string | UnsafeHtml, perc: number ) {
279
+ this.query<Label>( "#sub-text").appendContent( new SimpleText( { cls:"error", text } ) );
280
+ this.query<Progress>( "#prog").setValue( perc );
281
+
282
+ this.#has_errors = true;
283
+ }
284
+
285
+ setText( text: string | UnsafeHtml, perc: number ) {
286
+ this.query<Label>( "#sub-text").setContent( new SimpleText( { text } ) );
287
+ this.query<Progress>( "#prog").setValue( perc );
288
+ }
289
+
290
+ clear( ) {
291
+ this.#has_errors = true;
292
+ this.query<Label>( "#sub-text").clearContent( );
293
+ }
294
+
295
+ done( ) {
296
+ if( this.#has_errors ) {
297
+ this.query("#btnbar").show( true );
298
+ }
299
+ else {
300
+ this.setTimeout( "close", 5000, ( ) => { this.show(false);} );
301
+ }
302
+ }
303
+ }
@@ -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,22 @@
26
26
  cursor: ns-resize;
27
27
  }
28
28
 
29
+ &.vsize,
30
+ &.hsize {
31
+ position: relative;
32
+ align-self: stretch;
33
+ min-width: 4px;
34
+ min-height: 4px;
35
+
36
+ &:hover {
37
+ background-color: var( --border );
38
+ }
39
+ }
40
+
41
+ .hsize {
42
+ cursor: ew-resize;
43
+ }
44
+
29
45
  &.top {
30
46
  @include horz;
31
47
  top: 0;
@@ -87,4 +103,10 @@
87
103
  bottom: 0;
88
104
  cursor: nw-resize;
89
105
  }
90
- }
106
+ }
107
+
108
+ .x4hbox {
109
+ &>.x4csizer {
110
+ cursor: ew-resize;
111
+ }
112
+ }
@@ -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" | "hsize" | "vsize";
37
37
 
38
38
  /**
39
39
  *
@@ -52,22 +52,28 @@ 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=='hsize' || type=='vsize') ) {
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=="hsize" || this._type.includes("left") ) {
64
70
  this._delta.x = e.pageX-rc.left;
65
71
  }
66
72
  else {
67
73
  this._delta.x = e.pageX-(rc.left+rc.width);
68
74
  }
69
75
 
70
- if( this._type.includes("top") ) {
76
+ if( this._type=="vsize" || this._type.includes("top") ) {
71
77
  this._delta.y = e.pageY-rc.top;
72
78
  }
73
79
  else {
@@ -104,21 +110,25 @@ 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=="vsize" ) {
114
+ nr.height = (rc.top+rc.height)-pt.y;
115
+ horz = false;
116
+ }
117
+ else if( this._type.includes("bottom") ) {
109
118
  //nr.top = rc.top;
110
119
  nr.height = (pt.y-rc.top);
111
120
  horz = false;
112
121
  }
113
-
114
- if( this._type.includes("left") ) {
122
+ else if( this._type.includes("left") ) {
115
123
  nr.left = pt.x;
116
124
  nr.width = ((rc.left+rc.width)-pt.x);
117
125
  }
118
-
119
- if( this._type.includes("right") ) {
126
+ else if( this._type=="hsize" ) {
127
+ nr.width = ((rc.left+rc.width)-pt.x);
128
+ }
129
+ else if( this._type.includes("right") ) {
120
130
  nr.width = (pt.x-rc.left);
121
- }
131
+ }
122
132
 
123
133
  this._ref.setStyle( nr );
124
134
  //this._ref.setStyleValue( "flexGrow", 0 );
@@ -129,4 +139,18 @@ export class CSizer extends Component<ComponentProps,CSizerEvent> {
129
139
  e.preventDefault( );
130
140
  e.stopPropagation( );
131
141
  }
142
+ }
143
+
144
+ @class_ns( "x4" )
145
+ export class HSizer extends CSizer {
146
+ constructor( ) {
147
+ super( "hsize" );
148
+ }
149
+ }
150
+
151
+ @class_ns( "x4" )
152
+ export class VSizer extends CSizer {
153
+ constructor( ) {
154
+ super( "vsize" );
155
+ }
132
156
  }
@@ -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
  }
@@ -25,6 +25,7 @@ export type DataFieldValue = string | Date | number | boolean;
25
25
 
26
26
  export type ChangeCallback = (type: string, id?: DataRecordID) => void;
27
27
  export type CalcCallback = () => string;
28
+ export type SortCallback = ( v1: any, v2: any ) => number;
28
29
 
29
30
  export type FieldType = 'string' | 'int' | 'float' | 'date' | 'bool' | 'array' | 'object' | 'any' | 'calc';
30
31
  export type DataIndex = Uint32Array;
@@ -935,7 +936,8 @@ export class DataStore<T = any> extends EventSource<DataStoreEventMap> {
935
936
 
936
937
  interface sort_info {
937
938
  fidx: number,
938
- asc: boolean
939
+ asc: boolean,
940
+ cb: SortCallback,
939
941
  }
940
942
 
941
943
  let bads = 0; // unknown fields
@@ -943,7 +945,7 @@ export class DataStore<T = any> extends EventSource<DataStoreEventMap> {
943
945
 
944
946
  // if no fields are given, reset sort by id
945
947
  if ( sort===null ) {
946
- fidxs.push( { fidx: 0, asc: true } );
948
+ fidxs.push( { fidx: 0, asc: true, cb: null } );
947
949
  }
948
950
  else {
949
951
  fidxs = sort.map( (si) => {
@@ -954,7 +956,7 @@ export class DataStore<T = any> extends EventSource<DataStoreEventMap> {
954
956
  bads++;
955
957
  }
956
958
 
957
- return { fidx: fi, asc: si.ascending };
959
+ return { fidx: fi, asc: si.ascending, cb: si.callback };
958
960
  });
959
961
  }
960
962
 
@@ -969,11 +971,17 @@ export class DataStore<T = any> extends EventSource<DataStoreEventMap> {
969
971
  if( fidxs.length==1 ) {
970
972
 
971
973
  const field = fidxs[0].fidx;
974
+ const cb = fidxs[0].cb;
972
975
 
973
976
  index.sort( ( ia, ib ) => {
974
977
 
975
978
  let va = m.getRaw( field, this.getByIndex(ia) ) ?? '';
976
979
  let vb = m.getRaw( field, this.getByIndex(ib) ) ?? '';
980
+
981
+ if( cb ) {
982
+ return cb( va, vb );
983
+ }
984
+
977
985
  if (va > vb) { return 1; }
978
986
  if (va < vb) { return -1; }
979
987
  return 0;
@@ -987,13 +995,17 @@ export class DataStore<T = any> extends EventSource<DataStoreEventMap> {
987
995
  else {
988
996
  index.sort( ( ia, ib ) => {
989
997
 
990
- for( let fi=0; fi<fidxs.length; fi++ ) {
991
-
992
- let fidx = fidxs[fi].fidx;
993
- let mul = fidxs[fi].asc ? 1 : -1;
998
+ for( const fi of fidxs ) {
999
+ let fidx = fi.fidx;
1000
+ let mul = fi.asc ? 1 : -1;
994
1001
 
995
1002
  let va = m.getRaw( fidx, this.getByIndex(ia) ) ?? '';
996
1003
  let vb = m.getRaw( fidx, this.getByIndex(ib) ) ?? '';
1004
+
1005
+ if( fi.cb ) {
1006
+ return fi.cb( va, vb );
1007
+ }
1008
+
997
1009
  if (va > vb) { return mul; }
998
1010
  if (va < vb) { return -mul; }
999
1011
  }
@@ -1096,6 +1108,7 @@ export interface SortProp {
1096
1108
  field: string; //
1097
1109
  ascending: boolean; //
1098
1110
  numeric?: boolean; // numeric sort
1111
+ callback?: SortCallback;
1099
1112
  }
1100
1113
 
1101
1114
 
package/src/x4.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @file x4.d.ts
3
+ * @author Etienne Cochard
4
+ * @copyright (c) 2026 R-libre ingenierie, all rights reserved.
5
+ **/
6
+
7
+ declare module "*.svg" {
8
+ const content: string;
9
+ export default content;
10
+ }
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";