x4js 2.3.24 → 2.3.25

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/aicontext.md CHANGED
@@ -91,6 +91,8 @@ fire<K>(name: K, ev: E[K]): void
91
91
 
92
92
  - `capturing = true` inserts at the head of the listener list.
93
93
  - `fire()` automatically injects `source`, `type`, `stopPropagation()`, `preventDefault()` when absent.
94
+ - A listener that throws: `fire()` sends its error to `console.error`, calls the next listeners, and returns
95
+ normally; the one that fires never gets the error of a listener. (An `async` listener rejects its own promise.)
94
96
 
95
97
  ### `CoreEvent`
96
98
 
@@ -140,7 +142,7 @@ interface ComponentProps {
140
142
  flex?: number | boolean // true → "x4flex"; number → flex-grow
141
143
  disabled?: boolean
142
144
  hidden?: boolean
143
- tooltip?: string
145
+ tooltip?: string // shown as html: escape a text from a user (see initTooltips)
144
146
  existingDOM?: HTMLElement // wrap an existing DOM node
145
147
  }
146
148
 
@@ -220,7 +222,8 @@ childCount(): number
220
222
  enumChildComponents(recursive: boolean): Component[]
221
223
  enumChildNodes(recursive: boolean): Node[]
222
224
  visitChildren(cb: (el: Component) => boolean): void
223
- static parentElement<T>(dom: Node, cls?: Constructor<T>): T
225
+ static parentElement<T>(dom: Node, cls?: Constructor<T>): T // starts at the parent of dom, not at dom:
226
+ // test componentFromDOM(dom) first for the component of dom itself
224
227
  ```
225
228
 
226
229
  ### Visibility / state / focus / geometry
@@ -312,6 +315,9 @@ interface BoxProps extends ComponentProps { tag?: string }
312
315
 
313
316
  ### `Box.refs` — typed directory of the children to reach again later
314
317
 
318
+ Only a `Box` has `refs`: a class derived from `Component` (not from `Box`) has none, and `this.refs.x = ...` there
319
+ compiles with a `declare refs` but throws a `TypeError` at run time.
320
+
315
321
  `Box` owns a `protected refs: Record<string, Component>`. Declare its shape in the subclass, then assign each child
316
322
  to `refs` while building the content: the assignment evaluates to the component, so it is written inline in the
317
323
  content array. Do not keep such children in separate private fields, and do not use `refs.x` for a child that was
@@ -826,29 +832,46 @@ class FileDialog extends Component {} // programmatic file picker
826
832
  interface ListItem {
827
833
  id: ListboxID // number | string
828
834
  text: string | UnsafeHtml
829
- iconId?: string; data?: any; cls?: string; checked?: boolean
835
+ sub_cols?: (string | UnsafeHtml | Component)[] // extra columns after text (9 at most, ref-c2...ref-c10, see Header);
836
+ // a null value makes no cell. A Treeview draws them too
837
+ iconId?: string; data?: any; cls?: string
838
+ checked?: boolean // checkable: the starting state of the case (getChecked() gives the current one)
830
839
  }
831
840
  interface ListboxProps extends Omit<ComponentProps,"content"> {
832
841
  items?: ListItem[]
833
842
  renderer?: (item: ListItem) => Component
834
843
  title?: string; icon?: string
835
844
  header?: Header; footer?: Component
836
- checkable?: true; multisel?: true
845
+ checkable?: boolean // a case at the start of each line
846
+ multisel?: true
847
+ reselect?: boolean // a click on the item already selected fires selectionChange again
848
+ // (lists linked together: the view shows what was clicked last)
837
849
  emptyMsg?: string // shown in place of the items while there are none
838
850
  dblClick?: EventCallback<EvDblClick>
839
851
  selectionChange?: EventCallback<EvSelectionChange>
840
852
  contextMenu?: EventCallback<EvContextMenu>
853
+ checkChange?: EventCallback<EvCheckChange> // EvCheckChange { id: ListboxID, checked: boolean }
841
854
  }
842
855
  // emptyMsg: without it the text is _tr.global.empty_list ("Empty list"). It is shown by setItems
843
856
  // (the constructor calls it) when there is no item. The listbox then has the class "empty".
857
+ // click: no prop; listbox.on( "click", ev => ... ), ev.context the id of the item. It is fired on the item
858
+ // already selected too, which fires no selectionChange.
859
+ // checkable: renderItem puts the case (.x4check) first in the line, with a renderer of yours too;
860
+ // a checked line has the class "checked". A click on the case, or Space for the last item
861
+ // selected, changes it and fires checkChange: no click, no selection. A double click on the
862
+ // case is ignored (its two clicks change it twice). setChecked fires nothing; updateItem keeps
863
+ // the state of the case. With a header, the columns start after the case.
844
864
  class Listbox extends Component {
845
- setItems(items: ListItem[], keepSel?: boolean): void
865
+ setItems(items: ListItem[], keepSel?: boolean): void // one selectionChange at the end, with the selection it ends
866
+ // with, when there was one before or there is one after (keepSel: the same ids too)
846
867
  appendItem(item: ListItem, prepend?: boolean, select?: boolean): void
847
868
  updateItem(id: any, item: ListItem): void
848
869
  getItem(id: ListboxID): ListItem
849
870
  select(ids: ListboxID | ListboxID[], notify?: boolean): void
850
871
  clearSelection(fireEvent?: boolean): void
851
872
  getSelection(): ListboxID[]
873
+ getChecked(): ListboxID[] // checkable: in the order of the list
874
+ setChecked(ids: ListboxID[]): void // these checked, the others not; no event
852
875
  filter(filter: string | RegExp): void
853
876
  navigate(sens: kbNav): boolean
854
877
  ensureSelectionVisible(): void
@@ -1036,6 +1059,7 @@ interface ButtonProps extends ComponentProps {
1036
1059
  menu?: MenuElement[] | (() => MenuElement[]) // a click opens this Menu under the button, in place of the click event;
1037
1060
  // a function is called at each click; the button gets the class "with-menu"
1038
1061
  }
1062
+ // its <button> is type="button": in a <form>, Enter in a field does not click it. attrs: { type: "submit" } to submit
1039
1063
  class Button extends Component {
1040
1064
  click(): void // simulate a click
1041
1065
  setText(text: string|UnsafeHtml): void
@@ -1078,6 +1102,8 @@ class Popup extends Box {
1078
1102
  isOpen(): boolean
1079
1103
  // show()/hide() inherited from Component; show() routes to displayCenter()
1080
1104
  }
1105
+ // A modal popup (a Dialog, a MessageBox...) keeps the element that had the focus when it opens and gives
1106
+ // it back the focus when it closes, unless the focus was put elsewhere meanwhile.
1081
1107
  ```
