x4js 2.3.30 → 2.3.32

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.3.30",
3
+ "version": "2.3.32",
4
4
  "description": "A full TypeScript framework for web and desktop applications: components written as classes (layouts, inputs, lists, grids, dialogs, forms, charts...) and a command line to create, serve and build them.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,6 +30,7 @@
30
30
  "aicli.md",
31
31
  "aicontext.md",
32
32
  "aiguide.md",
33
+ "changelog.md",
33
34
  "README.md",
34
35
  "LICENSE"
35
36
  ],
@@ -47,7 +47,7 @@
47
47
  --ol-button-icon-color: var( --text-primary );
48
48
 
49
49
  --ol-button-border-focus: var( --accent-background-focus );
50
- --ol-button-color-focus: var( --accent-background );
50
+ --ol-button-color-focus: var( --accent-text );
51
51
  }
52
52
 
53
53
  .x4button {
@@ -29,9 +29,10 @@
29
29
 
30
30
  --checkbox-button-border: var( --border );
31
31
  --checkbox-button-color: var( --text-ternary );
32
- --checkbox-button-color-checked: var( --accent-background-active );
32
+ --checkbox-button-color-checked: var( --accent-text );
33
33
  --checkbox-button-background-checked: var( --background-secondary );
34
34
  --checkbox-button-border-checked: var( --accent-background );
35
+ --checkbox-button-icon-size: 24px; // set by a class of the application on a checkbox: it wins over :root
35
36
 
36
37
  --checkbox-danger-color: var( --alert-background );
37
38
  --checkbox-danger-background: var( --alert-background-light );
@@ -187,8 +188,8 @@
187
188
  #icon {
188
189
  color: var( --checkbox-ink );
189
190
  fill: var( --checkbox-ink );
190
- width: 24px;
191
- height: 24px;
191
+ width: var( --checkbox-button-icon-size );
192
+ height: var( --checkbox-button-icon-size );
192
193
  }
193
194
  }
194
195
 
