x4js 2.3.18 → 2.3.20

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 (38) hide show
  1. package/aicli.md +3 -3
  2. package/aicontext.md +51 -23
  3. package/cli/config.mjs +14 -7
  4. package/cli/copy-plugin.mjs +8 -0
  5. package/cli/create.mjs +8 -2
  6. package/cli/dev.mjs +4 -1
  7. package/cli/less-plugin.mjs +5 -1
  8. package/cli/main.mjs +15 -5
  9. package/cli/rawfile-plugin.mjs +23 -3
  10. package/cli/templates.mjs +5 -1
  11. package/package.json +1 -1
  12. package/src/components/calendar/calendar.module.scss +16 -1
  13. package/src/components/calendar/calendar.ts +52 -9
  14. package/src/components/checkbox/checkbox.ts +1 -0
  15. package/src/components/combobox/combobox.ts +51 -23
  16. package/src/components/dialog/dialog.ts +4 -1
  17. package/src/components/form/form.ts +9 -5
  18. package/src/components/gridview/gridview.ts +2 -1
  19. package/src/components/input/input.ts +63 -38
  20. package/src/components/keyboard/keyboard.module.scss +35 -4
  21. package/src/components/keyboard/keyboard.ts +120 -21
  22. package/src/components/listbox/listbox.ts +6 -1
  23. package/src/components/messages/messages.ts +12 -3
  24. package/src/components/popup/popup.ts +19 -20
  25. package/src/components/radio/radio.ts +8 -4
  26. package/src/components/rating/rating.ts +38 -48
  27. package/src/components/slider/slider.ts +38 -8
  28. package/src/components/spreadsheet/spreadsheet.ts +140 -131
  29. package/src/components/switch/switch.ts +2 -1
  30. package/src/components/textarea/textarea.module.scss +2 -4
  31. package/src/components/textarea/textarea.ts +2 -1
  32. package/src/components/textedit/textedit.ts +12 -2
  33. package/src/core/core_application.ts +8 -4
  34. package/src/core/core_data.ts +64 -26
  35. package/src/core/core_dom.ts +9 -1
  36. package/src/core/core_pdf.ts +1200 -46
  37. package/src/core/core_router.ts +42 -44
  38. package/src/core/core_state.ts +22 -5
@@ -181,7 +181,8 @@ private _view: Viewport;
181
181
 
182
182
  navigate( sens: kbNav ) {
183
183
 
184
- if( !this._lastsel ) {
184
+ // an item of id 0 is one
185
+ if( this._lastsel===undefined ) {
185
186
  if( sens==kbNav.next ) sens = kbNav.first;
186
187
  else sens = kbNav.last;
187
188
  }
@@ -395,6 +396,7 @@ private _view: Viewport;
395
396
  if( itm ) {
396
397
  this._multisel.add( id );
397
398
  itm.addClass( "selected" );
399
+ this._lastsel = id; // the arrows go on from it
398
400
  }
399
401
  });
400
402
 
@@ -434,6 +436,9 @@ private _view: Viewport;
434
436
  }
435
437
 
436
438
  clearSelection( fireEvent = true ) {
439
+ // nothing selected: the arrows start from the first item
440
+ this._lastsel = undefined;
441
+
437
442
  if( this._multisel.size ) {
438
443
  this._clearSelection( );
439
444
  if( fireEvent ) {
@@ -106,10 +106,13 @@ export class MessageBox extends Dialog<DialogProps>
106
106
  box.on( "btnclick", ( ev ) => {
107
107
  asap( ( ) => {
108
108
  resolve( ev.button );
109
- box.close()
109
+ box.close()
110
110
  });
111
111
  });
112
112
 
113
+ // closed without a button (Escape, the close box): nothing was chosen
114
+ box.on( "closed", ( ) => resolve( null ) );
115
+
113
116
  box.show();
114
117
  } );
115
118
 
@@ -175,10 +178,13 @@ export class InputBox extends Dialog<DialogProps>
175
178
  box.on( "btnclick", ( ev ) => {
176
179
  asap( ( ) => {
177
180
  resolve( ev.button=="ok" ? box.getValue( ) : null );
178
- box.close()
181
+ box.close()
179
182
  });
180
183
  });
181
184
 
185
+ // closed without a button (Escape, the close box): nothing was chosen
186
+ box.on( "closed", ( ) => resolve( null ) );
187
+
182
188
  box.show();
183
189
  } );
184
190
 
@@ -233,10 +239,13 @@ export class PromptBox extends Dialog<DialogProps>
233
239
  box.on( "btnclick", ( ev ) => {
234
240
  asap( ( ) => {
235
241
  resolve( ev.button );
236
- box.close()
242
+ box.close()
237
243
  });
238
244
  });
