x4js 2.2.55 → 2.2.57

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x4js",
3
- "version": "2.2.55",
3
+ "version": "2.2.57",
4
4
  "type": "module",
5
5
  "main": "src/x4.ts",
6
6
  "module": "src/x4.ts",
@@ -5,7 +5,7 @@
5
5
  * / \____ _|
6
6
  * /__/\__\ |_|
7
7
  *
8
- * @file boxes.ts
8
+ * @file boxes.module.scss
9
9
  * @author Etienne Cochard
10
10
  *
11
11
  * @copyright (c) 2026 R-libre ingenierie
@@ -14,529 +14,53 @@
14
14
  * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
15
  **/
16
16
 
17
- import { asap, class_ns, isArray, isNumber } from '../../core/core_tools';
18
- import { Component, ComponentEvents, ComponentProps, EvSelectionChange } from "../../core/component"
19
- import { EventCallback } from '../../core/core_events';
17
+ @use "../shared.scss";
20
18
 
21
- import "./boxes.module.scss";
19
+ .x4box {
20
+ // reset fieldset
21
+ margin: 0;
22
+ padding: 0;
23
+ border: none;
22
24
 
23
- export interface BoxProps extends ComponentProps {
24
- /** Optional HTML tag to use for the box. */
25
- tag?: string;
25
+ min-width: 0;
26
+ min-height: 0;
27
+ flex-shrink: 0;
28
+ align-self: stretch;
26
29
  }
27
30
 
28
- /**
29
- * A generic container component for grouping and laying out child components.
30
- *
31
- * The `refs` object is a typed directory of the children you need to reach
32
- * again later. Assign a child to `refs` as you build the content: the
33
- * assignment expression evaluates to the component itself, so it can be
34
- * inlined directly in the content array.
35
- *
36
- * @example
37
- * using refs: in your code, you can type
38
- *
39
- * class MyBox extends Box {
40
- * declare refs: {
41
- * a: Combobox;
42
- * b: Input;
43
- * }
44
- *
45
- * constructor( props ) {
46
- * super( props );
47
- *
48
- * this.setContent( [
49
- * this.refs.a = new ComboBox( { ... } );
50
- * this.refs.b = new Input( { ... } );
51
- * ])
52
- *
53
- * onSomeEvent() {
54
- * this.refs.a.getValue();
55
- * }
56
- * }
57
- *
58
- *
59
- *
60
- */
61
-
62
- @class_ns( "x4" )
63
- export class Box<P extends BoxProps=BoxProps,E extends ComponentEvents=ComponentEvents> extends Component<P,E> {
64
- protected refs: Record<string,Component> = {};
65
- }
66
-
67
-
68
- /**
69
- * A horizontal box layout component.
70
- * Arranges child components in a horizontal line.
71
- */
72
-
73
- @class_ns( "x4" )
74
- export class HBox<P extends BoxProps=BoxProps,E extends ComponentEvents=ComponentEvents> extends Box<P,E> {
75
- }
76
-
77
- /**
78
- * A vertical box layout component.
79
- * Arranges child components in a vertical stack.
80
- * The CSS class for this component is automatically generated as `x4vbox`.
81
- */
82
-
83
- @class_ns( "x4" )
84
- export class VBox<P extends BoxProps=BoxProps,E extends ComponentEvents=ComponentEvents> extends Box<P,E> {
85
- constructor( p: P ) {
86
- super( p );
31
+ .x4hbox {
32
+ @extend %hbox;
33
+ &.align-start {
34
+ align-items: start;
87
35
  }
88
36
  }
89
37
 
90
-
91
- type ContentBuilder = ( ) => Component;
92
-
93
-
94
- /**
95
- * Represents an item in a {@link StackBox}.
96
- */
97
-
98
- interface StackItem {
99
- /** Unique name for the stack item. */
100
- name: string;
101
- /** Content of the stack item, either a component or a builder function. */
102
- content: Component | ContentBuilder;
103
- title?: string;
104
- }
105
-
106
- /**
107
- * Events specific to the {@link StackBox} component.
108
- */
109
-
110
- interface StackeBoxEvents extends ComponentEvents {
111
- /** Fired when the current page changes. */
112
- pageChange?: EvSelectionChange;
38
+ .x4vbox {
39
+ @extend %vbox;
113
40
  }
114
41
 
115
- /**
116
- * Properties for the {@link StackBox} component.
117
- */
118
-
119
- export interface StackBoxProps extends Omit<ComponentProps,"content"> {
120
- /** Name of the default page to display. */
121
- default: string;
122
-
123
- /** List of stack items. */
124
- items: StackItem[];
125
-
126
- /** Callback for page change events. */
127
- pageChange?: EventCallback<EvSelectionChange>;
128
- }
129
-
130
-
131
- interface StackItemEx extends StackItem {
132
- page: Component;
133
- }
134
-
135
- /**
136
- * A stack of widgets where only one widget is visible at a time.
137
- * The CSS class for this component is automatically generated as `x4stackbox`.
138
- */
139
-
140
- @class_ns( "x4" )
141
- export class StackBox<P extends StackBoxProps = StackBoxProps, E extends StackeBoxEvents = StackeBoxEvents> extends Box<StackBoxProps,StackeBoxEvents> {
42
+ .x4stackbox {
43
+ display: flex;
142
44
 
143
- protected _items: StackItemEx[];
144
- protected _cur: number;
145
-
146
- constructor( props: StackBoxProps ) {
147
- super( props );
148
-
149
- this.mapPropEvents( props, "pageChange" );
150
-
151
- this._items = props.items?.map( itm => {
152
- return { ...itm, page: null as any};
153
- });
154
-
155
- if( props.default ) {
156
- this.select( props.default );
157
- }
158
- else if( this._items.length ) {
159
- this.select( this._items[0].name );
160
- }
161
- }
162
-
163
- /**
164
- * Adds a new item to the stack.
165
- * @param item - The item to add.
166
- */
167
-
168
- addItem( item: StackItem ) {
169
- this._items.push( {
170
- name: item.name,
171
- content: item.content,
172
- page: null
173
- });
174
- }
175
-
176
- /**
177
- * Removes an item from the stack by its name.
178
- * @param name - The name of the item to remove.
179
- */
180
-
181
- removeItem( name: string ) {
182
- const index = this._items.findIndex( x => x.name==name );
183
- if( index>=0 ) {
184
- const pg = this._items[index];
185
- if( pg?.page ) {
186
- this.removeChild( pg.page );
187
- }
188
-
189
- this._items.splice( index, 1 );
190
- }
191
- }
192
-
193
- /**
194
- * Selects a page by its name.
195
- * @param name - The name of the page to select.
196
- * @returns The selected page component, if any.
197
- */
198
-
199
- select( name: string ) {
200
- let sel = this.query( `:scope > .selected` );
201
- if( sel ) {
202
- sel.setClass( "selected", false );
203
- (sel as any).deactivate?.( );
204
- }
205
-
206
- this._cur = this._items.findIndex( x => x.name==name );
207
- const pg = this._items[this._cur];
208
-
209
- if( pg ) {
210
- if( !pg.page ) {
211
- pg.page = this._createPage( pg );
212
- this.appendContent( pg.page );
213
- }
214
-
215
- sel = pg.page;
216
- if( sel ) {
217
- (sel as any).activate?.( );
218
- sel.setClass( "selected", true );
219
- }
220
-
221
- asap( ( ) => this.fire( "pageChange", { selection: [pg.name], empty: !sel } ) );
222
- }
223
-
224
- return pg?.page;
225
- }
226
-
227
- /**
228
- *
229
- */
230
-
231
- private _createPage( page: StackItemEx ) {
232
-
233
- let content: Component;
234
- if( page.content instanceof Function ) {
235
- content = page.content( );
236
- page.content = content; // keep it
237
- }
238
- else {
239
- content = page.content;
240
- }
241
-
242
- content?.setData( "stackname", page.name );
243
- return content;
244
- }
245
-
246
- /**
247
- * Retrieves a page by its name.
248
- * @param name - The name of the page to retrieve.
249
- * @returns The page content, if found.
250
- */
251
-
252
- getPage( name: string ) {
253
- const pg = this._items.find( x => x.name==name );
254
- return pg ? pg.content : null;
45
+ &>* {
46
+ @extend %fit;
47
+ position: absolute !important;
255
48
  }
256
49
 
257
- /**
258
- * Gets the total number of pages in the stack.
259
- * @returns The number of pages.
260
- */
261
-
262
- getPageCount( ) {
263
- return this._items.length;
264
- }
265
-
266
- /**
267
- * Enumerates the names of all pages in the stack.
268
- * @returns An array of page names.
269
- */
270
-
271
- enumPageNames( ) {
272
- return this._items.map( x => x.name );
273
- }
274
-
275
- /**
276
- * Retrieves a stack item by its name.
277
- * @param name - The name of the item to retrieve.
278
- * @returns The stack item, if found.
279
- */
280
-
281
- getItem( name: string ) {
282
- const pg = this._items.find( x => x.name==name );
283
- return pg;
284
- }
285
-
286
- /**
287
- * Gets the name of the currently selected page.
288
- * @returns The name of the current page, if any.
289
- */
290
-
291
- getCurPage( ) {
292
- const c = this._items[this._cur];
293
- return c?.name;
50
+ &>*:not(.selected) {
51
+ display: none !important;
294
52
  }
295
53
  }
296
54
 
297
- // :: ASSIST BOX ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
298
-
299
-
300
- /**
301
- * A specialized stack box for assisted navigation, such as wizards or carousels.
302
- * The CSS class for this component is automatically generated as `x4assistbox`.
303
- */
304
-
305
- @class_ns( "x4" )
306
- export class AssistBox extends StackBox {
307
-
308
- /**
309
- * Selects the next or previous page in the stack.
310
- * @param nxt - If `true`, selects the next page; otherwise, selects the previous page.
311
- */
312
-
313
- selectNextPage( nxt = true ) {
314
- let p;
315
- if( nxt && this._cur<this._items.length-1 ) {
316
- p = this._items[this._cur+1];
317
- }
318
- else if( !nxt && this._cur>0 ) {
319
- p = this._items[this._cur-1];
320
- }
321
-
322
- if( p ) {
323
- this.select( p.name );
324
- }
325
- }
326
-
327
- /**
328
- * Checks if the current page is the first page.
329
- * @returns `true` if the current page is the first page.
330
- */
331
-
332
- isFirstPage( ) {
333
- return this._cur==0;
334
- }
335
-
336
- /**
337
- * Checks if the current page is the last page.
338
- * @returns `true` if the current page is the last page.
339
- */
340
-
341
- isLastPage( ) {
342
- return this._cur==this._items.length-1;
343
- }
344
- }
345
-
346
-
347
-
348
- // :: GRIDBOX ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
349
-
350
- interface GridBoxItem {
351
- row: number; // starts at 0
352
- col: number; // starts at 0
353
- item: Component;
354
- }
355
-
356
- export interface GridBoxProps extends Omit<BoxProps,"content"> {
357
- rows?: number | string | string[];
358
- columns?: number | string | string[];
359
- items?: GridBoxItem[];
360
- }
361
-
362
- /**
363
- * Grid-based layout container.
364
- * Auto-generates CSS class: `x4gridbox`.
365
- */
366
-
367
- @class_ns("x4")
368
- export class GridBox<P extends GridBoxProps=GridBoxProps,E extends ComponentEvents=ComponentEvents> extends Box<P,E> {
369
-
370
- constructor( props: P ) {
371
- super( props );
372
-
373
- if( props.rows!==undefined ) {
374
- this.setRows( props.rows );
375
- }
376
-
377
- if( props.columns!==undefined ) {
378
- this.setCols( props.columns );
379
- }
380
-
381
- if( props.items ) {
382
- this.setItems( props.items );
383
- }
384
- }
385
-
386
- /**
387
- * Sets grid rows (e.g., `2`, `"1fr 2fr"`, `["1fr", "2fr"]`).
388
- * @param r - Rows definition.
389
- */
390
-
391
- setRows( r: number | string | string[] ) {
392
- if( isArray(r) ) {
393
- r = r.join( " " );
394
- }
395
- else if( isNumber(r) ) {
396
- r = `repeat( ${r}, 1fr )`;
397
- }
398
-
399
- this.setStyleValue( "gridTemplateRows", r );
400
- }
401
-
402
- /**
403
- * Sets grid columns (e.g., `3`, `"1fr 1fr"`, `["auto", "1fr"]`).
404
- * @param r - Columns definition.
405
- */
406
-
407
- setCols( r: number | string | string[] ) {
408
- if( isArray(r) ) {
409
- r = r.join( " " );
410
- }
411
- else if( isNumber(r) ) {
412
- r = `repeat( ${r}, 1fr )`;
413
- }
414
-
415
- this.setStyleValue( "gridTemplateColumns", r );
416
- }
417
-
418
- /**
419
- * Sets the number of rows.
420
- * @param n - Row count.
421
- */
422
-
423
- setRowCount( n: number ) {
424
- this.setStyleValue( "gridTemplateRows", `repeat(${n})` );
425
- }
426
-
427
- /**
428
- * Sets the number of columns.
429
- * @param n - Column count.
430
- */
431
-
432
- setColCount( n: number ) {
433
- this.setStyleValue( "gridTemplateColumns", `repeat(${n})` );
434
- }
435
-
436
- /**
437
- * Sets grid template areas (e.g., `["a a", "b c"]`).
438
- * @param t - Template strings.
439
- */
440
-
441
- setTemplate( t: string[] ) {
442
- this.setAttribute( "grid-template-area", t.map( x => '"' + x + '"' ).join(" ") );
443
- }
444
-
445
- /**
446
- * Places items at specific grid positions.
447
- * @param items - Array of `{row, col, item}`.
448
- */
449
-
450
- setItems( items: GridBoxItem[] ) {
451
- items.forEach( x => {
452
- x.item.setStyle( {
453
- gridColumn: (x.col+1)+"",
454
- gridRow: (x.row+1)+"",
455
- } );
456
- });
457
-
458
- this.setContent( items.map( x => x.item ) );
459
- }
460
- }
461
-
462
-
463
- // :: MASONRY ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
464
-
465
- // from a nice article of Andy Barefoot
466
- // https://medium.com/@andybarefoot/a-masonry-style-layout-using-css-grid-8c663d355ebb
467
-
468
- interface MasonryProps extends Omit<BoxProps,"content"> {
469
- items: Component[];
55
+ .x4gridbox {
56
+ display: grid;
470
57
  }
471
58
 
472
- /**
473
- * Masonry-style layout (Pinterest-like).
474
- * Auto-generates CSS class: `x4masonrybox`.
475
- */
476
-
477
- @class_ns("x4")
478
- export class MasonryBox extends Box<MasonryProps> {
479
-
480
- constructor(props: MasonryProps ) {
481
- super(props);
482
-
483
- this.addDOMEvent( 'resized', () => {
484
- this.resizeAllItems( );
485
- });
486
-
487
- if( props.items ) {
488
- this.setItems( props.items );
489
- }
490
- }
491
-
492
- /**
493
- * Resizes a single masonry item.
494
- * @param item - Item to resize.
495
- */
496
-
497
- resizeItem(item: Component) {
498
- const style = this.getComputedStyle();
499
-
500
- const rowHeight = parseInt(style['gridAutoRows']);
501
- const rowGap = parseInt(style['rowGap']);
59
+ .x4masonrybox {
60
+ display: grid;
61
+ grid-gap: 10px;
62
+ grid-template-columns: repeat(auto-fill, minmax(250px,1fr));
63
+ grid-auto-rows: 10px;
502
64
 
503
- let content = item.query('.content');
504
- if( !content ) {
505
- content = item;
506
- }
507
-
508
- if (content && (rowHeight + rowGap)) {
509
- const rc = content.getBoundingRect();
510
- const rowSpan = Math.ceil( (rc.height + rowGap) / (rowHeight + rowGap) );
511
- item.setStyleValue('gridRowEnd', "span " + rowSpan);
512
- }
513
- }
514
-
515
- /**
516
- * Resizes all items to fit the grid.
517
- */
518
-
519
- resizeAllItems( ) {
520
- const els = this.queryAll( ".item" );
521
- els.forEach( itm => {
522
- this.resizeItem( itm );
523
- } );
524
- }
525
-
526
- /**
527
- * Sets masonry items.
528
- * @param items - Array of components.
529
- */
530
-
531
- setItems( items: Component[] ) {
532
- const els = items.map( x => {
533
- return new Box( {
534
- cls: 'item',
535
- content: x
536
- } );
537
- });
538
-
539
- this.setContent( els );
540
- }
65
+ overflow: hidden;
541
66
  }
542
-
@@ -62,6 +62,9 @@ export interface BoxProps extends ComponentProps {
62
62
  @class_ns( "x4" )
63
63
  export class Box<P extends BoxProps=BoxProps,E extends ComponentEvents=ComponentEvents> extends Component<P,E> {
64
64
  protected refs: Record<string,Component> = {};
65
+ constructor( props: P ) {
66
+ super( { tag: "fieldset", ...props } );
67
+ }
65
68
  }
66
69
 
67
70
 
@@ -38,4 +38,83 @@
38
38
  fill: var( --border );
39
39
  }
40
40
  }
41
+ }
42
+
43
+ .x4btngroup.packed {
44
+ gap: 0px;
45
+
46
+ .x4button {
47
+ background-color: var( --ol-button-background );
48
+ margin: 0;
49
+ color: var( --ol-button-color );
50
+
51
+ .x4icon {
52
+ height: 1.5em;
53
+ width: 1.5em;
54
+ }
55
+
56
+ #icon {
57
+ height: 1.5em;
58
+ width: 1.5em;
59
+ }
60
+
61
+ &:not(:disabled) {
62
+ #icon {
63
+ .fa-secondary {
64
+ fill: var( --accent-background-hover );
65
+ opacity: 0.7;
66
+ }
67
+
68
+ .fa-primary {
69
+ fill: var( --accent-background );
70
+ }
71
+ }
72
+
73
+ &.icon-danger:not(:disabled) {
74
+ #icon {
75
+ .fa-primary {
76
+ fill: var( --alert-background );
77
+ }
78
+ }
79
+ }
80
+
81
+ &.icon-success {
82
+ #icon {
83
+ .fa-primary {
84
+ fill: var( --success-background );
85
+ }
86
+ }
87
+ }
88
+
89
+ &:hover{
90
+ background-color: var( --ol-button-background-hover );
91
+ color: var( --ol-button-color-hover );
92
+ }
93
+
94
+ &:active{
95
+ background-color: var( --ol-button-background-active );
96
+ color: var( --ol-button-color-active );
97
+ #icon {
98
+ color: var( --button-icon-color );
99
+ }
100
+ }
101
+
102
+ &:focus {
103
+ background-color: var( --ol-button-background-hover );
104
+ }
105
+ }
106
+
107
+ &:disabled {
108
+ color: var( --disabled-color-dark );
109
+ #icon {
110
+ .fa-primary {
111
+ fill: var( --disabled-color-dark ) !important;
112
+ }
113
+
114
+ .fa-secondary {
115
+ fill: var( --disabled-background ) !important;
116
+ }
117
+ }
118
+ }
119
+ }
41
120
  }
@@ -85,6 +85,10 @@
85
85
  }