@@ -54,6 +54,7 @@ interface CheckboxProps extends ComponentProps {
54
54
  * --checkbox-button-color-checked
55
55
  * --checkbox-button-background-checked
56
56
  * --checkbox-button-border-checked
57
+ * --checkbox-button-icon-size
57
58
  * --checkbox-danger-color
58
59
  * --checkbox-danger-background
59
60
  * ```
@@ -25,6 +25,15 @@
25
25
  --menuitem-color-active: var( --accent-color-active );
26
26
  --menuitem-background-hover: var( --accent-background-hover );
27
27
  --menuitem-color-hover: var( --accent-color-hover );
28
+ --menuitem-shortcut-color: var( --text-secondary );
29
+
30
+ --menubar-background: var( --background-primary );
31
+ --menubar-border: var( --border );
32
+
33
+ --menubaritem-color: var( --text-primary );
34
+ --menubaritem-background-hover: var( --accent-background-hover );
35
+ --menubaritem-color-hover: var( --accent-color-hover );
36
+ --menubaritem-color-disabled: var( --disabled-color-dark );
28
37
  }
29
38
 
30
39
  .x4menu {
@@ -84,6 +93,20 @@
84
93
  @extend %flex;
85
94
  }
86
95
 
96
+ // the keys of the command: after the text, which takes the width, at the right
97
+ #shortcut {
98
+ color: var(--menuitem-shortcut-color);
99
+ padding-left: 16px;
100
+ white-space: nowrap;
101
+ }
102
+
103
+ // hovered, active, disabled: in the colour of the text
104
+ &:hover, &.opened, &:active, &[disabled] {
105
+ #shortcut {
106
+ color: inherit;
107
+ }
108
+ }
109
+
87
110
  &.title {
88
111
  color: var( --text-primary );
89
112
  cursor: auto;
@@ -124,4 +147,40 @@
124
147
  border-bottom: 1px solid var(--border);
125
148
  margin-bottom: 4px;
126
149
  }
150
+ }
151
+
152
+ .x4menubar {
153
+ padding: 0 4px;
154
+
155
+ background-color: var(--menubar-background);
156
+ border-bottom: 1px solid var(--menubar-border);
157
+
158
+ .x4cmenubaritem {
159
+ @extend %hbox;
160
+
161
+ gap: 6px;
162
+ padding: 4px 10px;
163
+ color: var(--menubaritem-color);
164
+ cursor: pointer;
165
+ user-select: none;
166
+
167
+ transition: background-color 0.1s;
168
+
169
+ #icon {
170
+ width: 16px;
171
+ height: 16px;
172
+ }
173
+
174
+ // hovered, or its menu open: as an item of the menu
175
+ &:hover:not([disabled]),
176
+ &.opened {
177
+ background-color: var(--menubaritem-background-hover);
178
+ color: var(--menubaritem-color-hover);
179
+ }
180
+
181
+ &[disabled] {
182
+ color: var(--menubaritem-color-disabled);
183
+ cursor: auto;
184
+ }
185
+ }
127
186
  }
@@ -17,9 +17,11 @@
17
17
  import { Component } from "../../core/component"
18
18
  import { DOMEventHandler } from '../../core/core_dom';
19
19
  import { Timer, UnsafeHtml, isFunction, isString } from '../../core/core_tools';
20
+ import { shortcutText } from '../../core/core_shortcuts';
20
21
 
21
22
  import { Popup, PopupProps } from '../popup/popup';
22
23
  import { Icon } from '../icon/icon';
24
+ import { BoxProps, HBox } from '../boxes/boxes';
23
25
 
24
26
  import icons from "../assets/icons";
25
27
  import "./menu.module.scss"
@@ -38,6 +40,13 @@ export interface MenuItem {
38
40
  disabled?: boolean;
39
41
  checked?: boolean | ( () => boolean);
40
42
  click?: DOMEventHandler;
43
+
44
+ /**
45
+ * the keys of the command, shown at the right of the item: "Mod+S" is written
46
+ * "Ctrl+S", "⌘S" on a Mac (shortcutText). Shown only: the shortcut itself
47
+ * is added by Application.addShortcut or Component.addShortcut
48
+ */
49
+ shortcut?: string;
41
50
  }
42
51
 
43
52
  export type MenuElement = MenuItem | Component | string;
@@ -56,6 +65,7 @@ export interface MenuProps extends Omit<PopupProps,"content"> {
56
65
  * --menuitem-color-active
57
66
  * --menuitem-background-hover
58
67
  * --menuitem-color-hover
68
+ * --menuitem-shortcut-color
59
69
  * ```
60
70
  */
61
71
 
@@ -97,6 +107,7 @@ class CMenuItem extends Component {
97
107
  this.setContent( [
98
108
  new Icon({ id:"icon", iconId }),
99
109
  new Component( { id: "text", content: itm.text } ),
110
+ itm.shortcut ? new Component( { id: "shortcut", content: shortcutText( itm.shortcut ) } ) : null,
100
111
  // a sub menu: a triangle at the right, in the colour of the text
101
112
  itm.menu ? new Icon( { id: "sub", iconId: icons.caret } ) : null,
102
113
  ]);
@@ -207,3 +218,134 @@ export class Menu extends Popup {
207
218
  }
208
219
  }
209
220
 
221
+ /**
222
+ *
223
+ */
224
+
225
+ export interface MenuBarItem {
226
+ text: string;
227
+ icon?: string;
228
+ disabled?: boolean;
229
+
230
+ /** its menu; a function is called at each opening: it gives the items of that time */
231
+ items: MenuElement[] | ( ( ) => MenuElement[] );
232
+ }
233
+
234
+ export interface MenuBarProps extends Omit<BoxProps,"content"> {
235
+ items: MenuBarItem[];
236
+ }
237
+
238
+ /**
239
+ * a title of the bar
240
+ */
241
+
242
+ class CMenuBarItem extends Component {
243
+ static classPrefix = "x4";
244
+
245
+ constructor( itm: MenuBarItem ) {
246
+ super( { disabled: itm.disabled } );
247
+
248
+ this.setContent( [
249
+ itm.icon ? new Icon( { id: "icon", iconId: itm.icon } ) : null,
250
+ new Component( { id: "text", content: itm.text } ),
251
+ ] );
252
+ }
253
+ }
254
+
255
+ /**
256
+ * A row of titles, each one opens its menu under it.
257
+ * A click on a title opens its menu, a second one closes it. While a menu is open, the
258
+ * mouse on another title opens the menu of that one.
259
+ *
260
+ * For a web application. A desktop application (Electron...) should not use it: the menu
261
+ * of the system is preferable, where the user expects it (the top of the screen on a Mac)
262
+ * and with the keyboard of the system.
263
+ *
264
+ * @example
265
+ * new MenuBar( { items: [
266
+ * { text: "File", items: [ { text: "Open…", shortcut: "Mod+O", click: open }, "-", { text: "Quit", click: quit } ] },
267
+ * { text: "Edit", items: ( ) => [ { text: "Undo", disabled: !canUndo( ), click: undo } ] },
268
+ * ] } );
269
+ *
270
+ * @cssvar
271
+ * ```
272
+ * --menubar-background
273
+ * --menubar-border
274
+ * --menubaritem-color
275
+ * --menubaritem-background-hover
276
+ * --menubaritem-color-hover
277
+ * --menubaritem-color-disabled
278
+ * ```
279
+ */
280
+
281
+ export class MenuBar extends HBox<MenuBarProps> {
282
+ static classPrefix = "x4";
283
+
284
+ private _menu: Menu; // the menu open, null when none
285
+ private _title: CMenuBarItem; // the title of that menu
286
+ private _closed: CMenuBarItem; // the title whose menu closed last
287
+ private _closedAt = -1; // when (performance.now)
288
+
289
+ constructor( props: MenuBarProps ) {
290
+ super( props );
291
+
292
+ this.setContent( props.items.map( itm => {
293
+ const title = new CMenuBarItem( itm );
294
+
295
+ // The menu closes by itself as soon as the button goes down outside of it,
296
+ // before the title hears of it: it was the menu of this title if it closed
297
+ // since this very press began.
298
+ let wasOpen = false;
299
+
300
+ title.addDOMEvent( "pointerdown", ev => { wasOpen = this._closed===title && this._closedAt >= ev.timeStamp; } );
301
+ title.addDOMEvent( "click", ( ) => {
302
+ if( !wasOpen ) {
303
+ this._open( title, itm );
304
+ }
305
+ } );
306
+
307
+ title.addDOMEvent( "mouseenter", ( ) => {
308
+ if( this._menu && this._title!==title ) {
309
+ this._open( title, itm );
310
+ }
311
+ } );
312
+
313
+ return title;
314
+ } ) );
315
+ }
316
+
317
+ /**
318
+ * closes the menu open, opens the one of the title under it
319
+ */
320
+
321
+ private _open( title: CMenuBarItem, itm: MenuBarItem ) {
322
+ if( title.isDisabled( ) ) {
323
+ return;
324
+ }
325
+
326
+ if( this._menu ) {
327
+ this._menu.dismiss( );
328
+ }
329
+
330
+ const menu = new Menu( { items: isFunction( itm.items ) ? itm.items( ) : itm.items } );
331
+
332
+ menu.on( "closed", ( ) => {
333
+ title.removeClass( "opened" );
334
+
335
+ this._closed = title;
336
+ this._closedAt = performance.now( );
337
+
338
+ if( this._menu===menu ) {
339
+ this._menu = null;
340
+ this._title = null;
341
+ }
342
+ } );
343
+
344
+ this._menu = menu;
345
+ this._title = title;
346
+ title.addClass( "opened" );
347
+
348
+ menu.displayNear( title.getBoundingRect( ), "top left", "bottom left" );
349
+ }
350
+ }
351
+
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor"><!--!Font Awesome Free 6.6.0 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 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor"><!--!Font Awesome Free 6.6.0 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 512z"/><path class="mark" d="M256 128c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" fill="currentColor"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 240C302.3 240 288 254.3 288 272C288 285.3 277.3 296 264 296C250.7 296 240 285.3 240 272C240 227.8 275.8 192 320 192C364.2 192 400 227.8 400 272C400 319.2 364 339.2 344 346.5L344 350.3C344 363.6 333.3 374.3 320 374.3C306.7 374.3 296 363.6 296 350.3L296 342.2C296 321.7 310.8 307 326.1 302C332.5 299.9 339.3 296.5 344.3 291.7C348.6 287.5 352 281.7 352 272.1C352 254.4 337.7 240.1 320 240.1zM288 432C288 414.3 302.3 400 320 400C337.7 400 352 414.3 352 432C352 449.7 337.7 464 320 464C302.3 464 288 449.7 288 432z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" fill="currentColor"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576z"/><path class="mark" d="M320 240C302.3 240 288 254.3 288 272C288 285.3 277.3 296 264 296C250.7 296 240 285.3 240 272C240 227.8 275.8 192 320 192C364.2 192 400 227.8 400 272C400 319.2 364 339.2 344 346.5L344 350.3C344 363.6 333.3 374.3 320 374.3C306.7 374.3 296 363.6 296 350.3L296 342.2C296 321.7 310.8 307 326.1 302C332.5 299.9 339.3 296.5 344.3 291.7C348.6 287.5 352 281.7 352 272.1C352 254.4 337.7 240.1 320 240.1zM288 432C288 414.3 302.3 400 320 400C337.7 400 352 414.3 352 432C352 449.7 337.7 464 320 464C302.3 464 288 449.7 288 432z"/></svg>
@@ -22,6 +22,10 @@
22
22
  --msgbox-icon-color: var( --alert-background );
23
23
  --msgbox-question-icon-color: var( --accent-background ); // a question, an InputBox, a PromptBox
24
24
 
25
+ // the "!" and the "?" in the disc of the icon: as the text on the red, on the accent
26
+ --msgbox-icon-mark-color: var( --alert-color );
27
+ --msgbox-question-icon-mark-color: var( --accent-color );
28
+
25
29
  --msgbox-log-background: var( --background-ternary ); // the lines of a ProgressionBox
26
30
  --msgbox-log-border: var( --border );
27
31
  --msgbox-log-border-focus: var( --border-focus );
@@ -44,6 +48,22 @@
44
48
  }
