x4js 2.3.20 → 2.3.21

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.
@@ -64,7 +64,8 @@ export class Dialog<P extends DialogProps = DialogProps, E extends DialogEvents
64
64
  private _title: Label;
65
65
 
66
66
  constructor(props: P) {
67
- super({ tag: "dialog", modal: true, ...props });
67
+ // content is the content of its form
68
+ super({ tag: "dialog", modal: true, ...props, content: undefined });
68
69
 
69
70
  this._ismodal = this.props.modal;
70
71
 
@@ -95,7 +96,7 @@ export class Dialog<P extends DialogProps = DialogProps, E extends DialogEvents
95
96
  }) : null,
96
97
  ]
97
98
  }),
98
- this.form = props.form ? props.form : new Form({}),
99
+ this.form = props.form ? props.form : new Form({ content: props.content }),
99
100
  new BtnGroup({
100
101
  id: "btnbar",
101
102
  reverse: true,
@@ -133,9 +134,19 @@ export class Dialog<P extends DialogProps = DialogProps, E extends DialogEvents
133
134
  }
134
135
  })
135
136
 
136
- asap( ( ) => {
137
- this.focusNext( true );
138
- } );
137
+ }
138
+
139
+ /**
140
+ * each time it opens: the focus on its first field
141
+ */
142
+
143
+ override show( show = true ): this {
144
+ super.show( show );
145
+ if( show ) {
146
+ asap( ( ) => this.focusNext( true ) );
147
+ }
148
+
149
+ return this;
139
150
  }
140
151
 