239
245
 
246
+ // closed without a button (Escape, the close box): nothing was chosen
247
+ box.on( "closed", ( ) => resolve( null ) );
248
+
240
249
  box.show();
241
250
  } );
242
251
  }
@@ -46,6 +46,22 @@ function getRoot( ) {
46
46
  return document.body;
47
47
  }
48
48
 
49
+ /**
50
+ * on the document while the event goes down (capture): a press outside
51
+ * every popup closes the group opened last, the others at the next presses.
52
+ * The event is left as it is, what is pressed handles it as usual.
53
+ * One function for all the popups: it is removed by the one that empties the list.
54
+ */
55
+
56
+ function dismissLast( e: UIEvent ) {
57
+ if( autoclose_list.some( x => x.dom.contains( e.target as Node ) ) ) {
58
+ return;
59
+ }
60
+
61
+ // the newest popup: dismiss( ) closes its whole group (a menu and its sub-menus)
62
+ autoclose_list[autoclose_list.length-1].dismiss( );
63
+ }
64
+
49
65
  /**
50
66
  * @cssvar
51
67
  * ```
@@ -233,7 +249,7 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
233
249
  if( idx>=0 ) {
234
250
  autoclose_list.splice( idx, 1 );
235
251
  if( autoclose_list.length==0 ) {
236
- document.removeEventListener( "pointerdown", this._dismiss, true );
252
+ document.removeEventListener( "pointerdown", dismissLast, true );
237
253
  }
238
254
  }
239
255
  }
@@ -263,7 +279,7 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
263
279
 
264
280
  if( this.props.autoClose ) {
265
281
  if( autoclose_list.length==0 ) {
266
- document.addEventListener( "pointerdown", this._dismiss, true );
282
+ document.addEventListener( "pointerdown", dismissLast, true );
267
283
  }
268
284
 
269
285
  autoclose_list.push( this );
@@ -320,23 +336,6 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
320
336
  this._do_hide( );
321
337
  }
322
338
 
323
- /**
324
- * binded
325
- *
326
- * listened on the document while the event goes down (capture): a press
327
- * closes the popups even when what is pressed stops its propagation.
328
- * the event is left as it is, what is pressed handles it as usual.
329
- */
330
-
331
- private _dismiss = ( e: UIEvent ) => {
332
- const onac = autoclose_list.some( x=> x.dom.contains(e.target as Node) )
333
- if( onac ) {
334
- return;
335
- }
336
-
337
- this.dismiss( );
338
- }
339
-
340
339
  /**
341
340
  * dismiss all popup belonging to the same group as 'this'
342
341
  */
@@ -369,7 +368,7 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
369
368
  const list = inc_group.reverse( );
370
369
  autoclose_list = excl_group;
371
370
  if( autoclose_list.length==0 ) {
372
- document.removeEventListener( "pointerdown", this._dismiss, true );
371
+ document.removeEventListener( "pointerdown", dismissLast, true );
373
372
  }
374
373
 
375
374
  list.forEach( x => x.close() );
@@ -19,7 +19,7 @@ import { class_ns } from '../../core/core_tools';
19
19
  import { EventCallback } from '../../core/core_events';
20
20
 
21
21
  import { Label } from '../label/label';
22
- import { Input } from '../input/input'
22
+ import { Input, getRadioOwner } from '../input/input'
23
23
  import { HBox } from '../boxes/boxes';
24
24
 
25
25
  import icons from "../assets/icons"
@@ -152,10 +152,14 @@ export class Radio extends Component<RadioProps,RadioEvents> {
152
152
  */
153
153
 
154
154
  checkValue( vname: string ) {
155
- const grp = this.props.name;
156
- const el = this.parentElement().query<Radio>( `input[name="${grp}"][value="${vname}"]` );
155
+ // the radios of the group: in the form that holds this one, else in
156
+ // its dialog, else in the document (as a Form reads them)
157
+ const owner = getRadioOwner( this.dom );
158
+ const selector = `input[name="${this.props.name}"][value="${vname}"]`;
159
+
160
+ const el = owner.querySelector( selector ) as HTMLInputElement;
157
161
  if( el ) {
158
- el.setCheck( true );
162
+ el.checked = true;
159
163
  }
160
164
  }
161
165
  }
@@ -14,12 +14,11 @@
14
14
  * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
15
  **/
16
16
 
17
- import { Component, ComponentEvents, componentFromDOM, ComponentProps, EvChange } from '../../core/component';
17
+ import { Component, ComponentEvents, ComponentProps, EvChange } from '../../core/component';
18
18
  import { EventCallback } from '../../core/core_events';