1082
1108
 
1083
1109
  ### `Dialog`
@@ -1101,6 +1127,10 @@ class Dialog extends Popup {
1101
1127
  show(): this // each time: the focus on its first field
1102
1128
  showAsync(): Promise<string> // the name of the clicked button; null when closed without a button (Escape, close box)
1103
1129
  }
1130
+ // Keys: Escape closes it (or clicks `closable` when it is a button name). Enter clicks its default
1131
+ // button ("ok.default" in buttons), except in a textarea or an element contenteditable, where Enter is
1132
+ // a new line and Ctrl+Enter (Cmd+Enter) clicks it. Out of a dialog, Enter goes to the next field,
1133
+ // except in a textarea or a contenteditable.
1104
1134
  ```
1105
1135
 
1106
1136
  ### `MessageBox` / `InputBox` / `PromptBox` / `ProgressionBox`
@@ -1120,10 +1150,22 @@ class PromptBox extends Dialog {
1120
1150
  class ProgressionBox extends Dialog {
1121
1151
  constructor(title: string)
1122
1152
  addText(text: string|UnsafeHtml, perc: number): void
1123
- addError(text: string|UnsafeHtml, perc: number): void
1124
- clear(): void
1125
- done(): void // hide after 5s if no error, else show OK button
1126
- }
1153
+ addError(text: string|UnsafeHtml, perc: number): void // its icon, in red; the title bar in red until clear()
1154
+ addWarning(text: string|UnsafeHtml, perc: number): void // its icon, in orange
1155
+ addSection(title: string|UnsafeHtml, perc: number): void // a section (<details>, open): the lines that follow go
1156
+ // in it; a click on its title folds it; its title has the icon
1157
+ // of the worst of its lines. Without any, the lines go in the log
1158
+ setText(text: string|UnsafeHtml, perc: number): void // in place of all the lines
1159
+ clear(): void // no line, no error, no button OK
1160
+ done(): void // shows OK: after an error or a warning it waits for a click,
1161
+ // their counts on the left of the bar ("2 errors / 1 warning",
1162
+ // from _tr.global.progression), the icon of the worst;
1163
+ // else it counts "OK (5s)" and closes the box at 0
1164
+ }
1165
+ // ProgressionBox: its lines in a <pre>, 10 lines high, scrolled both ways (the box keeps its size);
1166
+ // a string is text, an UnsafeHtml html. Ctrl+A in the lines selects them only (a folded section too).
1167
+ // Colours: --msgbox-log-background, -border, -border-focus, -error-color, -warning-color,
1168
+ // -scrollbar (the thumb of its scroll bars; their track is its background).
1127
1169
  ```