86
86
  }
87
87
 
88
+ &:disabled> #icon {
89
+ fill: var( --button-color-disabled );
90
+ }
91
+
88
92
  &> #label {
89
93
  flex-grow: 1;
90
94
 
@@ -10,7 +10,7 @@
10
10
  width: 100%;
11
11
  display: block;
12
12
  overflow: hidden;
13
- min-height: 2em;
13
+ min-height: 1em;
14
14
  border-bottom: 1px solid var( --border );
15
15
 
16
16
 
@@ -1,4 +1,4 @@
1
- import { class_ns } from '../../core/core_tools';
1
+ import { class_ns, getScrollbarSize } from '../../core/core_tools';
2
2
  import { Component, ComponentProps } from '../../core/component';
3
3
  import { HBox } from '../boxes/boxes';
4
4
  import { Label } from '../label/label';
@@ -106,8 +106,9 @@ export class Header extends HBox<HeaderProps> {
106
106
  } );
107
107
 
108
108
  const rc = this.getBoundingRect( );
109
+ const cs = this.getComputedStyle( );
109
110
 
110
- let rest = (rc.width-filled-10);
111
+ let rest = (rc.width-filled-getScrollbarSize()) - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight);
111
112
  const unit = Math.ceil( rest/count );
112
113
 
