x4js 2.3.31 → 2.3.32
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/aiguide.md +2 -0
- package/changelog.md +1357 -0
- package/package.json +2 -1
package/changelog.md
ADDED
|
@@ -0,0 +1,1357 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
What changes in x4js, the library and its command line. The newest first.
|
|
4
|
+
A version gets its date when it is published.
|
|
5
|
+
|
|
6
|
+
## 2.3.32 (2026-11-06)
|
|
7
|
+
|
|
8
|
+
### Package
|
|
9
|
+
|
|
10
|
+
- The package holds this changelog (`changelog.md`, in its `files`): what changes from one version to another, in `node_modules/x4js` too.
|
|
11
|
+
|
|
12
|
+
## 2.3.31 (2026-11-03)
|
|
13
|
+
|
|
14
|
+
### Themes
|
|
15
|
+
|
|
16
|
+
- Dark theme: the icon of a tooltip in the colour of the descriptions (`--tooltip-icon-color: var( --text-secondary )`), seen on its background.
|
|
17
|
+
- `--accent-text`: the accent as the colour of a text on the background. In the light theme `--accent-background-active`; in the dark theme a light blue (`#78aeda`), where the blues of the accent are lost on the background. The text of a checkbox or a radio `button` checked and of an `outline` button focused take it (`--checkbox-button-color-checked`, `--radio-button-color-checked`, `--ol-button-color-focus`): read in the dark theme. In the light theme, an outline button focused has its text in the darkest blue of the accent.
|
|
18
|
+
- Dark theme: the edge of a checkbox or a radio `button` checked and of an `outline` button focused in `--accent-text` too (`--checkbox-button-border-checked`, `--radio-button-border-checked`, `--ol-button-border-focus`), seen on the background.
|
|
19
|
+
|
|
20
|
+
### Popup
|
|
21
|
+
|
|
22
|
+
- `dismiss( true )` closes the popups of its group opened after it; on a popup that is no longer open, it closes none. To close a whole group: `dismiss( )`.
|
|
23
|
+
- A press in a popup that closes by itself (`autoClose`) closes the popups of the other groups opened after it, over it: a context menu, the picker of a ColorInput. Its own group stays open (a menu and its sub-menus). Two popups that must stay open together, one used while the other is shown: give them the same group.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
const palette = new Popup( { autoClose: "tools", content: colors } );
|
|
27
|
+
const brushes = new Popup( { autoClose: "tools", content: sizes } ); // a press in palette keeps brushes
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Menu
|
|
31
|
+
|
|
32
|
+
- A menu opened less than 400 ms after the mouse was on an item of another menu (a context menu, for instance) stays open: the delay of that item, when its menu has closed meanwhile, closes nothing.
|
|
33
|
+
- An item can show the keys of its command at its right: `shortcut`, written by `shortcutText` ("Mod+S" is "Ctrl+S", "⌘S" on a Mac). It is shown only: the shortcut itself is still added by `Application.addShortcut` or `Component.addShortcut`. Its colour: `--menuitem-shortcut-color`; hovered, in the colour of the text.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
new Menu( { items: [
|
|
37
|
+
{ text: "Save", shortcut: "Mod+S", click: save },
|
|
38
|
+
{ text: "Save as…", shortcut: "Shift+Mod+S", click: saveAs },
|
|
39
|
+
] } );
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### MenuBar
|
|
43
|
+
|
|
44
|
+
- New, in menu.ts: a row of titles, each one opens its menu under it. A click on a title opens its menu, a second one closes it; while a menu is open, the mouse on another title opens the menu of that one. The items of a title are those of a `Menu`, or a function called at each opening (as the `menu` of a `Button`). No keyboard yet. For a web application: a desktop application (Electron...) should use the menu of the system. Its colours: `--menubar-*`, `--menubaritem-*`.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
new MenuBar( { items: [
|
|
48
|
+
{ text: "File", items: [ { text: "Open…", shortcut: "Mod+O", click: open }, "-", { text: "Quit", click: quit } ] },
|
|
49
|
+
{ text: "Edit", items: ( ) => [ { text: "Undo", disabled: !canUndo( ), click: undo } ] },
|
|
50
|
+
] } );
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### MessageBox
|
|
54
|
+
|
|
55
|
+
- An error (the kind without `icon_type`) has its buttons in the colours of its title bar, the ones of the alerts (`--alert-*`): a filled button in red, an outline one with a red border when it has the focus, its text in the colour of the text.
|
|
56
|
+
- The "!" of an error and the "?" of a question are drawn in the disc of their icon, white in both themes: `--msgbox-icon-mark-color` (`--alert-color`), `--msgbox-question-icon-mark-color` (`--accent-color`).
|
|
57
|
+
|
|
58
|
+
### Notification
|
|
59
|
+
|
|
60
|
+
- A long text goes on several lines in its frame (30% of the window wide at most), a long word or an address cut at its edge; its close button stays in it.
|
|
61
|
+
|
|
62
|
+
### TextEdit
|
|
63
|
+
|
|
64
|
+
- Stretched in a row higher than it, its field goes down to its line.
|
|
65
|
+
|
|
66
|
+
### Checkbox
|
|
67
|
+
|
|
68
|
+
- The icon of a checkbox `button` is `--checkbox-button-icon-size` wide and high (24px). A class of the application sets it:
|
|
69
|
+
|
|
70
|
+
```css
|
|
71
|
+
.small-toggle { --checkbox-button-icon-size: 16px; }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Radio
|
|
75
|
+
|
|
76
|
+
- The icon of a radio `button` is `--radio-button-icon-size` wide and high (24px), set by a class of the application as for a checkbox.
|
|
77
|
+
- Migration: `--size` is no longer read. An application that set `--size` on its radio buttons sets `--radio-button-icon-size`.
|
|
78
|
+
|
|
79
|
+
### Spreadsheet
|
|
80
|
+
|
|
81
|
+
- `clearSelection( notify = true )`: no cell selected; with `notify`, `selectionChange` is fired, its selection empty.
|
|
82
|
+
- A click, a double click or a right click on the header or on the footer (a title, the sizer of a column) selects no cell; a right click there fires `contextMenu` with a null context, as out of the cells.
|
|
83
|
+
|
|
84
|
+
### Treeview
|
|
85
|
+
|
|
86
|
+
- `contextMenu`, as for a Listbox: a right click selects the item under the mouse, then fires `contextMenu`, its `context` the id of the item (null out of the items); the menu of the browser is not shown.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
new Treeview( { items, contextMenu: ( ev ) => showMenu( ev.context, ev.uievent.clientX, ev.uievent.clientY ) } );
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Clipboard
|
|
93
|
+
|
|
94
|
+
- An image offered in several formats is read as the PNG, by `getClipboard` and by `getEventClipboard`: a copy from Inkscape gives a SVG first, then a PNG. Without a PNG, the first image.
|
|
95
|
+
|
|
96
|
+
## 2.3.30 (2026-10-29)
|
|
97
|
+
|
|
98
|
+
### Package
|
|
99
|
+
|
|
100
|
+
- The package has its description (`description` in its package.json): the search of npm shows it.
|
|
101
|
+
|
|
102
|
+
### Themes
|
|
103
|
+
|
|
104
|
+
- The blues of the dark theme come from `#2d6fa5` (`$color--dark-base`), with the intervals of the light theme: the accent (a60) `#245883`, hovered `#286291`, focused `#1b4262`, active `#112b40`, a selection `#183b57`.
|
|
105
|
+
- The shadow of a Dialog, of the list of a Combobox, of a Notification and of a tooltip takes its colour from `--shadow-color`: black at 10%, at 50% in the dark theme.
|
|
106
|
+
- Dialog: a border, `--color-dialog-border`; none in the light theme, `#313131` in the dark one (the `widget.border` of VS Code), where its edge is lost on the background.
|
|
107
|
+
|
|
108
|
+
### Popup
|
|
109
|
+
|
|
110
|
+
- While a popup that closes by itself is shown (`autoClose`: a menu, the list of a combobox), a wheel outside it is ignored: the page under it does not scroll and leave it where it was. In the popup, the wheel scrolls as usual.
|
|
111
|
+
|
|
112
|
+
### Menu
|
|
113
|
+
|
|
114
|
+
- The triangle of an item that opens a sub menu is an icon (`caret`, in the icons of x4): in the colour of the text of the item, hovered too, in the dark theme too; at the right of the menu, under the others.
|
|
115
|
+
|
|
116
|
+
### Tooltips
|
|
117
|
+
|
|
118
|
+
- A long text goes on several lines: a tooltip is 30% of the window wide at most, `--tooltip-max-width`. A tooltip that must keep the width of its content (a tour of the application, an image): `--tooltip-max-width: none`.
|
|
119
|
+
|
|
120
|
+
```css
|
|
121
|
+
:root { --tooltip-max-width: none; } /* the tooltips as wide as their text */
|
|
122
|
+
```
|
|
123
|
+
- A tooltip goes when its element is removed from the page or hidden (it is checked every second while the tooltip is shown), and at a click.
|
|
124
|
+
|
|
125
|
+
## 2.3.29 (2026-10-29)
|
|
126
|
+
|
|
127
|
+
### Components with an entry of their own
|
|
128
|
+
|
|
129
|
+
- A big component, or one seldom used, is not in `x4js`: it has an entry of its own, and an application that does not import it does not carry its code nor its CSS. `x4js/monaco` was one already; `x4js/gantt`, `x4js/planner` and `x4js/mapview` are the new ones.
|
|
130
|
+
|
|
131
|
+
### Gantt (`x4js/gantt`)
|
|
132
|
+
|
|
133
|
+
- New: tasks on a calendar. On the left their list, a tree with columns (start, end, duration, progress, or a field). On the right a bar for each task, an arrow from each task it waits for (`deps`), groups and milestones. The pointer moves a bar, stretches it by its ends, sets its progress (`change`, `preventDefault` puts it back). Scales hour, day, week, month; ctrl+wheel zooms around the pointer (`scaleChange`). A colour by task or by group, a tooltip, `readonly`, disabled.
|
|
134
|
+
- The titles of its columns, when not given: `_tr.global.gantt`, in the four languages.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { Gantt } from "x4js/gantt";
|
|
138
|
+
new Gantt( { tasks, columns: [ { id: "title", width: 200 }, { id: "start" }, { id: "end" } ], change: ( ev ) => save( ev.task, ev.start, ev.end ) } );
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Planner (`x4js/planner`)
|
|
142
|
+
|
|
143
|
+
- New: events by day, by week or by month. The days in columns, the working hours filling the view, the others reached by the scroll; the whole days in a band or in the working hours (`band`); the days off narrower, or not shown. A drag on an empty place selects a range (`select`), a drag moves an event or stretches it (`change`), ctrl+drag copies it (`copy`). A colour and an icon by event, the line of now, marks for the events out of the view. `readonly`, disabled.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { Planner } from "x4js/planner";
|
|
147
|
+
const planner = new Planner( { events, select: ( ev ) => create( ev.start, ev.end ), change: ( ev ) => save( ev.event, ev.start, ev.end ) } );
|
|
148
|
+
planner.addShortcut( "Delete", ( ) => remove( planner.getSelection( ) ) );
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Radio
|
|
152
|
+
|
|
153
|
+
- A radio has the room of a checkbox around it (`margin: 5px`, `padding: 4px`): a row of radios is 34px high, as a row of checkboxes. `button` keeps its own.
|
|
154
|
+
- Its round is in the middle of its line, whatever the size of `<html>`.
|
|
155
|
+
|
|
156
|
+
### Calendar
|
|
157
|
+
|
|
158
|
+
- The days out of `minDate..maxDate` have a background on their cell (`--calendar-disabled-background`), their text as the other days; the days out of the month are as big as the others.
|
|
159
|
+
- Its menu of the months gives the months of the year shown that have days between `minDate` and `maxDate`, as its menu of the years gives their years.
|
|
160
|
+
|
|
161
|
+
### Themes
|
|
162
|
+
|
|
163
|
+
- The blues of the dark theme come from `#5796ca` (`$color--dark-base` in themes.scss): the accent `#5796ca`, hovered `#448ac4`, a selection `#1b3a54`. Its greys stay those of VS Code Dark Modern.
|
|
164
|
+
|
|
165
|
+
### Formats, dates
|
|
166
|
+
|
|
167
|
+
- `formatPercent( value, decimals? )`: a percent in the current locale, of a value from 0 to 100 ("89 %" in fr-FR, "89%" in en-US).
|
|
168
|
+
- `formatUnit( value, unit, decimals? )`: a value and its unit of Intl, short, in the current locale ("12 j", "3 h" in fr-FR, "12 days", "3 hr" in en-US).
|
|
169
|
+
- `date_week_start( date )`: the monday of the week of a date, at the same time.
|
|
170
|
+
- `date_add_days( date, days )`: a date some days after (or before), at the same time; a day of summer time counts as one.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
formatPercent( 89 ); // "89 %"
|
|
174
|
+
formatUnit( 12, "day" ); // "12 j"
|
|
175
|
+
date_week_start( new Date( 2026, 9, 29 ) ); // monday 26/10/2026
|
|
176
|
+
date_add_days( new Date( 2026, 9, 29 ), 7 ); // 05/11/2026
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Combobox, TextEdit
|
|
180
|
+
|
|
181
|
+
- The button of a Combobox (its arrow) and the gadgets of a TextEdit have the background of their field (`--background-input`) and its height: they were a small box on the background of the page, darker than the field in the dark theme.
|
|
182
|
+
|
|
183
|
+
### TextEdit, AutoComplete, Combobox, TextArea: the label above
|
|
184
|
+
|
|
185
|
+
- The class `label-top` puts the label above its field, smaller, darker while the field has the focus: on a field, or on a box that holds fields (a Form, the main view: all the application). `labelWidth` is not used then. Its variables: `--label-top-color`, `--label-top-color-focus`, `--label-top-size`, `--label-top-gap`, `--label-top-gap-textarea`, `--label-top-suffix` (`":"` after the label).
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
new Form( { cls: "label-top", content: [
|
|
189
|
+
new TextEdit( { label: "Name" } ),
|
|
190
|
+
new Combobox( { label: "Country", items } ),
|
|
191
|
+
new TextArea( { label: "Comment" } ),
|
|
192
|
+
] } );
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Switch
|
|
196
|
+
|
|
197
|
+
- Its sizes come from one height, `1.125rem` rounded to an even number of pixels (18px when `<html>` has 16px): the track twice as wide less 2px, the thumb at 1px of its edges. All in whole pixels, it is drawn the same wherever it falls in the page, and it follows the size of `<html>`. Without the CSS `round()` (browsers before 2024), its sizes of 16px.
|
|
198
|
+
- Its input has `role="switch"`: a screen reader says "switch".
|
|
199
|
+
- The class `square` gives its track and its thumb corners barely rounded; the class `danger` the colour of an alert when it is on (`--switch-danger`).
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
new Switch( { label: "Delete the files too", cls: "danger" } );
|
|
203
|
+
new Switch( { label: "Wi-Fi", cls: "square" } );
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Slider
|
|
207
|
+
|
|
208
|
+
- Its sizes are all in `rem`, its focus too: its thumb, its track and its box keep their proportions when the size of `<html>` changes (the same pixels at 16px).
|
|
209
|
+
|
|
210
|
+
### Tag
|
|
211
|
+
|
|
212
|
+
- The text of a tag is in `--accent-color`, the color of a text on the accent (its background). Its icon has the color of its text, also when a class sets `--tag-color`; `--tag-icon-color` gives it another one.
|
|
213
|
+
|
|
214
|
+
### Canvas
|
|
215
|
+
|
|
216
|
+
- `Canvas<P, E>`: a class derived from it gives its props and its events (`CanvasEventMap`, now exported), as `Component<P, E>`.
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
class Meter extends Canvas<MeterProps, MeterEvents> { }
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Carousel
|
|
223
|
+
|
|
224
|
+
- New: slides that move, side by side (`perView`): arrows, dots, a swipe with the finger or the mouse, the arrow keys, and an `autoplay` that pauses under the pointer. A slide is a component, or an image (`src`, `alt`, `caption`, `fit`) loaded when it comes near the view. `loop`, `change` (the user or the autoplay), `select`, `next`, `prev`. The slides out of the view are inert; disabled, it is faded and grey.
|
|
225
|
+
- i18n: `_tr.global.carousel` (`carousel`, `slide`, `prev`, `next`, `position`), the texts for screen readers, in the four languages.
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
new Carousel( {
|
|
229
|
+
height: 300, autoplay: 5000,
|
|
230
|
+
items: photos.map( p => ( { src: p.url, alt: p.title, caption: p.title } ) ),
|
|
231
|
+
} );
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Bargraph
|
|
235
|
+
|
|
236
|
+
- New: a level meter of segments, drawn in a canvas, horizontal or `vertical`. The colors go from min to max: the accent by default, pale to full, or `colors` as Gauge; `overshoot`: the segments from that value in the alert color. `animated`: up quickly, down slowly; `peak`: the highest segment stays lit a second. `setValue` can be called at each frame: one loop for all the bargraphs, a drawing only when a segment changes.
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
const meter = new Bargraph( { vertical: true, overshoot: 85, peak: true } );
|
|
240
|
+
meter.setValue( level );
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### MapView (`x4js/mapview`)
|
|
244
|
+
|
|
245
|
+
- New: a map of tiles, openstreetmap by default (`tiles`, `attribution` for another provider), without any library. The pointer moves it; the wheel, a double click, its buttons and the keys zoom it, animated; two fingers pinch it. `markers` (a pin, its tooltip), and `shapes`: zones under the markers, a polygon, a circle of a radius in meters, a line (`color`, `width`). Events `move`, `click` (where), `markerClick`, `shapeClick`; `setCenter`, `setZoom`, `fitBounds`. The dark theme turns its tiles dark (`--mapview-tile-filter`).
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
import { MapView } from "x4js/mapview";
|
|
249
|
+
new MapView( {
|
|
250
|
+
height: 400, center: { lat: 48.8566, lng: 2.3522 }, zoom: 13,
|
|
251
|
+
markers: [ { lat: 48.8584, lng: 2.2945, title: "Tour Eiffel" } ],
|
|
252
|
+
shapes: [ { type: "circle", center: { lat: 48.8584, lng: 2.2945 }, radius: 500 } ],
|
|
253
|
+
} );
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Knob
|
|
257
|
+
|
|
258
|
+
- New: a rotary button, on a range (`min` to `max`, on an arc of 270°) or `infinite` (`turn`: the value of a whole turn, 360 by default). The pointer turns it around its center, the wheel while it has the focus, the arrows by `step` (Ctrl, PageUp, PageDown: 10 steps), Home and End go to min and max. `label` and `icon` under the dial, `size` its diameter. With a `name`, a Form reads its value. Infinite, a double click sets it back to its first value.
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
new Knob( { label: "Volume", value: 25, change: ( ev ) => player.setVolume( ev.value ) } );
|
|
262
|
+
new Knob( { infinite: true, turn: 20 } );
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Chart
|
|
266
|
+
|
|
267
|
+
- `type: "radar"`: an axis for each point, from the center (`yaxis.min`, 0 by default) to the outer ring (`yaxis.max`), the first one at the top; each serie a closed line, tinted inside, with its points and their tooltips, as a line. A ring for each tick of the y scale (`yaxis.step`, `yaxis.ticks`). `serie.fill: "none"` draws the line only.
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
new Chart( {
|
|
271
|
+
type: "radar",
|
|
272
|
+
labels: [ "Speed", "Power", "Range", "Comfort", "Price" ],
|
|
273
|
+
series: [
|
|
274
|
+
{ name: "Model A", values: [ 8, 6, 7, 4, 5 ] },
|
|
275
|
+
{ name: "Model B", values: [ 5, 8, 4, 7, 6 ] },
|
|
276
|
+
],
|
|
277
|
+
} );
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## 2.3.28 (2026-10-23)
|
|
281
|
+
|
|
282
|
+
### Package
|
|
283
|
+
|
|
284
|
+
- The package has its `LICENSE` file (MIT), and `"license": "MIT"` in its package.json.
|
|
285
|
+
|
|
286
|
+
### Themes
|
|
287
|
+
|
|
288
|
+
- `--background-odd`: the background of a row out of two (#f9f9f9 in the light theme); the odd rows of PropertyGrid take it.
|
|
289
|
+
|
|
290
|
+
### TextArea
|
|
291
|
+
|
|
292
|
+
- `required: true` puts a `*` before its label, as on a TextEdit. Its colour: `--textarea-required`.
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
new TextArea( { label: "Comment", name: "comment", required: true } );
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Gridview, Spreadsheet
|
|
299
|
+
|
|
300
|
+
- Disabled, they are faded as a Box (`--box-disabled-opacity`).
|
|
301
|
+
- The bars of a `percent` column are centered in the height of the row.
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
grid.enable( false );
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Treeview
|
|
308
|
+
|
|
309
|
+
- The icons of the items are grey: `--treeitem-icon-color`. Disabled, the texts and the icons take the disabled colour.
|
|
310
|
+
|
|
311
|
+
### Button
|
|
312
|
+
|
|
313
|
+
- An outline button held down keeps its icon visible, in the colour of its text.
|
|
314
|
+
|
|
315
|
+
### Tabs
|
|
316
|
+
|
|
317
|
+
- The icons of the tabs are grey: `--tab-icon-color`.
|
|
318
|
+
|
|
319
|
+
### Input, TextArea
|
|
320
|
+
|
|
321
|
+
- The placeholder is readable in the dark theme: `--input-placeholder` is `--disabled-color-dark` (it had the colour of the background of the field).
|
|
322
|
+
|
|
323
|
+
### ColorInput
|
|
324
|
+
|
|
325
|
+
- In a narrow box, its field gets narrower: it no longer overflows (a horizontal scroll in a PropertyGrid).
|
|
326
|
+
|
|
327
|
+
### PropertyGrid
|
|
328
|
+
|
|
329
|
+
- The description of a group is under its title, smaller, in italics and indented; a collapsible group is as high as the others.
|
|
330
|
+
- The button of a collapsible group keeps its icon visible when it has the focus.
|
|
331
|
+
|
|
332
|
+
### MessageBox, InputBox, PromptBox, ProgressionBox
|
|
333
|
+
|
|
334
|
+
- The style of their big icon is for it only: an icon of their message, of their editor or of a line of their log keeps its own. The stars of a Rating in a PromptBox show the rating again (they were all coloured as checked, 64px high).
|
|
335
|
+
|
|
336
|
+
### Checkbox
|
|
337
|
+
|
|
338
|
+
- `icon`: an icon before its label; `label` is optional (an icon alone, with a tooltip).
|
|
339
|
+
- `cls: "button"`, as for Radio: a toggle button (`button outline` rounded). The box is hidden, its input still reached by Tab and the space bar. Its colours: `--checkbox-button-*`.
|
|
340
|
+
- `left` puts its label before its box again.
|
|
341
|
+
- `cls: "danger"`: checked in the red of the alerts, its box, or its button with `button danger` (`--checkbox-danger-color`, `--checkbox-danger-background`).
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
new Checkbox( { cls: "button", icon: icon_bold, tooltip: "Bold" } );
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Radio
|
|
348
|
+
|
|
349
|
+
- `button`: a border of the theme around each radio (`--radio-button-border`); the icon of the one checked is in the accent, an icon drawn in currentColor too; its text is no longer bold, so that its width does not change with its state.
|
|
350
|
+
- `cls: "danger"`: checked in the red of the alerts, its round, or its button with `button danger` (`--radio-danger-color`, `--radio-danger-background`). The colours of `button` go through `--radio-button-*`, the round checked through `--radio-check-background`.
|
|
351
|
+
- `button`: the hidden round keeps its input in the page, so that Tab and the arrows reach the radios; the focus of the keyboard is shown around them (`--radio-focus`).
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
new Radio( { cls: "button", icon: icon_folder, label: "Folders", name: "view", value: "folders" } );
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### HSplitBox, VSplitBox, HSizer, VSizer
|
|
358
|
+
|
|
359
|
+
- A sizer between two boxes keeps its panel in the box: dragged to an edge, the panel stops at the space left (its flex neighbour at its padding), and at 0 on the other side. Its basis is `flex: 0 1 <size>px`.
|
|
360
|
+
- `HSizer` and `VSizer` say the child they resize in words: `"before"` or `"after"` (the default). A boolean still works: `true` after, `false` before.
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
new HBox( { content: [ list, new HSizer( "before" ), new VBox( { flex: 1 } ) ] } );
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Combobox
|
|
367
|
+
|
|
368
|
+
- Its label is on the right of its `labelWidth`, its colon against the field, as the label of a TextEdit: in a form, they line up.
|
|
369
|
+
|
|
370
|
+
### Video
|
|
371
|
+
|
|
372
|
+
- The image fills the box of the Video, fitted (`object-fit: contain`): a camera or a file no longer makes it overflow its box.
|
|
373
|
+
- `src`: a file to play, as a prop or a property; `stream` is optional (a stream wins over src).
|
|
374
|
+
|
|
375
|
+
```ts
|
|
376
|
+
new Video( { src: "movie.mp4", width: 320, height: 180, muted: true, autoplay: true } );
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### PDF
|
|
380
|
+
|
|
381
|
+
- `X4PDFBuilder.buildBlob()`: the document as a Blob of type application/pdf; `buildURL()`: an object url of it.
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
iframe.setAttribute( "src", pdf.buildURL( ) );
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### SvgBuilder
|
|
388
|
+
|
|
389
|
+
- `SvgText.verticalAlign` sets `dominant-baseline`, read by all browsers (`alignment-baseline` is ignored by Firefox before 149). `"bottom"` puts the bottom of the text on y, `"baseline"` its baseline: code that used them sees its texts move.
|
|
390
|
+
- `SvgText.fontWeight( "light" )` gives the weight 300 (`light` is no value of font-weight).
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
svg.text( 10, 40, "Total" ).verticalAlign( "center" );
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## 2.3.27 (2026-10-22)
|
|
397
|
+
|
|
398
|
+
### MonacoEditor
|
|
399
|
+
|
|
400
|
+
- `x4js/monaco` builds again: it still used `@class_ns`, removed in 2.3.26.
|
|
401
|
+
|
|
402
|
+
### Shortcuts
|
|
403
|
+
|
|
404
|
+
- `isMac` is exported: true on a Mac, an iPhone or an iPad, where `Mod` is Cmd.
|
|
405
|
+
|
|
406
|
+
### Component
|
|
407
|
+
|
|
408
|
+
- `closest( selector )` and `Component.closest( dom, selector )`: the nearest component whose element matches a css selector, starting at itself (`parentElement` starts at the parent).
|
|
409
|
+
- `contains( other )`: true when a node, or the element of a component, is the element of the component or inside it.
|
|
410
|
+
- `Listbox` and `Treeview` find the item clicked with `Component.closest`.
|
|
411
|
+
|
|
412
|
+
```ts
|
|
413
|
+
const item = Component.closest( ev.target as Element, ".x4timeline > .item" );
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Timeline
|
|
417
|
+
|
|
418
|
+
- `Timeline`: events one under the other on a line, each with its icon in a round mark, a title, a sentence and its date. The date is written relative to now, in the locale ("il y a 2 heures", "yesterday"); without a date, no time is shown. A `done` event has its mark in the accent; the line to the next mark is in the accent when the next event is done, dashed after an event not done. `vertical: false` puts the events side by side; `renderer` gives what is beside the mark. A click anywhere in an event fires `click`, its item as `context`. Its colours: `--timeline-*`.
|
|
419
|
+
|
|
420
|
+
```ts
|
|
421
|
+
new Timeline( {
|
|
422
|
+
items: [
|
|
423
|
+
{ icon: branch, title: "New branch", text: "fix-notifications, from master", date: created, done: true },
|
|
424
|
+
{ icon: review, title: "Code review", text: "Waiting for a reviewer" },
|
|
425
|
+
],
|
|
426
|
+
click: ev => this.openStep( ev.context ),
|
|
427
|
+
} );
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
## 2.3.26 (2026-10-22)
|
|
431
|
+
|
|
432
|
+
### CSS classes
|
|
433
|
+
|
|
434
|
+
- A component of x4 declares the prefix of its css class with `static classPrefix = "x4"`. `PopupCalendar`, `FileDialog` and `SvgComponent` have it too: `x4popupcalendar`, `x4filedialog`, `x4svgcomponent`.
|
|
435
|
+
- The decorator `@class_ns` and `x4_class_ns_sym` are removed.
|
|
436
|
+
- Migration: an application had no reason to use `@class_ns`: take it off (its class then has no prefix), or declare `static classPrefix = "my"` in the class. A css rule on `.popupcalendar`, `.filedialog` or `.svgcomponent` gets the prefix `x4`.
|
|
437
|
+
|
|
438
|
+
```ts
|
|
439
|
+
class Badge extends Label { static classPrefix = "my"; } // mybadge x4label x4
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Locale
|
|
443
|
+
|
|
444
|
+
- The formats of numbers, amounts and dates follow a locale: the one of the language (`_tr.global.locale`: "fr-FR" for fr, "en-US" for en, "es-ES", "de-DE"), or the one `Application.setLocale( locale, currency )` forces. The currency is "EUR" by default. `Application.getLocale( )`, `Application.getCurrency( )`.
|
|
445
|
+
- `formatNumber( value, decimals?, grouping? )`, `formatMoney( value )`, `formatDate( date )`, `formatDateTime( date )`: in the current locale. Gridview and Spreadsheet format their columns number, money, date, date-time with them; Chart its labels, TickLine its tooltips (the decimal separator of the locale).
|
|
446
|
+
- `date_format( date, options )` reads its options: Intl in the current locale.
|
|
447
|
+
- A regional variant: a language based on another, with its locale.
|
|
448
|
+
- Migration: an application in another language than French gets its grids, charts and amounts in the formats of that language. To keep the French formats: `Application.setLocale( "fr-FR" )` at the start.
|
|
449
|
+
|
|
450
|
+
```ts
|
|
451
|
+
Application.setLocale( "fr-CH", "CHF" );
|
|
452
|
+
createLanguage( "fr-ch", "fr" );
|
|
453
|
+
addTranslation( "fr-ch", { global: { locale: "fr-CH" } } );
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Sizers
|
|
457
|
+
|
|
458
|
+
- `HSizer` and `VSizer` that size the panel after them (in a `HSplitBox`, the panel after a flex one) fire `resize` too.
|
|
459
|
+
- A `pointercancel` (a touch taken by the browser...) ends the resize: `stop` is fired, the moves after do not resize.
|
|
460
|
+
- The column of a `Gridview` or a `Spreadsheet` dragged by its sizer is 16px wide at least, the min-width of a cell.
|
|
461
|
+
|
|
462
|
+
### Box
|
|
463
|
+
|
|
464
|
+
- A `Box` (so `HBox`, `VBox`, `Form`, `Panel`, `Popup`...) is a `<div>`. Disabled, its content is inert (no click, no focus, no input), as before, and all of it is faded by `--box-disabled-opacity` (0.5): the labels and the components too, not only the native fields. A disabled Box in a disabled Box is faded once.
|
|
465
|
+
- In Chrome, a Box that scrolls keeps its scroll when a Box in it changes.
|
|
466
|
+
- Migration: a css rule of the application on `fieldset`, or on `:disabled` for the fields in a disabled Box, does not apply any more: use `.x4box[disabled]`. A Box that must be a `<fieldset>`: `tag: "fieldset"`.
|
|
467
|
+
|
|
468
|
+
```css
|
|
469
|
+
:root { --box-disabled-opacity: 0.4; }
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### Form
|
|
473
|
+
|
|
474
|
+
- `Form` takes the type of its events as a second parameter, as `VBox` does: a class derived from it declares its own events.
|
|
475
|
+
|
|
476
|
+
```ts
|
|
477
|
+
interface EntryEvents extends ComponentEvents { save: EvChange; }
|
|
478
|
+
class EntryForm extends Form<FormProps, EntryEvents> { }
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### ProgressionBox
|
|
482
|
+
|
|
483
|
+
- Its lines are in a fixed font, a `<div>` each (class `line`). An error is red, on a light red line with a red bar on its left; a warning the same in orange; no icon on the line (the title of a section and the count beside OK keep theirs). Their colours: `--msgbox-log-error-background`, `--msgbox-log-warning-background`.
|
|
484
|
+
|
|
485
|
+
### Theme
|
|
486
|
+
|
|
487
|
+
- `--alert-background-light` and `--warning-background-light`: the red and the orange at 15%.
|
|
488
|
+
|
|
489
|
+
### Gridview
|
|
490
|
+
|
|
491
|
+
- The sort arrow of a column takes the colour of the header (`--gridview-header-sort-color`): light in the dark theme.
|
|
492
|
+
|
|
493
|
+
### SettingsView
|
|
494
|
+
|
|
495
|
+
- Its sections have no frame and the background of the page (white in the light theme); their title is in the accent colour, without band (`--settingsview-section-title-color`).
|
|
496
|
+
|
|
497
|
+
## 2.3.25 (2026-10-21)
|
|
498
|
+
|
|
499
|
+
### Shortcuts
|
|
500
|
+
|
|
501
|
+
- The shortcuts of a view answer while the focus is nowhere only when the view is shown: the view of a tab not shown does not answer any more.
|
|
502
|
+
|
|
503
|
+
### Events
|
|
504
|
+
|
|
505
|
+
- `fire` calls each listener in a `try/catch`: a listener that throws sends its error to `console.error`, the next listeners are called, and the code that fires goes on. This goes for the events of a component and for `Application.fireGlobal`.
|
|
506
|
+
|
|
507
|
+
### Button, Form
|
|
508
|
+
|
|
509
|
+
- A `Button` is `type="button"`: in a `<form>`, Enter in a field does not click it (the browser clicks the first `submit` of the form). `attrs: { type: "submit" }` gives a submit.
|
|
510
|
+
- A `Form` is never sent by the browser: its `submit` is prevented (Enter in its only field would send it, and reload the page).
|
|
511
|
+
- A `Form` with `flex: false` has the height of its content (a toolbar, a line).
|
|
512
|
+
- Migration: a `Button` that must submit a `<form>` of the page (not a `Form` of x4) gets `attrs: { type: "submit" }`.
|
|
513
|
+
|
|
514
|
+
```ts
|
|
515
|
+
new Button( { label: "Send", attrs: { type: "submit" } } );
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### Dialog
|
|
519
|
+
|
|
520
|
+
- A modal box (a `Dialog`, a `MessageBox`, an `InputBox`...) keeps the element that had the focus when it opens, and gives it back the focus when it closes, unless the focus was put elsewhere meanwhile. After an `InputBox` over a `Dialog`, Enter and Escape work again in the `Dialog`.
|
|
521
|
+
- Enter in a `textarea` or an element `contenteditable` is a new line: the dialog does not click its default button there; Ctrl+Enter (Cmd+Enter on a Mac) does. Out of a dialog, Enter in such a field is a new line too, not the move to the next field.
|
|
522
|
+
|
|
523
|
+
### Notification
|
|
524
|
+
|
|
525
|
+
- Its props of a component are given to it: `id`, `cls`, `style`...
|
|
526
|
+
- With `loading`, its icon turns.
|
|
527
|
+
|
|
528
|
+
```ts
|
|
529
|
+
new Notification( { id: "saving", title: "Saving", text: "report.pdf...", loading: true } ).display( 5 );
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
### Listbox
|
|
533
|
+
|
|
534
|
+
- `appendItem` on an empty list takes off its message "Empty list".
|
|
535
|
+
- `reselect`: a click of the user on the item already selected fires `selectionChange` again, with the same selection. For lists linked together whose view shows what was clicked last. `select( )` by the code, the arrows and a right click do not.
|
|
536
|
+
|
|
537
|
+
```ts
|
|
538
|
+
new Listbox( { reselect: true, items: clients, selectionChange: ( ev ) => showClient( ev.selection[0] ) } );
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
- `setItems` fires one `selectionChange`, after the items are drawn, with the selection they end with, when there was a selection before or there is one after; with `keepSel` and the same ids too.
|
|
542
|
+
- `checkable`: a case at the start of each line, with a renderer of yours too. `ListItem.checked` is its starting state. A click on the case, or Space for the last item selected, changes it and fires `checkChange { id, checked }`, without click nor selection. `getChecked( )` gives the ids checked, `setChecked( ids )` changes them without event. Its colours: `--listbox-check-*`.
|
|
543
|
+
|
|
544
|
+
```ts
|
|
545
|
+
const list = new Listbox( {
|
|
546
|
+
checkable: true,
|
|
547
|
+
items: [ { id: 1, text: "alpha" }, { id: 2, text: "beta", checked: true } ],
|
|
548
|
+
checkChange: ( ev ) => console.log( ev.id, ev.checked, list.getChecked( ) ),
|
|
549
|
+
} );
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### PropertyGrid
|
|
553
|
+
|
|
554
|
+
- Its editors have no id: the name of a property (`label`, `icon`...) is not an id in the page. `setPropValue` finds an editor by the name of its property.
|
|
555
|
+
- `live` works for a text and a password too: the callback at each key.
|
|
556
|
+
|
|
557
|
+
```ts
|
|
558
|
+
{ name: "title", type: "string", value: "Save", live: true, callback: ( name, value ) => preview.setText( value ) }
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### ProgressionBox
|
|
562
|
+
|
|
563
|
+
- Its lines are in a `<pre>`, 10 lines high, which scrolls both ways: the box keeps its size. A string is text, an `UnsafeHtml` html.
|
|
564
|
+
- An error has its icon and is in red, and puts the title bar of the box in red, as the one of an error of MessageBox, until `clear( )`. `addWarning( text, perc )` adds a warning, its icon, in orange. After an error or a warning, `done( )` waits for OK, and shows their counts on the left of the bar, with the icon of an error if there is one, else the one of a warning: "2 errors / 1 warning". Their texts: `_tr.global.progression` (`error`, `errors`, `warning`, `warnings`).
|
|
565
|
+
- `addSection( title, perc )` starts a section, open: the lines that follow go in it. A click on its title folds it or opens it. Its title has the icon of the worst of its lines, an error or a warning.
|
|
566
|
+
- Ctrl+A in the lines selects them only, those of a folded section too.
|
|
567
|
+
- Its colours: `--msgbox-log-border`, `--msgbox-log-border-focus`, `--msgbox-log-error-color`, `--msgbox-log-warning-color`, and `--msgbox-log-scrollbar` for the thumb of its scroll bars, whose track is the background of the lines.
|
|
568
|
+
|
|
569
|
+
```ts
|
|
570
|
+
box.addSection( "folder 1", 0 );
|
|
571
|
+
box.addWarning( "data-4.csv: 2 empty lines skipped", 40 );
|
|
572
|
+
box.addError( "data-6.csv cannot be read", 60 );
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
## 2.3.24 (2026-10-21)
|
|
576
|
+
|
|
577
|
+
### Theme
|
|
578
|
+
|
|
579
|
+
- Themes: `Application.setTheme( name )` puts the class `x4theme-<name>` on `<html>`. `""` is the light theme, by default; `"dark"` a dark theme, like the one of VS Code. An application adds its own under `html.x4theme-<name>` in its css. `Application.getTheme( )` gives the name of the theme.
|
|
580
|
+
|
|
581
|
+
```ts
|
|
582
|
+
Application.setTheme( "dark" );
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### Label
|
|
586
|
+
|
|
587
|
+
- The classes `right` and `al-right` put its content on its right.
|
|
588
|
+
|
|
589
|
+
### ProgressionBox
|
|
590
|
+
|
|
591
|
+
- Its lines are in a textarea read only: they are text, a markup is shown as it is written.
|
|
592
|
+
- `done( )` shows the button OK. After an error it waits for it; else it counts the seconds left, "OK (5s)", and closes the box at 0. A click on OK closes it at once.
|
|
593
|
+
- `clear( )` puts the box back as at the start: no line, no error, no button OK.
|
|
594
|
+
|
|
595
|
+
### Keyboard
|
|
596
|
+
|
|
597
|
+
- Backspace pressed once erases one letter.
|
|
598
|
+
|
|
599
|
+
## 2.3.23 (2026-10-21)
|
|
600
|
+
|
|
601
|
+
### Theme
|
|
602
|
+
|
|
603
|
+
- The greys of the theme are mixes of the two ends of the scale, `--color-primary-a0` and `--color-primary-a100`. This covers `--background-ternary`, `--text-secondary`, `--text-ternary`, `--disabled-background`, `--disabled-color-dark`, `--border`, `--border-light` and `--border-hover`. With the scale of x4 they keep their values (`#999`, `#e1e1e1`…); a theme that reverses the scale gets them light on dark. `--disabled-color` is `--color-primary-a0`.
|
|
604
|
+
- `--alert-color`, `--warning-color` and `--success-color` are white: the red, orange and green do not follow the scale.
|
|
605
|
+
- New global variables, which the components use: `--background-hover` (`#eee`), `--background-active` (`#ccc`), `--background-input` (white at 60%), `--disabled-color-light` (`#ccc`), `--icon-color` (black at 44%) and `--icon-color-active` (`--accent-color` at 64%).
|
|
606
|
+
- The variables of the components point to the global ones and follow the scale with them:
|
|
607
|
+
- backgrounds, `--background-primary`: Listbox, Treeview, Menu, BtnGroup, TickLine, Chart, PropertyGrid and its gadgets;
|
|
608
|
+
- texts, `--text-primary`: Input, Tag, the gadgets of PropertyGrid, the needle of Gauge;
|
|
609
|
+
- the fields of Input and PropertyGrid, `--background-input`;
|
|
610
|
+
- the outline buttons (`--ol-button-*`), the background of Switch, the icons of Label and of a pressed button;
|
|
611
|
+
- the title and the icon of a tooltip, `--color-primary-a0`.
|
|
612
|
+
- Chart: its texts take `--text-ternary` (`#666`), its grid `--border`, its axis `--border-hover`. The dot of the needle of Gauge takes `--text-ternary`.
|
|
613
|
+
- `--accent-background-light`: the accent at 25%. DockingView takes it for a title under the pointer and for the place where a dragged panel will go (`--dock-preview-background`, new).
|
|
614
|
+
- SettingsView: a setting under the pointer takes `--color-primary-a20`.
|
|
615
|
+
- The colours of the rules go through variables of their component, which point to the globals: the background of Dialog (`--color-dialog-background`), the title of Listbox (`--listbox-title-background`, `--listbox-title-color`), the title of Panel (`--panel-title-background`), the lines of a ProgressionBox (`--msgbox-log-background`), Checkbox (`--checkbox-check-color`), Radio (`--radio-background`, `--radio-check-color`, `--radio-square-border-checked`), the thumb of Switch (`--switch-thumb`), a tab under the pointer (`--tab-color-hover`), the image of a tooltip (`--tooltip-image-background`).
|
|
616
|
+
- Select: its variables are defined (`--select-border`, `--select-border-focus`, `--select-background`) with `--select-color` for its text. Its arrow is drawn in `--select-arrow-color` (`--text-ternary`): a small triangle.
|
|
617
|
+
- SimpleTextArea, TextArea: the background and the text of an input (`--textarea-background`, `--textarea-color`), and `--textarea-selection-color` for the text selected.
|
|
618
|
+
- The variables that the rules read are all defined, with the look the components have: the text of the tabs (`--tab-color`, `--tab-color-selected`), of the title of Panel (`--panel-title-color`, which the title reads), of the days of Calendar (`--calendar-day-color`), of the items of Treeview (`--treeitem-color`, and `--treeitem-color-hover`, which the item under the pointer reads), all `--text-primary`; the background of Form (`--form-background`) and of Popup (`--popup-background`), transparent.
|
|
619
|
+
- Dialog, the hue slider of ColorPicker, Menu and Popup have no border colour nor radius read from a variable that does not exist; the empty rows of Gridview and Spreadsheet no background.
|
|
620
|
+
|
|
621
|
+
```scss
|
|
622
|
+
:root {
|
|
623
|
+
--color-primary-a0: #1e1e1e; // the greys and the borders follow
|
|
624
|
+
--color-primary-a100: #f0f0f0;
|
|
625
|
+
}
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### MessageBox, InputBox, PromptBox
|
|
629
|
+
|
|
630
|
+
- Only an error has its title bar and its icon in the colour of the alerts (`--msgbox-caption-background`, `--msgbox-caption-color`, `--msgbox-icon-color`). A question, an InputBox and a PromptBox have the title bar of a Dialog, and their icon in `--msgbox-question-icon-color` (the accent).
|
|
631
|
+
- A MessageBox has the class of its kind: `error` (by default) or `question`.
|
|
632
|
+
- The pen of an InputBox and of a PromptBox takes the colour of its icon.
|
|
633
|
+
|
|
634
|
+
```ts
|
|
635
|
+
MessageBox.show( "Delete it ?", [ "yes", "no" ], "Question", "question" ); // the title bar of a Dialog
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### Color
|
|
639
|
+
|
|
640
|
+
- `new Color( )` reads every colour the browser knows: `hsl( )`, `oklch( )`, `color-mix( )`, and a variable that holds one (`"var(--border)"`).
|
|
641
|
+
- `mix( other, amount )` mixes two colours, alpha included: 0 gives the colour, 1 the other one.
|
|
642
|
+
- `darken( percent )` goes toward black, as `lighten( percent )` goes toward white. Both round each component.
|
|
643
|
+
- `luminance( )` and `contrast( other )` give the relative luminance and the contrast ratio of WCAG.
|
|
644
|
+
- `textColor( light, dark )` gives a copy of the one of the two colours that contrasts most with the colour: white or black when none is given.
|
|
645
|
+
|
|
646
|
+
```ts
|
|
647
|
+
const band = new Color( logo ).mix( new Color( "#000000" ), 0.2 ).toHexString( ); // 20% toward black
|
|
648
|
+
label.setStyleValue( "color", new Color( client.color ).textColor( ).toHexString( ) );
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
## 2.3.22 (2026-10-21)
|
|
652
|
+
|
|
653
|
+
### SettingsView, SettingItem
|
|
654
|
+
|
|
655
|
+
- New: `SettingsView`, a page of settings as the settings of VS Code. The summary of the sections is on the left, the sections on the right, one after the other. A click in the summary brings its section to the top, and the summary follows the scroll (event `sectionChange`).
|
|
656
|
+
- `SettingItem`: one setting, its name, what it does, then its controls. Its controls keep their values: in a `Form`, `getValues` / `setValues` read and write them.
|
|
657
|
+
- `searchable`: a field above the sections shows the settings whose name or description holds the words typed, without case nor accents. Escape empties it.
|
|
658
|
+
- The default texts come from `_tr.global.settings` (`search`, `nomatch`).
|
|
659
|
+
|
|
660
|
+
```ts
|
|
661
|
+
new SettingsView( { searchable: true, sections: [
|
|
662
|
+
{ id: "editor", title: "Editor", content: [
|
|
663
|
+
new SettingItem( { title: "Font size", description: "The size of the text, in pixels.", content: new Input( { type: "number", name: "fontSize", value: 14 } ) } ),
|
|
664
|
+
] },
|
|
665
|
+
] } );
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### i18n
|
|
669
|
+
|
|
670
|
+
- Two new languages, `es` and `de`, with every text of `_tr.global`. A key they lack is taken in `en`, then in `fr`.
|
|
671
|
+
- Their dates: `D/M/Y` in es, `D.M.Y` in de (`21.10.2026`), with their names of days and months.
|
|
672
|
+
|
|
673
|
+
```ts
|
|
674
|
+
selectLanguage( "de" );
|
|
675
|
+
formatIntlDate( new Date( ) ); // "21.10.2026"
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
### Keyboard
|
|
679
|
+
|
|
680
|
+
- The keys that draw the keyboard again (shift, `123`, `abc`, the accents, the signs) act on the click of their key, the others still on the contact (`pointerdown`): under WebKitGTK, the field keeps the focus and the keyboard stays shown.
|
|
681
|
+
- `numpadOnly`: on a number or date field, the keyboard stays on the pad, without the `abc` key back to the letters. The other fields keep all the keys.
|
|
682
|
+
|
|
683
|
+
```ts
|
|
684
|
+
new Keyboard( { numpadOnly: true } );
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
## 2.3.21 (2026-10-15)
|
|
688
|
+
|
|
689
|
+
### Panel, GridBox
|
|
690
|
+
|
|
691
|
+
- `Panel.setTitle( )` changes the text of the title: its icon stays.
|
|
692
|
+
- `GridBox.setTemplate( )` lays the box out: `grid-template-areas` is set in its style.
|
|
693
|
+
|
|
694
|
+
### Dialog, MessageBox
|
|
695
|
+
|
|
696
|
+
- A dialog puts the focus on its first field each time it is shown, not only the first time.
|
|
697
|
+
- `new Dialog( { content } )`: without `form`, `content` is the content of its form.
|
|
698
|
+
- `MessageBox.show( msg, buttons, title, "question" )` shows the icon of question.
|
|
699
|
+
|
|
700
|
+
```ts
|
|
701
|
+
new Dialog( { title: "Name", buttons: ["ok","cancel"], content: new TextEdit( { name: "name", label: "Name" } ) } ).show( );
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
### Chart
|
|
705
|
+
|
|
706
|
+
- A `yaxis.min` above the values, or a `yaxis.max` under them, draws an empty range at that bound, without NaN.
|
|
707
|
+
- `xaxis.step` under 0.5 shows a label on each point, the page is no longer stuck.
|
|
708
|
+
- A stacked serie without `values` counts 0.
|
|
709
|
+
- `xaxis.min` is not used: the doc says so.
|
|
710
|
+
|
|
711
|
+
### SVG
|
|
712
|
+
|
|
713
|
+
- `SvgIcon` keeps all the elements of its svg, and reads a svg that has a `%` (`width="100%"`): a data url is decoded only when it is encoded.
|
|
714
|
+
- `addStop( "50%", ... )` writes `50%`, `addStop( 20, ... )` writes `20%`.
|
|
715
|
+
- `strokeCap( "square" )`.
|
|
716
|
+
- `addClass( "" )` returns the item: it can be chained.
|
|
717
|
+
- `getNumAttr( )` gives 0 for an attribute not set, and the decimals of a value (`1.5`).
|
|
718
|
+
- The doc of `arc( )`: 0 is the top, 90 the right.
|
|
719
|
+
|
|
720
|
+
### PDF
|
|
721
|
+
|
|
722
|
+
- `ctx.image( jpeg, ... )` reads the size of the image in its frame header: a JPEG with an EXIF thumbnail gets its own size, not the one of the thumbnail.
|
|
723
|
+
|
|
724
|
+
### Command line
|
|
725
|
+
|
|
726
|
+
- `x4js create`: the tsconfig.json of every template sets `experimentalDecorators`, which the decorators `@data.*` of a `DataModel` need. An application created before has to add `"experimentalDecorators": true` to its `compilerOptions`.
|
|
727
|
+
|
|
728
|
+
### Gridview, Spreadsheet
|
|
729
|
+
|
|
730
|
+
- Their colours come from the theme: the backgrounds from `--background-primary`, the texts from `--text-primary`, the lines from `--border`. A dark theme (the scale `--color-primary-*` reversed) gives a dark grid, without redefining the variables `--gridview-*` or `--spreadsheet-*`.
|
|
731
|
+
|
|
732
|
+
### Gridview
|
|
733
|
+
|
|
734
|
+
- `selectItem( id, true )` on a grid that is not shown yet selects the row without an error; nothing is scrolled.
|
|
735
|
+
|
|
736
|
+
### Keyboard
|
|
737
|
+
|
|
738
|
+
- A double click, or a quick double tap of a finger, on a key writes it twice, not three times: a key is read at each contact (`pointerdown`), the focus stays in the field.
|
|
739
|
+
|
|
740
|
+
### Video
|
|
741
|
+
|
|
742
|
+
- Its class is `x4video`: its stylesheet applies (a black background, the player over all the box). A stylesheet of the application that targets `.video` has to target `.x4video`.
|
|
743
|
+
- `play( )` returns a promise of `true` when the playback started, `false` when it could not (the browser wants a click of the user first, the source cannot be played): no "Uncaught (in promise)". Without any source, the browser waits for one.
|
|
744
|
+
|
|
745
|
+
```ts
|
|
746
|
+
if( !await video.play( ) ) {
|
|
747
|
+
playButton.show( ); // the user starts it
|
|
748
|
+
}
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
### svgLoader
|
|
752
|
+
|
|
753
|
+
- A file not found is kept as such: `load( )` rejects it again, without asking for it again, instead of resolving `null`. An `Icon` on it stays empty, without the text `null`.
|
|
754
|
+
- The loads asked while a file is loading all get its answer, a failure too: none waits for ever.
|
|
755
|
+
|
|
756
|
+
### Icon
|
|
757
|
+
|
|
758
|
+
- `var:home` is read as the variable `--home`, as `var:--home` is: the name of the variable may be given without its `--`. It is the variable set on `<html>` by the code (`document.documentElement.style.setProperty( "--home", svg )`). `ctx.icon( )` of the PDF reads the same names.
|
|
759
|
+
|
|
760
|
+
### Link
|
|
761
|
+
|
|
762
|
+
- With a `click` handler, the link is followed unless the handler calls `ev.preventDefault( )`. A handler that only reacts to the click, without it, now lets the browser go to `href` (a link `href: "#"` goes to the top of the page).
|
|
763
|
+
- `content` given: the link is its content, it is no longer replaced by an empty label.
|
|
764
|
+
- `setText( )` changes the text of the label: its icon and its alignment stay.
|
|
765
|
+
|
|
766
|
+
```ts
|
|
767
|
+
new Link( { href: "#", text: "Open", click: ( ev ) => { ev.preventDefault( ); this.open( ); } } );
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
### Drag and drop
|
|
771
|
+
|
|
772
|
+
- A drop target is told `enter` at once, before the first `drag`, from the first drag of the page on. `leave` still comes up to 300 ms after the pointer went away.
|
|
773
|
+
|
|
774
|
+
### Stylesheet
|
|
775
|
+
|
|
776
|
+
- All the `Stylesheet` of a page share one `<style id="x4-dynamic-css">`: a rule set again by its name, from any of them, is replaced in its place.
|
|
777
|
+
- A definition that is not valid throws its error and leaves the rules as they are: the rule of that name stays, and the next `setRule( )` replaces it.
|
|
778
|
+
- Deprecated: `Stylesheet` (`setRule( )`, `getVar( )`). A CSS variable does it: `setStyleVariable( )` on a component, `var( --name )` in its stylesheet; `getStyleVariable( )` to read one.
|
|
779
|
+
|
|
780
|
+
### unsafe
|
|
781
|
+
|
|
782
|
+
- The template tag `unsafe` writes `0`, `false` and `NaN`, as a template does: `` unsafe`<b>${count}</b>` `` with a count of 0 gives `<b>0</b>`. Only `null` and `undefined` write nothing.
|
|
783
|
+
|
|
784
|
+
### Shortcuts
|
|
785
|
+
|
|
786
|
+
- The space bar is written `Space` in a sequence: `"Ctrl+Space"` is a shortcut.
|
|
787
|
+
|
|
788
|
+
### Dates
|
|
789
|
+
|
|
790
|
+
- `date_calc_weeknum( )`, and `w` / `W` of `formatIntlDate`, give the week of ISO 8601: weeks from monday, the week 1 holds the first thursday of the year. The 29/12/2025 is in the week 1, the 01/01/2027 in the week 53; there is no week 0, and summer time does not move the weeks. The weeks of the `Calendar` follow.
|
|
791
|
+
- `date_diff( )` gives the gap whichever date comes first, and the hours with their minutes: `"2 heures 5 minutes"` (`_tr.global.diff_date_hours`: `'{0} heures {1} minutes'`).
|
|
792
|
+
|
|
793
|
+
### i18n
|
|
794
|
+
|
|
795
|
+
- `addTranslation( )` on the language that is selected: `_tr` has the new translations at once.
|
|
796
|
+
- A key missing in a language is taken from its base at the same path, however it is read: a group kept in a variable (`const g = _tr.app; g.title`) too. A key that no language has is told once, with its path.
|
|
797
|
+
|
|
798
|
+
### Color
|
|
799
|
+
|
|
800
|
+
- `new Color( r, g, b )` and `setRgb( r, g, b )` without alpha give an opaque color: `toRgbString( )` gives `"rgb(255,0,0)"`, `toHexString( )` `"#ff0000"`.
|
|
801
|
+
- `isInvalid( )` tells about the last color given: after a text that cannot be read, `setRgb( )` or `setHsv( )` give a valid color.
|
|
802
|
+
- `toRgbString( )` never gives the alpha. `toRgbString( true )` gives it when the color is not opaque (`"rgba(255,0,0,0.500)"`), an opaque color stays `"rgb(...)"`. A code that needs the transparency asks for it with `true`.
|
|
803
|
+
|
|
804
|
+
```ts
|
|
805
|
+
const c = new Color( "#ff000080" );
|
|
806
|
+
c.toRgbString( ); // "rgb(255,0,0)"
|
|
807
|
+
c.toRgbString( true ); // "rgba(255,0,0,0.502)"
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
- `setRgb( )` and `setHsv( )` round the components: a color that goes through hsv keeps its value (`#c57c7c` stays `rgb(197,124,124)`).
|
|
811
|
+
|
|
812
|
+
### ColorPicker
|
|
813
|
+
|
|
814
|
+
- Opened, it shows its color: the thumb of the saturation on its saturation and its value, the alpha slider on its alpha.
|
|
815
|
+
- New `alpha: false`: no alpha slider, the colors are opaque (the one given, `setColor`, the swatches, the eye dropper, ctrl+↑/↓).
|
|
816
|
+
- The swatches are drawn over checkers: a color that is not opaque is seen as such. The swatch "no color" (`nullable`) is white, crossed out in red (`--colorpicker-none`).
|
|
817
|
+
- A click without a move takes the point, in the saturation, the hue and the alpha.
|
|
818
|
+
- The arrows move the picker only: the page or the popup that holds it does not scroll.
|
|
819
|
+
|
|
820
|
+
### ColorInput
|
|
821
|
+
|
|
822
|
+
- A color that is not opaque (its alpha changed in the picker, `rgba(...)` typed) is shown with its alpha: the text in `rgba(...)`, the swatch over checkers (`#00000000` shows the checkers alone).
|
|
823
|
+
- While it is typed, the text is left alone: the color follows it (`#111` then `#111fff`). When the field is left, the text is written from the color, in the form that was typed (`#...` or `rgb...`): a text that could not be read shows the color that was kept. The picker writes in the form of the field, `format` when it has none.
|
|
824
|
+
- New `alpha: false`: the colors are opaque, typed, picked or given, and the picker has no alpha slider.
|
|
825
|
+
- No color: the swatch is white, crossed out in red (`--colorinput-none`); the checkers are for a transparent color.
|
|
826
|
+
- New `placeholder`: the text shown in the field while it is empty.
|
|
827
|
+
|
|
828
|
+
```ts
|
|
829
|
+
new ColorInput( { color: "#126e9e", picker: true, alpha: false } );
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
### AutoComplete
|
|
833
|
+
|
|
834
|
+
- A text shorter than `minChars` closes the suggestions and forgets them: ↓ asks for new ones, on the text of the field (`search` with `ev.text` empty when the field is), and does not show those of the previous text.
|
|
835
|
+
|
|
836
|
+
### TextArea
|
|
837
|
+
|
|
838
|
+
- Without a height, its textarea has the height of its rows (2), and the height its handle gives (`resize`). Given a height, the `TextArea` is filled by its textarea under the label.
|
|
839
|
+
- A `SimpleTextArea` (the one of a `TextArea` too) is never smaller than one character, its padding and border included: a height or a handle that ask for less give a line high and a character wide.
|
|
840
|
+
|
|
841
|
+
### Form
|
|
842
|
+
|
|
843
|
+
- `autoComplete` (and `setAutoComplete( )`) is set on the `<form>`: its fields follow it, those added later too. A field with an `autocomplete` of its own keeps it.
|
|
844
|
+
|
|
845
|
+
## 2.3.20 (2026-10-14)
|
|
846
|
+
|
|
847
|
+
### PDF
|
|
848
|
+
|
|
849
|
+
- New `ctx.svgPath( d, x, y, scale, fill, stroke?, lineWidth? )`: the `d` of a SVG path, drawn in the PDF. The origin of the SVG falls on (x, y), its y going down as in SVG; `scale` is in points by unit of the SVG. `fill` and `stroke` as for `path( )`, `null` for what is not drawn. `M L H V C S Q T A Z`, absolute and relative: the quadratic curves and the arcs become Bézier curves. Another command throws an error: a drawing is never cut short.
|
|
850
|
+
- New `ctx.icon( svg, x, y, w, h, opts? )`: a whole SVG icon, as an `Icon` takes it (`"<svg..."`, `"data:image/svg+xml,..."`, an imported `.svg`, `"var:name"`). Its viewBox is fitted and centered in w x h, (x, y) the bottom left corner. `path`, `rect` (rounded too), `circle`, `ellipse`, `line`, `polyline`, `polygon`, in groups with their `transform`; `fill`, `stroke`, `stroke-width`, `opacity`, `fill-opacity`, `fill-rule`, as attributes or in `style`. Gradients, texts, images, `<use>` and the rules of a `<style>` are not drawn.
|
|
851
|
+
- `opts.color`: the color of `currentColor` and of what has no fill, black when not given: a one-color icon takes the color of the page. `opts.background`: an opacity is mixed with it, white when not given. A PDF has no transparency, which printers do not like: the part at 40 % of a duotone icon is its color mixed with the background.
|
|
852
|
+
- A file (`"icons/pills.svg"`) cannot be read while a page is drawn: `ctx.icon` throws. Load it first, with `await svgLoader.load( url )`.
|
|
853
|
+
|
|
854
|
+
```ts
|
|
855
|
+
import pills from "./assets/pills-duotone-solid.svg";
|
|
856
|
+
|
|
857
|
+
pdf.page( 595, 842, ctx => {
|
|
858
|
+
ctx.icon( pills, 50, 700, 40, 40, { color: "#ffffff", background: "#D55E00" } );
|
|
859
|
+
ctx.svgPath( "M0 0 A20 20 0 0 1 40 0 Z", 100, 760, 1, "#f2c94c" );
|
|
860
|
+
} );
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
## 2.3.19 (2026-10-14)
|
|
864
|
+
|
|
865
|
+
### Application
|
|
866
|
+
|
|
867
|
+
- `mountPoint` is honored: the main view is mounted in the element it selects, whenever `setMainView` is called, a page whose script is a module too. An app that gives `mountPoint` has to check its layout: its view is in that element, not in `<body>`.
|
|
868
|
+
- `fireGlobal` right after `setupSocketMessaging( )`: the message is sent once the socket is open, those fired while it connects are dropped (`send` throws on a socket that connects).
|
|
869
|
+
|
|
870
|
+
### DataStore, DataView
|
|
871
|
+
|
|
872
|
+
- The filters `<`, `<=`, `=`, `>=`, `>`, `<>` on a field `int`, `float` or `date` compare numbers, a date by its time: `{ op: "<", field: "age", value: 9 }` keeps the ages under 9, not those whose text comes before `"9"`. `value` can be a number. A field that is null passes no comparison but `<>`.
|
|
873
|
+
- Sort on several fields: a `callback` follows `ascending`, and two records it finds equal are sorted by the next field. `numeric: true` sorts by the numbers of the values (Gridview gives it for its columns `number`, `money`, `percent`, `checkbox`): `"9"` comes before `"10"`.
|
|
874
|
+
- A filter whose `value` is a `RegExp` is tested on the field as it is: `caseSensitive` does not apply to it, the regexp handles the case (`/^jo/i`).
|
|
875
|
+
- `moveTo( other )` gives the records to `other` and empties the store: the two stores do not share the same array.
|
|
876
|
+
- A model whose class declares an id while its parent class has one gives an assertion (`cannot define multiple record id`). A class without decorators of its own gives none.
|
|
877
|
+
- Deprecated: the loading from an url (`url`, `autoload`, `solver`, `load( )`, `reload( )`, `DataProxy`). It is to be reviewed; load the data, then give them to `setData( )`.
|
|
878
|
+
|
|
879
|
+
### Checkbox, Switch
|
|
880
|
+
|
|
881
|
+
- `value` is used. Without it, or with a boolean one, a `Form` reads a boolean, as always. With it, a `Form` reads the value when checked and `null` when not, as a `<form>` of the browser does; `setValues` checks it for that value (or `true`). The `<input>` carries it too.
|
|
882
|
+
|
|
883
|
+
### Radio
|
|
884
|
+
|
|
885
|
+
- `checkValue( v )` finds the radios of its group wherever they are in its form (a dialog has one), each in a box of its own too; out of a form, in the page. The radios of the same name in another form, of the page or of a dialog, are not touched.
|
|
886
|
+
- A `Form` reads the radios of its own group: two forms of a page can each have a group of the same name.
|
|
887
|
+
|
|
888
|
+
### TextEdit
|
|
889
|
+
|
|
890
|
+
- Its props go to the box or to the input, not to both: `style`, `tooltip`, `hidden`, `flex`, `stretch`, `height` are on the box; `dom_events` are on the input (`focus` and `blur` work, a `keydown` runs once by key).
|
|
891
|
+
|
|
892
|
+
### SimpleTextArea
|
|
893
|
+
|
|
894
|
+
- Alone in a box, it keeps its height (`flex-shrink: 0`): a box short of room does not crush it. In a `TextArea`, it follows the size of the `TextArea`.
|
|
895
|
+
- No handle to resize it by default (`resize: none`), in a `TextArea` too: `resize` asks for one. `resize: true` gives `"both"`. A textarea that has to keep its handle needs `resize: true`.
|
|
896
|
+
|
|
897
|
+
### Calendar
|
|
898
|
+
|
|
899
|
+
- A month further, by ◀ ▶ or by the menus of the month and of the year, keeps the day but not past the end of the month: from 31 January, ▶ gives 28 February (not 3 March); from 29 February 2024, the year 2025 gives 28 February 2025.
|
|
900
|
+
- `minDate` and `maxDate` are applied: the days out of them are struck through and cannot be chosen, and the date stays between them (navigation, the button "today", `date` of the props).
|
|
901
|
+
- `setDate( d )` keeps a copy of `d`, `getDate( )` gives a copy: the calendar and its caller do not share the object.
|
|
902
|
+
- A calendar too short for its weeks (6 in some months) cuts what does not fit: it is not drawn over what follows.
|
|
903
|
+
|
|
904
|
+
### Slider
|
|
905
|
+
|
|
906
|
+
- The thumb is placed from `min`: on `min: 10, max: 20`, the value 10 is at the start of the track, 15 in the middle. `min` and `max` can have decimals.
|
|
907
|
+
- `change` is fired when the user moves the thumb (drag, arrows): not at the construction, nor by `setValue`, `setMin`, `setMax`.
|
|
908
|
+
- New `name`: a `Form` reads the slider, as a number, and `setValues` moves its thumb. New `getValue( )`.
|
|
909
|
+
|
|
910
|
+
### Listbox
|
|
911
|
+
|
|
912
|
+
- The arrows go on from the item selected, by a click or by `select( )` in the code. After `setItems( )` or `clearSelection( )`, they start from the first item. An item of id `0` is one: ↓ goes to the next.
|
|
913
|
+
|
|
914
|
+
### Combobox
|
|
915
|
+
|
|
916
|
+
- The arrows choose: the item reached is shown in the field and `selectionChange` is fired, the popup stays open; Enter and Escape close it on that choice.
|
|
917
|
+
- Editable (`readonly: false`): a text typed that is no longer the one of the item chosen leaves no item chosen, with a `selectionChange` empty. `getSelection( )` and a `Form` read nothing.
|
|
918
|
+
- `selectItem( null )`, and `Form.setValues( { combo: null } )`, empty the field too.
|
|
919
|
+
|
|
920
|
+
### Input
|
|
921
|
+
|
|
922
|
+
- `required` and `readonly` are set for every type but checkbox and radio: a `date` or a `time` in `readonly` cannot be changed. `placeholder` is set on text, email, password and number, the types on which the browser shows it (`date` and `time` show their own pattern).
|
|
923
|
+
- The wheel on a `number` that has the focus keeps it between `min` and `max`, on the step, and starts from a valid value when the field is empty (`stepUp` / `stepDown` of the browser).
|
|
924
|
+
- A `number` with a `step` shows as many decimals as the step, 4 at most: `step: 0.25` keeps `1.25`, `step: 2.5` keeps `2.5`.
|
|
925
|
+
- The radios of a form are read without error when the form is under an element that is not a component (the element of `mountPoint`), or out of the document.
|
|
926
|
+
|
|
927
|
+
### Form
|
|
928
|
+
|
|
929
|
+
- `validate( )`: the values that the validator changes are written back in the fields that carry the name, whatever they are (`Select`, `TextArea`, `Combobox`, `Rating`...), and in every radio of a group: the one that holds the value is checked.
|
|
930
|
+
- `setValidator( )` called again replaces the validator.
|
|
931
|
+
|
|
932
|
+
### Rating
|
|
933
|
+
|
|
934
|
+
- Without `steps`, a rating has its 5 stars.
|
|
935
|
+
- A click on a star sets the value to its number (1 for the first) and fires `change` once, also after `setSteps( )`. `value` is the count of stars lit, for the props, `setValue` and the click alike. The star of the value, clicked again, sets it to 0: none is lit.
|
|
936
|
+
- `setShape( icon )` redraws the stars with the icon.
|
|
937
|
+
- In a `Form`, the `name` is on the rating: `setValues( { note: 3 } )` lights 3 stars, `getValues( )` gives the number. The rating has no hidden `<input>` any more: a `<form>` sent by the browser itself does not get the value.
|
|
938
|
+
|
|
939
|
+
### State
|
|
940
|
+
|
|
941
|
+
- The methods of an array that move its elements (`shift`, `unshift`, `splice`, `sort`, `reverse`) keep the state right: the array holds the objects themselves, and a change of an element gives one event, on the path where the element is now (`items[0].n` after a `shift`).
|
|
942
|
+
- An object written back where it already is fires nothing, as for the other values (`state.items[0] = state.items[0]`).
|
|
943
|
+
|
|
944
|
+
### Router
|
|
945
|
+
|
|
946
|
+
- The back button of the browser calls the route that `navigate( )` calls: the first that matches, in the order of `get( )`. `"/*"`, declared last, is the route for the rest (`params.wild`); for what runs at each navigation, the event `change`.
|
|
947
|
+
- The handler gets the same `path` from both, as in the address: `"/detail/5"`, without `#`.
|
|
948
|
+
- The parameters are decoded: `/detail/caf%C3%A9` gives `params.id === "café"`. A lone `%` is left as it is.
|
|
949
|
+
|
|
950
|
+
### Command line
|
|
951
|
+
|
|
952
|
+
- `x4js <command> --help` (or `-h`) shows the help, which now gives the options of every command.
|
|
953
|
+
- `x4js templates` takes no argument: one given is an error.
|
|
954
|
+
- `dev --open`: when no browser can be started (no `xdg-open` on Linux), a warning is shown and the server goes on.
|
|
955
|
+
- `copy`: `to` accepts environment variables (`$NAME`), as the other paths do. It is checked once expanded: it cannot go out of `outdir`, nor be on a drive of its own (`"D:assets"` on Windows).
|
|
956
|
+
- `copy`: a `from` that does not exist gives a warning (`copy: not found: …`). In `dev`, its folder is watched: the file is copied as soon as it is created.
|
|
957
|
+
- A `.svg` imported from a package (`import icon from "@acme/icons/close.svg"`) is found in `node_modules`, and is a `data:` URL in base64 as a local one.
|
|
958
|
+
- `.less`: when `less` is not installed, the error is not kept: once `less` is installed, the next build of `dev` uses it.
|
|
959
|
+
- `create`: when a file of the template cannot be downloaded, the folder is removed once every other download is over. A path of the template that holds a `\` is refused.
|
|
960
|
+
|
|
961
|
+
### Keyboard
|
|
962
|
+
|
|
963
|
+
- The letters of the language (`é è à ç`, `ä ö ü ß`...) are no longer on rows of their own above the space bar: they are on a page of their own, called by a key of the bottom row that shows the first two of them (`éè`, `äö`). The page of the letters has its five rows again, whatever the language. A language that adds no letter (`en`) has no such key.
|
|
964
|
+
- New page of the signs that the letters do not have (`- / : ; ( ) € & @ "`, `\ | ~ _ § £ $ ^ %`, `[ ] { } < > = + * #`), called by the key `#+=` of the bottom row.
|
|
965
|
+
- On these two pages, `Abc`, at the place of the key that called the page, comes back to the letters. Shift changes the case of the letters of the language, as it does on the page of the letters.
|
|
966
|
+
- `mode` gives the page that is shown: `"alpha"`, `"accents"`, `"symbols"`, `"number"` or `"date"`. On the page of the letters it was `"lower"` or `"upper"`: the case is now the new field `upper`. The class of the page follows `mode`: a stylesheet that styled `.lower` or `.upper` has to style `.alpha`.
|
|
967
|
+
- The keys of each page have a color of their own: white for the letters, `--keyboard-accents-color` for the letters of the language, `--keyboard-symbols-color` for the signs. The key that calls a page has its color. New CSS variables: `--keyboard-key-color`, `--keyboard-accents-color`, `--keyboard-symbols-color`.
|
|
968
|
+
|
|
969
|
+
### PDF
|
|
970
|
+
|
|
971
|
+
- Accented letters and the signs of Windows-1252 (`é à ç œ € … « » ’ ™`...) are written as they are. The text was written in UTF-8, which the font does not read: `é` came out as `é`. A character that Windows-1252 does not have comes out as `?`. `measureText` gives these characters their own width: `align: "center"` and `"right"` are right with them.
|
|
972
|
+
- `text( )` writes every text in WinAnsiEncoding, one that starts with `(` too: `"(note) total"` is shown as it is.
|
|
973
|
+
- Bold: `bold: true` in the options of `text( )` writes in Helvetica-Bold. `measureText( str, size, bold )` measures it.
|
|
974
|
+
- New `path( segments, fill, stroke?, lineWidth? )` on the page: a path of `m` (move), `l` (line), `c` (curve) and `h` (close), filled, outlined, or both. `null` for what is not drawn.
|
|
975
|
+
- Colors were wrong, all but black: their components were written from 0 to 255, where PDF reads them from 0 to 1. `#808080` came out white.
|
|
976
|
+
- New props of the builder: `title`, `author`, `subject`. The reader shows the title in place of the name of the file.
|
|
977
|
+
- The document is printed at its size, not fitted to the paper (`PrintScaling /None`). It is a PDF 1.6.
|
|
978
|
+
- New `buildAsync( )`: the same document, its streams compressed (`FlateDecode`, by the `CompressionStream` of the browser). `build( )` does not compress, as before.
|
|
979
|
+
|
|
980
|
+
```ts
|
|
981
|
+
const pdf = new X4PDFBuilder( { title: "Invoice 42" } );
|
|
982
|
+
pdf.page( 595, 842, ctx => {
|
|
983
|
+
ctx.text( "Total : 1 250 €", 50, 800, 14, { bold: true } );
|
|
984
|
+
ctx.path( [ ["m", 50, 700], ["l", 150, 700], ["c", 170, 720, 170, 760, 150, 780], ["h"] ], "#7cc7ff", "#000000" );
|
|
985
|
+
} );
|
|
986
|
+
const bytes = await pdf.buildAsync( );
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
- New `ctx.barcode( type, text, x, y, w, h, opts? )`: a Data Matrix (`"datamatrix"`) or a Code 128 (`"code128"`), drawn in the PDF without any dependency. A Data Matrix is the square of side min( w, h ), (x, y) its bottom left corner; a Code 128 has its bars on w x h. The quiet zone is left to the page: 1 module around a Data Matrix, 10 on the sides of a Code 128. `opts.color`: black when not given.
|
|
990
|
+
- `opts.gs1`: the text is a GS1 element string, its AIs between parentheses (`"(01)03400930000120(17)270101(10)AB12"`). FNC1 starts the code and ends the fields of variable length: a GS1 DataMatrix, a GS1-128.
|
|
991
|
+
- Data Matrix: ECC200, square, from 10 x 10 to 26 x 26: up to 44 characters or 88 digits, characters from 0 to 255. Code 128: ASCII, its sets A, B and C chosen for the shortest code. A text that does not fit, or a character that cannot be encoded, throws an error: a code is never cut.
|
|
992
|
+
- New `renderBarcodeImage( type, text, opts? )`, for the screen: the same codes as an SVG image (`data:image/svg+xml,...`), for the `src` of an `Image` or of an `Icon`. On white, with its quiet zone: it can be read on a dark theme too. `opts.module`: the size of a module in px, 4 when not given; a Code 128 is 50 modules high. It is in `core_pdf` because it shares its encoders; it is the only function of the module that needs the DOM.
|
|
993
|
+
- `PageContext`, the type of the `ctx` given to `page( )`, is exported: a function that draws on a page can name it.
|
|
994
|
+
|
|
995
|
+
```ts
|
|
996
|
+
pdf.page( 595, 842, ctx => {
|
|
997
|
+
ctx.barcode( "datamatrix", "G1234K7", 50, 700, 21, 21 );
|
|
998
|
+
ctx.barcode( "code128", "(01)03400930000120(17)270101", 50, 600, 200, 40, { gs1: true } );
|
|
999
|
+
} );
|
|
1000
|
+
|
|
1001
|
+
new Image( { src: renderBarcodeImage( "datamatrix", "G1234K7" ) } )
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
## 2.3.18 (2026-10-13)
|
|
1005
|
+
|
|
1006
|
+
### Command line
|
|
1007
|
+
|
|
1008
|
+
- `outdir` cannot be a folder that contains the project (`".."`, or a variable that points to a parent): `x4js build` empties the output folder before it builds, so such a folder is refused with an error, as the project root itself is.
|
|
1009
|
+
|
|
1010
|
+
### Clipboard
|
|
1011
|
+
|
|
1012
|
+
- New: the clipboard of the system, in `core_clipboard`: a text, an image, files. It keeps `navigator.clipboard` out of the applications: that it is missing out of https, and that the browser may refuse, is handled there.
|
|
1013
|
+
- `setClipboard( data )` and `getClipboard( )`: from a menu or a button. They are asynchronous and never fail: `setClipboard` gives `false` when the system refused. What the application set last is kept, and it is what `getClipboard` gives when the system refuses or cannot be asked (a page that is not served in https): copy and paste still work inside the application.
|
|
1014
|
+
- `canReadClipboard( )`: `false` when `getClipboard` has nothing to give, to disable a "Paste" item.
|
|
1015
|
+
- `setEventClipboard( ev, data )` and `getEventClipboard( ev )`: in a `copy`, `cut` or `paste` event, where the browser gives its clipboard at once and asks nothing. `getEventClipboard` is the only way to read files.
|
|
1016
|
+
- An image is written as a PNG: another format is converted. Files cannot be written.
|
|
1017
|
+
- The events `copy`, `cut` and `paste` are known to `addDOMEvent` and to `dom_events`, with a `ClipboardEvent`.
|
|
1018
|
+
|
|
1019
|
+
```ts
|
|
1020
|
+
// the keys of the user
|
|
1021
|
+
this.addDOMEvent( "copy", ev => {
|
|
1022
|
+
setEventClipboard( ev, { text: this.getSelectedText( ) } );
|
|
1023
|
+
} );
|
|
1024
|
+
|
|
1025
|
+
this.addDOMEvent( "paste", ev => {
|
|
1026
|
+
const data = getEventClipboard( ev ); // { text, image, files }
|
|
1027
|
+
if( data.image ) {
|
|
1028
|
+
ev.preventDefault( );
|
|
1029
|
+
this.setPicture( data.image );
|
|
1030
|
+
}
|
|
1031
|
+
} );
|
|
1032
|
+
|
|
1033
|
+
// a menu, a button
|
|
1034
|
+
new Button( { label: "Copy", click: ( ) => setClipboard( { text: this.getSelectedText( ) } ) } )
|
|
1035
|
+
|
|
1036
|
+
new Button( { label: "Paste", click: async ( ) => {
|
|
1037
|
+
const data = await getClipboard( );
|
|
1038
|
+
if( data.text ) {
|
|
1039
|
+
this.insert( data.text );
|
|
1040
|
+
}
|
|
1041
|
+
} } )
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
### Gridview, Spreadsheet
|
|
1045
|
+
|
|
1046
|
+
- With a fixed column, the flex columns fill the grid up to its right edge: the width they share is the one of the scrolled part, which the fixed columns are not in.
|
|
1047
|
+
- When a column was resized by its sizer, the body of the grid gets the width of its columns once the flex columns have taken what is left: every column is shown, and there is nothing to scroll beyond the last one.
|
|
1048
|
+
- A double click on the sizer of a column makes it flex: a column that is not (it was given no flex, or its sizer was dragged) takes its share of the place that is left, with a flex of 1. To stop being flex, a column has its sizer dragged. In a `Gridview` the column is not sorted: this is the double click on the rest of its header.
|
|
1049
|
+
|
|
1050
|
+
### Gridview
|
|
1051
|
+
|
|
1052
|
+
- The selection is made of records, not of rows: when the grid is sorted or filtered, the same records stay selected, and `getSelection( )` and `getFirstSel( )` give them. What they return is the same: records, or `null`.
|
|
1053
|
+
- A record that leaves the view leaves the selection, and `selectionChange` is fired: a filter that hides it, a record that is deleted, data that is replaced. The selected records that are still in the view stay selected.
|
|
1054
|
+
- A click in a column header keeps the selection: it sorts or sizes the column. After a sort by a double click on a header, the grid scrolls to what is selected.
|
|
1055
|
+
- New prop `multisel`: several rows can be selected. Ctrl + click (Cmd on a Mac) adds a row or removes it, Shift + click selects from the row that was selected last to the one that is clicked, Ctrl + A selects every record of the view, as the new method `selectAll( )` does. A click alone selects one row. `getSelection( )` gives all the selected records, and `selectionChange` too, in its `selection`. To select all keeps the id of each record: on a very large view it costs memory.
|
|
1056
|
+
|
|
1057
|
+
```ts
|
|
1058
|
+
const grid = new Gridview( {
|
|
1059
|
+
store,
|
|
1060
|
+
columns,
|
|
1061
|
+
multisel: true,
|
|
1062
|
+
selectionChange: ev => remove.enable( !ev.empty ),
|
|
1063
|
+
} );
|
|
1064
|
+
|
|
1065
|
+
const records = grid.getSelection( ); // every selected record, or null
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
- `setStore( )` closes the view of the store it replaces: a store that is not shown anymore does not redraw the grid, and does not keep it alive.
|
|
1069
|
+
- A grid that leaves the document closes its view, and opens it when it comes back: out of the document it is not redrawn by its store, and its store does not keep it alive. Nothing has to be called by the application.
|
|
1070
|
+
- A grid that comes back in the document (the one of a dialog that is opened again) draws its rows, and has its mouse handlers once: `dblClick`, `contextMenu` and the wheel act once for one gesture.
|
|
1071
|
+
- A column whose `id` is not a field of the model (a column of actions) is drawn by its `renderer`: it has nothing else to draw it. A column that is a field still gives its `renderer` the records where the field has a value.
|
|
1072
|
+
- The event `click` is fired by a click on a row, once the row is selected: `ev.context` is its record, as for `dblClick`. A handler given as `click` in the props is called from now on.
|
|
1073
|
+
- One column has the mark of the sort, the one that is sorted, also when the sort comes from `sortCol( )`.
|
|
1074
|
+
- The sort given in the props (`sort`) is applied whenever the grid comes in the document, however long after it was made. The sorted column is kept with the columns, not read in the header: a grid that leaves the document and comes back still shows it.
|
|
1075
|
+
- With 200 rows and more, a notch of the wheel over a fixed column scrolls one row, as it does over the rest of the grid.
|
|
1076
|
+
- The arrow keys do nothing in a grid that has no row.
|
|
1077
|
+
- `getSelection( )` and `getFirstSel( )` only give records that exist: one that was deleted while the grid was out of the document is left out.
|
|
1078
|
+
|
|
1079
|
+
### DataStore
|
|
1080
|
+
|
|
1081
|
+
- A store finds its records by id when the model declares its id after other fields: its index is sorted by the field marked `@data.id( )`, the one `getById`, `update` and `delete` search, and no more by the first field. A view without a sort is in the order of the ids too. `unSerialize( data, id )` puts `id` in that field.
|
|
1082
|
+
|
|
1083
|
+
### DataView
|
|
1084
|
+
|
|
1085
|
+
- New methods `close( )` and `open( )`: the view stops following its store, and follows it again, filtered and sorted with what the store holds at that moment. A store keeps every view that listens to it, so a view that is not used anymore has to be closed, or it is still filtered and sorted at each change of the store. A view is open when it is made.
|
|
1086
|
+
- A sorted view is sorted once when its store changes: records that compare equal keep the order they have after `sort( )`.
|
|
1087
|
+
|
|
1088
|
+
- One `view_change` for one change: `change` when the store changes, `filter` when the view is filtered. The view is filtered and sorted again before it, without an event for each of these steps: a `Gridview` is redrawn once.
|
|
1089
|
+
|
|
1090
|
+
```ts
|
|
1091
|
+
const view = store.createView( { filter } );
|
|
1092
|
+
...
|
|
1093
|
+
view.close( ); // done with it
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
### Spreadsheet
|
|
1097
|
+
|
|
1098
|
+
- The cells of a fixed column follow its sizer while it is dragged, as its header does: they have the `data-col` of their column, by which the sizer finds them.
|
|
1099
|
+
- A spreadsheet that comes back in the document (the one of a dialog that is opened again) draws its rows, and has its mouse handlers once: `dblClick`, `contextMenu` and the wheel act once for one gesture.
|
|
1100
|
+
- With 200 rows and more, a notch of the wheel over a fixed column scrolls one row, as it does over the rest of the grid.
|
|
1101
|
+
- `lock( false )` called once too many leaves the spreadsheet unlocked: it goes on drawing. The `Gridview` does the same.
|
|
1102
|
+
|
|
1103
|
+
### Popup
|
|
1104
|
+
|
|
1105
|
+
- A press outside the popups that close by themselves (`autoClose`) closes the group opened last; the next press closes the one before. A color picker or a context menu opened from a popup closes first, then the popup. One listener on the document serves all the popups, and it is removed when the last one closes, whichever it is.
|
|
1106
|
+
|
|
1107
|
+
### Dialog, MessageBox, InputBox, PromptBox
|
|
1108
|
+
|
|
1109
|
+
- The promise of `showAsync( )` is settled whatever closes the box: with the name of the button that was clicked, or with `null` when the box is closed without a button (Escape, the close box). The code that awaits it goes on.
|
|
1110
|
+
|
|
1111
|
+
### Keyboard
|
|
1112
|
+
|
|
1113
|
+
- New prop `locale` and new method `setLocale( locale )`: the language of the keys of the virtual keyboard, with or without its country (`"fr-FR"`, `"de-CH"`, `"pl"`). `"fr-FR"` when nothing is said.
|
|
1114
|
+
- 22 languages: `cs` `da` `de` `en` `es` `et` `fi` `fr` `ga` `hr` `hu` `it` `lt` `lv` `mt` `nl` `pl` `pt` `ro` `sk` `sl` `sv`. A layout is an AZERTY, a QWERTY or a QWERTZ, plus the letters of the language (`é è à ç`, `ä ö ü ß`, `ą ć ę ł`...) on rows of their own, above the space bar.
|
|
1115
|
+
- The countries whose keyboard is not the one of their language have theirs: `de-CH` (no `ß`), `fr-CH`, `it-CH` and `fr-LU` (QWERTZ), `nl-BE` (AZERTY). Any other country gets the keyboard of its language, and a language that is not in the list a QWERTY.
|
|
1116
|
+
- `en` is a QWERTY.
|
|
1117
|
+
|
|
1118
|
+
```ts
|
|
1119
|
+
const keyboard = new Keyboard( { locale: "de-CH" } );
|
|
1120
|
+
keyboard.setLocale( "pl" );
|
|
1121
|
+
```
|
|
1122
|
+
|
|
1123
|
+
### TextArea
|
|
1124
|
+
|
|
1125
|
+
- A `TextArea` and a `SimpleTextArea` handle all the props of `BaseProps`, as an `Input` does: they are the props they take.
|
|
1126
|
+
- `placeholder` is shown, drawn as the one of an `Input`.
|
|
1127
|
+
- `autofocus` and `required` are set on the field.
|
|
1128
|
+
- The events `change` (at each key that is typed, the text is `ev.value`) and `focus` (`ev.focus_out`) are fired.
|
|
1129
|
+
- New method `isValid( )`: `false` when the text is required and empty. A `Form` reads it: `validate( )` fails on a `TextArea` that is `required` and empty.
|
|
1130
|
+
- `SimpleTextAreaProps`, `TextAreaProps` and `TextAreaEvents` are exported: an application can name them, to type its own props or to derive a class. `TextAreaProps` is `SimpleTextAreaProps` plus `label`: a `SimpleTextArea` has no label.
|
|
1131
|
+
|
|
1132
|
+
```ts
|
|
1133
|
+
new TextArea( { label: "Comment", name: "comment", placeholder: "A few words...", required: true } )
|
|
1134
|
+
new SimpleTextArea( { flex: 1, placeholder: "Your message", change: ev => send.enable( ev.value!=="" ) } )
|
|
1135
|
+
```
|
|
1136
|
+
|
|
1137
|
+
### DOM events
|
|
1138
|
+
|
|
1139
|
+
- A component moved from one place of the document to another in the same task gets no `created`, also when it is moved into a node that is new: it was created before. A `DockingView` puts its panels in new boxes at each change of its layout, so a `Gridview` or a `Spreadsheet` in a panel goes on drawing its rows.
|
|
1140
|
+
- Of several handlers of one event on one node, one that returns `true` stops those that follow it. Only `true` does: an arrow function without braces returns what its expression does (`( ) => panel.show( false )` returns the panel), and this stops nothing. The handler a component puts for itself is then always called, whatever the one given in `dom_events` returns.
|
|
1141
|
+
- An event of `unbubbleEvents` that the browser does not bubble (`focus`, `blur`, `mouseenter`, `mouseleave`...) stops at the node that gets it: the handler of an ancestor is not called for one of its descendants. `focusin` and `focusout` are the ones that go up. Those of the table that the browser bubbles (`contextmenu`, `dragenter`, `dragover`) go up.
|
|
1142
|
+
- `resized` is for the node that changed size only, as `created` and `removed` are for theirs: the handler of an ancestor is not called when one of its descendants changes size. `unbubbleEvents` has `resized`, the name that is sent.
|
|
1143
|
+
|
|
1144
|
+
### Component
|
|
1145
|
+
|
|
1146
|
+
- `content: 0`, in the props of a component, shows `0`: a number is a content as another.
|
|
1147
|
+
- `loadPState`: a change of the state is written 500 ms later, also when the component left the DOM in between. The write has a timer of its own: the timers of a component are cleared when it leaves.
|
|
1148
|
+
- `removeClass` and `toggleClass` skip the empty names that a space too many makes (`" a"`, `"a b"`), as `addClass` does: the browser refuses an empty class name.
|
|
1149
|
+
|
|
1150
|
+
### Drag and drop
|
|
1151
|
+
|
|
1152
|
+
- `dragManager` forgets the dragged component when its drag ends: a drag of files that come from the desktop has no source, and its target must not be given one. The target that was left gets its `leave` at that moment, while the source is known.
|
|
1153
|
+
- `DropInfo`, `DropCallback` and `DropFilterCallback`, the types of what `registerDropTarget` takes, are exported.
|
|
1154
|
+
|
|
1155
|
+
## 2.3.17 (2026-10-12)
|
|
1156
|
+
|
|
1157
|
+
### ColorInput, ColorPicker
|
|
1158
|
+
|
|
1159
|
+
- "No color" (`nullable`) is drawn as a fine checkerboard, the usual sign of what is transparent, in the swatch of the field and in the picker. It was a red line across, drawn on nothing, which could hardly be read on a dark theme.
|
|
1160
|
+
|
|
1161
|
+
### Combobox
|
|
1162
|
+
|
|
1163
|
+
- New prop `renderer`: a function that draws an item of the list, as the one of a `Listbox`. Without it, nothing changes. The field itself still shows the text of the item that is chosen.
|
|
1164
|
+
|
|
1165
|
+
```ts
|
|
1166
|
+
new Combobox( {
|
|
1167
|
+
label: "Country",
|
|
1168
|
+
items: countries,
|
|
1169
|
+
renderer: item => new Label( { icon: flags[item.id], text: item.text } ),
|
|
1170
|
+
} )
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1173
|
+
### Notification
|
|
1174
|
+
|
|
1175
|
+
- A notification follows the theme. Its background was white whatever the theme, and its text took its color from a variable that does not exist (`--color-gray-7`), so it had the color of the text around: on a dark theme, a light text on white. The background is now `--background-primary`, and three variables are new: `--notification-title-color` (`--text-primary`), `--notification-text-color` and `--notification-button-color` (`--text-ternary`). On the default theme the background is still white; the text is the grey it was meant to be.
|
|
1176
|
+
|
|
1177
|
+
### Progress, Gauge, Switch
|
|
1178
|
+
|
|
1179
|
+
- `Progress`: `min` and `max` may be left out, they are then 0 and 100. Without them the bar had no width at all. And the bar now starts at `min`: a value of 50 between 50 and 100 is an empty bar, where it was a full one (the value was divided by the range without `min` being taken off it first).
|
|
1180
|
+
- `Gauge`: without `min` and `max`, the range is 0 to 100. Without them the needle was not drawn where it should.
|
|
1181
|
+
- `Switch`: new prop `name`. With it, a `Form` reads and writes the switch as it does a `Checkbox`.
|
|
1182
|
+
|
|
1183
|
+
```ts
|
|
1184
|
+
new Progress( { value: 30 } ) // 30% of 0..100
|
|
1185
|
+
new Progress( { value: 75, min: 50, max: 100 } ) // half of the bar
|
|
1186
|
+
new Switch( { label: "Notifications", name: "notify", checked: true } )
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
### DockingView
|
|
1190
|
+
|
|
1191
|
+
- Tools can be docked above and under the contents: two more bands, `top` and `bottom`, between those of the left and of the right. They are for what is wide and low (a log, a list of results, a timeline). Their tools are side by side; one put above or under another makes the band higher.
|
|
1192
|
+
- New prop `sides`, on the view and on a panel: the bands a tool may be docked in. The left and the right ones when nothing is said, as before: an application that does not use it behaves as it did. A tool is never offered a place in a band it may not be in, neither beside one of its tools nor as one of their tabs.
|
|
1193
|
+
- `DockLayout` has two optional bands, `top` and `bottom`; a layout that does not use them does not have them, and is saved as it always was. A tool that a saved layout has in a band it may not be in anymore goes where the default layout has it.
|
|
1194
|
+
|
|
1195
|
+
```ts
|
|
1196
|
+
const view = new DockingView( {
|
|
1197
|
+
sides: ["left", "right"], // what tools may do, when they say nothing
|
|
1198
|
+
panels: [
|
|
1199
|
+
{ id: "tree", title: "Objects", content: tree },
|
|
1200
|
+
{ id: "log", title: "Log", content: log, sides: ["bottom"] },
|
|
1201
|
+
{ id: "page", kind: "content", content: editor },
|
|
1202
|
+
],
|
|
1203
|
+
layout: {
|
|
1204
|
+
left: { size: 180, root: { type: "stack", size: 0, panels: ["tree"], active: "tree" } },
|
|
1205
|
+
right: { size: 0, root: null },
|
|
1206
|
+
bottom: { size: 160, root: { type: "stack", size: 0, panels: ["log"], active: "log" } },
|
|
1207
|
+
content: { type: "stack", size: 0, panels: ["page"], active: "page" },
|
|
1208
|
+
floats: [],
|
|
1209
|
+
hidden: [],
|
|
1210
|
+
},
|
|
1211
|
+
} );
|
|
1212
|
+
```
|
|
1213
|
+
|
|
1214
|
+
### Input
|
|
1215
|
+
|
|
1216
|
+
- A number field built without a value no longer throws when it has a `step` under 1 or over 1. `setNumValue` empties the field when it is given no number (undefined, null or NaN), instead of formatting it.
|
|
1217
|
+
|
|
1218
|
+
## 2.3.16 (2026-10-09)
|
|
1219
|
+
|
|
1220
|
+
### DockingView
|
|
1221
|
+
|
|
1222
|
+
- New component: a view whose panels the user arranges by dragging their title.
|
|
1223
|
+
- Two kinds of panels, which never mix. Tools are docked in a band on the left or on the right: one above the other, side by side, or as tabs. Contents (what is worked on, as the editors of an IDE) share the room between the bands: split, or as tabs.
|
|
1224
|
+
- Props `floating` (tools can float above the rest) and `closable` (tools have a button that hides them); `showPanel()` brings a panel back where it was.
|
|
1225
|
+
- The layout is plain data (`DockLayout`): `getState()`, `setState()`, `resetLayout()`, and the event `layoutChange`. With the prop `persist`, it is remembered in the storage of the application, floating panels included.
|
|
1226
|
+
|
|
1227
|
+
```ts
|
|
1228
|
+
const view = new DockingView( {
|
|
1229
|
+
floating: true,
|
|
1230
|
+
closable: true,
|
|
1231
|
+
persist: "myapp.layout",
|
|
1232
|
+
panels: [
|
|
1233
|
+
{ id: "tools", title: "Tools", content: tools },
|
|
1234
|
+
{ id: "page", kind: "content", content: editor },
|
|
1235
|
+
],
|
|
1236
|
+
layout: {
|
|
1237
|
+
left: { size: 180, root: { type: "stack", size: 0, panels: ["tools"], active: "tools" } },
|
|
1238
|
+
right: { size: 0, root: null },
|
|
1239
|
+
content: { type: "stack", size: 0, panels: ["page"], active: "page" },
|
|
1240
|
+
floats: [],
|
|
1241
|
+
hidden: [],
|
|
1242
|
+
},
|
|
1243
|
+
} );
|
|
1244
|
+
|
|
1245
|
+
view.showPanel( "tools", false ); // hidden; view.showPanel( "tools" ) shows it again
|
|
1246
|
+
```
|
|
1247
|
+
|
|
1248
|
+
## 2.3.15 (2026-10-09)
|
|
1249
|
+
|
|
1250
|
+
### Shortcuts
|
|
1251
|
+
|
|
1252
|
+
- New: keyboard shortcuts, in `core_shortcuts`. A sequence is written as it is read: `"Mod+C"`, `"Shift+Mod+Z"`, `"Delete"`. `Mod` is the key of the commands: Ctrl on Windows and Linux, Cmd on a Mac.
|
|
1253
|
+
- `Component.addShortcut( keys, callback, options? )`: a shortcut of a view. It works while the focus is in the component, and while the focus is nowhere if it is the last component with shortcuts that had it.
|
|
1254
|
+
- `Application.addShortcut( keys, callback, options? )`: a shortcut of the whole application.
|
|
1255
|
+
- A key typed in a field (`input`, `textarea`, `select`) is not a shortcut, unless the option `editable` is set. Nor is a key that what has the focus already used.
|
|
1256
|
+
- `shortcutText( keys )` writes a sequence for the user: `"Ctrl+Z"`, `"⌘Z"` on a Mac.
|
|
1257
|
+
|
|
1258
|
+
```ts
|
|
1259
|
+
class MyView extends VBox {
|
|
1260
|
+
constructor( props: BoxProps ) {
|
|
1261
|
+
super( props );
|
|
1262
|
+
|
|
1263
|
+
this.addShortcut( "Mod+C", ( ) => this.copy( ) );
|
|
1264
|
+
this.addShortcut( ["Mod+Y", "Shift+Mod+Z"], ( ) => this.redo( ) );
|
|
1265
|
+
|
|
1266
|
+
this.setContent( new Button( { label: "Undo", tooltip: shortcutText( "Mod+Z" ), click: ( ) => this.undo( ) } ) );
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
Application.instance( ).addShortcut( "Mod+S", ( ) => save( ), { editable: true } );
|
|
1271
|
+
```
|
|
1272
|
+
|
|
1273
|
+
### Popup
|
|
1274
|
+
|
|
1275
|
+
- A popup with `autoClose` (a menu, the picker of a `ColorInput`) now closes on any press outside of it, even when what is pressed stops the propagation of the event. The press is no longer cancelled (`preventDefault`): what is pressed handles it as usual.
|
|
1276
|
+
|
|
1277
|
+
### Core
|
|
1278
|
+
|
|
1279
|
+
- `Component.loadPState()`: a property set on the state it returns was never saved. It wrote the object the state was made from, which the state copies; it now writes the state itself.
|
|
1280
|
+
- `JSON.stringify()` of a state, or of a part of it, no longer logs `state error, unable to find toJSON` for each object and array it goes through. The JSON was already right.
|
|
1281
|
+
- `makeState()`: the example in its comment gave `"items.3"` as the path of an array element; it is `"items[3]"`.
|
|
1282
|
+
|
|
1283
|
+
## 2.3.14 (2026-10-09)
|
|
1284
|
+
|
|
1285
|
+
### ColorInput
|
|
1286
|
+
|
|
1287
|
+
- New `change` event, fired when the user changes the color (typing, eye dropper, picker). It gives the `Color` and its text, `"#rrggbb"`.
|
|
1288
|
+
- New methods: `getColor()`, `getValue()`, `setColor()`, `setSwatches()`.
|
|
1289
|
+
- New props, all optional:
|
|
1290
|
+
- `picker`: a click on the swatch opens a color picker.
|
|
1291
|
+
- `swatches`: colors offered in the picker, as a list or as a function called each time the picker opens.
|
|
1292
|
+
- `nullable`: "no color" can be chosen; the value is then `null`.
|
|
1293
|
+
- `format`: `"rgb"` (default, as before) or `"hex"`, how the field writes the color.
|
|
1294
|
+
- `name`: with it, a `Form` reads and writes the color.
|
|
1295
|
+
- Answers to `queryInterface( "form-element" )`.
|
|
1296
|
+
- The eye dropper button is as high as the field and easier to aim at.
|
|
1297
|
+
- A color that is `null`, `undefined` or `""` shows nothing, instead of red or `rgb(NaN,NaN,NaN)`.
|
|
1298
|
+
|
|
1299
|
+
```ts
|
|
1300
|
+
const input = new ColorInput( {
|
|
1301
|
+
color: "#126e9e",
|
|
1302
|
+
format: "hex",
|
|
1303
|
+
picker: true,
|
|
1304
|
+
nullable: true,
|
|
1305
|
+
swatches: ["#002a40", "#126e9e", "#8ad0f5"],
|
|
1306
|
+
change: ev => console.log( ev.value ), // "#rrggbb", or null for "no color"
|
|
1307
|
+
} );
|
|
1308
|
+
|
|
1309
|
+
input.setColor( "#ff0000" ); // does not fire change
|
|
1310
|
+
```
|
|
1311
|
+
|
|
1312
|
+
### ColorPicker
|
|
1313
|
+
|
|
1314
|
+
- New props `swatches` and `nullable`: colors, and "no color", offered under the sliders.
|
|
1315
|
+
- New methods: `getColor()`, `setColor()`, `setSwatches()`.
|
|
1316
|
+
- The `change` event keeps `color` and gains `value`, the color as `"#rrggbb"` (`"#rrggbbaa"` when it is not opaque).
|
|
1317
|
+
- After the eye dropper, the saturation marker follows the color that was taken.
|
|
1318
|
+
|
|
1319
|
+
```ts
|
|
1320
|
+
const picker = new ColorPicker( { color: "#126e9e", swatches: ["#002a40", "#8ad0f5"] } );
|
|
1321
|
+
|
|
1322
|
+
picker.on( "change", ev => console.log( ev.value, ev.color ) );
|
|
1323
|
+
picker.setColor( "#ff0000" ); // does not fire change
|
|
1324
|
+
```
|
|
1325
|
+
|
|
1326
|
+
### PropertyGrid
|
|
1327
|
+
|
|
1328
|
+
- New type `'color'`: the editor is a `ColorInput` with its picker. `swatches` and `nullable` are passed to it; the callback receives `"#rrggbb"`, or `null` for "no color".
|
|
1329
|
+
- The column of the titles is as wide as the longest title, and at most half of the grid. It was always half of it.
|
|
1330
|
+
- A row has no box of its own anymore (`display: contents`): its background and its bottom line are carried by its cells. A stylesheet that styled `.row` has to style `.row > .cell`.
|
|
1331
|
+
- New CSS variables, with the values that were hard coded: `--propertygrid-hover-background`, `--propertygrid-focus-background`, `--propertygrid-input-background`, `--propertygrid-group-color`.
|
|
1332
|
+
|
|
1333
|
+
```ts
|
|
1334
|
+
new PropertyGrid( {
|
|
1335
|
+
groups: [ {
|
|
1336
|
+
title: "Text",
|
|
1337
|
+
items: [
|
|
1338
|
+
{ type: "string", name: "text", title: "Text", value: "Hello" },
|
|
1339
|
+
{
|
|
1340
|
+
type: "color", name: "back", title: "Background", value: null,
|
|
1341
|
+
nullable: true,
|
|
1342
|
+
swatches: ( ) => ["#002a40", "#126e9e"],
|
|
1343
|
+
callback: ( name, value ) => console.log( name, value ),
|
|
1344
|
+
},
|
|
1345
|
+
],
|
|
1346
|
+
} ],
|
|
1347
|
+
} );
|
|
1348
|
+
```
|
|
1349
|
+
|
|
1350
|
+
### Core
|
|
1351
|
+
|
|
1352
|
+
- `style` (props) and `setStyle()` accept numbers, as `setStyleValue()` did: `px` is added unless the property has no unit. New type `StyleProps`.
|
|
1353
|
+
- `Rect.moveTo()` moved the rectangle to `( x, x )`; fixed.
|
|
1354
|
+
|
|
1355
|
+
```ts
|
|
1356
|
+
box.setStyle( { left: 10, top: 20 } );
|
|
1357
|
+
```
|