x4js 2.2.13 → 2.2.14

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
@@ -11,7 +11,7 @@ If these components are not enough, you can create your in few lines.
11
11
  see [home](https://x4js.org)
12
12
 
13
13
  ## API Documentation
14
- see [API](https://x4js.org/api/)
14
+ see [API](docs/index.html)
15
15
 
16
16
  ## Ideas
17
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x4js",
3
- "version": "2.2.13",
3
+ "version": "2.2.14",
4
4
  "type": "module",
5
5
  "main": "src/x4.ts",
6
6
  "module": "src/x4.ts",
@@ -9,7 +9,9 @@
9
9
  ".": "./src/x4.ts"
10
10
  },
11
11
  "scripts": {
12
- "build-publish": "tsx ./publish.ts"
12
+ "build-publish": "tsx ./publish.ts",
13
+ "docs": "typedoc",
14
+ "docs:watch": "typedoc --watch"
13
15
  },
14
16
  "files": [
15
17
  "src",
@@ -31,5 +33,8 @@
31
33
  ],
32
34
  "devDependencies": {
33
35
  "typescript": "^5.8.3"
36
+ },
37
+ "dependencies": {
38
+ "typedoc": "^0.28.19"
34
39
  }
35
40
  }
@@ -78,6 +78,7 @@ export class Checkbox extends Component<CheckboxProps,CheckBoxEvents> {
78
78
 
79
79
  svgLoader.load( icon ).then( svg => {
80
80
  this.query<Label>( '.inner' ).dom.insertAdjacentHTML( "beforeend", svg );
81
+ // no error because intenral data image
81
82
  });
82
83
 
83
84
  this.addDOMEvent('click', (e) => this._on_click(e)); // for outside click
@@ -60,6 +60,8 @@ export class ColorInput extends HBox<ColorInputProps> {
60
60
  eyeDropper.open( ).then( ( result: any ) => {
61
61
  color = new Color( result.sRGBHex );
62
62
  updateColor( color );
63
+ }).catch( (_: any ) => {
64
+ /* silence */
63
65
  });
64
66
  } } ) : null
65
67
  ])
@@ -427,7 +427,9 @@ export class ColorPicker extends VBox<ColorPickerProps,ColorPickerChangeEvents>
427
427
  this._sat.updateBaseColor( hsv );
428
428
  this._hue.updateHue( hsv );
429
429
  updateColor( );
430
- });
430
+ }).catch( (_: any )=> {
431
+ /* silence */
432
+ } );
431
433
  })
432
434
  }
433
435
 
@@ -15,7 +15,7 @@
15
15
  **/
16
16
 
17
17
  import { Component, ComponentEvents, ComponentProps, EvSelectionChange, makeUniqueComponentId } from '../../core/component';
18
- import { class_ns, IComponentInterface, IFormElement, kbNav } from '../../core/core_tools';
18
+ import { class_ns, IComponentInterface, IFormElement, kbNav, safeText, sanitizeHtml, UnsafeHtml } from '../../core/core_tools';
19
19
  import { EventCallback } from '../../core/core_events';
20
20
 
21
21
  import { Listbox, ListboxID, ListItem } from '../listbox/listbox';
@@ -131,9 +131,7 @@ export class Combobox extends Component<ComboboxProps,ComboboxEvents> {
131
131
  list.select( sel, false );
132
132
  }
133
133
 
134
- //TODO: unsafehtml
135
- //@ts-ignore
136
- this._input.setValue( itm ? itm.text : "" );
134
+ this._input.setValue( safeText(itm.text) );
137
135
 
138
136
  if( !this._prevent_close ) {
139
137
  this._popup.show( false );
@@ -267,8 +265,7 @@ export class Combobox extends Component<ComboboxProps,ComboboxEvents> {
267
265
  isValid: ( ) => { return this._input.isValid(); }
268
266
  };
269
267
 
270
- //@ts-ignore
271
- return i as T;
268
+ return i as unknown as T;
272
269
  }
273
270
 
274
271
  return super.queryInterface( name );
@@ -101,6 +101,9 @@ export class Dialog<P extends DialogProps = DialogProps, E extends DialogEvents
101
101
  })
102
102
  ]);
103
103
 
