x4js 2.2.40 → 2.2.42
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/package.json +1 -1
- package/src/components/assets/icons.ts +10 -0
- package/src/components/checkbox/checkbox.ts +3 -8
- package/src/components/components.ts +1 -0
- package/src/components/gridview/gridview.ts +3 -5
- package/src/components/listbox/listbox.ts +31 -2
- package/src/components/radio/radio.ts +3 -2
- package/src/components/tag/tag.module.scss +30 -0
- package/src/components/tag/tag.ts +33 -0
- package/src/components/treeview/treeview.module.scss +1 -1
- package/src/components/treeview/treeview.ts +28 -5
- package/src/core/component.ts +2 -2
- package/src/components/treeview/chevron-down-light.svg +0 -1
package/package.json
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
|
|
2
|
+
const x4icons = {
|
|
3
|
+
check: `<svg viewBox="0 0 10 7" fill="none" aria-hidden="true"><path d="M4 4.586L1.707 2.293A1 1 0 1 0 .293 3.707l3 3a.997.997 0 0 0 1.414 0l5-5A1 1 0 1 0 8.293.293L4 4.586z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>`,
|
|
4
|
+
empty: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path opacity=".4" d="M32 96l0 200.6 21.7-54.3C65.9 211.9 95.3 192 128 192l320 0 0-32c0-17.7-14.3-32-32-32l-117.5 0c-25.5 0-49.9-10.1-67.9-28.1L204.1 73.4c-6-6-14.1-9.4-22.6-9.4L64 64C46.3 64 32 78.3 32 96zM49.1 426.1c-2 4.9-1.4 10.5 1.6 14.9s7.9 7 13.2 7l320 0 80 0c6.5 0 12.4-4 14.9-10.1l64-160c2-4.9 1.4-10.5-1.6-14.9s-7.9-7-13.2-7l-400 0c-6.5 0-12.4 4-14.9 10.1l-64 160z"/><path d="M448 160l0 32 32 0 0-32c0-35.3-28.7-64-64-64L298.5 96c-17 0-33.3-6.7-45.3-18.7L226.7 50.7c-12-12-28.3-18.7-45.3-18.7L64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l320 0 80 0c19.6 0 37.3-11.9 44.6-30.2l64-160c5.9-14.8 4.1-31.5-4.8-44.7S543.9 224 528 224l-400 0c-19.6 0-37.3 11.9-44.6 30.2L32 382.8 32 96c0-17.7 14.3-32 32-32l117.5 0c8.5 0 16.6 3.4 22.6 9.4l22.6-22.6L204.1 73.4l26.5 26.5c18 18 42.4 28.1 67.9 28.1L416 128c17.7 0 32 14.3 32 32zM384 448L64 448c-5.3 0-10.3-2.6-13.2-7s-3.6-10-1.6-14.9l64-160c2.4-6.1 8.3-10.1 14.9-10.1l400 0c5.3 0 10.3 2.6 13.2 7s3.6 10 1.6 14.9l-64 160C476.4 444 470.5 448 464 448l-80 0z"/></svg>`,
|
|
5
|
+
radio: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 5 5" aria-hidden="true"><circle class="fa-primary" cx="2.5" cy="2.5" r="2.5" ></circle><circle class="fa-secondary" cx="2.5" cy="2.5" r="1.25"></circle></svg>`,
|
|
6
|
+
chevron: `<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="M267.3 395.3c-6.2 6.2-16.4 6.2-22.6 0l-192-192c-6.2-6.2-6.2-16.4 0-22.6s16.4-6.2 22.6 0L256 361.4 436.7 180.7c6.2-6.2 16.4-6.2 22.6 0s6.2 16.4 0 22.6l-192 192z"/></svg>`,
|
|
7
|
+
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default x4icons;
|
|
@@ -3,12 +3,11 @@ import { EventCallback } from '../../core/core_events';
|
|
|
3
3
|
|
|
4
4
|
import { Input } from '../input/input';
|
|
5
5
|
import { Label } from '../label/label';
|
|
6
|
+
import { class_ns } from '../../core/core_tools';
|
|
6
7
|
|
|
7
|
-
import
|
|
8
|
+
import icons from "../assets/icons";
|
|
8
9
|
|
|
9
10
|
import "./checkbox.module.scss"
|
|
10
|
-
import icon from "./check.svg";
|
|
11
|
-
import { class_ns } from '../../core/core_tools';
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* Checkbox events
|
|
@@ -76,11 +75,7 @@ export class Checkbox extends Component<CheckboxProps,CheckBoxEvents> {
|
|
|
76
75
|
} ),
|
|
77
76
|
])
|
|
78
77
|
|
|
79
|
-
|
|
80
|
-
const svg = `<svg viewBox="0 0 10 7" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M4 4.586L1.707 2.293A1 1 0 1 0 .293 3.707l3 3a.997.997 0 0 0 1.414 0l5-5A1 1 0 1 0 8.293.293L4 4.586z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd">
|
|
81
|
-
</path></svg>`
|
|
82
|
-
|
|
83
|
-
this.query<Label>( '.inner' ).dom.insertAdjacentHTML( "beforeend", svg );
|
|
78
|
+
this.query<Label>( '.inner' ).dom.insertAdjacentHTML( "beforeend", icons.check );
|
|
84
79
|
|
|
85
80
|
/*
|
|
86
81
|
svgLoader.load( icon ).then( svg => {
|
|
@@ -37,6 +37,7 @@ export * from "./slider/slider"
|
|
|
37
37
|
export * from "./spreadsheet/spreadsheet"
|
|
38
38
|
export * from "./switch/switch"
|
|
39
39
|
export * from "./tabs/tabs"
|
|
40
|
+
export * from "./tag/tag"
|
|
40
41
|
export * from "./textarea/textarea"
|
|
41
42
|
export * from "./textedit/textedit"
|
|
42
43
|
export * from "./tickline/tickline"
|
|
@@ -27,12 +27,10 @@ import { Box } from '../boxes/boxes';
|
|
|
27
27
|
import { CSizer } from '../sizers/sizer'
|
|
28
28
|
import { Viewport } from '../viewport/viewport';
|
|
29
29
|
import { Label, SimpleText } from '../label/label';
|
|
30
|
+
import icons from "../assets/icons"
|
|
30
31
|
|
|
31
32
|
import { _tr } from '../../core/core_i18n';
|
|
32
33
|
|
|
33
|
-
const check_icon = `<svg viewBox="0 0 10 7" fill="none" aria-hidden="true"><path d="M4 4.586L1.707 2.293A1 1 0 1 0 .293 3.707l3 3a.997.997 0 0 0 1.414 0l5-5A1 1 0 1 0 8.293.293L4 4.586z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>`;
|
|
34
|
-
const empty_icon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path opacity=".4" d="M32 96l0 200.6 21.7-54.3C65.9 211.9 95.3 192 128 192l320 0 0-32c0-17.7-14.3-32-32-32l-117.5 0c-25.5 0-49.9-10.1-67.9-28.1L204.1 73.4c-6-6-14.1-9.4-22.6-9.4L64 64C46.3 64 32 78.3 32 96zM49.1 426.1c-2 4.9-1.4 10.5 1.6 14.9s7.9 7 13.2 7l320 0 80 0c6.5 0 12.4-4 14.9-10.1l64-160c2-4.9 1.4-10.5-1.6-14.9s-7.9-7-13.2-7l-400 0c-6.5 0-12.4 4-14.9 10.1l-64 160z"/><path d="M448 160l0 32 32 0 0-32c0-35.3-28.7-64-64-64L298.5 96c-17 0-33.3-6.7-45.3-18.7L226.7 50.7c-12-12-28.3-18.7-45.3-18.7L64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l320 0 80 0c19.6 0 37.3-11.9 44.6-30.2l64-160c5.9-14.8 4.1-31.5-4.8-44.7S543.9 224 528 224l-400 0c-19.6 0-37.3 11.9-44.6 30.2L32 382.8 32 96c0-17.7 14.3-32 32-32l117.5 0c8.5 0 16.6 3.4 22.6 9.4l22.6-22.6L204.1 73.4l26.5 26.5c18 18 42.4 28.1 67.9 28.1L416 128c17.7 0 32 14.3 32 32zM384 448L64 448c-5.3 0-10.3-2.6-13.2-7s-3.6-10-1.6-14.9l64-160c2.4-6.1 8.3-10.1 14.9-10.1l400 0c5.3 0 10.3 2.6 13.2 7s3.6 10 1.6 14.9l-64 160C476.4 444 470.5 448 464 448l-80 0z"/></svg>`;
|
|
35
|
-
|
|
36
34
|
import "./gridview.module.scss"
|
|
37
35
|
|
|
38
36
|
export type CellRenderer = (rec: DataRecord) => Component;
|
|
@@ -677,7 +675,7 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
677
675
|
switch (type) {
|
|
678
676
|
case "checkbox": {
|
|
679
677
|
if (data) {
|
|
680
|
-
return new Icon({ cls: "cell-check" + cls, iconId:
|
|
678
|
+
return new Icon({ cls: "cell-check" + cls, iconId: icons.check });
|
|
681
679
|
}
|
|
682
680
|
|
|
683
681
|
return undefined;
|
|
@@ -1141,7 +1139,7 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
1141
1139
|
|
|
1142
1140
|
// empty ?
|
|
1143
1141
|
if( rowc==0 ) {
|
|
1144
|
-
this._body.setContent( new Label( { cls: "empty vertical", icon:
|
|
1142
|
+
this._body.setContent( new Label( { cls: "empty vertical", icon: icons.empty, text: this.props.emptyMsg ?? _tr.global.empty_list } ) );
|
|
1145
1143
|
return;
|
|
1146
1144
|
}
|
|
1147
1145
|
|
|
@@ -22,15 +22,20 @@ import { ScrollView, Viewport } from '../viewport/viewport';
|
|
|
22
22
|
import { Header } from '../header/header';
|
|
23
23
|
|
|
24
24
|
import { HBox } from '../boxes/boxes';
|
|
25
|
-
import { Label } from '../label/label';
|
|
25
|
+
import { Label, SimpleText } from '../label/label';
|
|
26
|
+
|
|
27
|
+
import { _tr } from '../../x4.js';
|
|
28
|
+
import icons from '../assets/icons'
|
|
26
29
|
|
|
27
30
|
import "./listbox.module.scss"
|
|
28
31
|
|
|
32
|
+
|
|
29
33
|
export type ListboxID = number | string;
|
|
30
34
|
|
|
31
35
|
export interface ListItem {
|
|
32
36
|
id: ListboxID;
|
|
33
37
|
text: string | UnsafeHtml;
|
|
38
|
+
sub_cols?: (string | UnsafeHtml | Component)[]; // extra columns
|
|
34
39
|
|
|
35
40
|
iconId?: string;
|
|
36
41
|
data?: any;
|
|
@@ -62,6 +67,7 @@ export interface ListboxProps extends Omit<ComponentProps,'content'> {
|
|
|
62
67
|
footer?: Component,
|
|
63
68
|
checkable?: true,
|
|
64
69
|
multisel?: true,
|
|
70
|
+
emptyMsg?: string,
|
|
65
71
|
|
|
66
72
|
dblClick?: EventCallback<EvDblClick>;
|
|
67
73
|
selectionChange?: EventCallback<EvSelectionChange>;
|
|
@@ -449,6 +455,7 @@ export class Listbox extends Component<ListboxProps,ListboxEvents> {
|
|
|
449
455
|
}
|
|
450
456
|
else {
|
|
451
457
|
update_sel = oldSel.length>0;
|
|
458
|
+
this._view.setContent( new Label( { cls: "empty vertical", icon: icons.empty, text: this.props.emptyMsg ?? _tr.global.empty_list } ) );
|
|
452
459
|
}
|
|
453
460
|
|
|
454
461
|
if( update_sel ) {
|
|
@@ -477,9 +484,31 @@ export class Listbox extends Component<ListboxProps,ListboxEvents> {
|
|
|
477
484
|
*/
|
|
478
485
|
|
|
479
486
|
defaultRenderer( item: ListItem ): Component {
|
|
487
|
+
|
|
488
|
+
const mk_col = ( _: any, index: number ) => {
|
|
489
|
+
const c = item.sub_cols[index];
|
|
490
|
+
if( c===undefined || c===null ) {
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if( c instanceof Component ) {
|
|
495
|
+
return c;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return new SimpleText( { cls: `column ref-c${index+2}`, text: c } )
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
let cols: Component[] = null;
|
|
502
|
+
if( item.sub_cols ) {
|
|
503
|
+
cols = item.sub_cols.map( mk_col );
|
|
504
|
+
}
|
|
505
|
+
|
|
480
506
|
return new HBox( {
|
|
481
507
|
cls: item.cls,
|
|
482
|
-
content:
|
|
508
|
+
content: [
|
|
509
|
+
new Label( { cls: `column ref-c1`, icon: item.iconId, text: item.text } ),
|
|
510
|
+
...cols,
|
|
511
|
+
],
|
|
483
512
|
} )
|
|
484
513
|
}
|
|
485
514
|
|
|
@@ -22,6 +22,8 @@ import { Label } from '../label/label';
|
|
|
22
22
|
import { Input } from '../input/input'
|
|
23
23
|
import { HBox } from '../boxes/boxes';
|
|
24
24
|
|
|
25
|
+
import icons from "../assets/icons"
|
|
26
|
+
|
|
25
27
|
import "./radio.module.scss";
|
|
26
28
|
|
|
27
29
|
/**
|
|
@@ -84,8 +86,7 @@ export class Radio extends Component<RadioProps,RadioEvents> {
|
|
|
84
86
|
} ),
|
|
85
87
|
])
|
|
86
88
|
|
|
87
|
-
|
|
88
|
-
this._check.dom.insertAdjacentHTML( "beforeend", svg );
|
|
89
|
+
this._check.dom.insertAdjacentHTML( "beforeend", icons.radio );
|
|
89
90
|
|
|
90
91
|
//svgLoader.load( icon ).then( svg => {
|
|
91
92
|
// this._check.dom.insertAdjacentHTML( "beforeend", svg );
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file tag.module.scss
|
|
3
|
+
* @author Etienne Cochard
|
|
4
|
+
* @copyright (c) 2026 R-libre ingenierie, all rights reserved.
|
|
5
|
+
**/
|
|
6
|
+
|
|
7
|
+
.x4tag {
|
|
8
|
+
color: black;
|
|
9
|
+
border-radius: 16px;
|
|
10
|
+
border: 1px solid var( --border );
|
|
11
|
+
padding: 2px 8px;
|
|
12
|
+
gap: 4px;
|
|
13
|
+
font-weight: normal;
|
|
14
|
+
|
|
15
|
+
.x4icon:empty {
|
|
16
|
+
display: none;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.x4icon {
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.x4simpletext {
|
|
23
|
+
color: inherit;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&.alert {
|
|
27
|
+
color: var( --alert-color );
|
|
28
|
+
background-color: var( --alert-background );
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file tag.ts
|
|
3
|
+
* @author Etienne Cochard
|
|
4
|
+
* @copyright (c) 2026 R-libre ingenierie, all rights reserved.
|
|
5
|
+
**/
|
|
6
|
+
|
|
7
|
+
import { ComponentProps } from '../../core/component';
|
|
8
|
+
import { class_ns, UnsafeHtml } from '../../core/core_tools';
|
|
9
|
+
import { HBox } from '../boxes/boxes';
|
|
10
|
+
import { Icon } from '../icon/icon';
|
|
11
|
+
import { SimpleText } from '../label/label';
|
|
12
|
+
|
|
13
|
+
import "./tag.module.scss";
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
export interface TagProps extends ComponentProps {
|
|
17
|
+
label?: string | UnsafeHtml;
|
|
18
|
+
icon?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@class_ns( "x4" )
|
|
23
|
+
export class Tag extends HBox<TagProps> {
|
|
24
|
+
|
|
25
|
+
constructor( props: TagProps ) {
|
|
26
|
+
super( props );
|
|
27
|
+
|
|
28
|
+
this.setContent( [
|
|
29
|
+
new Icon( { iconId: props.icon } ),
|
|
30
|
+
new SimpleText( { text: props.label } ),
|
|
31
|
+
])
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -19,13 +19,15 @@ import { EventCallback } from '../../core/core_events';
|
|
|
19
19
|
import { Component, ComponentEvent, ComponentEvents, ComponentProps, EvClick, EvDblClick, EvSelectionChange, componentFromDOM } from '../../core/component';
|
|
20
20
|
|
|
21
21
|
import { ScrollView, Viewport } from '../viewport/viewport';
|
|
22
|
-
import { Label } from '../label/label';
|
|
22
|
+
import { Label, SimpleText } from '../label/label';
|
|
23
23
|
import { ListboxID, ListItem } from '../listbox/listbox';
|
|
24
24
|
import { Box, BoxProps, HBox, VBox } from '../boxes/boxes';
|
|
25
25
|
import { Icon } from '../icon/icon';
|
|
26
26
|
|
|
27
|
+
import { _tr } from '../../x4.js';
|
|
28
|
+
import icons from '../assets/icons'
|
|
29
|
+
|
|
27
30
|
import "./treeview.module.scss";
|
|
28
|
-
import folder_icon from "./chevron-down-light.svg"
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
//import folder_closed from "./folder-minus-light.svg"
|
|
@@ -53,6 +55,7 @@ export interface TreeItem extends ListItem {
|
|
|
53
55
|
interface TreeviewProps extends Omit<ComponentProps,"content"> {
|
|
54
56
|
items: TreeItem[];
|
|
55
57
|
footer?: Component;
|
|
58
|
+
emptyMsg?: string;
|
|
56
59
|
selectionChange?: EventCallback<EvSelectionChange>;
|
|
57
60
|
dblClick?: EventCallback<EvDblClick>;
|
|
58
61
|
click?: EventCallback<EvClick>;
|
|
@@ -83,7 +86,7 @@ class CTreeViewItem extends Box {
|
|
|
83
86
|
|
|
84
87
|
if( item ) {
|
|
85
88
|
this._label = new HBox( {cls:"label item", content: [
|
|
86
|
-
this._icon = new Icon( { cls:
|
|
89
|
+
this._icon = new Icon( { cls: 'arrow', iconId: item.children ? icons.chevron : undefined } ),
|
|
87
90
|
new Label( { tag: "span", cls: "", text: item.text, icon: item.iconId } ),
|
|
88
91
|
]});
|
|
89
92
|
|
|
@@ -91,6 +94,23 @@ class CTreeViewItem extends Box {
|
|
|
91
94
|
this._label.addClass( item.cls );
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
if( item.sub_cols ) {
|
|
98
|
+
const mk_col = ( _: any, index: number ) => {
|
|
99
|
+
const c = item.sub_cols[index];
|
|
100
|
+
if( c===undefined || c===null ) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if( c instanceof Component ) {
|
|
105
|
+
return c;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return new SimpleText( { cls: `column ref-c${index+2}`, text: c } )
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this._label.appendContent( item.sub_cols.map( mk_col ) );
|
|
112
|
+
}
|
|
113
|
+
|
|
94
114
|
this._label.setInternalData( "id", item.id );
|
|
95
115
|
|
|
96
116
|
if( item.children ) {
|
|
@@ -133,7 +153,7 @@ class CTreeViewItem extends Box {
|
|
|
133
153
|
}
|
|
134
154
|
|
|
135
155
|
setItems( items: TreeItem[ ] ) {
|
|
136
|
-
if( items ) {
|
|
156
|
+
if( items && items.length ) {
|
|
137
157
|
const childs = items.map( itm => {
|
|
138
158
|
return new CTreeViewItem( {}, itm );
|
|
139
159
|
})
|
|
@@ -202,10 +222,13 @@ export class Treeview extends Component<TreeviewProps,TreeviewEvents> {
|
|
|
202
222
|
|
|
203
223
|
const root = new CTreeViewItem( { cls: "root"}, null );
|
|
204
224
|
|
|
205
|
-
if( items ) {
|
|
225
|
+
if( items && items.length ) {
|
|
206
226
|
root.setItems( items );
|
|
207
227
|
this._view.setContent( root );
|
|
208
228
|
}
|
|
229
|
+
else {
|
|
230
|
+
this._view.setContent( new Label( { cls: "empty vertical", icon: icons.empty, text: this.props.emptyMsg ?? _tr.global.empty_list } ) );
|
|
231
|
+
}
|
|
209
232
|
}
|
|
210
233
|
|
|
211
234
|
private _on_click( ev: UIEvent ) {
|
package/src/core/component.ts
CHANGED
|
@@ -416,8 +416,8 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
|
|
|
416
416
|
* @param name - The suffix of the `data-` attribute (e.g., for `data-foo`, use `"foo"`).
|
|
417
417
|
* @returns The string value of the `data-*` attribute, or `null` if not present.
|
|
418
418
|
*
|
|
419
|
-
* @cf setIntData
|
|
420
|
-
* @cf setInternalData
|
|
419
|
+
* @cf Component.setIntData, Component.getIntData (number)
|
|
420
|
+
* @cf Component.setInternalData, Component.getInternalData (typed data)
|
|
421
421
|
*/
|
|
422
422
|
|
|
423
423
|
getData( name: string ) : string {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<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="M267.3 395.3c-6.2 6.2-16.4 6.2-22.6 0l-192-192c-6.2-6.2-6.2-16.4 0-22.6s16.4-6.2 22.6 0L256 361.4 436.7 180.7c6.2-6.2 16.4-6.2 22.6 0s6.2 16.4 0 22.6l-192 192z"/></svg>
|