1128
1170
 
1129
1171
  ### `Notification`
@@ -1154,6 +1196,9 @@ class Form extends Box {
1154
1196
  validate(): FormValues // null if invalid; the values the validator changes are written back in their fields (any element with the name, every radio of a group)
1155
1197
  }
1156
1198
  // Collects all children (recursive) that have a "name" attribute and implement queryInterface("form-element").
1199
+ // Never sent by the browser: its "submit" is prevented (Enter in its only field would send it).
1200
+ // A Form is flex by default (it takes the free height of its box) and has a padding of 16px.
1201
+ // flex: false: the height of its content (a toolbar, a line); its padding set back by a css rule.
1157
1202
  ```
1158
1203
 
1159
1204
  ---
@@ -1405,7 +1450,7 @@ class MyView extends VBox {
1405
1450
  }
1406
1451
  ```
1407
1452
 
1408
- - A component shortcut also works while the focus is nowhere, if the component is the last one with shortcuts that had the focus: of two views shown together, the one the user works in answers.
1453
+ - A component shortcut also works while the focus is nowhere, if the component is the last one with shortcuts that had the focus: of two views shown together, the one the user works in answers. Not while that component is hidden (in a tab not shown, for instance).
1409
1454
  - A key typed in an `input`, a `textarea` or a `select` is not a shortcut, unless `editable` is set.
1410
1455
  - A key that what has the focus already used (it called `preventDefault` or `stopPropagation`) is not a shortcut.
1411
1456
  - A shortcut always takes its key (`preventDefault`). For a key that must sometimes be left alone, use a `keydown` handler instead.
@@ -1720,6 +1765,11 @@ createPainter(c2d: CanvasRenderingContext2D, w: number, h: number): CanvasEx /
1720
1765
  initTooltips(): void // initialize the global tooltip system
1721
1766
  ```
1722
1767
 
1768
+ No tooltip is shown until the application calls `initTooltips()` once (the prop `tooltip` only sets an attribute).
1769
+ `initTooltips()` shows the text of the attribute `tooltip` (the prop `tooltip`), or the one `getTip()`
1770
+ of a `"tip-handler"` gives, **as html**. A text that comes from a user is escaped by the application
1771
+ with `sanitizeHtml( text )`: `tooltip: \`Open <b>${sanitizeHtml( file.name )}</b>\``.
1772
+
1723
1773
  ---
1724
1774
 
1725
1775
  ## State (`core_state`)
package/aiguide.md CHANGED
@@ -20,7 +20,7 @@ Prefer:
20
20
  - plain objects for application data
21
21
  - `State` for observable application state
22
22
  - local events for things that happened locally
23
- - `GlobalEvent` for application-wide or distributed messages
23
+ - a global message (`Application.fireGlobal`) for application-wide or distributed messages
24
24
  - `DataStore` for specialized tabular data
25
25
  - persistent `Component` objects backed by the real DOM
26
26
  - CSS and CSS variables for presentation and theming
@@ -84,13 +84,13 @@ For a simple event payload, `context` can carry the relevant identity or value w
84
84
 