104
+ this.setAria( "role", "dialog" );
105
+ this.setAria( "aria-describedby", props.title );
106
+
104
107
  this.addDOMEvent("keydown", (ev) => {
105
108
 
106
109
  if (ev.key == 'Escape') {
@@ -206,8 +209,7 @@ export class Dialog<P extends DialogProps = DialogProps, E extends DialogEvents
206
209
  focusNext: ( n: boolean ) => { return this.focusNext( n ); }
207
210
  };
208
211
 
209
- //@ts-ignore
210
- return i as T;
212
+ return i as unknown as T;
211
213
  }
212
214
 
213
215
  return super.queryInterface( name );
@@ -219,6 +221,7 @@ export class Dialog<P extends DialogProps = DialogProps, E extends DialogEvents
219
221
 
220
222
  setTitle( title: string ) {
221
223
  this._title.setText( title );
224
+ this.setAria( "aria-describedby", title );
222
225
  }
223
226
 
224
227
  /**
@@ -14,8 +14,8 @@
14
14
  * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
15
  **/
16
16
 
17
- import { class_ns } from '../../core/core_tools.ts';
18
- import { Component, ComponentProps } from '../../core/component.ts';
17
+ import { class_ns } from '../../core/core_tools';
18
+ import { Component, ComponentProps } from '../../core/component';
19
19
 
20
20
  import "./icon.module.scss"
21
21
 
@@ -52,6 +52,10 @@ class SvgLoader {
52
52
  const ww = this.waiters.get( file );
53
53
  ww.forEach( cb => cb(data ) );
54
54
  })
55
+ .catch( e => {
56
+ this.cache.set( file, null );
57
+ reject( e );
58
+ } )
55
59
  }
56
60
  });
57
61
  }
@@ -61,6 +65,8 @@ class SvgLoader {
61
65
  if( res.ok ) {
62
66
  return res.text( );
63
67
  }
68
+
69
+ throw new Error( `file not found: ${file}` );
64
70
  }
65
71
 
66
72
  }
@@ -149,7 +155,9 @@ export class Icon extends Component<IconProps> {
149
155
  svgLoader.load( iconId ).then( svg => {
150
156
  this.clearContent( );
151
157
  this.dom.insertAdjacentHTML('beforeend', svg );
152
- });
158
+ }).catch( _ => {
159
+ /*silence*/
160
+ } );
153
161
  }
154
162
  else {
155
163
  this.setContent( new Component( { tag: "img", attrs: { src: iconId } } ) );
@@ -527,8 +527,7 @@ export class Input extends Component<InputProps,InputEvents> {
527
527
  isValid: ( ) => { return this.isValid(); }
528
528
  };
529
529
 
530
- //@ts-ignore
531
- return i as T;
530
+ return i as unknown as T;
532
531
  }
533
532
 
534
533
  return super.queryInterface( name );
