x4js 2.3.28 → 2.3.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 (43) hide show
  1. package/README.md +1 -1
  2. package/aicontext.md +385 -21
  3. package/package.json +8 -3
  4. package/src/components/assets/icons.ts +2 -0
  5. package/src/components/bargraph/bargraph.module.scss +44 -0
  6. package/src/components/bargraph/bargraph.ts +362 -0
  7. package/src/components/base.scss +71 -1
  8. package/src/components/calendar/calendar.module.scss +6 -5
  9. package/src/components/calendar/calendar.ts +14 -8
  10. package/src/components/canvas/canvas.ts +5 -4
  11. package/src/components/carousel/carousel.module.scss +165 -0
  12. package/src/components/carousel/carousel.ts +423 -0
  13. package/src/components/chart/chart.module.scss +8 -0
  14. package/src/components/chart/chart.ts +170 -12
  15. package/src/components/combobox/combobox.module.scss +2 -1
  16. package/src/components/components.ts +3 -0
  17. package/src/components/dialog/dialog.module.scss +2 -0
  18. package/src/components/dialog/dialog.ts +1 -0
  19. package/src/components/gantt/gantt.module.scss +462 -0
  20. package/src/components/gantt/gantt.ts +1254 -0
  21. package/src/components/knob/knob.module.scss +113 -0
  22. package/src/components/knob/knob.ts +355 -0
  23. package/src/components/mapview/mapview.module.scss +177 -0
  24. package/src/components/mapview/mapview.ts +764 -0
  25. package/src/components/menu/menu.module.scss +4 -6
  26. package/src/components/menu/menu.ts +4 -2
  27. package/src/components/planner/planner.module.scss +688 -0
  28. package/src/components/planner/planner.ts +1792 -0
  29. package/src/components/popup/popup.ts +29 -3
  30. package/src/components/radio/radio.module.scss +4 -1
  31. package/src/components/shared.scss +2 -1
  32. package/src/components/slider/slider.module.scss +22 -20
  33. package/src/components/switch/switch.module.scss +68 -44
  34. package/src/components/switch/switch.ts +6 -3
  35. package/src/components/tag/tag.module.scss +4 -4
  36. package/src/components/textedit/textedit.module.scss +5 -2
  37. package/src/components/themes.scss +27 -6
  38. package/src/components/tooltips/tooltips.scss +8 -0
  39. package/src/components/tooltips/tooltips.ts +31 -0
  40. package/src/core/core_i18n.ts +65 -0
  41. package/src/core/core_tools.ts +44 -0
  42. package/src/x4doc.ts +3 -0
  43. package/src/components/menu/caret-right-solid.svg +0 -1
@@ -62,6 +62,32 @@ function dismissLast( e: UIEvent ) {
62
62
  autoclose_list[autoclose_list.length-1].dismiss( );
63
63
  }
64
64
 