45
49
  }
46
50
 
51
+ // the buttons of an error in the red of its title bar: a filled one, the focus of an outline
52
+ // one (its text stays the one of the text: a red text is not read on a dark background)
53
+ &.error {
54
+ --button-background: var( --alert-background );
55
+ --button-background-hover: var( --alert-background-active );
56
+ --button-background-focus: var( --alert-background-active );
57
+ --button-background-active: var( --alert-background-active );
58
+ --button-color: var( --alert-color );
59
+ --button-color-hover: var( --alert-color );
60
+ --button-color-focus: var( --alert-color );
61
+ --button-color-active: var( --alert-color );
62
+
63
+ --ol-button-border-focus: var( --alert-background );
64
+ --ol-button-color-focus: var( --ol-button-color );
65
+ }
66
+
47
67
  & .x4form {
48
68
  min-width: 400px;
49
69
 
@@ -51,6 +71,10 @@
51
71
  > .x4hbox > .x4icon {
52
72
  color: var( --msgbox-icon-color );
53
73
  height: 96px;
74
+
75
+ .mark {
76
+ fill: var( --msgbox-icon-mark-color );
77
+ }
54
78
  }
55
79
 
56
80
  .x4label {
@@ -61,6 +85,10 @@
61
85
 
62
86
  &.question .x4form > .x4hbox > .x4icon {
63
87
  color: var( --msgbox-question-icon-color );
88
+
89
+ .mark {
90
+ fill: var( --msgbox-question-icon-mark-color );
91
+ }
64
92
  }
65
93
  }
66
94
 
@@ -37,6 +37,8 @@ export interface MessageBoxProps extends DialogProps {
37
37
  * --msgbox-caption-color
38
38
  * --msgbox-icon-color
39
39
  * --msgbox-question-icon-color
40
+ * --msgbox-icon-mark-color
41
+ * --msgbox-question-icon-mark-color
40
42
  * --msgbox-log-background
41
43
  * --msgbox-log-border
42
44
  * --msgbox-log-border-focus
@@ -47,7 +49,11 @@ export interface MessageBoxProps extends DialogProps {
47
49
  * --msgbox-log-scrollbar
48
50
  * ```
49
51
  * --msgbox-caption-* and --msgbox-icon-color are the ones of an error; a question, an InputBox
50
- * and a PromptBox have the title bar of a Dialog and their icon in --msgbox-question-icon-color
52
+ * and a PromptBox have the title bar of a Dialog and their icon in --msgbox-question-icon-color.
53
+ * The "!" of an error and the "?" of a question are drawn in the disc of their icon, in
54
+ * --msgbox-icon-mark-color and --msgbox-question-icon-mark-color.
55
+ * The buttons of an error are in the colours of the alerts (--alert-*): a filled one, the border
56
+ * of an outline one that has the focus
51
57
  */
52
58
 
53
59
  export class MessageBox extends Dialog<DialogProps>
@@ -54,7 +54,10 @@
54
54
  color: var( --notification-icon-default );
55
55
  }
56
56
 
57
+ // a box does not shrink (.x4box): this one does, down to the frame, its close button kept in it
57
58
  .body {
59
+ flex-shrink: 1;
60
+
58
61
  .title {
59
62
  font-weight: bold;
60
63
  padding: 6px 6px 3px 6px;
@@ -64,6 +67,7 @@
64
67
  .text {
65
68
  padding: 3px 6px 6px 6px;
66
69
  color: var( --notification-text-color );
70
+ overflow-wrap: anywhere; // a long word, an address: cut at the edge
67
71
  }
68
72
  }
69
73
 
@@ -49,17 +49,31 @@ function getRoot( ) {
49
49
  /**
50
50
  * on the document while the event goes down (capture): a press outside
51
51
  * every popup closes the group opened last, the others at the next presses.
52
+ * A press in a popup closes the groups opened after it, over it (a context
53
+ * menu, the picker of a ColorInput), not its own.
52
54
  * The event is left as it is, what is pressed handles it as usual.
53
55
  * One function for all the popups: it is removed by the one that empties the list.
54
56
  */
55
57
 
56
58
  function dismissLast( e: UIEvent ) {
57
- if( autoclose_list.some( x => x.contains( e.target as Node ) ) ) {
59
+ const target = e.target as Node;
60
+
61
+ // the newest popup the press is in
62
+ let idx = autoclose_list.length-1;
63
+ while( idx>=0 && !autoclose_list[idx].contains( target ) ) {
64
+ idx--;
65
+ }
66
+
67
+ // outside every popup: the newest one; dismiss( ) closes its whole group (a menu and its sub-menus)
68
+ if( idx<0 ) {
69
+ autoclose_list[autoclose_list.length-1].dismiss( );
58
70
  return;
59
71
  }
60
72
 
61
- // the newest popup: dismiss( ) closes its whole group (a menu and its sub-menus)
62
- autoclose_list[autoclose_list.length-1].dismiss( );
73
+ // in a popup: the groups opened after it, not its own (a menu and its sub-menus)
74
+ const group = autoclose_list[idx].getData( "close" );
75
+ const over = autoclose_list.slice( idx+1 ).filter( x => x.getData( "close" )!==group );
76
+ over.reverse( ).forEach( x => x.dismiss( ) );
63
77
  }
64
78
 
65
79
  /**
@@ -395,6 +409,11 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
395
409
  let aidx = -1;
396
410
  if( after ) {
397
411
  aidx = autoclose_list.indexOf( this );
412
+
413
+ // no longer open: none was opened after it
414
+ if( aidx<0 ) {
415
+ return;
416
+ }
398
417
  }
399
418
 
400
419
  autoclose_list.forEach( (x,idx) => {
@@ -22,9 +22,10 @@
22
22
 
23
23
  --radio-button-border: var( --border );
24
24
  --radio-button-color: var( --text-ternary );
25
- --radio-button-color-checked: var( --accent-background-active );
25
+ --radio-button-color-checked: var( --accent-text );
26
26
  --radio-button-background-checked: var( --background-secondary );
27
27
  --radio-button-border-checked: var( --accent-background );
28
+ --radio-button-icon-size: 24px; // set by a class of the application on a radio: it wins over :root
28
29
 
29
30
  --radio-danger-color: var( --alert-background );
30
31
  --radio-danger-background: var( --alert-background-light );
@@ -132,8 +133,6 @@
132
133
  // square: its former name, kept for the code that has it (not documented)
133
134
  .x4radio.button,
134
135
  .x4radio.square {
135
- --size: 24px;
136
-
137
136
  &.outline {
138
137
  border-radius: 22px;;
139
138
  }
@@ -159,8 +158,8 @@
159
158
  #icon {
160
159
  fill: var( --radio-button-color );
161
160
  color: var( --radio-button-color );
162
- width: var( --size );
163
- height: var( --size );
161
+ width: var( --radio-button-icon-size );
162
+ height: var( --radio-button-icon-size );
164
163
  }
165
164
  }
166
165
 
@@ -62,6 +62,7 @@ interface RadioProps extends ComponentProps {
62
62
  * --radio-button-color-checked
63
63
  * --radio-button-background-checked
64
64
  * --radio-button-border-checked
65
+ * --radio-button-icon-size
65
66
  * --radio-danger-color
66
67
  * --radio-danger-background
67
68
  * ```
@@ -388,22 +388,29 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
388
388
  }
389
389
  })