85
85
  Do not put transient actions into State merely to trigger behavior.
86
86
 
87
- ### GlobalEvent
87
+ ### Global messages
88
88
 
89
- Use `GlobalEvent` for application-wide messages.
89
+ Use a global message (`Application.fireGlobal`) for application-wide messages.
90
90
 
91
- A GlobalEvent may also be transported between processes, clients or a server.
91
+ A global message may also be transported between processes, clients or a server.
92
92
 
93
- Views should not care how a GlobalEvent is transported.
93
+ Views should not care how a global message is transported.
94
94
 
95
95
  In particular, do not expose WebSocket details to UI components merely because WebSocket is used as the transport.
96
96
 
@@ -295,9 +295,24 @@ Do not use events as a replacement for persistent application state.
295
295
 
296
296
  ---
297
297
 
298
- ## 9. GlobalEvent
298
+ ## 9. Global messages
299
299
 
300
- Use `GlobalEvent` for messages whose scope is the application or distributed system rather than a local component relationship.
300
+ A global message is sent by `Application.fireGlobal( msg, params )` and received by
301
+ `component.onGlobalEvent( ev => ... )`, `ev` an `EvMessage { msg, params }`. The listener of
302
+ `onGlobalEvent` is taken off when the component leaves the page.
303
+
304
+ ```ts
305
+ Application.fireGlobal( "device.updated", { id: deviceId } );
306
+
307
+ this.onGlobalEvent( ( ev ) => {
308
+ if( ev.msg == "device.updated" ) {
309
+ this.refresh( ev.params.id );
310
+ }
311
+ } );
312
+ ```
313
+
314
+ Use a global message for what concerns the application or a distributed system, rather than a
315
+ local component relationship.
301
316
 
302
317
  Global messages should normally use stable application-level names, for example:
303
318
 
@@ -318,11 +333,11 @@ The receiver can obtain the authoritative object through the application's model
318
333
 
319
334
  For high-frequency realtime information such as telemetry, carrying the actual data can be appropriate.
320
335
 
321
- Do not force every GlobalEvent into the same payload strategy.
336
+ Do not force every global message into the same payload strategy.
322
337
 
323
338
  Most importantly:
324
339
 
325
- > GlobalEvent represents the message, not its transport.
340
+ > A global message represents the message, not its transport.
326
341
 
327
342
  A view should normally not contain WebSocket-specific logic.
328
343
 
@@ -361,7 +376,7 @@ Model obtains or modifies domain data
361
376
  State represents current application state
362
377
  Component presents that state
363
378
  Events communicate local occurrences
364
- GlobalEvent communicates application-wide occurrences
379
+ Global msg communicates application-wide occurrences (Application.fireGlobal)
365
380
  ```
366
381
 
367
382
  Do not add additional layers unless they solve an actual problem.
@@ -500,11 +515,36 @@ Do not hide a control a component creates: configure the component through its p
500
515
 
501
516
  If the props cannot express it, say so instead of working around it.
502
517
 
503
- To retheme an application, redefine the primary colour scale (`--color-primary-a5` … `--color-primary-a90`) once per theme.
504
-
505
- Do not recolour components one by one, and do not override the variables derived from the scale.
518
+ A theme is a class on `<html>`. `Application.setTheme( name )` puts `x4theme-<name>` there and
519
+ takes off the one before; `Application.setTheme( "" )` goes back to the light theme, the default;
520
+ `Application.getTheme( )` gives the name. x4 has one theme: `"dark"`, like Dark Modern of VS Code.
521
+
522
+ An application writes its own theme in its CSS, under `html.x4theme-<name>`, the way
523
+ `themes.scss` writes the dark one:
524
+
525
+ - the colour scale, `--color-primary-a0` (the background) to `--color-primary-a100` (the text).
526
+ The greys of x4 (backgrounds, texts, borders) are mixes of `a0` and `a100`: a scale whose
527
+ `a0` is dark and `a100` light gives them dark too;
528
+ - the global variables the scale does not give, when the theme wants other values:
529
+ `--background-*`, `--text-*`, `--border*`, `--disabled-*`, `--accent-*`, `--icon-color*`,
530
+ `--alert-*`, `--warning-*`, `--success-*`;
531
+ - `color-scheme: dark` for a dark theme: the native parts (select, scroll bars, spinners).
532
+
533
+ The variables of a component (`--listbox-*`, `--dialog-*`…, listed in the `@cssvar` of its
534
+ class) point to the global ones: the theme changes them all at once. Set one only when that
535
+ component must differ from the rest; do not recolour components one by one.
536
+
537
+ ```css
538
+ html.x4theme-pink {
539
+ --color-primary-a0: #fff5f8;
540
+ /* … a5 to a90 … */
541
+ --color-primary-a100: #3a1020;
542
+ }
543
+ ```
506
544
 
507
- A dark theme gets its own scale.
545
+ ```ts
546
+ Application.setTheme( "pink" );
547
+ ```
508
548
 
509
549
  ---
510
550
 
@@ -602,7 +642,7 @@ Component event
602
642
  ↓
603
643
  Application operation
604
644
  ↓
605
- State change / GlobalEvent
645
+ State change / global message
606
646
  ↓
607
647
  Existing Components update
608
648
  ```