113
114
  //console.log( "filled", filled );
@@ -44,6 +44,7 @@
44
44
 
45
45
  &>.x4header {
46
46
  border-bottom: 1px solid var( --border );
47
+ padding: 0 4px;
47
48
  }
48
49
 
49
50
  &>.title {
@@ -82,65 +83,64 @@
82
83
  }
83
84
  }
84
85
 
85
- .x4item {
86
- @extend %flex;
87
-
88
-
89
- padding: 4px;
90
- background-color: transparent;
91
- color: var( --text-primary );
92
-
93
- // multicolumns
94
- &:has( >.x4hbox ) {
95
- padding: 4px 0;
96
- }
97
-
98
- --label-color: var( --listbox-item-color );
99
-
100
- .x4label,
101
- .x4simpletext {
102
- padding: 2px 6px;
103
-
104
- #icon {
105
- color: var( --listbox-icon-color );
106
- fill: var( --listbox-icon-color );
107
- }
108
- }
109
-
110
- &:hover {
111
- background-color: var( --listbox-item-background-hover );
112
- --label-color: var( --listbox-item-color-hover );
113
- }
114
-
115
- &.selected {
116
- background-color: var( --listbox-item-background-sel );
117
- color: var( --listbox-item-color-sel );
118
-
119
- --label-color: var( --listbox-item-color-sel );
120
-
121
- &> .x4label {
122
- #icon {
123
- color: var( --listbox-item-color-sel );
124
- fill: var( --listbox-item-color-sel );
125
- }
126
- }
127
- }
128
-
129
- .column {
130
- background-color: transparent;
131
- overflow: hidden;
132
- }
133
-
134
- .ref-c1 { width: var( --ref-c1-width); }
135
- .ref-c2 { width: var( --ref-c2-width); }
136
- .ref-c3 { width: var( --ref-c3-width); }
137
- .ref-c4 { width: var( --ref-c4-width); }
138
- .ref-c5 { width: var( --ref-c5-width); }
139
-
140
- //&:active{
141
- //background-color: var( --color-80 );
142
- //color: var(--color-0);
143
- //}
86
+ .x4item {
87
+ @extend %flex;
88
+
89
+ padding: 4px;
90
+ background-color: transparent;
91
+ color: var( --text-primary );
92
+
93
+ // multicolumns
94
+ &:has( >.x4hbox ) {
95
+ padding: 4px 0;
96
+ }
97
+
98
+ --label-color: var( --listbox-item-color );
99
+
100
+ .x4label,
101
+ .x4simpletext {
102
+ padding: 2px 6px;
103
+
104
+ #icon {
105
+ color: var( --listbox-icon-color );
106
+ fill: var( --listbox-icon-color );
107
+ }
108
+ }
109
+
110
+ &:hover {
111
+ background-color: var( --listbox-item-background-hover );
112
+ --label-color: var( --listbox-item-color-hover );
113
+ }
114
+
115
+ &.selected {
116
+ background-color: var( --listbox-item-background-sel );
117
+ color: var( --listbox-item-color-sel );
118
+
119
+ --label-color: var( --listbox-item-color-sel );
120
+
121
+ &> .x4label {
122
+ #icon {
123
+ color: var( --listbox-item-color-sel );
124
+ fill: var( --listbox-item-color-sel );
125
+ }
126
+ }
127
+ }
128
+
129
+ .column {
130
+ background-color: transparent;
131
+ overflow: hidden;
132
+ }
133
+
134
+ .ref-c1 { width: var( --ref-c1-width); }
135
+ .ref-c2 { width: var( --ref-c2-width); }
136
+ .ref-c3 { width: var( --ref-c3-width); }
137
+ .ref-c4 { width: var( --ref-c4-width); }
138
+ .ref-c5 { width: var( --ref-c5-width); }
139
+
140
+ //&:active{
141
+ //background-color: var( --color-80 );
142
+ //color: var(--color-0);
143
+ //}
144
144
 