390
390
 
391
+ // the cell of the data under the mouse; undefined out of them
391
392
  const targetCell = (e: MouseEvent) => {
392
393
  let el = e.target as Element;
393
394
  while (el && !el.classList.contains("cell")) {
394
395
  el = el.parentElement;
395
396
  }
396
397
 
397
- if (el) {
398
- const cel = componentFromDOM(el);
399
- return {
400
- ref: cel.getIntData("ref"),
401
- row: cel.getInternalData("row"),
402
- col: cel.getInternalData("col"),
403
- }
398
+ if (!el) {
399
+ return undefined;
404
400
  }
405
401
 
406
- return undefined;
402
+ // a cell of the header or of the footer (a title, the sizer of a column): no row
403
+ const cel = componentFromDOM(el);
404
+ const row = cel.getInternalData("row");
405
+ if (row === undefined) {
406
+ return undefined;
407
+ }
408
+
409
+ return {
410
+ ref: cel.getIntData("ref"),
411
+ row,
412
+ col: cel.getInternalData("col"),
413
+ }
407
414
  }
408
415
 
409
416
 
@@ -416,11 +423,11 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
416
423
  // click on another cell while editing:
417
424
  // validate now, rows may be rebuilt before the click event (and the click lost)
418
425
  const ref = targetCell(e);
419
- if( ref && ref.row!==undefined ) {
426
+ if( ref ) {
420
427
  this._endEdit( true, true );
421
428
 
422
429
  if (!this._selection.has(ref.ref)) {
423
- this._clearSelection( false );
430
+ this.clearSelection( false );
424
431
  this._addSelection(ref.ref,true);
425
432
  }
426
433
 
@@ -438,7 +445,7 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
438
445
  if (ref) {
439
446
  //TODO: multiselection
440
447
  if (!this._selection.has(ref.ref)) {
441
- this._clearSelection( false );
448
+ this.clearSelection( false );
442
449
  this._addSelection(ref.ref,true);
443
450
  }
444
451
  }
@@ -454,7 +461,7 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
454
461
  if (ref) {
455
462
  //TODO: multiselection
456
463
  if (!this._selection.has(ref.ref)) {
457
- this._clearSelection(false);
464
+ this.clearSelection(false);
458
465
  this._addSelection(ref.ref,true);
459
466
  }
460
467
 
@@ -479,7 +486,7 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
479
486
  if (ref) {
480
487
  //TODO: multiselection
481
488
  if (!this._selection.has(ref.ref)) {
482
- this._clearSelection( false );
489
+ this.clearSelection( false );
483
490
  this._addSelection(ref.ref, true );
484
491
  }
485
492
 
@@ -638,7 +645,7 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
638
645
 
639
646
  if (sens == kbNav.first || sens == kbNav.last) {
640
647
  let nline = sens == kbNav.first ? 0 : this._store.getRowCount() - 1;
641
- this._clearSelection(false);
648
+ this.clearSelection(false);
642
649
  this._addSelection(mkid(nline,0), true);
643
650
  this._scrollToIndex(nline);
644
651
  return true;
@@ -649,7 +656,7 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
649
656
 
650
657
  let nline = sens == kbNav.next ? fline + 1 : fline - 1;
651
658
  if (nline >= 0 && nline < this._store.getRowCount()) {
652
- this._clearSelection(false);
659
+ this.clearSelection(false);
653
660
  this._addSelection( mkid(nline,col), true);
654
661
  this._scrollToIndex( nline );
655
662
  return true;
@@ -670,7 +677,7 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
670
677
  }
671
678
 
672
679
  if (nline != fline) {
673
- this._clearSelection( false );
680
+ this.clearSelection( false );
674
681
  this._addSelection(mkid(nline,col), true);
675
682
 
676
683
  if (this._store.getRowCount() < SCROLL_LIMIT) {
@@ -686,7 +693,7 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
686
693
 
687
694
  let ncol = sens == kbNav.right ? col+1 : col-1;
688
695
  if (ncol >= 0 && ncol < this._columns.length ) {
689
- this._clearSelection(false);
696
+ this.clearSelection(false);
690
697
  this._addSelection( mkid(fline,ncol), true);
691
698
 
692
699
  // fixed columns are always visible
@@ -1374,10 +1381,10 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
1374
1381
  }
1375
1382
  }
1376
1383
  /**
1377
- *
1384
+ * no cell selected; notify: selectionChange is fired, its selection empty
1378
1385
  */
1379
1386
 
1380
- private _clearSelection(notify = true) {
1387
+ clearSelection(notify = true) {
1381
1388
  for (const ref of this._selection.keys()) {
1382
1389
  const els = this.queryAll(`.cell[data-ref="${ref}"]`)
1383
1390
  els.forEach(el => {
@@ -1433,7 +1440,7 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
1433
1440
 
1434
1441
  selectItem(row: number, col: number, append = false) {
1435
1442
  if (!append) {
1436
- this._clearSelection(false);
1443
+ this.clearSelection(false);
1437
1444
  }
1438
1445
 
1439
1446
  this._addSelection(mkid(row, col), true);
@@ -1745,7 +1752,7 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
1745
1752
  }
1746
1753
 
1747
1754
  if( this._selection.size!=1 || !this._selection.has(ref) ) {
1748
- this._clearSelection( false );
1755
+ this.clearSelection( false );
1749
1756
  this._addSelection( ref, true );
1750
1757
  }
1751
1758
 
@@ -59,6 +59,7 @@
59
59
  @extend %flex;
60
60
  outline: none;
61
61
  margin: 0;
62
+ align-self: stretch; // the height of the zone: its ground down to its line
62
63
  }
63
64
 
64
65
  .x4button {
@@ -88,7 +88,10 @@ export class TextEdit<E extends ComponentEvents = ComponentEvents> extends HBox<
88
88
 
89
89
  constructor( props: TextEditProps ) {
90
90
  // dom_events are for the input (focus, blur, keydown...): not on the box too,
91
- // where keydown would run a second time, and focus never (it does not bubble)
91
+ // where keydown would run a second time, and focus never (it does not bubble).
92
+ // They are set when the input is made, before the handlers of Input and of the class
93
+ // (the keys of an AutoComplete): they run first. To run after them, add a handler to
94
+ // getInput( ) once the component is made.
92
95
  super( { ...props, dom_events: undefined } );
93
96
 
94
97
  if( !props.inputId ) {