@@ -643,6 +683,15 @@ const app_fr = { app: { open: "Ouvrir", close: "Fermer" } };
643
683
  const app_en: typeof app_fr = { app: { open: "Open", close: "Close" } };
644
684
  ```
645
685
 
686
+ `_tr` is typed with the texts of x4 only: `_tr.app.open` does not compile. Give the
687
+ application its own typed view of `_tr`, once, from its first language:
688
+
689
+ ```ts
690
+ export const tx = _tr as typeof _tr & typeof app_fr;
691
+
692
+ new Button( { label: tx.app.open } );
693
+ ```
694
+
646
695
  ---
647
696
 
648
697
  ## Final rule
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x4js",
3
- "version": "2.3.24",
3
+ "version": "2.3.25",
4
4
  "type": "module",
5
5
  "main": "src/x4.ts",
6
6
  "module": "src/x4.ts",
@@ -19,6 +19,8 @@ const x4icons = {
19
19
  danger: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24V264c0 13.3-10.7 24-24 24s-24-10.7-24-24V152c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>`,
20
20
  loading: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor"><!--!Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc.--><path d="M207.4 20.4c2.4 8.5-2.6 17.3-11.2 19.7C101.5 66.2 32 153 32 256c0 123.7 100.3 224 224 224s224-100.3 224-224c0-103-69.5-189.8-164.3-215.9c-8.5-2.4-13.5-11.2-11.2-19.7s11.2-13.5 19.7-11.2C432.5 39.1 512 138.2 512 256c0 141.4-114.6 256-256 256S0 397.4 0 256C0 138.2 79.5 39.1 187.7 9.2c8.5-2.4 17.3 2.6 19.7 11.2z"/></svg>`,
21
21
  close_box: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" fill="currentColor"><!--!Font Awesome Pro 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc.--><path d="M192 233.4L59.5 100.9 36.9 123.5 169.4 256 36.9 388.5l22.6 22.6L192 278.6 324.5 411.1l22.6-22.6L214.6 256 347.1 123.5l-22.6-22.6L192 233.4z"/></svg>`,
22
+ // a triangle, a "!" cut in it
23
+ warning: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor"><path fill-rule="evenodd" d="M8 1L15.5 14.5H.5zM7.25 5.5h1.5V10h-1.5zM7 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0z"/></svg>`,
22
24
  };
23
25
 
24
26
  export default x4icons;