145
145
  }
146
146
 
@@ -152,7 +152,7 @@
152
152
  border-top: 1px solid var(--border);
153
153
  margin: 0;
154
154
 
155
- &.x4btngroup {
155
+ &.--x4btngroup {
156
156
  gap: 0px;
157
157
 
158
158
  .x4button {
@@ -113,6 +113,7 @@ private _view: Viewport;
113
113
 
114
114
  if( props.footer ) {
115
115
  props.footer.setAttribute( "id", "footer" );
116
+ props.footer.addClass( "packed" );
116
117
  }
117
118
 
118
119
  if( props.header ) {
@@ -172,12 +172,13 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
172
172
  displayAt( x: number, y: number ) {
173
173
  const zm = getGlobalZoom( );
174
174
 
175
- //TODO: check is already visible
176
- this.setStyle( {
177
- left: (x/zm)+"px",
178
- top: (y/zm)+"px",
179
- })
175
+ x /= zm;
176
+ y /= zm;
180
177
 
178
+ this.setStyleValue( "left", x );
179
+ this.setStyleValue( "top", y );
180
+
181
+ //TODO: check is already visible
181
182
  this._do_show( ); // to compute size
182
183
 
183
184
  const rc = this.getBoundingRect( ).scale( 1/zm );
@@ -11,6 +11,7 @@
11
11
 
12
12
  --propertygrid-odd-background: var(--color-primary-a5);
13
13
  --propertygrid-even-background: transparent;
14
+ --propertygrid-disabled-background: rgb(170 170 170 / 14%);
14
15
  }
15
16
 
16
17
  .x4propertygrid {
@@ -31,71 +32,6 @@
31
32
  &> #footer {
32
33
  border-top: 1px solid var(--border);
33
34
  margin: 0;
34
-
35
- &.x4btngroup {
36
- gap: 0px;
37
-
38
- .x4button {
39
- background-color: var( --ol-button-background );
40
- margin: 0;
41
- color: var( --ol-button-color );
42
-
43
- &:disabled {
44
- color: var( --ol-button-color-disabled );
45
- }
46
-
47
- .x4icon {
48
- height: 1.5em;
49
- width: 1.5em;
50
-
51
- .fa-primary, .fa-secondary {
52
- fill: var( --accent-background );
53
- }
54
- }
55
-
56
- &.icon-danger {
57
- .x4icon {
58
- .fa-secondary {
59
- fill: var( --disabled-color-dark);
60
- opacity: 0.7;
61
- }
62
-
63
- .fa-primary {
64
- fill: var( --alert-background );
65
- }
66
- }
67
- }
68
-
69
- &.icon-success {
70
- .x4icon {
71
- .fa-primary {
72
- fill: var( --success-background );
73
- }
74
- }
75
- }
76
-
77
- &:hover{
78
- background-color: var( --ol-button-background-hover );
79
- color: var( --ol-button-color-hover );
80
- }
81
-
82
- &:active{
83
- background-color: var( --ol-button-background-active );
84
- color: var( --ol-button-color-active );
85
- #icon {
86
- color: var( --button-icon-color );
87
- }
88
- }
89
-
90
- &:focus {
91
- background-color: var( --ol-button-background-hover );
92
- }
93
-
94
- &:disabled {
95
- color: var( --disabled-color-dark );
96
- }
97
- }
98
- }
99
35
  }
100
36
 
101
37
  .root {
@@ -158,13 +94,18 @@
158
94
  //border-left: 1px solid var(--color-primary-a50);
159
95
  //border-right: 1px solid var(--color-primary-a50);
160
96
 
161
- &.even {
97
+ &.even:not(:disabled) {
162
98
  background-color: var(--propertygrid-even-background)
163
99
  }
164
- &.odd {
100
+
101
+ &.odd:not(:disabled) {
165
102
  background-color: var(--propertygrid-odd-background)
166
103
  }
167
104
 
105
+ &.odd:disabled {
106
+ background-color: var(--propertygrid-disabled-background)
107
+ }
108
+
168
109
  &:has(:focus) {
169
110
  background-color: var(--color-primary-a20);
170
111
  }
@@ -79,6 +79,7 @@ export class PropertyGrid extends VBox {
79
79
 
80
80
  if( props.footer ) {
81
81
  props.footer.setAttribute( "id", "footer" );
82
+ props.footer.addClass( "packed" );
82
83
  }
83
84
 
84
85
  const scroller = new ScrollView( { cls: "body" } );
@@ -40,6 +40,8 @@
40
40
  border: 1px solid var( --tooltip-border );
41
41
  background-color: var( --tooltip-background );
42
42
 
43
+ width: max-content;
44
+
43
45
  .x4icon {
44
46
  padding: 2px 4px;
45
47
  svg {
@@ -899,18 +899,20 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
899
899
 
900
900
  enable( ena = true ): this {
901
901
  this.setAttribute( "disabled", !ena ? 'true' : null );
902
+ this.setAttribute( "inert", !ena ? 'true' : null );
902
903
 
904
+ /*
905
+ boxes are now fieldset, this is native
903
906
  if( this.dom instanceof HTMLInputElement || this.dom instanceof HTMLButtonElement ) {
904
907
  this.dom.disabled = !ena;
905
908
  }
906
909
 
907
- // propagate diable state to all input children
908
- const nodes = this.enumChildNodes( true );
910
+ // propagate enable state to all input children
911
+ const nodes = this.enumChildComponents( true );
909
912
  nodes.forEach( x => {
910
- if( x instanceof HTMLInputElement || x instanceof HTMLButtonElement ) {
911
- x.disabled = !ena;
912
- }
913
+ x.enable( ena );
913
914
  });
915
+ */
914
916
 
915
917
  return this;
916
918
  }