x4js 2.3.29 → 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.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  </picture>
5
5
 
6
6
  # x4js
7
- **A TypeScript framework for people who build applications, not markup.**
7
+ **A full TypeScript framework for web and desktop applications.**
8
8
 
9
9
  ## Home page
10
10
  see [X4 home page](https://x4js.org)
package/aicontext.md CHANGED
@@ -1335,7 +1335,8 @@ class Tabs extends Box {
1335
1335
 
1336
1336
  ```ts
1337
1337
  interface PopupProps extends ComponentProps {
1338
- autoClose?: boolean | string // true, or a group name
1338
+ autoClose?: boolean | string // true, or a group name: closed by a press outside; while it is shown,
1339
+ // a wheel outside every popup is ignored (the page does not scroll)
1339
1340
  sizable?: boolean; movable?: boolean
1340
1341
  }
1341
1342
  class Popup extends Box {
@@ -1835,13 +1836,13 @@ by the browser, in its own locale, whatever this one.
1835
1836
  /* the globals the components use: --background-primary / -secondary / -ternary / -hover / -active / -input,
1836
1837
  --text-primary / -secondary / -ternary, --border / -light / -hover / -focus, --accent-background / -hover /
1837
1838
  -focus / -active / -light, --accent-color (the text on the accent), --disabled-*, --icon-color / -active,
1838
- --alert-*, --warning-*, --success-* */
1839
+ --alert-*, --warning-*, --success-*, --shadow-color (the shadow of what floats: dialog, list, notification, tooltip) */
1839
1840
  ```
1840
1841
  // The greys are mixes of a0 and a100: a theme that reverses the scale gets them light on dark.
1841
1842
  // Each component reads its own variables (the @cssvar of its class), which point to these globals:
1842
1843
  // a theme redefines the globals, or one variable of a component.
1843
1844
  // Themes: Application.setTheme(name) puts the class x4theme-<name> on <html>. "" is the light theme (by default),
1844
- // "dark" the dark theme of x4 (the greys of VS Code Dark Modern, its blues from #5796ca). An application adds its own theme in its css,
1845
+ // "dark" the dark theme of x4 (the greys of VS Code Dark Modern, its blues from #2d6fa5: the accent #245883). An application adds its own theme in its css,
1845
1846
  // html.x4theme-pink { --color-primary-a0: ...; ... }, then Application.setTheme("pink").
1846
1847
  // The font: --font-name and --font-size (14px), on :root (normalize.scss); body takes them. To change the size
1847
1848
  // of the text, set --font-size ( :root { --font-size: 13px } ), not the font-size of <html>: the components
@@ -2218,6 +2219,8 @@ class CMover {
2218
2219
  }
2219
2220
  createPainter(c2d: CanvasRenderingContext2D, w: number, h: number): CanvasEx // Canvas painter
2220
2221
  initTooltips(): void // initialize the global tooltip system
2222
+ // a tooltip: 30vw wide at most (--tooltip-max-width; none: the width of its text); it goes at a click, and when its
2223
+ // element is removed or hidden (checked every second while it is shown)
2221
2224
  ```
2222
2225
 
2223
2226
  JSX, in `.tsx` files: in tsconfig.json `"jsx": "react"` and `"jsxFactory": "x4_react.create_element"` (esbuild reads
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "x4js",
3
- "version": "2.3.29",
3
+ "version": "2.3.30",
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.",
4
5
  "license": "MIT",
5
6
  "type": "module",
6
7
  "main": "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 to the right: a menu item that opens a sub menu
23
+ caret: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 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="M246.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-128-128c-9.2-9.2-22.9-11.9-34.9-6.9s-19.8 16.6-19.8 29.6l0 256c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l128-128z"/></svg>`,
22
24
  // a triangle, a "!" cut in it
23
25
  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>`,
24
26
  };
@@ -20,6 +20,7 @@
20
20
  --color-dialog-caption: var( --accent-background-active );
21
21
  --color-dialog-caption-text: var( --accent-color );
22
22
  --color-dialog-background: var( --background-primary );
23
+ --color-dialog-border: transparent; // its shadow is enough on a light background
23
24
  }
24
25
 
25
26
  .x4dialog {
@@ -28,6 +29,7 @@
28
29
 
29
30
  position: absolute !important;
30
31
  background-color: var( --color-dialog-background );
32
+ border: 1px solid var( --color-dialog-border );
31
33
  max-width: 99vw;
32
34
  outline: none;
33
35
 
@@ -55,6 +55,7 @@ interface DialogEvents extends PopupEvents {
55
55
  * --color-dialog-caption
56
56
  * --color-dialog-caption-text
57
57
  * --color-dialog-background
58
+ * --color-dialog-border
58
59
  * ```
59
60
  */
60
61
 
@@ -92,12 +92,10 @@
92
92
  font-weight: 600;
93
93
  }
94
94
 
95
- &.popup {
96
- &:after {
97
- //<todo: see that
98
- content: url("./caret-right-solid.svg");
99
- width: 6px;
100
- }
95
+ // the triangle of a sub menu: after the text, which takes the width, at the right
96
+ #sub {
97
+ width: 6px;
98
+ height: 12px;
101
99
  }
102
100
 
103
101
  &:hover:not(.title),
@@ -95,8 +95,10 @@ class CMenuItem extends Component {
95
95
  }
96
96
 
97
97
  this.setContent( [
98
- new Icon({ id:"icon", iconId }),
99
- new Component( { id: "text", content: itm.text } )
98
+ new Icon({ id:"icon", iconId }),
99
+ new Component( { id: "text", content: itm.text } ),
100
+ // a sub menu: a triangle at the right, in the colour of the text
101
+ itm.menu ? new Icon( { id: "sub", iconId: icons.caret } ) : null,
100
102
  ]);
101
103
 
102
104
  if( itm.menu ) {
@@ -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() );
@@ -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 {
@@ -19,8 +19,9 @@
19
19
  $color--base: #569cd6;
20
20
  $color--interval: 8.4%;
21
21
 
22
- // the blues of the dark theme, from their own base
23
- $color--dark-base: #5796ca;
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;
24
25
 
25
26
  :root {
26
27
  --color-primary-a0: #ffffff;
@@ -93,6 +94,9 @@ $color--dark-base: #5796ca;
93
94
  --border-hover: color-mix(in srgb, var(--color-primary-a100) 23.9%, var(--color-primary-a0)); // #c2c2c2
94
95
  --border-focus: var(--accent-background);
95
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
+
96
100
  // the red, orange and green do not follow the scale: their text stays white
97
101
  --alert-background: var(--color-danger-a50);
98
102
  --alert-background-active: var(--color-danger-a60);
@@ -123,14 +127,14 @@ html.x4theme-dark {
123
127
  --color-primary-a20: #37373d; // list.inactiveSelectionBackground
124
128
  --color-primary-a30: #3c3c3c; // input.border
125
129
 
126
- // the blues: from $color--dark-base (chosen); where VS Code has list.activeSelectionBackground,
127
- // button.background and focusBorder, button.hoverBackground
128
- --color-primary-a40: #{color.adjust($color--dark-base, $lightness: -35%)}; // a selection
129
- --color-primary-a50: #{$color--dark-base}; // a button, the focus
130
- --color-primary-a55: #{color.adjust($color--dark-base, $lightness: -5%)}; // a button hovered
131
- --color-primary-a60: #{$color--dark-base};
132
- --color-primary-a70: #{color.adjust($color--dark-base, $lightness: -5%)};
133
- --color-primary-a80: #{color.adjust($color--dark-base, $lightness: -35%)};
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
134
138
 
135
139
  --color-primary-a90: #cccccc;
136
140
  --color-primary-a100: #cccccc; // foreground
@@ -162,6 +166,10 @@ html.x4theme-dark {
162
166
  --color-dialog-caption: #181818; // titleBar.activeBackground
163
167
  --color-dialog-caption-text: #cccccc; // titleBar.activeForeground
164
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);
165
173
 
166
174
  // the tiles of a MapView are light: turned dark, their colours kept
167
175
  --mapview-tile-filter: invert( 1 ) hue-rotate( 180deg ) brightness( 0.95 ) contrast( 0.9 );
@@ -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
 
@@ -1 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 512" fill="var(--color-gray-8)"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M246.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-128-128c-9.2-9.2-22.9-11.9-34.9-6.9s-19.8 16.6-19.8 29.6l0 256c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l128-128z"/></svg>