@@ -134,6 +134,11 @@ export class Button extends Component<ButtonProps,ButtonEvents> {
134
134
  constructor( props: ButtonProps ) {
135
135
  super( { ...props, tag: 'button', content: null } );
136
136
 
137
+ // a button, not a submit: in a <form>, Enter in a field would click the first submit
138
+ if( !props.attrs?.type ) {
139
+ this.setAttribute( "type", "button" );
140
+ }
141
+
137
142
  this.mapPropEvents( props, 'click' );
138
143
 
139
144
  if( props.autorepeat ) {
@@ -125,6 +125,12 @@ export class Dialog<P extends DialogProps = DialogProps, E extends DialogEvents
125
125
  }
126
126
  }
127
127
  else if (ev.key == 'Enter') {
128
+ // in a text of several lines Enter is a new line: Ctrl+Enter (Cmd+Enter) validates
129
+ const target = ev.target as HTMLElement;
130
+ if( ( target.tagName==="TEXTAREA" || target.isContentEditable ) && !ev.ctrlKey && !ev.metaKey ) {
131
+ return;
132
+ }
133
+
128
134
  const def = this.query<Button>('button.default');
129
135
  if (def) {
130
136
  ev.preventDefault();
@@ -31,8 +31,9 @@
31
31
  flex-direction: row;
32
32
  }
33
33
 
34
+ // flex: false: the height of its content (flex-grow alone would leave it at 0, flex-basis 0)
34
35
  &.no-flex {
35
- flex-grow: 0;
36
+ flex: none;
36
37
  }
37
38
  }
38
39
 
@@ -43,6 +43,9 @@ export class Form<P extends FormProps = FormProps> extends Box<P> {
43
43
  constructor( props: P ) {
44
44
  super( { tag: "form", ...props } );
45
45
 
46
+ // read by getValues, never sent by the browser (Enter in its only field would send it)
47
+ this.addDOMEvent( "submit", ( ev ) => ev.preventDefault( ) );
48
+
46
49
  if( props.flex===false ) {
47
50
  this.addClass( "no-flex" );
48
51
  }
@@ -34,6 +34,13 @@
34
34
 
35
35
  --listbox-title-background: var( --background-primary );
36
36
  --listbox-title-color: var( --text-primary );
37
+
38
+ --listbox-check-border: var( --border );
39
+ --listbox-check-border-hover: var( --border-hover );
40
+ --listbox-check-border-sel: var( --accent-color );
41
+ --listbox-check-background: var( --accent-background );
42
+ --listbox-check-background-disabled: var( --disabled-background );
43
+ --listbox-check-color: var( --accent-color );
37
44
  }
38
45
 
39
46
  .x4listbox {
@@ -50,6 +57,11 @@
50
57
  padding: 0 4px;
51
58
  }
52
59
 
60
+ // the columns start after the case
61
+ &.checkable>.x4header {
62
+ padding-left: calc( 4px + 6px + 1rem );
63
+ }
64
+
53
65
  &>.title {
54
66
  background: var( --listbox-title-background );
55
67
  padding: 4px;
@@ -129,9 +141,49 @@
129
141
  }
130
142
  }
131
143
 
144
+ // checkable: the case at the start of the line
145
+ &>.x4check {
146
+ flex: none;
147
+ display: inline-flex;
148
+ align-items: center;
149
+ justify-content: center;
150
+ align-self: center;
151
+ vertical-align: middle;
152
+ box-sizing: border-box;
153
+ width: 1rem;
154
+ height: 1rem;
155
+ margin-left: 6px;
156
+ border: 1px solid var( --listbox-check-border );
157
+ color: var( --listbox-check-color );
158
+ cursor: pointer;
159
+
160
+ svg {
161
+ width: 70%;
162
+ height: 70%;
163
+ visibility: hidden;
164
+ }
165
+
166
+ &:hover {
167
+ border-color: var( --listbox-check-border-hover );
168
+ }
169
+ }
170
+
171
+ &.checked>.x4check {
172
+ background-color: var( --listbox-check-background );
173
+ border-color: var( --listbox-check-background );
174
+
175
+ svg {
176
+ visibility: visible;
177
+ }
178
+ }
179
+
180
+ &.selected>.x4check {
181
+ border-color: var( --listbox-check-border-sel );
182
+ }
183
+
132
184
  .column {
133
- background-color: transparent;
134
- overflow: hidden;
185
+ background-color: transparent;
186
+ overflow: hidden;
135
187
  }
136
188
 
137
189
  // 10 columns max (see Header)
@@ -232,6 +284,11 @@
232
284
  &.selected {
233
285
  background-color: var( --listbox-item-color-sel-disabled );
234
286
  }
287
+
288
+ &.checked>.x4check {
289
+ background-color: var( --listbox-check-background-disabled );
290
+ border-color: var( --listbox-check-background-disabled );
291
+ }
235
292
  }
236
293
  }
237
294
  }