65
+ /**
66
+ * a wheel outside every popup is ignored: the page under them does not scroll, it would leave
67
+ * them where they were. In a popup (a long menu), it scrolls as usual
68
+ */
69
+
70
+ function ignoreWheel( e: WheelEvent ) {
71
+ if( !autoclose_list.some( x => x.contains( e.target as Node ) ) ) {
72
+ e.preventDefault( );
73
+ }
74
+ }
75
+
76
+ /**
77
+ * the listeners of the document while a popup that closes by itself is shown
78
+ */
79
+
80
+ function watchOutside( on: boolean ) {
81
+ if( on ) {
82
+ document.addEventListener( "pointerdown", dismissLast, true );
83
+ document.addEventListener( "wheel", ignoreWheel, { capture: true, passive: false } );
84
+ }
85
+ else {
86
+ document.removeEventListener( "pointerdown", dismissLast, true );
87
+ document.removeEventListener( "wheel", ignoreWheel, true );
88
+ }
89
+ }
90
+
65
91
  /**
66
92
  * @cssvar
67
93
  * ```
@@ -256,7 +282,7 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
256
282
  if( idx>=0 ) {
257
283
  autoclose_list.splice( idx, 1 );
258
284
  if( autoclose_list.length==0 ) {
259
- document.removeEventListener( "pointerdown", dismissLast, true );
285
+ watchOutside( false );
260
286
  }
261
287
  }
262
288
  }
@@ -295,7 +321,7 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
295
321
 
296
322
  if( this.props.autoClose ) {
297
323
  if( autoclose_list.length==0 ) {
298
- document.addEventListener( "pointerdown", dismissLast, true );
324
+ watchOutside( true );
299
325
  }
300
326
 
301
327
  autoclose_list.push( this );
@@ -384,7 +410,7 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
384
410
  const list = inc_group.reverse( );
385
411
  autoclose_list = excl_group;
386
412
  if( autoclose_list.length==0 ) {
387
- document.removeEventListener( "pointerdown", dismissLast, true );
413
+ watchOutside( false );
388
414
  }
389
415
 
390
416
  list.forEach( x => x.close() );
@@ -53,11 +53,14 @@
53
53
  flex-direction: row;
54
54
  align-items: center;
55
55
 
56
- margin-left: 5px;
56
+ // the room of a checkbox
57
+ margin: 5px;
58
+ padding: 4px;
57
59
  gap: 4px;
58
60
  cursor: pointer;
59
61
 
60
62
  .inner {
63
+ align-self: center; // a Box stretches: the round in the middle of the line
61
64
  position: relative;
62
65
  height: 1rem;
63
66
  width: 1rem;
@@ -62,8 +62,9 @@
62
62
  box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
63
63
  }
64
64
 
65
+ // its colour from the theme (--shadow-color): darker on a dark background
65
66
  %shadow-xl {
66
- box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
67
+ box-shadow: 0 20px 25px -5px var( --shadow-color ), 0 8px 10px -6px var( --shadow-color );
67
68
  }
68
69
 
69
70
  %shadow-xxl {
@@ -21,15 +21,17 @@
21
21
  --slider-thumb-border-focus: var( --border-focus );
22
22
  }
23
23
 
24
+ // all in rem: the thumb, its focus, the track and the box keep their proportions whatever the
25
+ // size of <html>
24
26
  .x4slider {
25
27
  outline: none;
26
28
  touch-action: none;
27
29
  overflow: hidden;
28
30
 
29
31
  &:not(.vertical) {
30
- padding: 10px 12px;
31
- margin: 5px 0;
32
- height: 28px;
32
+ padding: 0.625rem 0.75rem;
33
+ margin: 0.3125rem 0;
34
+ height: 1.75rem;
33
35
 
34
36
  .track {
35
37
  background-color: var( --slider-track );
@@ -37,13 +39,13 @@
37
39
 
38
40
  position: relative;
39
41
  top: 50%;
40
- height: 3px;
42
+ height: 0.1875rem;
41
43
  }
42
44
 
43
45
  .bar {
44
46
  position: absolute;
45
47
  width: 61%;
46
- height: 3px;
48
+ height: 0.1875rem;
47
49
  //padding: 4px 0;
48
50
  //background-color: var( --color-60 );
49
51
  }
@@ -54,12 +56,12 @@
54
56
  outline: none;
55
57
 
56
58
  background-color: var( --slider-thumb-background );
57
- border: 3px solid var( --slider-thumb-border );
58
- border-radius: 8px;
59
+ border: 0.1875rem solid var( --slider-thumb-border );
60
+ border-radius: 50%;
59
61
 
60
62
  top: 50%;
61
- width: 16px;
62
- height: 16px;
63
+ width: 1rem;
64
+ height: 1rem;
63
65
 
64
66
  transform: translate(-50%,-50%);
65
67
  cursor: pointer;
@@ -67,9 +69,9 @@
67
69
  }
68
70
 
69
71
  &.vertical {
70
- padding: 12px 10px;
71
- margin: 0 5px;
72
- width: 28px;
72
+ padding: 0.75rem 0.625rem;
73
+ margin: 0 0.3125rem;
74
+ width: 1.75rem;
73
75
 
74
76
  .track {
75
77
  background-color: var( --slider-track );
@@ -77,12 +79,12 @@
77
79
 
78
80
  position: relative;
79
81
  left: 50%;
80
- width: 3px;
82
+ width: 0.1875rem;
81
83
  }
82
84
 
83
85
  .bar {
84
86
  position: absolute;
85
- width: 3px;
87
+ width: 0.1875rem;
86
88
  height: 61%;
87
89
  //padding: 4px 0;
88
90
  //background-color: var( --color-60 );
@@ -94,12 +96,12 @@
94
96
  outline: none;
95
97
 
96
98
  background-color: var( --slider-thumb-background );
97
- border: 3px solid var( --slider-thumb-border );
98
- border-radius: 8px;
99
+ border: 0.1875rem solid var( --slider-thumb-border );
100
+ border-radius: 50%;
99
101
 
100
102
  top: 61%;
101
- width: 16px;
102
- height: 16px;
103
+ width: 1rem;
104
+ height: 1rem;
103
105
 
104
106
  transform: translate(-50%,-50%);
105
107
  cursor: pointer;
@@ -110,9 +112,9 @@
110
112
 
111
113
 
112
114
  &:focus .thumb {
113
- outline-offset: 3px;
115
+ outline-offset: 0.1875rem;
114
116
  outline-color: var(--slider-thumb-border-focus);
115
117
  outline-style: solid;
116
- outline-width: 1px;
118
+ outline-width: 0.0625rem;
117
119
  }
118
120
  }
@@ -1,16 +1,16 @@
1
- /**
1
+ /**
2
2
  * ___ ___ __
3
3
  * \ \/ / / _
4
4
  * \ / /_| |_
5
- * / \____ _|
5
+ * / \____ _|
6
6
  * /__/\__\ |_|
7
- *
7
+ *
8
8
  * @file switch.module.scss
9
- * @author Etienne Cochard
10
- *
9
+ * @author Etienne Cochard
10
+ *
11
11
  * @copyright (c) 2026 R-libre ingenierie
12
12
  *
13
- * Use of this source code is governed by an MIT-style license
13
+ * Use of this source code is governed by an MIT-style license
14
14
  * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
15
  **/
