x4js 2.3.27 → 2.3.28
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/LICENSE +21 -0
- package/README.md +5 -0
- package/aicontext.md +32 -5
- package/package.json +2 -1
- package/src/components/autocomplete/autocomplete.ts +5 -1
- package/src/components/boxes/boxes.ts +2 -2
- package/src/components/button/button.module.scss +4 -3
- package/src/components/checkbox/checkbox.module.scss +67 -1
- package/src/components/checkbox/checkbox.ts +22 -6
- package/src/components/colorinput/colorinput.module.scss +1 -0
- package/src/components/combobox/combobox.module.scss +4 -1
- package/src/components/gridview/gridview.module.scss +6 -0
- package/src/components/input/input.module.scss +1 -1
- package/src/components/messages/messages.module.scss +9 -4
- package/src/components/propgrid/progrid.module.scss +13 -5
- package/src/components/radio/radio.module.scss +54 -31
- package/src/components/radio/radio.ts +12 -1
- package/src/components/sizers/sizer.ts +17 -5
- package/src/components/spreadsheet/spreadsheet.module.scss +6 -0
- package/src/components/tabs/tabs.module.scss +2 -0
- package/src/components/tabs/tabs.ts +1 -0
- package/src/components/textarea/textarea.module.scss +9 -0
- package/src/components/textarea/textarea.ts +1 -0
- package/src/components/themes.scss +1 -0
- package/src/components/treeview/treeview.module.scss +6 -1
- package/src/components/treeview/treeview.ts +1 -0
- package/src/components/video/video.module.scss +5 -2
- package/src/components/video/video.ts +14 -1
- package/src/core/core_pdf.ts +17 -0
- package/src/core/core_react.ts +12 -18
- package/src/core/core_svg.ts +9 -6
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 R-libre ingenierie
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
<picture>
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/rlibre/x4/main/logox4-v3-dark.svg">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/rlibre/x4/main/logox4-v3.svg" alt="x4 logo" width="96">
|
|
4
|
+
</picture>
|
|
5
|
+
|
|
1
6
|
# x4js
|
|
2
7
|
**A TypeScript framework for people who build applications, not markup.**
|
|
3
8
|
|
package/aicontext.md
CHANGED
|
@@ -740,9 +740,14 @@ new SimpleTextArea({ flex: 1, placeholder: 'Your message', resize: false })
|
|
|
740
740
|
|
|
741
741
|
```ts
|
|
742
742
|
interface CheckboxProps extends ComponentProps {
|
|
743
|
-
label
|
|
743
|
+
label?: string; icon?: string // no label: the icon alone (give it a tooltip)
|
|
744
|
+
checked?: boolean; value?: boolean|number|string; name?: string
|
|
744
745
|
change?: EventCallback<EvChange>
|
|
745
746
|
}
|
|
747
|
+
// cls: "left" (label on the left), "button" ("button outline" rounded), "danger" (checked in red, its box or its
|
|
748
|
+
// button: "button danger"); without the box, the input is still reached by Tab (--checkbox-button-*, -danger-*)
|
|
749
|
+
// A toggle button is a Checkbox with cls "button": getCheck() tells whether it is pressed, change when it changes
|
|
750
|
+
// new Checkbox( { cls: "button", icon: icon_bold, tooltip: "Bold", change: ( ev ) => this.setBold( ev.value ) } )
|
|
746
751
|
// in a Form: without value (or a boolean one), a boolean; with one, the value when checked, null when not
|
|
747
752
|
class Checkbox extends Component {
|
|
748
753
|
getCheck(): boolean; setCheck(ck: boolean): void
|
|
@@ -766,6 +771,8 @@ class Radio extends Component {
|
|
|
766
771
|
setLabel(text: string): void
|
|
767
772
|
checkValue(vname: string): void // check the radio whose value === vname, in the group of its form (else of the page)
|
|
768
773
|
}
|
|
774
|
+
// cls: "button" (a button; "button outline" rounded), "danger" (checked in red, its round or its button: "button
|
|
775
|
+
// danger"). Without the round, the input is still reached by Tab.
|
|
769
776
|
```
|
|
770
777
|
|
|
771
778
|
### `Switch`
|
|
@@ -845,6 +852,8 @@ class AutoComplete extends TextEdit {
|
|
|
845
852
|
getSelection(): ListboxID // id of the picked item, undefined for a free text
|
|
846
853
|
setSelection(item: ListItem): void; showDropDown(): void
|
|
847
854
|
}
|
|
855
|
+
// Keys: ↓ (or ↑), the list closed, opens it: the suggestions found last, or else a search of the text as it is
|
|
856
|
+
// (even empty: minChars is for typing). The list open: ↑ ↓ choose, Enter picks, Escape closes.
|
|
848
857
|
// Use Combobox for a fixed, known list; AutoComplete when suggestions come from a search (server).
|
|
849
858
|
// In a Form the value is the field content (text or id, per itemValue).
|
|
850
859
|
```
|
|
@@ -1121,6 +1130,8 @@ class Button extends Component {
|
|
|
1121
1130
|
setText(text: string|UnsafeHtml): void
|
|
1122
1131
|
setIcon(icon: string): void
|
|
1123
1132
|
}
|
|
1133
|
+
// A toggle button (pressed / not pressed) is not a Button: it is a Checkbox with cls "button" (see Checkbox),
|
|
1134
|
+
// and one choice among several pressed buttons, Radios with cls "button".
|
|
1124
1135
|
```
|
|
1125
1136
|
|
|
1126
1137
|
### `Tabs`
|
|
@@ -1345,20 +1356,27 @@ class VSplitBox extends VBox // panels stacked, resizable height:
|
|
|
1345
1356
|
|
|
1346
1357
|
// Low level: only place sizers by hand when a SplitBox does not fit (popup edges, header cells...).
|
|
1347
1358
|
class CSizer extends Component // base splitter; events start, resize { size, width, height }, stop
|
|
1348
|
-
class HSizer extends CSizer
|
|
1349
|
-
|
|
1359
|
+
class HSizer extends CSizer { // horizontal splitter, between two children of a HBox
|
|
1360
|
+
constructor(side: "before" | "after" | boolean = "after") // the child it resizes; a boolean (older code): true after, false before
|
|
1361
|
+
}
|
|
1362
|
+
class VSizer extends CSizer { // vertical splitter, between two children of a VBox; same constructor
|
|
1363
|
+
constructor(side: "before" | "after" | boolean = "after")
|
|
1364
|
+
}
|
|
1365
|
+
// new HBox( { content: [ list, new HSizer( "before" ), new VBox( { flex: 1 } ) ] } ) // the list is resized
|
|
1350
1366
|
// A pointercancel (a touch taken by the browser) ends the resize as a pointerup does: stop is fired.
|
|
1351
1367
|
// The columns of a Gridview or a Spreadsheet are 16px wide at least (the min-width of a cell).
|
|
1352
1368
|
class ScrollView extends Component { getViewport(): Component }
|
|
1353
1369
|
class Viewport extends Component
|
|
1354
1370
|
class Canvas extends Component { getContext(): CanvasRenderingContext2D; paint(): void }
|
|
1355
|
-
class Video extends Box { // class x4video
|
|
1371
|
+
class Video extends Box { // class x4video; props: src? (a file, url), stream? (wins over src), autoplay?, muted?, playsInline?
|
|
1356
1372
|
play(): Promise<boolean> // false: could not start (a click of the user wanted, a source not playable); no source: waits for one
|
|
1357
1373
|
pause(); mute(set: boolean)
|
|
1358
1374
|
get player(): HTMLVideoElement; get duration(): number; get paused(): boolean
|
|
1359
1375
|
get volume(): number; get videoWidth(): number; get videoHeight(): number
|
|
1376
|
+
get src(): string; set src(url: string)
|
|
1360
1377
|
set stream(stream: MediaStream)
|
|
1361
1378
|
}
|
|
1379
|
+
// The image fills the box of the Video, fitted (object-fit: contain): the box keeps its size, whatever the size of the stream.
|
|
1362
1380
|
class Calendar extends VBox { // props: date?, minDate?, maxDate? (days out of them cannot be chosen), change ("change": the day clicked)
|
|
1363
1381
|
getDate(): Date; setDate(date: Date): void // copies: the calendar keeps its own date
|
|
1364
1382
|
}
|
|
@@ -1781,6 +1799,8 @@ class X4PDFBuilder {
|
|
|
1781
1799
|
page(width: number, height: number, callback: (ctx: PageContext) => void): void // points; without size 612 x 792
|
|
1782
1800
|
build(): Uint8Array
|
|
1783
1801
|
buildAsync(): Promise<Uint8Array> // streams compressed (FlateDecode)
|
|
1802
|
+
buildBlob(): Blob // the file, application/pdf (build)
|
|
1803
|
+
buildURL(): string // an object url of it: iframe src, link, window.open; URL.revokeObjectURL frees it
|
|
1784
1804
|
}
|
|
1785
1805
|
export interface PageContext { // origin bottom left, y goes up
|
|
1786
1806
|
text(str, x, y, size, opts?: { align?: 'left'|'center'|'right'; width?; color?; bold? }): void
|
|
@@ -1839,6 +1859,13 @@ createPainter(c2d: CanvasRenderingContext2D, w: number, h: number): CanvasEx /
|
|
|
1839
1859
|
initTooltips(): void // initialize the global tooltip system
|
|
1840
1860
|
```
|
|
1841
1861
|
|
|
1862
|
+
JSX, in `.tsx` files: in tsconfig.json `"jsx": "react"` and `"jsxFactory": "x4_react.create_element"` (esbuild reads
|
|
1863
|
+
them), and `"files": [ "node_modules/x4js/src/x4tsx.d.ts" ]` for the JSX types (`ref`); each `.tsx` file imports `x4_react`.
|
|
1864
|
+
`<X a={1}>{kids}</X>` is `new X( { a: 1, content: kids } )`; a tag in lower case is an html element (its attributes set,
|
|
1865
|
+
an attribute `on...` an event); arrays in braces are flattened, `null`, `undefined` and `false` give nothing (`setContent`
|
|
1866
|
+
does not flatten nested arrays: build such a content in JSX); `ref={[owner, "name"]}` keeps the element in
|
|
1867
|
+
`owner.refs.name`, the owner a Box.
|
|
1868
|
+
|
|
1842
1869
|
No tooltip is shown until the application calls `initTooltips()` once (the prop `tooltip` only sets an attribute).
|
|
1843
1870
|
`initTooltips()` shows the text of the attribute `tooltip` (the prop `tooltip`), or the one `getTip()`
|
|
1844
1871
|
of a `"tip-handler"` gives, **as html**. A text that comes from a user is escaped by the application
|
|
@@ -2079,6 +2106,6 @@ Optional keys: `external`, `define`, `esbuild`, `dev` (`host`, `port`, `https`,
|
|
|
2079
2106
|
- `Gridview` is backed by a `DataStore`; `Spreadsheet` is backed by a `Store` (different, cell-based).
|
|
2080
2107
|
- `DataRecordID` is currently typed `any`; `strictNullChecks` is intentionally off in the recommended `tsconfig.json`.
|
|
2081
2108
|
- `MonacoEditor` requires `MonacoEditor.start()` (async) before use; register extra typings with `MonacoEditor.addTypelib(name, code)`.
|
|
2082
|
-
- `x4_react.create_element` is the JSX pragma
|
|
2109
|
+
- `x4_react.create_element` is the JSX pragma (the setup: JSX factory & helpers).
|
|
2083
2110
|
- A DOM handler of an ancestor is called for the events of its descendants, as in the browser. Not for the events that do not bubble: `focus`, `blur`, `mouseenter`, `mouseleave` (use `focusin` / `focusout`, `mouseover` / `mouseout`), nor for those of x4: `created`, `removed`, `resized`.
|
|
2084
2111
|
- Several handlers of one event on one node are called in the order they were added. One that returns `true` stops those that follow it.
|
package/package.json
CHANGED
|
@@ -63,7 +63,7 @@ export interface AutoCompleteProps extends Omit<TextInputProps,"type"|"change">,
|
|
|
63
63
|
/** delay between the last key and the search, in ms (default 250) */
|
|
64
64
|
delay?: number;
|
|
65
65
|
|
|
66
|
-
/** number of chars needed to start a search (default 1) */
|
|
66
|
+
/** number of chars typed needed to start a search (default 1); ↓ searches whatever the length */
|
|
67
67
|
minChars?: number;
|
|
68
68
|
|
|
69
69
|
/** custom renderer for the suggestions */
|
|
@@ -84,6 +84,10 @@ const render_id_item = ( item: ListItem ) => {
|
|
|
84
84
|
/**
|
|
85
85
|
* Text field with suggestions asked on demand (search event).
|
|
86
86
|
*
|
|
87
|
+
* Keys: ↓ (or ↑), the list closed, opens it: the suggestions found last, or else a search of
|
|
88
|
+
* the text as it is, whatever its length (minChars is for typing). The list open: ↑ ↓ choose,
|
|
89
|
+
* Enter picks, Escape closes.
|
|
90
|
+
*
|
|
87
91
|
* @example
|
|
88
92
|
* new AutoComplete( {
|
|
89
93
|
* label: "Customer",
|
|
@@ -123,8 +123,8 @@ function split_content( panels: Component[], horz: boolean ) {
|
|
|
123
123
|
els.forEach( ( p, i ) => {
|
|
124
124
|
if( i>0 ) {
|
|
125
125
|
const prev = els[i-1];
|
|
126
|
-
const
|
|
127
|
-
res.push( horz ? new HSizer(
|
|
126
|
+
const side = prev.hasClass( "x4flex" ) && !p.hasClass( "x4flex" ) ? "after" : "before";
|
|
127
|
+
res.push( horz ? new HSizer( side ) : new VSizer( side ) );
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
res.push( p );
|
|
@@ -165,11 +165,12 @@
|
|
|
165
165
|
color: var( --ol-button-color-active );
|
|
166
166
|
fill: var( --ol-button-color-active );
|
|
167
167
|
|
|
168
|
+
// in the colour of the text: the background stays light, not the accent of a filled button
|
|
168
169
|
#icon {
|
|
169
|
-
color: var( --button-
|
|
170
|
-
fill: var( --button-
|
|
170
|
+
color: var( --ol-button-color-active );
|
|
171
|
+
fill: var( --ol-button-color-active );
|
|
171
172
|
}
|
|
172
|
-
}
|
|
173
|
+
}
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
.x4button.danger:not([disabled]) {
|
|
@@ -25,6 +25,16 @@
|
|
|
25
25
|
--checkbox-check-border: var( --border );
|
|
26
26
|
--checkbox-check-border-hover: var( --border-hover );
|
|
27
27
|
--checkbox-check-color: var( --accent-color );
|
|
28
|
+
--checkbox-focus: var( --accent-background );
|
|
29
|
+
|
|
30
|
+
--checkbox-button-border: var( --border );
|
|
31
|
+
--checkbox-button-color: var( --text-ternary );
|
|
32
|
+
--checkbox-button-color-checked: var( --accent-background-active );
|
|
33
|
+
--checkbox-button-background-checked: var( --background-secondary );
|
|
34
|
+
--checkbox-button-border-checked: var( --accent-background );
|
|
35
|
+
|
|
36
|
+
--checkbox-danger-color: var( --alert-background );
|
|
37
|
+
--checkbox-danger-background: var( --alert-background-light );
|
|
28
38
|
}
|
|
29
39
|
|
|
30
40
|
.x4checkbox {
|
|
@@ -136,8 +146,64 @@
|
|
|
136
146
|
order: 2;
|
|
137
147
|
}
|
|
138
148
|
|
|
139
|
-
|
|
149
|
+
// its label has no id: the element, before the box
|
|
150
|
+
&.left label {
|
|
140
151
|
order: 0;
|
|
141
152
|
text-align: left;
|
|
142
153
|
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// button: a button, its background and its border marked when checked (no bold: its width
|
|
157
|
+
// would change with its state). The box hidden, its input still in the page (not display: none),
|
|
158
|
+
// so that Tab and the space bar reach it; the focus of the keyboard shown around it
|
|
159
|
+
.x4checkbox.button {
|
|
160
|
+
// the colour of its icon and its text
|
|
161
|
+
--checkbox-ink: var( --checkbox-button-color );
|
|
162
|
+
|
|
163
|
+
gap: 0;
|
|
164
|
+
margin: 0;
|
|
165
|
+
padding: 8px;
|
|
166
|
+
border: 1px solid var( --checkbox-button-border );
|
|
167
|
+
|
|
168
|
+
&.outline {
|
|
169
|
+
border-radius: 22px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.inner {
|
|
173
|
+
width: 0;
|
|
174
|
+
height: 0;
|
|
175
|
+
overflow: hidden;
|
|
176
|
+
opacity: 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
&:has(input:focus-visible) {
|
|
180
|
+
outline: 1px dotted var( --checkbox-focus );
|
|
181
|
+
outline-offset: 2px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
label {
|
|
185
|
+
--label-color: var( --checkbox-ink );
|
|
186
|
+
|
|
187
|
+
#icon {
|
|
188
|
+
color: var( --checkbox-ink );
|
|
189
|
+
fill: var( --checkbox-ink );
|
|
190
|
+
width: 24px;
|
|
191
|
+
height: 24px;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
&:has(input:checked) {
|
|
196
|
+
--checkbox-ink: var( --checkbox-button-color-checked );
|
|
197
|
+
background-color: var( --checkbox-button-background-checked );
|
|
198
|
+
border-color: var( --checkbox-button-border-checked );
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// danger: checked in the red of the alerts, its box or its button
|
|
203
|
+
.x4checkbox.danger {
|
|
204
|
+
--checkbox-background-check: var( --checkbox-danger-color );
|
|
205
|
+
--checkbox-background-check-hover: var( --checkbox-danger-color );
|
|
206
|
+
--checkbox-button-color-checked: var( --checkbox-danger-color );
|
|
207
|
+
--checkbox-button-background-checked: var( --checkbox-danger-background );
|
|
208
|
+
--checkbox-button-border-checked: var( --checkbox-danger-color );
|
|
143
209
|
}
|
|
@@ -21,7 +21,8 @@ interface CheckBoxEvents extends ComponentEvents {
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
interface CheckboxProps extends ComponentProps {
|
|
24
|
-
label
|
|
24
|
+
label?: string; // The text label for the checkbox; none: its icon alone (with a tooltip)
|
|
25
|
+
icon?: string; // an icon before its label
|
|
25
26
|
checked?: boolean; // Optional boolean indicating if the checkbox is checked by default.
|
|
26
27
|
value?: boolean | number | string; // Optional value associated with the checkbox.
|
|
27
28
|
name?: string;
|
|
@@ -30,7 +31,13 @@ interface CheckboxProps extends ComponentProps {
|
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Checkbox component that can be checked or unchecked.
|
|
33
|
-
*
|
|
34
|
+
* Its look, by its class (cls): "left" (its label on the left), "button" (a button, "button
|
|
35
|
+
* outline" rounded), "danger" (checked in red, its box or its button). Without the box, its
|
|
36
|
+
* input is still reached by Tab.
|
|
37
|
+
*
|
|
38
|
+
* A toggle button is a Checkbox with the class "button":
|
|
39
|
+
* new Checkbox( { cls: "button", icon: icon_bold, tooltip: "Bold", change: ( ev ) => setBold( ev.value ) } )
|
|
40
|
+
*
|
|
34
41
|
* @cssvar
|
|
35
42
|
* ```
|
|
36
43
|
* --checkbox-background-check
|
|
@@ -41,6 +48,14 @@ interface CheckboxProps extends ComponentProps {
|
|
|
41
48
|
* --checkbox-check-border
|
|
42
49
|
* --checkbox-check-border-hover
|
|
43
50
|
* --checkbox-check-color
|
|
51
|
+
* --checkbox-focus
|
|
52
|
+
* --checkbox-button-border
|
|
53
|
+
* --checkbox-button-color
|
|
54
|
+
* --checkbox-button-color-checked
|
|
55
|
+
* --checkbox-button-background-checked
|
|
56
|
+
* --checkbox-button-border-checked
|
|
57
|
+
* --checkbox-danger-color
|
|
58
|
+
* --checkbox-danger-background
|
|
44
59
|
* ```
|
|
45
60
|
*/
|
|
46
61
|
|
|
@@ -80,11 +95,12 @@ export class Checkbox extends Component<CheckboxProps,CheckBoxEvents> {
|
|
|
80
95
|
})
|
|
81
96
|
]
|
|
82
97
|
}),
|
|
83
|
-
new Label( {
|
|
98
|
+
new Label( {
|
|
84
99
|
tag: 'label',
|
|
85
|
-
text: props.label,
|
|
86
|
-
|
|
87
|
-
|
|
100
|
+
text: props.label,
|
|
101
|
+
icon: props.icon,
|
|
102
|
+
labelFor: inputId,
|
|
103
|
+
id: undefined
|
|
88
104
|
} ),
|
|
89
105
|
])
|
|
90
106
|
|
|
@@ -72,7 +72,10 @@
|
|
|
72
72
|
&> #label {
|
|
73
73
|
|
|
74
74
|
&> .x4label {
|
|
75
|
-
|
|
75
|
+
// its text on the right of its width (labelWidth), its colon against the gap of the box:
|
|
76
|
+
// in a form, under the one of a TextEdit
|
|
77
|
+
justify-content: end;
|
|
78
|
+
padding: 0;
|
|
76
79
|
border-bottom: 1px solid transparent;
|
|
77
80
|
|
|
78
81
|
height: 100%;
|
|
@@ -73,6 +73,11 @@
|
|
|
73
73
|
|
|
74
74
|
background-color: var( --gridview-background );
|
|
75
75
|
border: 1px solid var( --gridview-border );
|
|
76
|
+
|
|
77
|
+
// disabled: faded as a Box (a Gridview is not one)
|
|
78
|
+
&[disabled] {
|
|
79
|
+
opacity: var( --box-disabled-opacity );
|
|
80
|
+
}
|
|
76
81
|
color: var( --gridview-cell-color );
|
|
77
82
|
|
|
78
83
|
.cell {
|
|
@@ -159,6 +164,7 @@
|
|
|
159
164
|
}
|
|
160
165
|
|
|
161
166
|
.percent {
|
|
167
|
+
align-self: center; // a Box stretches (top of the cell): centered in the row
|
|
162
168
|
width: 100%;
|
|
163
169
|
border: 1px solid var( --border );
|
|
164
170
|
height: 8px;
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
--input-sel-background: var( --accent-background );
|
|
22
22
|
--input-sel-color: var( --accent-color );
|
|
23
|
-
--input-placeholder: var( --disabled-
|
|
23
|
+
--input-placeholder: var( --disabled-color-dark ); // a text colour: seen on the background of the field, in both themes
|
|
24
24
|
--input-error: var( --alert-background );
|
|
25
25
|
|
|
26
26
|
--input-color-disabled: var( --disabled-color-dark );
|
|
@@ -46,7 +46,9 @@
|
|
|
46
46
|
|
|
47
47
|
& .x4form {
|
|
48
48
|
min-width: 400px;
|
|
49
|
-
|
|
49
|
+
|
|
50
|
+
// the icon of the box only, not those of its message
|
|
51
|
+
> .x4hbox > .x4icon {
|
|
50
52
|
color: var( --msgbox-icon-color );
|
|
51
53
|
height: 96px;
|
|
52
54
|
}
|
|
@@ -57,7 +59,7 @@
|
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
&.question .x4form .x4icon {
|
|
62
|
+
&.question .x4form > .x4hbox > .x4icon {
|
|
61
63
|
color: var( --msgbox-question-icon-color );
|
|
62
64
|
}
|
|
63
65
|
}
|
|
@@ -68,7 +70,9 @@
|
|
|
68
70
|
|
|
69
71
|
& .x4form {
|
|
70
72
|
min-width: 400px;
|
|
71
|
-
|
|
73
|
+
|
|
74
|
+
// the icon of the box only, not those of its editor (the stars of a Rating...)
|
|
75
|
+
> .x4hbox > .x4icon {
|
|
72
76
|
color: var( --msgbox-question-icon-color );
|
|
73
77
|
height: 64px;
|
|
74
78
|
}
|
|
@@ -141,7 +145,8 @@
|
|
|
141
145
|
}
|
|
142
146
|
}
|
|
143
147
|
|
|
144
|
-
|
|
148
|
+
// the spinner of the box only, not the icons of the lines of its log
|
|
149
|
+
> .x4hbox > .x4icon {
|
|
145
150
|
height: 64px;
|
|
146
151
|
animation: rotating 3s linear infinite;
|
|
147
152
|
fill: var(--accent-background);
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
--propertygrid-gadget-background: var( --background-primary );
|
|
10
10
|
--propertygrid-gadget-color: var( --text-primary );
|
|
11
11
|
|
|
12
|
-
--propertygrid-odd-background: var(--
|
|
12
|
+
--propertygrid-odd-background: var( --background-odd );
|
|
13
13
|
--propertygrid-even-background: transparent;
|
|
14
14
|
--propertygrid-disabled-background: rgb(170 170 170 / 14%);
|
|
15
15
|
--propertygrid-hover-background: var(--color-primary-a10);
|
|
@@ -54,8 +54,6 @@
|
|
|
54
54
|
padding: 6px 4px 4px 4px;
|
|
55
55
|
min-height: 2em;
|
|
56
56
|
background-color: var( --propertygrid-group-background );
|
|
57
|
-
margin-bottom: 8px;
|
|
58
|
-
box-shadow: 4px 3px 8px rgba(0, 0, 0, 0.4);
|
|
59
57
|
color: var( --propertygrid-group-color );
|
|
60
58
|
display: flex;
|
|
61
59
|
align-items: center;
|
|
@@ -81,9 +79,14 @@
|
|
|
81
79
|
font-weight: bold;
|
|
82
80
|
}
|
|
83
81
|
|
|
82
|
+
// the button that folds the group: as high as the title, its icon in the colour
|
|
83
|
+
// of the group when it has the focus (its background stays transparent)
|
|
84
84
|
&>.x4button {
|
|
85
|
+
--button-color-focus: var( --propertygrid-group-color );
|
|
85
86
|
background-color: transparent;
|
|
86
87
|
color: var( --propertygrid-group-color );
|
|
88
|
+
margin: 0;
|
|
89
|
+
padding: 0;
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
&>.x4label {
|
|
@@ -99,14 +102,19 @@
|
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
104
|
|
|
102
|
-
|
|
105
|
+
// under the title, in the box of the header
|
|
106
|
+
.desc {
|
|
103
107
|
font-style: italic;
|
|
104
108
|
font-weight: normal;
|
|
105
|
-
font-size:
|
|
109
|
+
font-size: 80%;
|
|
110
|
+
padding-top: 4px;
|
|
111
|
+
padding-left: 1em;
|
|
106
112
|
color: var( --text-ternary );
|
|
107
113
|
}
|
|
108
114
|
|
|
115
|
+
// the space is between the last row of a group and the next group, none under the title
|
|
109
116
|
&:not(:first-child ) {
|
|
117
|
+
margin-top: 8px;
|
|
110
118
|
//border-top: 1px solid var(--color-primary-a50);
|
|
111
119
|
}
|
|
112
120
|
|
|
@@ -16,8 +16,36 @@
|
|
|
16
16
|
|
|
17
17
|
:root {
|
|
18
18
|
--radio-background: var( --background-primary );
|
|
19
|
+
--radio-check-background: var( --accent-background );
|
|
19
20
|
--radio-check-color: var( --accent-color );
|
|
20
|
-
--radio-
|
|
21
|
+
--radio-focus: var( --accent-background );
|
|
22
|
+
|
|
23
|
+
--radio-button-border: var( --border );
|
|
24
|
+
--radio-button-color: var( --text-ternary );
|
|
25
|
+
--radio-button-color-checked: var( --accent-background-active );
|
|
26
|
+
--radio-button-background-checked: var( --background-secondary );
|
|
27
|
+
--radio-button-border-checked: var( --accent-background );
|
|
28
|
+
|
|
29
|
+
--radio-danger-color: var( --alert-background );
|
|
30
|
+
--radio-danger-background: var( --alert-background-light );
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// button: the round hidden, its input still in the page (not display: none),
|
|
34
|
+
// so that Tab and the arrows reach it; the focus of the keyboard shown around the radio
|
|
35
|
+
%radio-no-round {
|
|
36
|
+
gap: 0; // no space kept for the round
|
|
37
|
+
|
|
38
|
+
.inner {
|
|
39
|
+
width: 0;
|
|
40
|
+
height: 0;
|
|
41
|
+
overflow: hidden;
|
|
42
|
+
opacity: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
&:has(input:focus-visible) {
|
|
46
|
+
outline: 1px dotted var( --radio-focus );
|
|
47
|
+
outline-offset: 2px;
|
|
48
|
+
}
|
|
21
49
|
}
|
|
22
50
|
|
|
23
51
|
.x4radio {
|
|
@@ -69,7 +97,7 @@
|
|
|
69
97
|
height: 100%;
|
|
70
98
|
|
|
71
99
|
.fa-primary {
|
|
72
|
-
fill: var( --
|
|
100
|
+
fill: var( --radio-check-background );
|
|
73
101
|
}
|
|
74
102
|
|
|
75
103
|
.fa-secondary {
|
|
@@ -98,20 +126,20 @@
|
|
|
98
126
|
}
|
|
99
127
|
}
|
|
100
128
|
|
|
101
|
-
|
|
129
|
+
// square: its former name, kept for the code that has it (not documented)
|
|
130
|
+
.x4radio.button,
|
|
131
|
+
.x4radio.square {
|
|
102
132
|
--size: 24px;
|
|
103
133
|
|
|
104
134
|
&.outline {
|
|
105
135
|
border-radius: 22px;;
|
|
106
136
|
}
|
|
107
137
|
|
|
108
|
-
border: 1px solid
|
|
138
|
+
border: 1px solid var( --radio-button-border );
|
|
109
139
|
padding: 8px;
|
|
110
140
|
margin: 0;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
display: none;
|
|
114
|
-
}
|
|
141
|
+
|
|
142
|
+
@extend %radio-no-round;
|
|
115
143
|
|
|
116
144
|
label {
|
|
117
145
|
display: flex;
|
|
@@ -121,49 +149,44 @@
|
|
|
121
149
|
#text {
|
|
122
150
|
flex-grow: 1;
|
|
123
151
|
text-align: center;
|
|
124
|
-
color: var( --
|
|
152
|
+
color: var( --radio-button-color );
|
|
125
153
|
}
|
|
126
|
-
|
|
154
|
+
|
|
155
|
+
// fill and color: an icon is drawn in its fill, or in currentColor
|
|
127
156
|
#icon {
|
|
128
|
-
fill: var( --
|
|
157
|
+
fill: var( --radio-button-color );
|
|
158
|
+
color: var( --radio-button-color );
|
|
129
159
|
width: var( --size );
|
|
130
160
|
height: var( --size );
|
|
131
161
|
}
|
|
132
162
|
}
|
|
133
163
|
|
|
134
164
|
&:has(input:checked) {
|
|
135
|
-
background-color: var( --background-
|
|
136
|
-
border-color:
|
|
165
|
+
background-color: var( --radio-button-background-checked );
|
|
166
|
+
border-color: var( --radio-button-border-checked );
|
|
137
167
|
|
|
138
168
|
.x4label {
|
|
169
|
+
// no bold: the width of the button would change with its state
|
|
139
170
|
#text {
|
|
140
|
-
color: var( --
|
|
141
|
-
font-weight: bold;
|
|
171
|
+
color: var( --radio-button-color-checked );
|
|
142
172
|
}
|
|
143
173
|
|
|
144
174
|
#icon {
|
|
145
|
-
fill: var( --
|
|
175
|
+
fill: var( --radio-button-color-checked );
|
|
176
|
+
color: var( --radio-button-color-checked );
|
|
146
177
|
}
|
|
147
178
|
|
|
148
179
|
.fa-primary {
|
|
149
|
-
fill: var( --
|
|
180
|
+
fill: var( --radio-button-color-checked );
|
|
150
181
|
}
|
|
151
182
|
}
|
|
152
183
|
}
|
|
153
184
|
}
|
|
154
185
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
display: none;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
&:has(input:checked) {
|
|
165
|
-
background-color: var( --accent-background-active );
|
|
166
|
-
border-color: var( --radio-square-border-checked );
|
|
167
|
-
font-weight: 700;
|
|
168
|
-
}
|
|
186
|
+
// danger: checked in the red of the alerts, its round or its button
|
|
187
|
+
.x4radio.danger {
|
|
188
|
+
--radio-check-background: var( --radio-danger-color );
|
|
189
|
+
--radio-button-color-checked: var( --radio-danger-color );
|
|
190
|
+
--radio-button-background-checked: var( --radio-danger-background );
|
|
191
|
+
--radio-button-border-checked: var( --radio-danger-color );
|
|
169
192
|
}
|
|
@@ -47,12 +47,23 @@ interface RadioProps extends ComponentProps {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
+
* A radio: the ones with the same name are a group. Its look, by its class (cls): "button"
|
|
51
|
+
* (a button, "button outline" rounded), "danger" (checked in red, its round or its button).
|
|
52
|
+
* Without the round, its input is still reached by Tab.
|
|
50
53
|
*
|
|
51
54
|
* @cssvar
|
|
52
55
|
* ```
|
|
53
56
|
* --radio-background
|
|
57
|
+
* --radio-check-background
|
|
54
58
|
* --radio-check-color
|
|
55
|
-
* --radio-
|
|
59
|
+
* --radio-focus
|
|
60
|
+
* --radio-button-border
|
|
61
|
+
* --radio-button-color
|
|
62
|
+
* --radio-button-color-checked
|
|
63
|
+
* --radio-button-background-checked
|
|
64
|
+
* --radio-button-border-checked
|
|
65
|
+
* --radio-danger-color
|
|
66
|
+
* --radio-danger-background
|
|
56
67
|
* ```
|
|
57
68
|
*/
|
|
58
69
|
|
|
@@ -167,7 +167,9 @@ export class CSizer extends Component<ComponentProps,CSizerEvent> {
|
|
|
167
167
|
if( this._type.includes("-prev") || this._type.includes("-next") ) {
|
|
168
168
|
const neighbour = this._type.includes("-prev") ? this.prevElement( ) : this.nextElement( );
|
|
169
169
|
if( neighbour ) {
|
|
170
|
-
|
|
170
|
+
// shrink 1: no larger than the space the box has left (the flex neighbour at its
|
|
171
|
+
// padding); 0 at least, a negative basis is not a value
|
|
172
|
+
this._ref.setStyleValue( "flex", `0 1 ${Math.max( 0, size )}px` );
|
|
171
173
|
}
|
|
172
174
|
else {
|
|
173
175
|
this._ref.setStyle( nr );
|
|
@@ -185,18 +187,28 @@ export class CSizer extends Component<ComponentProps,CSizerEvent> {
|
|
|
185
187
|
}
|
|
186
188
|
}
|
|
187
189
|
|
|
190
|
+
/**
|
|
191
|
+
* a sizer between two children of a HBox: it resizes the one before it or the one after it
|
|
192
|
+
* (a boolean, as in older code: true after, false before)
|
|
193
|
+
*/
|
|
194
|
+
|
|
188
195
|
export class HSizer extends CSizer {
|
|
189
196
|
static classPrefix = "x4";
|
|
190
197
|
|
|
191
|
-
constructor(
|
|
192
|
-
super(
|
|
198
|
+
constructor( side: "before" | "after" | boolean = "after" ) {
|
|
199
|
+
super( side === "before" || side === false ? "hsize-prev" : "hsize-next" );
|
|
193
200
|
}
|
|
194
201
|
}
|
|
195
202
|
|
|
203
|
+
/**
|
|
204
|
+
* a sizer between two children of a VBox: it resizes the one before it (above) or the one
|
|
205
|
+
* after it (under); a boolean as for HSizer
|
|
206
|
+
*/
|
|
207
|
+
|
|
196
208
|
export class VSizer extends CSizer {
|
|
197
209
|
static classPrefix = "x4";
|
|
198
210
|
|
|
199
|
-
constructor(
|
|
200
|
-
super(
|
|
211
|
+
constructor( side: "before" | "after" | boolean = "after" ) {
|
|
212
|
+
super( side === "before" || side === false ? "vsize-prev" : "vsize-next" );
|
|
201
213
|
}
|
|
202
214
|
}
|
|
@@ -72,6 +72,11 @@
|
|
|
72
72
|
background-color: var( --spreadsheet-background );
|
|
73
73
|
border: 1px solid var( --spreadsheet-border );
|
|
74
74
|
color: var( --spreadsheet-cell-color );
|
|
75
|
+
|
|
76
|
+
// disabled: faded as a Box (a Spreadsheet is not one)
|
|
77
|
+
&[disabled] {
|
|
78
|
+
opacity: var( --box-disabled-opacity );
|
|
79
|
+
}
|
|
75
80
|
|
|
76
81
|
.cell {
|
|
77
82
|
padding: 4px;
|
|
@@ -179,6 +184,7 @@
|
|
|
179
184
|
}
|
|
180
185
|
|
|
181
186
|
.percent {
|
|
187
|
+
align-self: center; // a Box stretches (top of the cell): centered in the row
|
|
182
188
|
width: 100%;
|
|
183
189
|
border: 1px solid var( --border );
|
|
184
190
|
height: 8px;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
--tab-color-hover: var( --text-primary );
|
|
7
7
|
--tab-color: var( --text-primary );
|
|
8
8
|
--tab-color-selected: var( --text-primary );
|
|
9
|
+
--tab-icon-color: var( --icon-color );
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
.x4tabs {
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
border: none;
|
|
25
26
|
border-bottom: 2px solid transparent;
|
|
26
27
|
color: var( --tab-color );
|
|
28
|
+
--ol-button-icon-color: var( --tab-icon-color );
|
|
27
29
|
margin: 0;
|
|
28
30
|
outline: none;
|
|
29
31
|
transition: border-color 0.2s ease;
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
--textarea-selection-color: var( --accent-color );
|
|
24
24
|
--textarea-background: var( --background-input );
|
|
25
25
|
--textarea-color: var( --text-primary );
|
|
26
|
+
--textarea-required: var( --alert-background );
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
.x4simpletextarea {
|
|
@@ -77,6 +78,14 @@
|
|
|
77
78
|
display: none;
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
// required: a "*" before the label, as the one of a TextEdit
|
|
82
|
+
&:has( > .x4simpletextarea[required] ) > .x4label::before {
|
|
83
|
+
content: "*";
|
|
84
|
+
font-weight: bold;
|
|
85
|
+
color: var( --textarea-required );
|
|
86
|
+
margin-right: 2px;
|
|
87
|
+
}
|
|
88
|
+
|
|
80
89
|
.x4simpletextarea {
|
|
81
90
|
// its height of rows when the TextArea has none, the TextArea's height when it has one;
|
|
82
91
|
// a basis of 0 would leave it the height of its padding: a textarea scrolls, its min-height is 0
|
|
@@ -52,6 +52,7 @@ $color--interval: 8.4%;
|
|
|
52
52
|
--background-primary: var(--color-primary-a0);
|
|
53
53
|
--background-secondary: var(--color-primary-a10);
|
|
54
54
|
--background-ternary: color-mix(in srgb, var(--color-primary-a100) 4.3%, var(--color-primary-a0)); // #f4f4f4
|
|
55
|
+
--background-odd: color-mix(in srgb, var(--color-primary-a100) 2.4%, var(--color-primary-a0)); // #f9f9f9, a row out of two
|
|
55
56
|
--background-hover: color-mix(in srgb, var(--color-primary-a100) 6.7%, var(--color-primary-a0)); // #eee
|
|
56
57
|
--background-active: color-mix(in srgb, var(--color-primary-a100) 20%, var(--color-primary-a0)); // #ccc
|
|
57
58
|
--background-input: color-mix(in srgb, var(--color-primary-a0) 60%, transparent); // white at 60%
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
--treeview-background: var( --background-primary );
|
|
21
21
|
|
|
22
22
|
--treeitem-color: var( --text-primary );
|
|
23
|
+
--treeitem-icon-color: var( --icon-color );
|
|
23
24
|
|
|
24
25
|
--treeitem-backgound-hover: var( --background-secondary );
|
|
25
26
|
--treeitem-color-hover: var( --text-primary );
|
|
@@ -79,7 +80,7 @@
|
|
|
79
80
|
|
|
80
81
|
&>.label {
|
|
81
82
|
--label-color: var( --treeitem-color );
|
|
82
|
-
--label-icon-color: var( --treeitem-color );
|
|
83
|
+
--label-icon-color: var( --treeitem-icon-color );
|
|
83
84
|
|
|
84
85
|
gap: 4px;
|
|
85
86
|
|
|
@@ -210,6 +211,10 @@
|
|
|
210
211
|
border-color: var( --border );
|
|
211
212
|
color: var( --treeview-item-color-disabled );
|
|
212
213
|
|
|
214
|
+
// the texts and the icons of the items
|
|
215
|
+
--treeitem-color: var( --treeview-item-color-disabled );
|
|
216
|
+
--treeitem-icon-color: var( --treeview-item-color-disabled );
|
|
217
|
+
|
|
213
218
|
&>.body {
|
|
214
219
|
.x4viewport {
|
|
215
220
|
pointer-events: none;
|
|
@@ -20,11 +20,14 @@
|
|
|
20
20
|
background-color: black;
|
|
21
21
|
position: relative;
|
|
22
22
|
|
|
23
|
+
// the size of the box, whatever the size of the stream (a <video> keeps its own size
|
|
24
|
+
// with only left / right): the image fitted in it, black bands around
|
|
23
25
|
.vplayer {
|
|
24
26
|
position: absolute;
|
|
25
27
|
left: 0;
|
|
26
28
|
top: 0;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
width: 100%;
|
|
30
|
+
height: 100%;
|
|
31
|
+
object-fit: contain;
|
|
29
32
|
}
|
|
30
33
|
}
|
|
@@ -23,7 +23,8 @@ interface VideoProps extends ComponentProps {
|
|
|
23
23
|
autoplay?: boolean;
|
|
24
24
|
muted?: boolean;
|
|
25
25
|
playsInline?: boolean;
|
|
26
|
-
stream
|
|
26
|
+
src?: string; // a file to play (url); a stream, when there is one, is shown instead
|
|
27
|
+
stream?: MediaStream; // a camera, a screen, a canvas (captureStream)
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
/**
|
|
@@ -55,6 +56,10 @@ export class Video extends Box {
|
|
|
55
56
|
this.playsInline = props.playsInline;
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
if( props.src ) {
|
|
60
|
+
this.src = props.src;
|
|
61
|
+
}
|
|
62
|
+
|
|
58
63
|
this.stream = props.stream;
|
|
59
64
|
}
|
|
60
65
|
|
|
@@ -86,6 +91,14 @@ export class Video extends Box {
|
|
|
86
91
|
this.player.playsInline = set;
|
|
87
92
|
}
|
|
88
93
|
|
|
94
|
+
get src( ) {
|
|
95
|
+
return this.player.src;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
set src( url: string ) {
|
|
99
|
+
this.player.src = url;
|
|
100
|
+
}
|
|
101
|
+
|
|
89
102
|
set stream( stream: MediaStream ) {
|
|
90
103
|
this.player.srcObject = stream;
|
|
91
104
|
}
|
package/src/core/core_pdf.ts
CHANGED
|
@@ -1543,6 +1543,23 @@ export class X4PDFBuilder {
|
|
|
1543
1543
|
|
|
1544
1544
|
return result
|
|
1545
1545
|
}
|
|
1546
|
+
|
|
1547
|
+
/**
|
|
1548
|
+
* the document as a file, of type application/pdf (build, its streams not compressed)
|
|
1549
|
+
*/
|
|
1550
|
+
|
|
1551
|
+
buildBlob(): Blob {
|
|
1552
|
+
return new Blob([this.build() as BlobPart], { type: 'application/pdf' })
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
/**
|
|
1556
|
+
* an object url of the document: the src of an iframe, the href of a link, window.open.
|
|
1557
|
+
* URL.revokeObjectURL( url ) frees it when it is no longer shown
|
|
1558
|
+
*/
|
|
1559
|
+
|
|
1560
|
+
buildURL(): string {
|
|
1561
|
+
return URL.createObjectURL(this.buildBlob())
|
|
1562
|
+
}
|
|
1546
1563
|
}
|
|
1547
1564
|
|
|
1548
1565
|
/*
|
package/src/core/core_react.ts
CHANGED
|
@@ -13,25 +13,19 @@
|
|
|
13
13
|
* Use of this source code is governed by an MIT-style license
|
|
14
14
|
* that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* 1.
|
|
16
|
+
* JSX in .tsx files:
|
|
17
|
+
*
|
|
18
|
+
* 1. in tsconfig.json (esbuild reads it too):
|
|
19
19
|
* "compilerOptions": {
|
|
20
|
-
*
|
|
21
|
-
* "
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* 2.
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* after that, all things like that will be ok
|
|
31
|
-
*
|
|
32
|
-
* import { x4_create_element } from "x4react"
|
|
33
|
-
* const xx = <h1>This is a title</h1>
|
|
34
|
-
*
|
|
20
|
+
* "jsx": "react",
|
|
21
|
+
* "jsxFactory": "x4_react.create_element"
|
|
22
|
+
* },
|
|
23
|
+
* "files": [ "node_modules/x4js/src/x4tsx.d.ts" ] // the JSX types of x4 (ref)
|
|
24
|
+
*
|
|
25
|
+
* 2. in each .tsx file, the factory imported:
|
|
26
|
+
* import { Label, VBox, x4_react } from "x4js";
|
|
27
|
+
* const box = <VBox><Label text="Hello" /></VBox>;
|
|
28
|
+
*
|
|
35
29
|
*/
|
|
36
30
|
|
|
37
31
|
import { Component } from './component';
|
package/src/core/core_svg.ts
CHANGED
|
@@ -557,7 +557,8 @@ export class SvgText extends SvgItem {
|
|
|
557
557
|
* @returns The current `SvgText` instance for chaining.
|
|
558
558
|
*/
|
|
559
559
|
fontWeight( weight: 'light' | 'normal' | 'bold' ): this {
|
|
560
|
-
|
|
560
|
+
// "light" is no value of font-weight: its number
|
|
561
|
+
return this.setAttr( 'font-weight', weight=='light' ? '300' : weight );
|
|
561
562
|
}
|
|
562
563
|
|
|
563
564
|
/**
|
|
@@ -583,23 +584,25 @@ export class SvgText extends SvgItem {
|
|
|
583
584
|
}
|
|
584
585
|
|
|
585
586
|
/**
|
|
586
|
-
* change the vertical alignment
|
|
587
|
+
* change the vertical alignment: y is the top, the middle, the bottom or the baseline
|
|
588
|
+
* of the text. dominant-baseline, read by all the browsers (alignment-baseline is not
|
|
589
|
+
* by Firefox before 149)
|
|
587
590
|
*/
|
|
588
591
|
verticalAlign( align: 'top' | 'center' | 'bottom' | 'baseline' ): this {
|
|
589
592
|
|
|
590
593
|
let al;
|
|
591
594
|
switch( align ) {
|
|
592
595
|
case 'top': al = 'hanging'; break;
|
|
593
|
-
case 'center': al = '
|
|
594
|
-
case 'bottom': al = '
|
|
595
|
-
case 'baseline': al = '
|
|
596
|
+
case 'center': al = 'central'; break;
|
|
597
|
+
case 'bottom': al = 'ideographic'; break;
|
|
598
|
+
case 'baseline': al = 'alphabetic'; break;
|
|
596
599
|
default:
|
|
597
600
|
// If an invalid alignment is provided, do nothing and return this for chaining.
|
|
598
601
|
console.warn(`Invalid verticalAlign value: ${align}. Must be 'top', 'center', 'bottom', or 'baseline'.`);
|
|
599
602
|
return this;
|
|
600
603
|
}
|
|
601
604
|
|
|
602
|
-
return this.setAttr( '
|
|
605
|
+
return this.setAttr( 'dominant-baseline', al );
|
|
603
606
|
}
|
|
604
607
|
}
|
|
605
608
|
|