x4js 2.0.26 → 2.0.30

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 (73) hide show
  1. package/.vscode/launch.json +14 -0
  2. package/.vscode/settings.json +2 -0
  3. package/ai-comments.txt +97 -0
  4. package/demo/assets/house-light.svg +1 -0
  5. package/demo/assets/radio.svg +4 -0
  6. package/demo/index.html +12 -0
  7. package/demo/main.scss +23 -0
  8. package/demo/main.ts +324 -0
  9. package/demo/package.json +26 -0
  10. package/demo/scss.d.ts +4 -0
  11. package/demo/svg.d.ts +1 -0
  12. package/demo/tsconfig.json +14 -0
  13. package/lib/types/x4js.d.ts +0 -2374
  14. package/package.json +23 -47
  15. package/prepack.mjs +3 -0
  16. package/scripts/prepack.mjs +342 -0
  17. package/src/colors.scss +246 -0
  18. package/src/components/boxes/boxes.module.scss +1 -1
  19. package/src/components/boxes/boxes.ts +139 -28
  20. package/src/components/button/button.ts +80 -33
  21. package/src/components/combobox/combobox.ts +1 -1
  22. package/src/components/dialog/dialog.ts +4 -0
  23. package/src/components/gauge/gauge.module.scss +3 -0
  24. package/src/components/gauge/gauge.ts +1 -1
  25. package/src/components/gridview/gridview.ts +106 -8
  26. package/src/components/icon/icon.ts +42 -14
  27. package/src/components/input/input.ts +155 -76
  28. package/src/components/keyboard/keyboard.module.scss +1 -1
  29. package/src/components/keyboard/keyboard.ts +31 -9
  30. package/src/components/label/label.module.scss +9 -0
  31. package/src/components/label/label.ts +10 -6
  32. package/src/components/link/link.module.scss +44 -0
  33. package/src/components/link/link.ts +7 -1
  34. package/src/components/listbox/listbox.module.scss +18 -4
  35. package/src/components/listbox/listbox.ts +34 -15
  36. package/src/components/menu/menu.module.scss +14 -2
  37. package/src/components/menu/menu.ts +1 -1
  38. package/src/components/messages/messages.ts +13 -5
  39. package/src/components/panel/panel.module.scss +7 -0
  40. package/src/components/popup/popup.ts +14 -10
  41. package/src/components/propgrid/propgrid.ts +13 -3
  42. package/src/components/shared.scss +4 -0
  43. package/src/components/spreadsheet/spreadsheet.module.scss +308 -0
  44. package/src/components/spreadsheet/spreadsheet.ts +1223 -0
  45. package/src/components/tabs/tabs.module.scss +1 -0
  46. package/src/components/textarea/textarea.ts +8 -2
  47. package/src/components/textedit/textedit.ts +7 -0
  48. package/src/components/themes.scss +2 -0
  49. package/src/components/tooltips/tooltips.ts +15 -3
  50. package/src/core/component.ts +358 -162
  51. package/src/core/core_application.ts +129 -32
  52. package/src/core/core_colors.ts +382 -119
  53. package/src/core/core_data.ts +73 -86
  54. package/src/core/core_dom.ts +10 -0
  55. package/src/core/core_dragdrop.ts +32 -7
  56. package/src/core/core_element.ts +111 -4
  57. package/src/core/core_events.ts +48 -11
  58. package/src/core/core_i18n.ts +2 -0
  59. package/src/core/core_pdf.ts +454 -0
  60. package/src/core/core_router.ts +64 -5
  61. package/src/core/core_state.ts +1 -0
  62. package/src/core/core_styles.ts +11 -12
  63. package/src/core/core_svg.ts +348 -51
  64. package/src/core/core_tools.ts +105 -17
  65. package/src/x4.ts +1 -0
  66. package/src/x4tsx.d.ts +2 -1
  67. package/tsconfig.json +11 -0
  68. package/lib/README.txt +0 -20
  69. package/lib/cjs/x4.css +0 -1
  70. package/lib/cjs/x4.js +0 -2
  71. package/lib/esm/x4.css +0 -1
  72. package/lib/esm/x4.mjs +0 -2
  73. package/lib/styles/x4.css +0 -1
@@ -121,14 +121,14 @@ export class Keyboard extends HBox<KeyboardProps>
121
121
  this.hide( );
122
122
 