16
16
 
@@ -21,15 +21,21 @@
21
21
  --switch-background: var( --background-hover );
22
22
  --switch-background-disabled: var( --disabled-background );
23
23
  --switch-checked: var( --accent-background );
24
+ --switch-danger: var( --alert-background );
24
25
  --switch-thumb: var( --background-primary );
25
26
  }
26
27
 
28
+ // The sizes come from H, the height of the track: 1.125rem rounded to an even number of pixels
29
+ // (18px when <html> has 16px). The width is 2H - 2, the thumb H - 2 at 1px of the edges (inside
30
+ // the border): all in whole pixels, the switch is drawn the same wherever it falls in the page.
31
+ // The px values first: a browser without round() keeps them.
32
+
27
33
  .x4switch {
28
34
  display: flex;
29
35
  flex-direction: row;
30
36
  align-items: center;
31
37
  position: relative;
32
-
38
+
33
39
  gap: 4px;
34
40
  margin-left: 5px;
35
41
  padding: 4px;
@@ -47,62 +53,79 @@
47
53
  }
48
54
  }
49
55
 
56
+ // the track; the input over it takes the click, the focus, and the value for a Form
50
57
  .switch {
51
- width: 2.2rem;
52
- height: 18px;
53
58
  position: relative;
59
+ flex: none;
60
+
61
+ width: 34px;
62
+ width: calc( round( 1.125rem, 2px ) * 2 - 2px );
63
+ height: 18px;
64
+ height: round( 1.125rem, 2px );
65
+
66
+ border: 1px solid var( --switch-border );
67
+ border-radius: 9px;
68
+ border-radius: calc( round( 1.125rem, 2px ) / 2 );
69
+ background-color: var( --switch-background );
70
+
71
+ transition: background-color ease 0.2s, border-color ease 0.2s;
54
72
 
55
73
  input {
56
74
  position: absolute;
57
- width: 100%;
58
- height: 100%;
59
- appearance: none;
60
- padding: 0;
61
- margin: 0;
62
- left: 0;
63
- top: 0;
64
- cursor: pointer;
65
- }
66
-
67
- .track {
68
- position: absolute;
69
- pointer-events: none;
70
75
  left: 0;
71
76
  top: 0;
72
77
  width: 100%;
73
78
  height: 100%;
74
- border: 1px solid var( --switch-border );
75
-
76
- background-color: var( --switch-background );
77
- border-radius: 9px;
78
-
79
- transition: background-color ease 0.2s;
79
+ margin: 0;
80
+ padding: 0;
81
+ appearance: none;
82
+ opacity: 0; // none of the look of a field (its background): the track is under it
83
+ cursor: pointer;
80
84
  }
81
85
 
82
86
  .thumb {
83
87
  position: absolute;
84
88
  pointer-events: none;
85
- left: 0.05rem;
86
- top: 1px;
89
+ left: 0;
90
+ top: 0;
87
91
 
88
- width: 1rem;
89
- height: 1rem;
92
+ width: 16px;
93
+ width: calc( round( 1.125rem, 2px ) - 2px );
94
+ height: 16px;
95
+ height: calc( round( 1.125rem, 2px ) - 2px );
90
96
 
91
- border-radius: 8px;
97
+ border-radius: 50%;
92
98
  background-color: var( --switch-thumb );
93
99
  box-shadow: 0 0 2px rgba(0,0,0,0.5);
94
100
 
95
- transition: left ease 0.2s;
101
+ transition: transform ease 0.2s;
96
102
  }
97
103
  }