19
19
  import { HBox } from '../boxes/boxes';
20
- import { Input } from '../input/input';
21
20
  import { Icon } from '../icon/icon';
22
- import { class_ns } from '../../core/core_tools';
21
+ import { class_ns, IComponentInterface, IFormElement } from '../../core/core_tools';
23
22
 
24
23
  import icons from "../assets/icons"
25
24
 
@@ -54,50 +53,33 @@ export interface RatingProps extends ComponentProps {
54
53
  export class Rating extends HBox<RatingProps,RatingEventMap> {
55
54
 
56
55
  private m_els: Component[];
57
- private m_input: Input;
58
-
56
+
59
57
  constructor( props: RatingProps ) {
60
58
  super( props );
61
59
 
62
- props.steps = props.steps ?? 5;
60
+ // this.props: a copy of props (Box adds its tag)
61
+ this.props.steps = this.props.steps ?? 5;
62
+
63
+ // the name is on the rating: a Form finds it, and reads its value as a number (queryInterface)
64
+ if( props.name ) {
65
+ this.setAttribute( "name", props.name );
66
+ }
67
+
68
+ this.addDOMEvent( 'click', (e) => this._on_click(e) );
63
69
  this._update( );
64
70
  }
65
71
 
72
+ // value: the count of stars that are lit, 0 for none
66
73
  private _update( ) {
67
-
68
74
  const props = this.props;
69
-
70
- let shape = props.icon ?? icons.star.solid;
71
- let value = props.value ?? 0;
72
-
73
- this.m_input = new Input( {
74
- type: "text",
75
- hidden: true,
76
- name: props.name,
77
- value: ''+value
78
- } );
79
-
80
- this.addDOMEvent( 'click', (e) => this._on_click(e) );
75
+ const shape = props.icon ?? icons.star.solid;
76
+ const value = props.value ?? 0;
81
77
 
82
78
  this.m_els = [];
83
79
  for( let i=0; i<props.steps; i++ ) {
84
-
85
- let cls = 'item';
86
- if( i+1 <= value ) {
87
- cls += ' checked';
88
- }
89
-
90
- let c = new Icon( {
91
- cls,
92
- iconId: shape,
93
- } );
94
-
95
- c.setInternalData( "value", i );
96
-
97
- this.m_els.push( c );
80
+ this.m_els.push( new Icon( { cls: i < value ? 'item checked' : 'item', iconId: shape } ) );
98
81
  }
99
82
 
100
- this.m_els.push( this.m_input );
101
83
  this.setContent( this.m_els );
102
84
  }
103
85
 
@@ -107,12 +89,7 @@ export class Rating extends HBox<RatingProps,RatingEventMap> {
107
89
 
108
90
  setValue( v: number ) {
109
91
  this.props.value = v;
110
-
111
- for( let c=0; c<this.props.steps; c++ ) {
112
- this.m_els[c].setClass( 'checked', this.m_els[c].getInternalData('value')<=v );
113
- }
114
-
115
- this.m_input.setValue( ''+this.props.value );
92
+ this.m_els.forEach( ( c, i ) => c.setClass( 'checked', i < v ) );
116
93
  }
117
94
 
118
95
  setSteps( n: number ) {
@@ -121,18 +98,31 @@ export class Rating extends HBox<RatingProps,RatingEventMap> {
121
98
  }
122
99
 
123
100
  setShape( icon: string ) {
124
- this.removeClass( this.props.icon );
125
101
  this.props.icon = icon;
102
+ this._update( );
126
103
  }
127
104
 
105
+ // the star clicked: its svg, or the Icon itself
128
106
  private _on_click( ev: MouseEvent ) {
129
- let item = componentFromDOM( ev.target as HTMLElement );
130
- item = item.parentElement( Icon );
131
-
132
- if( item ) {
133
- this.setValue( item.getInternalData("value") );
107
+ const n = this.m_els.findIndex( c => c.dom.contains( ev.target as Node ) );
108
+ if( n>=0 ) {
109
+ // the star of the value, clicked again: none is lit
110
+ this.setValue( n+1===this.props.value ? 0 : n+1 );
111
+ this.fire( 'change', { value: this.props.value } );
134
112
  }
113
+ }
114
+
115
+ override queryInterface<T extends IComponentInterface>( name: string ): T {
116
+ if( name=="form-element" ) {
117
+ const i: IFormElement = {
118
+ getRawValue: ( ) => this.getValue( ),
119
+ setRawValue: ( v: any ) => { this.setValue( +v ); },
120
+ isValid: ( ) => true,
121
+ };
135
122
 
136
- this.fire( 'change', {value:this.props.value} );
137
- }
123
+ return i as unknown as T;
124
+ }
125
+
126
+ return super.queryInterface( name );
127
+ }
138
128
  }
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { EventCallback } from '../../core/core_events';
18
18
  import { Component, ComponentEvents, ComponentProps, EvChange } from '../../core/component';
19
- import { class_ns, Rect } from '../../core/core_tools';
19
+ import { class_ns, IComponentInterface, IFormElement, Rect } from '../../core/core_tools';
20
20
 
21
21
  import { HBox } from '../boxes/boxes';
22
22
  import { Input } from '../input/input';
@@ -33,6 +33,8 @@ interface SliderProps extends ComponentProps {
33
33
  max: number;
34
34
  step?: number;
35
35
  vertical?: boolean;
36
+ /** for a Form: it reads the value as a number */
37
+ name?: string;
36
38
  change?: EventCallback<EvChange>;
37
39
  }
38
40
 
@@ -83,6 +85,12 @@ export class Slider extends Component<SliderProps,SliderEvents> {
83
85
 
84
86
  this.setAttribute( "tabindex", 0 );
85
87
 
88
+ // the name is on the slider: a Form finds it (queryInterface). The hidden range keeps
89
+ // the value: the browser brings it between min and max, on the step
90
+ if( props.name ) {
91
+ this.setAttribute( "name", props.name );
92
+ }
93
+
86
94
  this.setDOMEvents( {
87
95
  pointerdown: ( ev ) => this._on_mousedown( ev ),
88
96
  pointermove: ( ev ) => this._on_mousemove( ev ),
@@ -106,8 +114,8 @@ export class Slider extends Component<SliderProps,SliderEvents> {
106
114
  }
107
115
 
108
116
  private _getMinMax( ) {
109
- const min = parseInt( this._range.getAttribute( 'min' ) );
110
- const max = parseInt( this._range.getAttribute( 'max' ) );
117
+ const min = parseFloat( this._range.getAttribute( 'min' ) );
118
+ const max = parseFloat( this._range.getAttribute( 'max' ) );
111
119
  return [min,max];
112
120
  }
113
121
 
@@ -134,15 +142,17 @@ export class Slider extends Component<SliderProps,SliderEvents> {
134
142
  const [min,max] = this._getMinMax( );
135
143
 
136
144
  this._range.setNumValue( min + (perc * (max-min) ) );
137
- this._update( );
145
+ this._update( true );
138
146
  }
139
147
  }
140
148
 
141
- private _update( ) {
149
+ // notify: a change made by the user (drag, arrows); not by the code, nor at the construction
150
+ private _update( notify = false ) {
142
151
  const value = this._range.getNumValue( );
143
152
  const [min,max] = this._getMinMax( );
144
153
 
145
- let perc = max>min ? value / Math.abs(max-min) * 100 : 0;
154
+ // from min: min is at 0%, max at 100%
155
+ let perc = max>min ? (value-min) / (max-min) * 100 : 0;
146
156
 
147
157
  if( !this.props.vertical ) {
148
158
  this._thumb.setStyleValue( "left", perc+"%" );
@@ -155,7 +165,9 @@ export class Slider extends Component<SliderProps,SliderEvents> {
155
165
  this._bar.setStyleValue( "height", perc+"%" );
156
166
  }
157
167
 
158
- this.fire( "change", { value } );
168
+ if( notify ) {
169
+ this.fire( "change", { value } );
170
+ }
159
171
  }
160
172
 
161
173
  private _on_mouseup( ev: PointerEvent ) {
@@ -184,7 +196,7 @@ export class Slider extends Component<SliderProps,SliderEvents> {
184
196
  }
185
197
 
186
198
  this._range.setNumValue( this._range.getNumValue()+inc );
187
- this._update( );
199
+ this._update( true );
188
200
  }
189
201
  }
190
202
 
@@ -202,4 +214,22 @@ export class Slider extends Component<SliderProps,SliderEvents> {
202
214
  this._range.setNumValue( v );
203
215
  this._update( );
204
216
  }
217
+
218
+ getValue( ) {
219
+ return this._range.getNumValue( );
220
+ }
221
+
222
+ override queryInterface<T extends IComponentInterface>( name: string ): T {
223
+ if( name=="form-element" ) {
224
+ const i: IFormElement = {
225
+ getRawValue: ( ) => this.getValue( ),
226
+ setRawValue: ( v: any ) => { this.setValue( +v ); },
227
+ isValid: ( ) => true,
228
+ };
229
+
230
+ return i as unknown as T;
231
+ }
232
+
233
+ return super.queryInterface( name );
234
+ }
205
235
  }