141
152
  private focusNext( next: boolean) : boolean {
@@ -105,10 +105,9 @@ export class Form<P extends FormProps = FormProps> extends Box<P> {
105
105
  */
106
106
 
107
107
  setAutoComplete( on = true ) {
108
- const items = this._get_inputs( );
109
- items.forEach( x => {
110
- x.setAttribute( "autocomplete", on ? "on" : "off" );
111
- } );
108
+ // on the <form>: its fields without an autocomplete of their own follow it,
109
+ // those added later too
110
+ this.setAttribute( "autocomplete", on ? "on" : "off" );
112
111
  }
113
112
 
114
113
  /**
@@ -16,31 +16,32 @@
16
16
 
17
17
 
18
18
  :root {
19
- --gridview-background: white;
19
+ // from the theme: a dark theme (its scale --color-primary-* reversed) gives a dark grid
20
+ --gridview-background: var( --background-primary );
20
21
  --gridview-border: var( --border );
21
22
 
22
- --gridview-header-cell-background: white;
23
- --gridview-header-cell-color: black;
24
- --gridview-header-cell-vline: #f0f0f0;
25
- --gridview-header-cell-border: #cccccc;
23
+ --gridview-header-cell-background: var( --background-primary );
24
+ --gridview-header-cell-color: var( --text-primary );
25
+ --gridview-header-cell-vline: var( --border );
26
+ --gridview-header-cell-border: var( --border );
26
27
 
27
28
  --grid-check-background: var( --accent-background );
28
- --grid-check-color: white;
29
- --grid-check-background-hover: white;
29
+ --grid-check-color: var( --accent-color );
30
+ --grid-check-background-hover: var( --background-primary );
30
31
  --grid-check-color-hover: var( --accent-background );
31
32
 
32
33
  --grid-perc-background: var( --accent-background );
33
- --grid-perc-color: white;
34
- --grid-perc-background-hover: white;
34
+ --grid-perc-color: var( --accent-color );
35
+ --grid-perc-background-hover: var( --background-primary );
35
36
  --grid-perc-color-hover: var( --accent-background );
36
37
 
37
38
 
38
- --gridview-cell-color: black;
39
- --gridview-cell-color-sel: white;
39
+ --gridview-cell-color: var( --text-primary );
40
+ --gridview-cell-color-sel: var( --accent-color );
40
41
  --gridview-cell-vline: var( --border );
41
42
 
42
- --gridview-row-background: white;
43
- --gridview-row-odd-background: white;
43
+ --gridview-row-background: var( --background-primary );
44
+ --gridview-row-odd-background: var( --background-primary );
44
45
  --gridview-row-border: var( --border );
45
46
 
46
47
  --gridview-row-background-hover: var( --background-secondary );
@@ -71,6 +72,7 @@
71
72
 
72
73
  background-color: var( --gridview-background );
73
74
  border: 1px solid var( --gridview-border );
75
+ color: var( --gridview-cell-color );
74
76
 
75
77
  .cell {
76
78
  padding: 4px;
@@ -159,7 +161,7 @@
159
161
  width: 100%;
160
162
  border: 1px solid var( --border );
161
163
  height: 8px;
162
- background-color: white;
164
+ background-color: var( --gridview-background );
163
165
  overflow: hidden;
164
166
 
165
167
  div {
@@ -219,7 +221,7 @@
219
221
  display: inline-flex;
220
222
  align-items: center;
221
223
 
222
- color: black;
224
+ color: var( --gridview-header-cell-color );
223
225
  font-weight: 500;
224
226
  justify-content: center;
225
227
 
@@ -320,7 +322,7 @@
320
322
  width: var( --fixed-width );
321
323
  bottom: 0;
322
324
 
323
- background-color: white;
325
+ background-color: var( --gridview-background );
324
326
  z-index: 1;
325
327
 
326
328
  .row {
@@ -559,6 +559,11 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
559
559
 
560
560
  private _scrollToIndex( index: number, block = 'nearest' ) {
561
561
 
562
+ // not built yet: nothing to scroll
563
+ if( !this._viewport ) {
564
+ return;
565
+ }
566
+
562
567
  // is it already visible ?
563
568
  let rows = this.queryAll(`.row[data-row="${index}"]`);
564
569
  if (rows.length) {
@@ -27,7 +27,7 @@ type solveCallback = (data:string)=>void;
27
27
 
28
28
  class SvgLoader {
29
29
  private cache: Map<string,string>;
30
- private waiters: Map<string,solveCallback[]>;
30
+ private waiters: Map<string,{ resolve: solveCallback, reject: ( e: any ) => void }[]>;
31
31
 
32
32
  constructor( ) {
33
33
  this.cache = new Map( );
@@ -37,26 +37,35 @@ class SvgLoader {
37
37
  async load( file: string ): Promise<string> {
38
38
 
39
39
  if( this.cache.has(file) ) {
40
- return Promise.resolve( this.cache.get(file) );
40
+ // null: the file was not found, it is not asked again
41
+ const svg = this.cache.get( file );
42
+ if( svg===null ) {
43
+ return Promise.reject( new Error( `file not found: ${file}` ) );
44
+ }
45
+
46
+ return Promise.resolve( svg );
41
47
  }
42
48
 
43
49
  return new Promise( (resolve,reject) => {
50
+ // already asked: the same answer
44
51
  if( this.waiters.has(file) ) {
45
- this.waiters.get(file).push( resolve );
46
- }
47
- else {
48
- this.waiters.set( file, [resolve] );
49
- this._load( file )
50
- .then( ( data: string ) => {
51
- this.cache.set( file, data );
52
- const ww = this.waiters.get( file );
53
- ww.forEach( cb => cb(data ) );
54
- })
55
- .catch( e => {
56
- this.cache.set( file, null );
57
- reject( e );
58
- } )
52
+ this.waiters.get(file).push( { resolve, reject } );
53
+ return;
59
54
  }
55
+
56
+ this.waiters.set( file, [{ resolve, reject }] );
57
+ this._load( file )
58
+ .then( ( data: string ) => {
59
+ this.cache.set( file, data );
60
+ this.waiters.get( file ).forEach( w => w.resolve( data ) );
61
+ this.waiters.delete( file );
62
+ })
63
+ .catch( e => {
64
+ // kept as not found: it is not asked again
65
+ this.cache.set( file, null );
66
+ this.waiters.get( file ).forEach( w => w.reject( e ) );
67
+ this.waiters.delete( file );
68
+ } );
60
69
  });
61
70
  }
62
71
 
@@ -83,6 +92,8 @@ export interface IconProps extends ComponentProps {
83
92
  /**
84
93
  * Identifier for the icon.
85
94
  * Can be a URL, a CSS variable (prefixed with `var:`), or inline SVG data.
95
+ * `var:home` (or `var:--home`) is the variable --home set on <html> by the code:
96
+ * document.documentElement.style.setProperty( "--home", svg ).
86
97
  * @example
87
98
  * // Using a CSS variable
88
99
  * { iconId: "var:home" }
@@ -139,8 +150,13 @@ export class Icon extends Component<IconProps> {
139
150
  if( iconId ) {
140
151
  if( iconId.startsWith('var:') ) {
141
152
  do {
142
- const path = iconId.substring( 4 );
143
- iconId = document.documentElement.style.getPropertyValue( path );
153
+ // "var:home" or "var:--home": the variable --home, set on <html>
154
+ let name = iconId.substring( 4 );
155
+ if( !name.startsWith( "--" ) ) {
156
+ name = "--" + name;
157
+ }
158
+
159
+ iconId = document.documentElement.style.getPropertyValue( name );
144
160
  } while( iconId.startsWith('var:') );
145
161
  }
146
162
 
@@ -284,15 +284,19 @@ export class Keyboard extends HBox<KeyboardProps>
284
284
 
285
285
  this.hide( );
286
286
 
287
+ // a key: each contact, a finger or the mouse, the second tap of a quick double tap too
288
+ this.addDOMEvent( "pointerdown", (e) => {
289
+ this.handleKeyEvent( e );
290
+ });
291
+
292
+ // the focus stays in the field
287
293
  this.addDOMEvent( "mousedown", (e) => {
288
- this.handleKeyEvent( e );
289
294
  e.preventDefault( );
290
295
  e.stopPropagation( );
291
296
  });
292
297
 
293
- // for rapid people
298
+ // a double click or a double tap: its two pointerdown gave the two keys
294
299
  this.addDOMEvent( "dblclick", (e) => {
295
- this.handleKeyEvent( e );
296
300
  e.preventDefault( );
297
301
  e.stopPropagation( );
298
302
  });
@@ -56,11 +56,14 @@ export class Link extends Component<LinkProps,LinkEvents> {
56
56
 
57
57
  this.mapPropEvents( props, "click" );
58
58
 
59
- this.setContent( new Label( {
60
- text: props.text,
61
- icon: props.icon,
62
- align: props.align,
63
- } ) );
59
+ // content given: it is the link; else a label (icon and text)
60
+ if( props.content===undefined ) {
61
+ this.setContent( new Label( {
62
+ text: props.text,
63
+ icon: props.icon,
64
+ align: props.align,
65
+ } ) );
66
+ }
64
67
 
65
68
  this.addDOMEvent('click', (e) => this._on_click(e));
66
69
  }
@@ -71,7 +74,14 @@ export class Link extends Component<LinkProps,LinkEvents> {
71
74
  */
72
75
 
73
76
  setText( text: string | UnsafeHtml ) {
74
- this.setContent( text );
77
+ // the text of the label: its icon and alignment stay
78
+ const label = this.query<Label>( ":scope > .x4label" );
79
+ if( label ) {
80
+ label.setText( text );
81
+ }
82
+ else {
83
+ this.setContent( text );
84
+ }
75
85
  }
76
86
 
77
87
  /**
@@ -83,7 +93,8 @@ export class Link extends Component<LinkProps,LinkEvents> {
83
93
  const xev: EvClick = {context: this.props.href };
84
94
  this.fire('click', xev );
85
95
 
86
- if( xev.preventDefault ) {
96
+ // the handler called ev.preventDefault( ): the link is not followed
97
+ if( xev.defaultPrevented ) {
87
98
  ev.preventDefault();
88
99
  ev.stopPropagation();
89
100
  }
@@ -66,7 +66,7 @@ export class MessageBox extends Dialog<DialogProps>
66
66
  content: [
67
67
  new HBox( {
68
68
  content: [
69
- new Icon( { iconId: error_icon }),
69
+ new Icon( { iconId: icon }),
70
70
  new Label( { text: msg } ),
71
71
  ]
72
72
  }),
@@ -39,7 +39,7 @@ interface PanelProps extends ComponentProps {
39
39
  @class_ns( "x4" )
40
40
  export class Panel extends VBox<PanelProps> {
41
41
 
42
- private _title: Component;
42
+ private _title: Label;
43
43
  private _body: Component;
44
44
 
45
45
  constructor( props: PanelProps ) {
@@ -57,6 +57,7 @@ export class Panel extends VBox<PanelProps> {
57
57
  }
58
58
 
59
59
  setTitle( title: string | UnsafeHtml ) {
60
- this._title.setContent( title )
60
+ // the text of the label: its icon stays
61
+ this._title.setText( title );
61
62
  }
62
63
  }
@@ -16,30 +16,31 @@
16
16
 
17
17
 
18
18
  :root {
19
- --spreadsheet-background: white;
19
+ // from the theme: a dark theme (its scale --color-primary-* reversed) gives a dark grid
20
+ --spreadsheet-background: var( --background-primary );
20
21
  --spreadsheet-border: var( --border );
21
22
 
22
- --spreadsheet-header-cell-background: white;
23
- --spreadsheet-header-cell-color: black;
24
- --spreadsheet-header-cell-vline: #f0f0f0;
25
- --spreadsheet-header-cell-border: #cccccc;
23
+ --spreadsheet-header-cell-background: var( --background-primary );
24
+ --spreadsheet-header-cell-color: var( --text-primary );
25
+ --spreadsheet-header-cell-vline: var( --border );
26
+ --spreadsheet-header-cell-border: var( --border );
26
27
 
27
28
  --spreadsheet-check-background: var( --accent-background );
28
- --spreadsheet-check-color: white;
29
- --spreadsheet-check-background-hover: white;
29
+ --spreadsheet-check-color: var( --accent-color );
30
+ --spreadsheet-check-background-hover: var( --background-primary );
30
31
  --spreadsheet-check-color-hover: var( --accent-background );
31
32
 
32
33
  --spreadsheet-perc-background: var( --accent-background );
33
- --spreadsheet-perc-color: white;
34
- --spreadsheet-perc-background-hover: white;
34
+ --spreadsheet-perc-color: var( --accent-color );
35
+ --spreadsheet-perc-background-hover: var( --background-primary );
35
36
  --spreadsheet-perc-color-hover: var( --accent-background );
36
37
 
37
- --spreadsheet-cell-color: black;
38
- --spreadsheet-cell-color-sel: white;
38
+ --spreadsheet-cell-color: var( --text-primary );
39
+ --spreadsheet-cell-color-sel: var( --accent-color );
39
40
  --spreadsheet-cell-vline: var( --border );
40
41
 
41
- --spreadsheet-row-background: white;
42
- --spreadsheet-row-odd-background: white;
42
+ --spreadsheet-row-background: var( --background-primary );
43
+ --spreadsheet-row-odd-background: var( --background-primary );
43
44
  --spreadsheet-row-border: var( --border );
44
45
 
45
46
  --spreadsheet-row-background-hover: var( --background-secondary );
@@ -70,6 +71,7 @@
70
71
 
71
72
  background-color: var( --spreadsheet-background );
72
73
  border: 1px solid var( --spreadsheet-border );
74
+ color: var( --spreadsheet-cell-color );
73
75
 
74
76
  .cell {
75
77
  padding: 4px;
@@ -180,7 +182,7 @@
180
182
  width: 100%;
181
183
  border: 1px solid var( --border );
182
184
  height: 8px;
183
- background-color: white;
185
+ background-color: var( --spreadsheet-background );
184
186
  overflow: hidden;
185
187
 
186
188
  div {
@@ -234,7 +236,7 @@
234
236
  display: inline-flex;
235
237
  align-items: center;
236
238
 
237
- color: black;
239
+ color: var( --spreadsheet-header-cell-color );
238
240
  font-weight: 500;
239
241
  justify-content: center;
240
242
 
@@ -307,7 +309,7 @@
307
309
  width: var( --fixed-width );
308
310
  bottom: 0;
309
311
 
310
- background-color: white;
312
+ background-color: var( --spreadsheet-background );
311
313
  z-index: 1;
312
314
 
313
315
  .row {
@@ -27,6 +27,12 @@
27
27
  flex-shrink: 0; // alone in a box, it keeps its height: the box does not crush it
28
28
  resize: none; // no handle unless the prop resize asks for one
29
29
  padding: 4px;
30
+
31
+ // never smaller than one character, its padding and border included (border-box):
32
+ // 10px = 2 x 4px of padding + 2 x 1px of border
33
+ min-height: calc( 1lh + 10px );
34
+ min-width: calc( 1ch + 10px );
35
+
30
36
  outline: none;
31
37
  font: inherit;
32
38
 
@@ -65,7 +71,8 @@
65
71
  }
66
72
 
67
73
  .x4simpletextarea {
68
- @extend %flex;
69
-
70
- }
74
+ // its height of rows when the TextArea has none, the TextArea's height when it has one;
75
+ // a basis of 0 would leave it the height of its padding: a textarea scrolls, its min-height is 0
76
+ flex: 1 0 auto;
77
+ }
71
78
  }
@@ -14,7 +14,9 @@
14
14
  * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
15
  **/
16
16
 
17
- import { Box, Component, ComponentProps } from 'x4js';
17
+ import { Component, ComponentProps } from '../../core/component';
18
+ import { class_ns } from '../../core/core_tools';
19
+ import { Box } from '../boxes/boxes';
18
20
 
19
21
  import "./video.module.scss"
20
22
 
@@ -29,6 +31,7 @@ interface VideoProps extends ComponentProps {
29
31
  *
30
32
  */
31
33
 
34
+ @class_ns( "x4" )
32
35
  export class Video extends Box {
33
36
 
34
37
  private _vplayer: HTMLVideoElement;
@@ -124,7 +127,18 @@ export class Video extends Box {
124
127
  this.player.pause( );
125
128
  }
126
129
 
127
- play( ) {
128
- this.player.play( );
130
+ /**
131
+ * @returns false when the playback could not start: the browser wants a click
132
+ * of the user first, or the source cannot be played
133
+ */
134
+
135
+ async play( ): Promise<boolean> {
136
+ try {
137
+ await this.player.play( );
138
+ return true;
139
+ }
140
+ catch( e ) {
141
+ return false;
142
+ }
129
143
  }
130
144
  }
@@ -83,8 +83,6 @@ export class Color {
83
83
 
84
84
  setValue(value: string): this {
85
85
 
86
- this.invalid = false;
87
-
88
86
  if( value.startsWith('#') ) {
89
87
  if (value.length == 7 && /#[0-9a-fA-F]{6}/.test(value)) {
90
88
  const hex = parseInt(value.slice(1), 16);
@@ -139,8 +137,10 @@ export class Color {
139
137
  }
140
138
  }
141
139
 
140
+ // red, to be seen, and marked as invalid
141
+ this.setRgb(255, 0, 0, 1);
142
142
  this.invalid = true;
143
- return this.setRgb(255, 0, 0, 1);
143
+ return this;
144
144
  }
145
145
 
146
146
  /**
@@ -205,23 +205,34 @@ export class Color {
205
205
  * @param r - Red component (0-255).
206
206
  * @param g - Green component (0-255).
207
207
  * @param b - Blue component (0-255).
208
- * @param a - Alpha component (0-1).
208
+ * @param a - Alpha component (0-1, default 1.0).
209
209
  * @returns The current Color instance.
210
210
  */
211
- setRgb(r: number, g: number, b: number, a: number): this {
212
- this.rgb = [clamp(r | 0, 0, 255), clamp(g | 0, 0, 255), clamp(b | 0, 0, 255), clamp(a, 0, 1)];
211
+ setRgb(r: number, g: number, b: number, a = 1.0): this {
212
+ // a new color: valid
213
+ this.invalid = false;
214
+ // rounded: hsv gives 123.999 for 124; "| 0" makes a NaN 0
215
+ const c = ( v: number ) => clamp( Math.round( v ) | 0, 0, 255 );
216
+ this.rgb = [c(r), c(g), c(b), clamp(a, 0, 1)];
213
217
  return this;
214
218
  }
215
219
 
216
220
  /**
217
221
  * Returns the CSS string representation of the color.
218
222
  *
219
- * @param withAlpha - Whether to include the alpha channel (default: true if alpha < 1).
220
- * @returns The CSS color string (e.g. "rgb(255,0,0)" or "rgba(255,0,0,0.5)").
223
+ * @param withAlpha - false (default): never the alpha; true: the alpha when the color is not opaque.
224
+ * @returns The CSS color string (e.g. "rgb(255,0,0)" or "rgba(255,0,0,0.500)").
221
225
  */
222
- toRgbString(withAlpha?: boolean): string {
226
+ toRgbString(withAlpha = false): string {
223
227
  const _ = this.rgb;
224
- return withAlpha === false || _[3] == 1 ? `rgb(${round(_[0])},${round(_[1])},${round(_[2])})` : `rgba(${round(_[0])},${round(_[1])},${round(_[2])},${_[3].toFixed(3)})`
228
+ const rgb = `${round(_[0])},${round(_[1])},${round(_[2])}`;
229
+
230
+ // an opaque color has no alpha to show
231
+ if( !withAlpha || _[3] >= 1 ) {
232
+ return `rgb(${rgb})`;
233
+ }
234
+
235
+ return `rgba(${rgb},${_[3].toFixed(3)})`;
225
236
  }
226
237
 
227
238
  /**
@@ -175,12 +175,13 @@ class DragManager {
175
175
  */
176
176
 
177
177
  private _startCheck() {
178
+ clearInterval( this.timer );
178
179
 
179
- if (this.timer) {
180
- clearInterval(this.timer);
181
- this._check( );
182
- }
180
+ // the target is told at once (and the one that was left)
181
+ this._check( );
183
182
 
183
+ // then checked again: over the children of a target, dragleave and dragover come
184
+ // in turn; it is left when no dragover came back in between
184
185
  this.timer = setInterval( () => this._check(), 300 );
185
186
  }
186
187