98
104
 
99
- &:has(input:checked) {
100
- .switch .thumb {
101
- left: 1.15rem;
105
+ // on: the colour on all the track, its border too; the thumb at the other end (H - 2 further)
106
+ &:has(input:checked) .switch {
107
+ background-color: var( --switch-checked );
108
+ border-color: var( --switch-checked );
109
+
110
+ .thumb {
111
+ transform: translateX( 16px );
112
+ transform: translateX( calc( round( 1.125rem, 2px ) - 2px ) );
102
113
  }
103
- .switch .track {
104
- background-color: var( --switch-checked );
105
- //border-color: var( --color-70 );
114
+ }
115
+
116
+ // danger: the colour of an alert when on
117
+ &.danger {
118
+ --switch-checked: var( --switch-danger );
119
+ }
120
+
121
+ // square: the corners of the track and of the thumb barely rounded
122
+ &.square .switch {
123
+ border-radius: 3px;
124
+ border-radius: round( 0.1875rem, 1px );
125
+
126
+ .thumb {
127
+ border-radius: 2px;
128
+ border-radius: round( 0.125rem, 1px );
106
129
  }
107
130
  }
108
131
 
@@ -110,7 +133,7 @@
110
133
  &, * {
111
134
  cursor: not-allowed;
112
135
  }
113
-
136
+
114
137
  input {
115
138
  pointer-events: none;
116
139
  }
@@ -120,9 +143,10 @@
120
143
  color: var( --checkbox-color-disabled );
121
144
  }
122
145
 
123
- .switch .track,
124
- &.x4switch:has(input:checked) .switch .track {
146
+ .switch,
147
+ &.x4switch:has(input:checked) .switch {
125
148
  background-color: var( --switch-background-disabled );
149
+ border-color: var( --switch-border );
126
150
  }
127
151
  }
128
- }
152
+ }
@@ -31,13 +31,16 @@ interface SwitchProps extends ComponentProps {
31
31
  }
32
32
 
33
33
  /**
34
- *
34
+ * cls "square": the track and the thumb with corners barely rounded; "danger": the colour of an
35
+ * alert when on.
36
+ *
35
37
  * @cssvar
36
38
  * ```
37
39
  * --switch-border
38
40
  * --switch-background
39
41
  * --switch-background-disabled
40
42
  * --switch-checked
43
+ * --switch-danger
41
44
  * --switch-thumb
42
45
  * ```
43
46
  */