123
123
  this.addDOMEvent( "mousedown", (e) => {
124
- this.handleKey( e );
124
+ this.handleKeyEvent( e );
125
125
  e.preventDefault( );
126
126
  e.stopPropagation( );
127
127
  });
128
128
 
129
129
  // for rapid people
130
130
  this.addDOMEvent( "dblclick", (e) => {
131
- this.handleKey( e );
131
+ this.handleKeyEvent( e );
132
132
  e.preventDefault( );
133
133
  e.stopPropagation( );
134
134
  });
@@ -148,7 +148,7 @@ export class Keyboard extends HBox<KeyboardProps>
148
148
  *
149
149
  */
150
150
 
151
- private handleKey( e: UIEvent ) {
151
+ private handleKeyEvent( e: UIEvent ) {
152
152
  let target = e.target as HTMLElement;
153
153
  let key;
154
154
 
@@ -165,6 +165,10 @@ export class Keyboard extends HBox<KeyboardProps>
165
165
  return;
166
166
  }
167
167
 
168
+ this._handleKey( key );
169
+ }
170
+
171
+ private _handleKey( key: number ) {
168
172
  switch( key ) {
169
173
  // bk space
170
174
  case 2: {
@@ -326,11 +330,16 @@ export class Keyboard extends HBox<KeyboardProps>
326
330
  private handleFocus( target: Element, enter: boolean ) {
327
331
 
328
332
  if( enter ) {
329
- if( target.tagName=='INPUT' && !(target as HTMLInputElement).readOnly ) {
330
- this.input = target as HTMLInputElement;
331
- this.visible = true;
332
- this.setTimeout( "vis", 200, this._updateVis );
333
- return;
333
+ if( target.tagName=='INPUT' ) {
334
+ const input = target as HTMLInputElement;
335
+ if( !input.readOnly &&
336
+ input.type!='checkbox' && input.type!='radio' &&
337
+ input.type!='range' && input.type!='file' ) {
338
+ this.input = input;
339
+ this.visible = true;
340
+ this.setTimeout( "vis", 200, this._updateVis );
341
+ return;
342
+ }
334
343
  }
335
344
  }
336
345
 
@@ -430,6 +439,7 @@ export class Keyboard extends HBox<KeyboardProps>
430
439
  let content = line[i];
431
440
  let key;
432
441
  let icon = null;
442
+ let repeat = false;
433
443
 
434
444
  if( content.length>2 && content[0]=='{' && content[content.length-1]=='}') {
435
445
 
@@ -452,6 +462,7 @@ export class Keyboard extends HBox<KeyboardProps>
452
462
 
453
463
  case 2:
454
464
  {
465
+ repeat = true;
455
466
  content = undefined;
456
467
  icon = icon_bksp;
457
468
  cls += ' cdel';
@@ -516,7 +527,18 @@ export class Keyboard extends HBox<KeyboardProps>
516
527
  key = line[i].charCodeAt(0);
517
528
  }
518
529
 
519
- let el = new Button( { cls, label: content, attrs: {'data-key': key}, icon } );
530
+ let el = new Button( {
531
+ cls,
532
+ label: content,
533
+ attrs: {'data-key': key},
534
+ icon,
535
+ autorepeat: repeat,
536
+ click: ( e ) => {
537
+ if( e.repeat ) {
538
+ this._handleKey( key )
539
+ }
540
+ }
541
+ } );
520
542
  tl.push( el );
521
543
  }
522
544
 
@@ -30,16 +30,25 @@
30
30
  background-color: var( --label-background );
31
31
  gap: 0.2em;
32
32
 
33
+ &.al-right
33
34
  &.right {
34
35
  text-align: right;
35
36
  justify-content: end;
36
37
  }
37
38
 
39
+ &.al-center,
38
40
  &.center {
39
41
  text-align: center;
40
42
  justify-content: center;
41
43
  }
42
44
 
45
+ &.al-right {
46
+ padding-left: 8px;
47
+ #icon {
48
+ order: 2;
49
+ }
50
+ }
51
+
43
52
  #text {
44
53
  &:empty {
45
54
  display: none;
@@ -24,6 +24,7 @@ export interface LabelProps extends ComponentProps {
24
24
  text?: string | UnsafeHtml;
25
25
  icon?: string;
26
26
  labelFor?: string;
27
+ align?: "left" | "center" | "right";
27
28
  }
28
29
 
29
30
  /**
@@ -32,29 +33,32 @@ export interface LabelProps extends ComponentProps {
32
33
 
33
34
  @class_ns( "x4" )
34
35
  export class Label extends Component<LabelProps> {
35
- #text: Component;
36
36
 
37
37
  constructor( p: LabelProps ) {
38
38
  super( { ...p, content: null } );
39
39
 
40
40
  this.setContent( [
41
41
  new Icon( { id:"icon", iconId: this.props.icon } ),
42
- this.#text = new Component( { tag: 'span', id: 'text' } )
42
+ new Component( { tag: 'span', id: 'text' } )
43
43
  ] );
44
44
 
45
45
  // small hack for react:
46
46
  // p.content may be the text
47
- const text = this.props.text;
48
- this.setText( text );
47
+ this.setText( this.props.text );
49
48
 
50
49
  if( p.labelFor ) {
51
50
  this.setAttribute( "for", p.labelFor );
52
51
  }
52
+
53
+ if( p.align ) {
54
+ this.addClass( "al-"+p.align );
55
+ }
53
56
  }
54
57
 
55
58
  setText( text: string | UnsafeHtml ) {
56
- this.#text.setContent( text );
57
- this.#text.setClass( "empty", !text );
59
+ const lab = this.query<Icon>( "#text" );
60
+ lab.setContent( text );
61
+ lab.setClass( "empty", !text );
58
62
  }
59
63
 
60
64
  setIcon( icon: string ) {
@@ -0,0 +1,44 @@
1
+ /**
2
+ * ___ ___ __
3
+ * \ \/ / / _
4
+ * \ / /_| |_
5
+ * / \____ _|
6
+ * /__/\__\ |_|
7
+ *
8
+ * @file link.module.scss
9
+ * @author Etienne Cochard
10
+ *
11
+ * @copyright (c) 2026 R-libre ingenierie
12
+ *
13
+ * Use of this source code is governed by an MIT-style license
14
+ * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
+ **/
16
+
17
+ .x4link {
18
+ cursor: pointer;
19
+
20
+ #text {
21
+ text-decoration: underline;
22
+ }
23
+
24
+ &[disabled] {
25
+ cursor: not-allowed;
26
+ border-color: var( --border );
27
+ color: var( --listbox-item-color-disabled );
28
+
29
+ &>.body {
30
+ .x4viewport {
31
+ pointer-events: none;
32
+ .x4item {
33
+ &.selected {
34
+ background-color: var( --listbox-item-color-sel-disabled );
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
40
+
41
+ &:focus-within {
42
+ border-color: var( --listbox-border-focus );
43
+ }
44
+ }
@@ -19,6 +19,7 @@ import { EventCallback } from '../../core/core_events';
19
19
  import { class_ns, UnsafeHtml } from '../../core/core_tools';
20
20
  import { Label } from '../label/label';
21
21
 
22
+ import "./link.module.scss"
22
23
 
23
24
  /**
24
25
  * Link events
@@ -36,6 +37,7 @@ interface LinkProps extends ComponentProps {
36
37
  href: string;
37
38
  text?: string | UnsafeHtml; // you can also use content for complexe content
38
39
  icon?: string;
40
+ align?: "left" | "center" | "right";
39
41
  click?: EventCallback<EvClick>;
40
42
  }
41
43
 
@@ -44,12 +46,16 @@ export class Link extends Component<LinkProps,LinkEvents> {
44
46
  constructor( props: LinkProps ) {
45
47
  super( { tag: "a", ...props} );
46
48
 
47
- this.setAttribute( "href", props.href );
49
+ if( props.href ) {
50
+ this.setAttribute( "href", props.href );
51
+ }
52
+
48
53
  this.mapPropEvents( props, "click" );
49
54
 
50
55
  this.setContent( new Label( {
51
56
  text: props.text,
52
57
  icon: props.icon,
58
+ align: props.align,
53
59
  } ) );
54
60
 
55
61
  this.addDOMEvent('click', (e) => this._on_click(e));
@@ -39,16 +39,20 @@
39
39
  border: 1px solid var( --listbox-border );
40
40
  background-color: var( --listbox-background );
41
41
 
42
- &:focus-within {
43
- border-color: var( --listbox-border-focus );
44
- }
45
-
46
42
  outline: none;
47
43
 
48
44
  &>.x4header {
49
45
  border-bottom: 1px solid var( --border );
50
46
  }
51
47
 
48
+ &>.title {
49
+ background: white;
50
+ padding: 4px;
51
+ font-weight: bold;
52
+ color: black;
53
+ border-bottom: 1px solid var(--border);
54
+ }
55
+
52
56
  &>.body {
53
57
  width: 100%;
54
58
  flex-basis: 0;
@@ -94,6 +98,12 @@
94
98
  }
95
99
  }
96
100
 
101
+ .ref-c1 { width: var( --ref-c1-width); }
102
+ .ref-c2 { width: var( --ref-c2-width); }
103
+ .ref-c3 { width: var( --ref-c3-width); }
104
+ .ref-c4 { width: var( --ref-c4-width); }
105
+ .ref-c5 { width: var( --ref-c5-width); }
106
+
97
107
  //&:active{
98
108
  //background-color: var( --color-80 );
99
109
  //color: var(--color-0);
@@ -162,4 +172,8 @@
162
172
  }
163
173
  }
164
174
  }
175
+
176
+ &:focus-within {
177
+ border-color: var( --listbox-border-focus );
178
+ }
165
179
  }
@@ -42,8 +42,7 @@ export interface ListItem {
42
42
  *
43
43
  */
44
44
 
45
- interface ListboxEvents extends ComponentEvents {
46
- //change: EvChange;
45
+ export interface ListboxEvents extends ComponentEvents {
47
46
  click?: EvClick;
48
47
  dblClick?: EvDblClick;
49
48
  contextMenu?: EvContextMenu;
@@ -54,9 +53,11 @@ interface ListboxEvents extends ComponentEvents {
54
53
  *
55
54
  */
56
55
 
57
- interface ListboxProps extends Omit<ComponentProps,'content'> {
56
+ export interface ListboxProps extends Omit<ComponentProps,'content'> {
58
57
  items?: ListItem[];
59
58
  renderer?: ( item: ListItem ) => Component;
59
+ title?: string;
60
+ icon?: string;
60
61
  header?: Header;
61
62
  footer?: Component,
62
63
  checkable?: true,
@@ -105,6 +106,7 @@ export class Listbox extends Component<ListboxProps,ListboxEvents> {
105
106
  }
106
107
 
107
108
  this.setContent( [
109
+ (props.title || props.icon) ? new Label( { cls: 'title', text: props.title, icon: props.icon }) : null,
108
110
  props.header ? props.header : null,
109
111
  scroller,
110
112
  props.footer,
@@ -118,7 +120,7 @@ export class Listbox extends Component<ListboxProps,ListboxEvents> {
118
120
  } );
119
121
 
120
122
  if( props.items ) {
121
- this.setItems( props.items );
123
+ this.setItems( props.items, false );
122
124
  }
123
125
  }
124
126
 
@@ -192,8 +194,15 @@ export class Listbox extends Component<ListboxProps,ListboxEvents> {
192
194
  }
193
195
  else {
194
196
  const selitem = this._itemWithID( this._lastsel );
195
- let nel = sens==kbNav.next ? selitem.nextElement() : selitem.prevElement();
196
- nel = next_visible( nel, sens==kbNav.next );
197
+
198
+ let nel;
199
+ if( selitem ) {
200
+ nel = sens==kbNav.next ? selitem.nextElement() : selitem.prevElement();
201
+ nel = next_visible( nel, sens==kbNav.next );
202
+ }
203
+ else {
204
+ nel = sens==kbNav.next ? this._view.firstChild() : this._view.lastChild( );
205
+ }
197
206
 
198
207
  if( nel ) {
199
208
  const id = nel.getInternalData( "id" );
@@ -407,10 +416,12 @@ export class Listbox extends Component<ListboxProps,ListboxEvents> {
407
416
  this._multisel.clear( );
408
417
  }
409
418
 
410
- clearSelection( ) {
419
+ clearSelection( fireEvent = true ) {
411
420
  if( this._multisel.size ) {
412
421
  this._clearSelection( );
413
- this.fire( "selectionChange", { selection: [], empty: true } );
422
+ if( fireEvent ) {
423
+ this.fire( "selectionChange", { selection: [], empty: true } );
424
+ }
414
425
  }
415
426
  }
416
427
 
@@ -426,24 +437,21 @@ export class Listbox extends Component<ListboxProps,ListboxEvents> {
426
437
  this._view.clearContent( );
427
438
  this._items = items ?? [];
428
439
 
429
- let upsel = false;
440
+ let update_sel = false;
430
441
 
431
442
  if( this._items.length ) {
432
443
  const content = items.map( x => this.renderItem(x) );
433
444
  this._view.setContent( content );
434
445
 
435
- if( keepSel ) {
446
+ if( keepSel && oldSel.length>0 ) {
436
447
  this.select( oldSel );
437
448
  }
438
- else {
439
- upsel = true;
440
- }
441
449
  }
442
450
  else {
443
- upsel = true;
451
+ update_sel = oldSel.length>0;
444
452
  }
445
453
 
446
- if( upsel ) {
454
+ if( update_sel ) {
447
455
  this.setTimeout( "sel", 100, ( ) => {
448
456
  this.fire( "selectionChange", { selection: [], empty: true } );
449
457
  } );
@@ -574,4 +582,15 @@ export class Listbox extends Component<ListboxProps,ListboxEvents> {
574
582
  getSelection( ) {
575
583
  return Array.from( this._multisel );
576
584
  }
585
+
586
+ ensureSelectionVisible( ) {
587
+ const sels = Array.from( this._multisel.values() );
588
+ if( sels.length) {
589
+ const item = this._itemWithID( sels[0] );
590
+ item?.scrollIntoView( {
591
+ behavior: "instant",
592
+ block: "nearest"
593
+ } )
594
+ }
595
+ }
577
596
  }
@@ -17,7 +17,7 @@
17
17
  @use "../shared.scss";
18
18
 
19
19
  :root {
20
- --menu-background: var( --background-primary );
20
+ --menu-background: var( --background-ternary );
21
21
  --menu-border: var( --border-hover );
22
22
 
23
23
  --menuitem-color: var( --text-ternary );;
@@ -28,7 +28,7 @@
28
28
  }
29
29
 
30
30
  .x4menu {
31
- @extend .shadow-lg;
31
+ @extend .shadow-xxl;
32
32
 
33
33
  position: absolute;
34
34
  overflow-y: auto;
@@ -39,6 +39,7 @@
39
39
 
40
40
  background-color: var(--menu-background);
41
41
  border: 1px solid var(--menu-border);
42
+ border-left: 4px solid var( --accent-background );
42
43
 
43
44
  max-height: calc( 100vh - 32px );
44
45
 
@@ -69,6 +70,17 @@
69
70
  }
70
71
  }
71
72
 
73
+ &.danger #icon {
74
+ .fa-primary {
75
+ fill: var( --color-danger-a50 );
76
+ }
77
+
78
+ .fa-secondary {
79
+ fill: var( --color-danger-a30 );
80
+ opacity: 1;
81
+ }
82
+ }
83
+
72
84
  #text {
73
85
  @extend .flex;
74
86
  }
@@ -34,7 +34,7 @@ export interface MenuItem {
34
34
  icon?: string;
35
35
  text: string | UnsafeHtml;
36
36
  menu?: Menu;
37
- disabled?: true;
37
+ disabled?: boolean;
38
38
  click?: DOMEventHandler;
39
39
  }
40
40
 
@@ -98,6 +98,11 @@ export class MessageBox extends Dialog<DialogProps>
98
98
  }
99
99
 
100
100
 
101
+ interface InputOptions {
102
+ password?: boolean;
103
+ trim?: boolean;
104
+ }
105
+
101
106
  @class_ns( "x4" )
102
107
  export class InputBox extends Dialog<DialogProps>
103
108
  {
@@ -110,7 +115,10 @@ export class InputBox extends Dialog<DialogProps>
110
115
  return input.getValue( );
111
116
  }
112
117
 
113
- private static _create( msg: string | UnsafeHtml, value: string, title: string ) {
118
+ private static _create( msg: string | UnsafeHtml, value: string, title: string, options?: InputOptions ) {
119
+
120
+ options = {trim: true, password: false, ...options };
121
+
114
122
  const box = new InputBox({
115
123
  modal: true,
116
124
  title,
@@ -122,7 +130,7 @@ export class InputBox extends Dialog<DialogProps>
122
130
  new Icon( { iconId: pen_icon }),
123
131
  new VBox( { flex: 1, content: [
124
132
  new Label( { text: msg } ),
125
- new Input( { value, type: "text" } )
133
+ new Input( { value, type: options.password ? "password" : "text", trim: options.trim } )
126
134
  ]})
127
135
  ]
128
136
  }),
@@ -138,11 +146,11 @@ export class InputBox extends Dialog<DialogProps>
138
146
  * idem with promise
139
147
  */
140
148
 
141
- static async showAsync( msg: string | UnsafeHtml, value: string, title?: string ) : Promise<string> {
149
+ static async showAsync( msg: string | UnsafeHtml, value: string, title?: string, options?: InputOptions ) : Promise<string> {
142
150
 
143
- return new Promise( (resolve, reject ) => {
151
+ return new Promise( (resolve, _reject ) => {
144
152
 
145
- const box = this._create( msg, value, title );
153
+ const box = this._create( msg, value, title, options );
146
154
 
147
155
  box.on( "btnclick", ( ev ) => {
148
156
  asap( ( ) => {
@@ -51,10 +51,17 @@
51
51
  border: none;
52
52
  border-top: 1px solid var( --border );
53
53
  margin-top: 1.5em;
54
+ border-radius: 0;
55
+
56
+ padding: 0;
54
57
 
55
58
  legend {
56
59
  background: none;
57
60
  top: -1.4em;
58
61
  left: 0;
59
62
  }
63
+
64
+ & >.body {
65
+ padding: 0;
66
+ }
60
67
  }
@@ -21,6 +21,7 @@ import { Rect, Point, class_ns, asap } from '../../core/core_tools';
21
21
  import { Box } from '../boxes/boxes'
22
22
 
23
23
  import "./popup.module.scss"
24
+ import { getGlobalZoom, getScrollbarSize } from '../../core/core_tools.js';
24
25
 
25
26
  export interface PopupEvents extends ComponentEvents {
26
27
  closed: ComponentEvent;
@@ -143,24 +144,27 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
143
144
  */
144
145
 
145
146
  displayAt( x: number, y: number ) {
147
+ const zm = getGlobalZoom( );
148
+
146
149
  //TODO: check is already visible
147
150
  this.setStyle( {
148
- left: x+"px",
149
- top: y+"px",
151
+ left: (x/zm)+"px",
152
+ top: (y/zm)+"px",
150
153
  })
151
154
 
152
155
  this._do_show( ); // to compute size
153
156
 
154
- const rc = this.getBoundingRect( );
155
- const width = window.innerWidth - 16;
156
- const height = window.innerHeight - 16;
157
-
158
- if( rc.right>width ) {
159
- this.setStyleValue( "left", width-rc.width );
157
+ const rc = this.getBoundingRect( ).scale( 1/zm );
158
+ const sbw = getScrollbarSize( );
159
+
160
+ const screen_width = window.innerWidth - sbw;
161
+ if( rc.right>screen_width ) {
162
+ this.setStyleValue( "left", screen_width-rc.width );
160
163
  }
161
164
 
162
- if( rc.bottom>height ) {
163
- this.setStyleValue( "top", height-rc.height );
165
+ const screen_height = window.innerHeight - sbw;
166
+ if( rc.bottom>screen_height ) {
167
+ this.setStyleValue( "top", screen_height-rc.height );
164
168
  }
165
169
  }
166
170
 
@@ -73,7 +73,7 @@ export class PropertyGrid extends VBox {
73
73
  if( props.groups ) {
74
74
  this.setItems( props.groups );
75
75
  }
76
- };
76
+ }
77
77
 
78
78
  /**
79
79
  *
@@ -125,8 +125,13 @@ export class PropertyGrid extends VBox {
125
125
  }
126
126
  }
127
127
 
128
+ let cls = "group";
129
+ if( g.cls ) {
130
+ cls += ' '+g.cls;
131
+ }
132
+
128
133
  const tr = new HBox({
129
- cls: 'group',
134
+ cls,
130
135
  content: [
131
136
  g.icon ? new Icon({ id: "icon", iconId: g.icon }) : null,
132
137
  new VBox( { content: [
@@ -240,9 +245,14 @@ export class PropertyGrid extends VBox {
240
245
  }
241
246
  });
242
247
  }
248
+
249
+ let cls = 'row';
250
+ if( item.cls ) {
251
+ cls += ' ' + item.cls;
252
+ }
243
253
 
244
254
  return new HBox({
245
- cls: 'row',
255
+ cls,
246
256
  content: [
247
257
  use_hdr ? new Component({ cls: 'cell hdr', content: item.title ?? item.name, tooltip: item.desc }) : null,
248
258
  new Component({ cls: 'cell', tag: "label", attrs: { "labelFor": item.name }, content: editor })
@@ -75,6 +75,10 @@
75
75
  box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
76
76
  }
77
77
 
78
+ .shadow-xxl {
79
+ box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.3), 0 8px 10px -6px rgb(0 0 0 / 0.3);
80
+ }
81
+
78
82
 
79
83
  @keyframes rotating {
80
84
  from {