@@ -88,6 +88,7 @@ export class Radio extends Component<RadioProps,RadioEvents> {
88
88
 
89
89
  svgLoader.load( icon ).then( svg => {
90
90
  this._check.dom.insertAdjacentHTML( "beforeend", svg );
91
+ // no error because intenral data image
91
92
  });
92
93
 
93
94
  this.addDOMEvent('click', (e) => this._on_click(e)); // for outside click
@@ -159,7 +159,7 @@ export class Slider extends Component<SliderProps,SliderEvents> {
159
159
  }
160
160
 
161
161
  private _on_key( ev: KeyboardEvent ) {
162
- console.log( ev.key );
162
+ //console.log( ev.key );
163
163
 
164
164
  let stp = this.props.step ?? 1;
165
165
  let inc = 0;
@@ -15,7 +15,7 @@
15
15
  **/
16
16
 
17
17
 
18
- import { Component, ComponentContent, ComponentEvents, ComponentProps, EvClick, EvContextMenu, EvDblClick, EvSelectionChange, componentFromDOM } from '../../core/component';
18
+ import { Component, ComponentContent, ComponentEvents, ComponentProps, EvChange, EvClick, EvContextMenu, EvDblClick, EvSelectionChange, componentFromDOM } from '../../core/component';
19
19
  import { GridColumn } from '../gridview/gridview'
20
20
 
21
21
  import { class_ns, isNumber, isString, UnsafeHtml } from '../../core/core_tools';
@@ -55,9 +55,6 @@ function mkid(row: number, col: number) {
55
55
  *
56
56
  */
57
57
 
58
- export interface EvChange extends CoreEvent {
59
- }
60
-
61
58
  export interface StoreEvents extends EventMap {
62
59
  changed: EvChange;
63
60
  }
@@ -157,7 +154,7 @@ export class Store extends CoreElement<StoreEvents> {
157
154
 
158
155
  private _changed() {
159
156
  if (!this._lock) {
160
- this.fire("changed", {});
157
+ this.fire("changed", {value:null});
161
158
  this._change = false;
162
159
  }
163
160
  else {
@@ -80,8 +80,7 @@ class SimpleTextArea extends Component<TextAreaProps> {
80
80
  isValid: ( ) => { return true; }
81
81
  };
82
82
 
83
- //@ts-ignore
84
- return i as T;
83
+ return i as unknown as T;
85
84
  }
86
85
 
87
86
  return super.queryInterface( name );
@@ -124,8 +123,7 @@ export class TextArea extends VBox {
124
123
  isValid: ( ) => { return true; }
125
124
  };
126
125
 
127
- //@ts-ignore
128
- return i as T;
126
+ return i as unknown as T;
129
127
  }
130
128
 
131
129
  return super.queryInterface( name );
@@ -287,7 +287,7 @@ export class Treeview extends Component<TreeviewProps,TreeviewEvents> {
287
287
  }
288
288
 
289
289
  default:
290
- console.log( ev.key );
290
+ //console.log( "unhandled key:", ev.key );
291
291
  return;
292
292
  }
293
293
 
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { isArray, UnsafeHtml, isNumber, Rect, Constructor, class_ns, x4_class_ns_sym, IRect } from './core_tools';
18
18
  import { CoreElement } from './core_element';
19
- import { ariaValues, unitless } from './core_styles';
19
+ import { AriaAttributes, unitless } from './core_styles';
20
20
  import { CoreEvent, EventMap } from './core_events';
21
21
  import { addEvent, DOMEventHandler, GlobalDOMEvents } from './core_dom';
22
22
  import { Application, EvMessage } from './core_application';
@@ -666,8 +666,8 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
666
666
  * @returns The component instance for chaining.
667
667
  */
668
668
 
669
- setAria( name: keyof ariaValues, value: string | number | boolean ): this {
670
- this.setAttribute( name, value );
669
+ setAria( name: keyof AriaAttributes, value: string | number | boolean ): this {
670
+ this.setAttribute( name as string, value );
671
671
  return this;
672
672
  }
673
673
 
@@ -517,16 +517,24 @@ export class DataProxy extends CoreElement<DataEventMap> {
517
517
  url += '?' + this.m_props.params.join( '&' );
518
518
  }
519
519
 
520
- const r = await fetch( url );
521
- if( r.ok ) {
522
- const raw = await r.json( );
523
-
524
- let json = raw;
525
- if( this.m_props.solver ) {
526
- json = this.m_props.solver( json );
527
- }
520
+ try {
521
+ const r = await fetch( url );
522
+ if( r.ok ) {
523
+ const raw = await r.json( );
524
+
525
+ let json = raw;
526
+ if( this.m_props.solver ) {
527
+ json = this.m_props.solver( json );
528
+ }
528
529
 
529
- this.fire( 'change', {value:json,context:raw} );
530
+ this.fire( 'change', {value:json,context:raw} );
531
+ }
532
+ else {
533
+ console.error( r.statusText );
534
+ }
535
+ }
536
+ catch( e ) {
537
+ console.error( e );
530
538
  }
531
539
  }
532
540
  }
@@ -116,7 +116,7 @@ export function dispatchEvent(ev: Event) {
116
116
  callback( ev );
117
117
  }
118
118
 
119
- if (ev.stopPropagation || ev.defaultPrevented || noup) {
119
+ if ( ev.defaultPrevented || noup) {
120
120
  break;
121
121
  }
122
122
  }
@@ -98,12 +98,12 @@ class DragManager {
98
98
 
99
99
  const dragEnter = (ev: DragEvent) => {
100
100
  if( filterCB && !filterCB(this.dragSource,ev.dataTransfer) ) {
101
- console.log( 'reject ', el );
101
+ //console.log( 'reject ', el );
102
102
  ev.dataTransfer.dropEffect = 'none';
103
103
  return;
104
104
  }
105
105
 
106
- console.log( 'accepted ', el );
106
+ //console.log( 'accepted ', el );
107
107
  ev.preventDefault();
108
108
  ev.dataTransfer.dropEffect = 'copy';
109
109
  };
@@ -112,7 +112,7 @@ class DragManager {
112
112
  //console.log( "dragover", ev.target );
113
113
 
114
114
  if( filterCB && !filterCB(this.dragSource,ev.dataTransfer) ) {
115
- console.log( 'reject ', el );
115
+ //console.log( 'reject ', el );
116
116
  ev.dataTransfer.dropEffect = 'none';
117
117
  return;
118
118
  }
@@ -8,6 +8,10 @@
8
8
  type StateData = boolean | number | string | Date | any;
9
9
  type State = Record<string,StateData>;
10
10
 
11
+ /**
12
+ * @experimental
13
+ */
14
+
11
15
  export class StateManager {
12
16
 
13
17
  private _state: StateData;
@@ -67,9 +67,54 @@ export const unitless: Record<string,1> = {
67
67
  strokeWidth: 1
68
68
  }
69
69
 
70
- export type ariaValues = {
71
- "aria-activedescendant": 1,
72
- "role": 1,
70
+ export type AriaAttributes = {
71
+ "role": 1;
72
+ "aria-label": 1;
73
+ "aria-labelledby": 1;
74
+ "aria-describedby": 1;
75
+ "aria-details": 1;
76
+ "aria-roledescription": 1;
77
+ "aria-keyshortcuts": 1;
78
+ "aria-placeholder": 1;
79
+ "aria-activedescendant": 1;
80
+ "aria-controls": 1;
81
+ "aria-owns": 1;
82
+ "aria-flowto": 1;
83
+ "aria-errormessage": 1;
84
+ "aria-hidden": 1;
85
+ "aria-disabled": 1;
86
+ "aria-busy": 1;
87
+ "aria-modal": 1;
88
+ "aria-required": 1;
89
+ "aria-readonly": 1;
90
+ "aria-checked": 1;
91
+ "aria-pressed": 1;
92
+ "aria-selected": 1;
93
+ "aria-expanded": 1;
94
+ "aria-invalid": 1;
95
+ "aria-current": 1;
96
+ "aria-haspopup": 1;
97
+ "aria-multiselectable": 1;
98
+ "aria-multiline": 1;
99
+ "aria-atomic": 1;
100
+ "aria-live": 1;
101
+ "aria-relevant": 1;
102
+ "aria-orientation": 1;
103
+ "aria-sort": 1;
104
+ "aria-valuemin": 1;
105
+ "aria-valuemax": 1;
106
+ "aria-valuenow": 1;
107
+ "aria-valuetext": 1;
108
+ "aria-level": 1;
109
+ "aria-setsize": 1;
110
+ "aria-posinset": 1;
111
+ "aria-rowcount": 1;
112
+ "aria-colcount": 1;
113
+ "aria-rowindex": 1;
114
+ "aria-colindex": 1;
115
+ "aria-rowspan": 1;
116
+ "aria-colspan": 1;
117
+ "aria-autocomplete": 1;
73
118
  }
74
119
 
75
120
  export function isUnitLess( name: string ) {
@@ -17,7 +17,7 @@
17
17
  import { Component, ComponentProps } from './component';
18
18
  import { isUnitLess } from "./core_styles";
19
19
  import { DOMEventHandler, GlobalDOMEvents, addEvent } from './core_dom';
20
- import { isNumber, isString } from './core_tools';
20
+ import { isNumber, isString, sanitizeHtml } from './core_tools';
21
21
 
22
22
  const SVG_NS = "http://www.w3.org/2000/svg";
23
23
 
@@ -526,7 +526,7 @@ export class SvgText extends SvgItem {
526
526
  this.setAttr( 'x', num(x)+'' );
527
527
  this.setAttr( 'y', num(y)+'' );
528
528
 
529
- this._dom.innerHTML = txt;
529
+ this._dom.innerHTML = sanitizeHtml( txt );
530
530
  }
531
531
 
532
532
  /**
@@ -212,16 +212,6 @@ export class Rect implements IRect {
212
212
  }
213
213
  }
214
214
 
215
- /**
216
- * generic size
217
- */
218
-
219
- export interface Size {
220
- w: number;
221
- h: number;
222
- }
223
-
224
-
225
215
  /**
226
216
  * generic Point
227
217
  * TODO: name it IPoint
@@ -994,3 +984,38 @@ export enum kbNav {
994
984
  right,
995
985
  }
996
986
 
987
+
988
+ const _escapes: Record<string, string> = {
989
+ '&': '&amp;',
990
+ '<': '&lt;',
991
+ '>': '&gt;',
992
+ '"': '&quot;',
993
+ "'": '&#39;',
994
+ };
995
+
996
+ const _rx_escape = /[&<>"']/g;
997
+
998
+ /**
999
+ * Sanitizes a user-provided string against XSS injection.
1000
+ * Escapes HTML special characters so the string is safe to display as text content.
1001
+ */
1002
+
1003
+ export function sanitizeHtml( input: string ): string {
1004
+ if( !_rx_escape.test( input ) ) {
1005
+ return input;
1006
+ }
1007
+
1008
+ _rx_escape.lastIndex = 0;
1009
+ return input.replace( _rx_escape, ch => _escapes[ch] )
1010
+ }
1011
+
1012
+
1013
+ /**
1014
+ * Returns a safe string from user content: UnsafeHtml passes through untouched
1015
+ * plain strings are escaped.
1016
+ */
1017
+
1018
+ export function safeText( v: string | UnsafeHtml ): string {
1019
+ if( !v ) { return ""; }
1020
+ return v instanceof UnsafeHtml ? v.toString() : sanitizeHtml( v );
1021
+ }