@@ -51,12 +54,12 @@ export class Switch extends HBox<SwitchProps> {
51
54
  const inputId = makeUniqueComponentId( );
52
55
 
53
56
  this.setContent( [
57
+ // the track, its thumb in it; the input over them (role switch: a screen reader says "switch, on")
54
58
  new Component( {
55
59
  cls: "switch",
56
60
  content: [
57
61
  // value: sent by a <form> of the browser when checked; a Form of x4 reads a boolean
58
- new Input( { type: "checkbox", id: inputId, name: props.name, value: props.value, checked: props.checked } ),
59
- new Component( { cls: "track" } ),
62
+ new Input( { type: "checkbox", id: inputId, name: props.name, value: props.value, checked: props.checked, attrs: { role: "switch" } } ),
60
63
  new Component( { cls: "thumb" } ),
61
64
  ]
62
65
  } ),
@@ -19,8 +19,7 @@
19
19
  --tag-border: var( --border );
20
20
  --tag-border-focus: var( --border-focus );
21
21
  --tag-background: var( --accent-background );
22
- --tag-color: var( --text-primary );
23
- --tag-icon-color: var( --tag-color );
22
+ --tag-color: var( --accent-color );
24
23
  }
25
24
 
26
25
  .x4tag {
@@ -37,9 +36,10 @@
37
36
  display: none;
38
37
  }
39
38
 
39
+ // --tag-icon-color is not set by x4: the icon has the color of the text of its tag
40
40
  .x4icon {
41
41
  height: 1em;
42
- color: var( --tag-icon-color );
42
+ color: var( --tag-icon-color, var( --tag-color ) );
43
43
  }
44
44
 
45
45
  &> .x4simpletext {
@@ -50,6 +50,6 @@
50
50
 
51
51
  &.alert {
52
52
  --tag-color: var( --alert-color );
53
- background-color: var( --alert-background );
53
+ --tag-background: var( --alert-background );
54
54
  }
55
55
  }
@@ -21,7 +21,7 @@
21
21
  --textedit-border-focus: var( --border-focus );
22
22
  --textedit-required: var( --alert-background );
23
23
 
24
- --textedit-btn-background: inherit;
24
+ --textedit-btn-background: var( --background-input ); // the one of its field: inherit was the page (dark theme)
25
25
  --textedit-btn-color: var( --text-secondary );
26
26
  --textedit-btn-color-hover: var( --text-primary );
27
27
 
@@ -64,6 +64,7 @@
64
64
  .x4button {
65
65
  margin: 0;
66
66
  padding: 0;
67
+ align-self: stretch; // the height of the field: no strip of the page above and under it
67
68
  background-color: var( --textedit-btn-background );
68
69
  color: var( --textedit-btn-color );
69
70
 
@@ -108,7 +109,9 @@
108
109
  color: var( --textedit-color-disabled );
109
110
  }
110
111
 
111
- #edit input {
112
+ // the field and its gadgets lose their background together
113
+ #edit input,
114
+ #edit .x4button {
112
115
  color: var( --textedit-color-disabled );
113
116
  background-color: unset;
114
117
  }
@@ -19,6 +19,10 @@
19
19
  $color--base: #569cd6;
20
20
  $color--interval: 8.4%;
21
21
 
22
+ // the blues of the dark theme, from their own base, with the intervals of the light one: its accent
23
+ // (a60, the base less one interval) #245883
24
+ $color--dark-base: #2d6fa5;
25
+
22
26
  :root {
23
27
  --color-primary-a0: #ffffff;
24
28
  --color-primary-a5: #{color.adjust($color--base, $lightness: 35%)};
@@ -90,6 +94,9 @@ $color--interval: 8.4%;
90
94
  --border-hover: color-mix(in srgb, var(--color-primary-a100) 23.9%, var(--color-primary-a0)); // #c2c2c2
91
95
  --border-focus: var(--accent-background);
92
96
 
97
+ // the shadow of what floats over the page (%shadow-xl: a dialog, a list, a notification, a tooltip)
98
+ --shadow-color: rgb(0 0 0 / 0.1);
99
+
93
100
  // the red, orange and green do not follow the scale: their text stays white
94
101
  --alert-background: var(--color-danger-a50);
95
102
  --alert-background-active: var(--color-danger-a60);
@@ -119,12 +126,16 @@ html.x4theme-dark {
119
126
  --color-primary-a10: #2a2d2e; // list.hoverBackground
120
127
  --color-primary-a20: #37373d; // list.inactiveSelectionBackground
121
128
  --color-primary-a30: #3c3c3c; // input.border
122
- --color-primary-a40: #04395e; // list.activeSelectionBackground
123
- --color-primary-a50: #0078d4; // button.background, focusBorder
124
- --color-primary-a55: #026ec1; // button.hoverBackground
125
- --color-primary-a60: #0078d4;
126
- --color-primary-a70: #026ec1;
127
- --color-primary-a80: #04395e;
129
+
130
+ // the blues: from $color--dark-base (chosen), as the light theme makes them; a40, a selection,
131
+ // where VS Code has list.activeSelectionBackground
132
+ --color-primary-a40: #{color.adjust($color--dark-base, $lightness: -19.4%)}; // a selection
133
+ --color-primary-a50: #{$color--dark-base};
134
+ --color-primary-a55: #{color.adjust($color--dark-base, $lightness: -5%)}; // the accent hovered
135
+ --color-primary-a60: #{color.adjust($color--dark-base, $lightness: -$color--interval)}; // the accent
136
+ --color-primary-a70: #{color.adjust($color--dark-base, $lightness: -$color--interval * 2)}; // focused
137
+ --color-primary-a80: #{color.adjust($color--dark-base, $lightness: -$color--interval * 3)}; // active
138
+
128
139
  --color-primary-a90: #cccccc;
129
140
  --color-primary-a100: #cccccc; // foreground
130
141
 
@@ -155,6 +166,16 @@ html.x4theme-dark {
155
166
  --color-dialog-caption: #181818; // titleBar.activeBackground
156
167
  --color-dialog-caption-text: #cccccc; // titleBar.activeForeground
157
168
  --color-dialog-background: #202020; // editorWidget.background
169
+ --color-dialog-border: #313131; // widget.border: its edge on the dark background
170
+
171
+ // a shadow seen on a dark background: darker
172
+ --shadow-color: rgb(0 0 0 / 0.5);
173
+
174
+ // the tiles of a MapView are light: turned dark, their colours kept
175
+ --mapview-tile-filter: invert( 1 ) hue-rotate( 180deg ) brightness( 0.95 ) contrast( 0.9 );
176
+
177
+ // the marks of the events out of the view of a Planner: the darkest blue (a90 is the text here)
178
+ --planner-edge-background: var( --accent-background-active );
158
179
 
159
180
  color-scheme: dark; // the native parts: select, scroll bars, number spinners
160
181
  }
@@ -27,6 +27,8 @@
27
27
  --tooltip-color: var( --color-primary-a90 );
28
28
  --tooltip-border: var( --color-primary-a20 );
29
29
  --tooltip-image-background: var( --background-primary );
30
+
31
+ --tooltip-max-width: 30vw; // a long text goes on the next line; none: the width of the text
30
32
  }
31
33
 
32
34
  .x4tooltip {
@@ -41,6 +43,12 @@
41
43
  border: 1px solid var( --tooltip-border );
42
44
  background-color: var( --tooltip-background );
43
45
  width: max-content;
46
+ max-width: var( --tooltip-max-width );
47
+
48
+ // its content (a Box: it does not shrink) as wide as the tooltip at most: the text goes on the next line
49
+ > .x4hbox {
50
+ flex-shrink: 1;
51
+ }
44
52
 
45
53
  .x4icon {
46
54
  padding: 2px 4px;
@@ -72,6 +72,14 @@ export function initTooltips( ) {
72
72
 
73
73
  }, true );
74
74
 
75
+ // a click: no tooltip, it would stay over what the click opens
76
+ document.addEventListener( "mousedown", ( ) => {
77
+ if( last_hit ) {
78
+ last_hit = null;
79
+ closeTT( );
80
+ }
81
+ }, true );
82
+
75
83
  document.addEventListener( "mousemove", ( ev: MouseEvent ) => {
76
84
  if( last_hit ) {
77
85
  mouse_pos = { x:ev.pageX, y:ev.pageY }
@@ -85,6 +93,11 @@ function showTT( text: string, rc: Rect, pt: Point ) {
85
93
  }
86
94
 
87
95
  timer.setTimeout( "update", 300, ( ) => {
96
+ // its element gone in the meantime
97
+ if( !last_hit || isGone( last_hit ) ) {
98
+ return;
99
+ }
100
+
88
101
  tooltip.setText( unsafeHtml(text) );
89
102
 
90
103
  let y = mouse_pos.y;
@@ -94,12 +107,29 @@ function showTT( text: string, rc: Rect, pt: Point ) {
94
107
 
95
108
  //tooltip.displayNear( rc, "top left", "bottom left", {x:0,y:4} );
96
109
  tooltip.displayAt( mouse_pos.x+17, y+5 );
110
+
111
+ // an element removed or hidden under the mouse has no mouseleave: it is checked every second
112
+ timer.setInterval( "check", 1000, ( ) => {
113
+ if( !last_hit || isGone( last_hit ) ) {
114
+ last_hit = null;
115
+ closeTT( );
116
+ }
117
+ } );
97
118
  } );
98
119
  }
99
120
 
100
121
  function closeTT( ) {
101
122
  tooltip.show( false );
102
123
  timer.clearTimeout( "update" );
124
+ timer.clearInterval( "check" );
125
+ }
126
+
127
+ /**
128
+ * the element of the tooltip, not shown any more: removed from the page, or hidden
129
+ */
130
+
131
+ function isGone( el: HTMLElement ) {
132
+ return !el.isConnected || el.getClientRects( ).length==0;
103
133
  }
104
134
 
105
135
  /**
@@ -114,6 +144,7 @@ function closeTT( ) {
114
144
  * --tooltip-color
115
145
  * --tooltip-border
116
146
  * --tooltip-image-background
147
+ * --tooltip-max-width
117
148
  * ```
118
149
  */
119
150