x4js 2.2.13 → 2.2.15
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 +1 -1
- package/package.json +7 -2
- package/src/components/checkbox/checkbox.ts +1 -0
- package/src/components/colorinput/colorinput.ts +2 -0
- package/src/components/colorpicker/colorpicker.ts +3 -1
- package/src/components/combobox/combobox.ts +3 -6
- package/src/components/dialog/dialog.ts +5 -2
- package/src/components/icon/icon.ts +11 -3
- package/src/components/input/input.ts +1 -2
- package/src/components/radio/radio.ts +1 -0
- package/src/components/slider/slider.ts +1 -1
- package/src/components/spreadsheet/spreadsheet.ts +2 -5
- package/src/components/textarea/textarea.ts +2 -4
- package/src/components/treeview/treeview.ts +1 -1
- package/src/core/component.ts +3 -3
- package/src/core/core_data.ts +17 -9
- package/src/core/core_dom.ts +1 -1
- package/src/core/core_dragdrop.ts +3 -3
- package/src/core/core_state.ts +4 -0
- package/src/core/core_styles.ts +48 -3
- package/src/core/core_svg.ts +2 -2
- package/src/core/core_tools.ts +35 -10
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "x4js",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.15",
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
18
|
-
import { Component, ComponentProps } from '../../core/component
|
|
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 } } ) );
|
|
@@ -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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
128
|
-
return i as T;
|
|
126
|
+
return i as unknown as T;
|
|
129
127
|
}
|
|
130
128
|
|
|
131
129
|
return super.queryInterface( name );
|
package/src/core/component.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
package/src/core/core_data.ts
CHANGED
|
@@ -517,16 +517,24 @@ export class DataProxy extends CoreElement<DataEventMap> {
|
|
|
517
517
|
url += '?' + this.m_props.params.join( '&' );
|
|
518
518
|
}
|
|
519
519
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/core/core_dom.ts
CHANGED
|
@@ -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
|
}
|
package/src/core/core_state.ts
CHANGED
package/src/core/core_styles.ts
CHANGED
|
@@ -67,9 +67,54 @@ export const unitless: Record<string,1> = {
|
|
|
67
67
|
strokeWidth: 1
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
export type
|
|
71
|
-
"
|
|
72
|
-
"
|
|
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 ) {
|
package/src/core/core_svg.ts
CHANGED
|
@@ -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
|
/**
|
package/src/core/core_tools.ts
CHANGED
|
@@ -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
|
+
'&': '&',
|
|
990
|
+
'<': '<',
|
|
991
|
+
'>': '>',
|
|
992
|
+
'"': '"',
|
|
993
|
+
"'": ''',
|
|
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
|
+
}
|