x4js 2.3.26 → 2.3.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +5 -0
- package/aicontext.md +73 -6
- package/package.json +2 -1
- package/src/components/autocomplete/autocomplete.ts +6 -2
- package/src/components/boxes/boxes.ts +2 -2
- package/src/components/button/button.module.scss +4 -3
- package/src/components/calendar/calendar.ts +1 -1
- package/src/components/chart/chart.ts +1 -1
- package/src/components/checkbox/checkbox.module.scss +67 -1
- package/src/components/checkbox/checkbox.ts +22 -6
- package/src/components/colorinput/colorinput.module.scss +1 -0
- package/src/components/combobox/combobox.module.scss +4 -1
- package/src/components/components.ts +1 -0
- package/src/components/dockingview/dockingview.ts +1 -1
- package/src/components/gridview/gridview.module.scss +6 -0
- package/src/components/input/input.module.scss +1 -1
- package/src/components/listbox/listbox.ts +31 -45
- package/src/components/messages/messages.module.scss +9 -4
- package/src/components/monaco/monaco.ts +1 -2
- package/src/components/popup/popup.ts +2 -2
- package/src/components/propgrid/progrid.module.scss +13 -5
- package/src/components/radio/radio.module.scss +54 -31
- package/src/components/radio/radio.ts +12 -1
- package/src/components/rating/rating.ts +1 -1
- package/src/components/sizers/sizer.ts +17 -5
- package/src/components/spreadsheet/spreadsheet.module.scss +6 -0
- package/src/components/tabs/tabs.module.scss +2 -0
- package/src/components/tabs/tabs.ts +1 -0
- package/src/components/textarea/textarea.module.scss +9 -0
- package/src/components/textarea/textarea.ts +1 -0
- package/src/components/themes.scss +1 -0
- package/src/components/timeline/timeline.module.scss +147 -0
- package/src/components/timeline/timeline.ts +146 -0
- package/src/components/treeview/treeview.module.scss +6 -1
- package/src/components/treeview/treeview.ts +17 -22
- package/src/components/video/video.module.scss +5 -2
- package/src/components/video/video.ts +14 -1
- package/src/core/component.ts +31 -2
- package/src/core/core_pdf.ts +17 -0
- package/src/core/core_react.ts +12 -18
- package/src/core/core_shortcuts.ts +2 -1
- package/src/core/core_svg.ts +9 -6
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 R-libre ingenierie
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
<picture>
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/rlibre/x4/main/logox4-v3-dark.svg">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/rlibre/x4/main/logox4-v3.svg" alt="x4 logo" width="96">
|
|
4
|
+
</picture>
|
|
5
|
+
|
|
1
6
|
# x4js
|
|
2
7
|
**A TypeScript framework for people who build applications, not markup.**
|
|
3
8
|
|
package/aicontext.md
CHANGED
|
@@ -36,7 +36,7 @@ CoreElement<E> named timers + event surface (no DOM)
|
|
|
36
36
|
│ │ ├── BtnGroup, Tabs, Video, Saturation
|
|
37
37
|
│ ├── Button, Input, Icon, Label, SimpleText, Checkbox, Radio, Link, SimpleTextArea
|
|
38
38
|
│ ├── Listbox, Gridview, Treeview, Select, Combobox, Slider, Gauge
|
|
39
|
-
│ ├── Progress, TickLine, Canvas, Image, Spreadsheet, MonacoEditor
|
|
39
|
+
│ ├── Progress, TickLine, Timeline, Canvas, Image, Spreadsheet, MonacoEditor
|
|
40
40
|
│ ├── SvgComponent → Chart
|
|
41
41
|
│ ├── CSizer → HSizer / VSizer
|
|
42
42
|
│ ├── FileDialog, ScrollView, Viewport, DockingView, Flex, Space
|
|
@@ -227,12 +227,16 @@ queryAll(selector: string): Component[]
|
|
|
227
227
|
firstChild<T>(): T; lastChild<T>(): T
|
|
228
228
|
nextElement<T>(): T; prevElement<T>(): T
|
|
229
229
|
parentElement<T>(cls?: Constructor<T>): T
|
|
230
|
+
closest<T>(selector: string): T // this one or an ancestor whose element matches the css selector; null if none
|
|
231
|
+
contains(other: Component | Node): boolean // other (a node, or the element of a component) is the element of this one or inside it
|
|
230
232
|
childCount(): number
|
|
231
233
|
enumChildComponents(recursive: boolean): Component[]
|
|
232
234
|
enumChildNodes(recursive: boolean): Node[]
|
|
233
235
|
visitChildren(cb: (el: Component) => boolean): void
|
|
234
236
|
static parentElement<T>(dom: Node, cls?: Constructor<T>): T // starts at the parent of dom, not at dom:
|
|
235
237
|
// test componentFromDOM(dom) first for the component of dom itself
|
|
238
|
+
static closest<T>(dom: Element, selector: string): T // starts at dom itself: in a click handler,
|
|
239
|
+
// Component.closest(ev.target as Element, '.item')
|
|
236
240
|
```
|
|
237
241
|
|
|
238
242
|
### Visibility / state / focus / geometry
|
|
@@ -614,6 +618,41 @@ class Rating extends HBox {
|
|
|
614
618
|
}
|
|
615
619
|
```
|
|
616
620
|
|
|
621
|
+
### `Timeline` — events on a line, each with its icon in a round mark
|
|
622
|
+
|
|
623
|
+
```ts
|
|
624
|
+
interface TimelineItem {
|
|
625
|
+
icon?: string // in the round mark
|
|
626
|
+
title: string | UnsafeHtml // in bold
|
|
627
|
+
text?: string | UnsafeHtml // the sentence under it, in grey
|
|
628
|
+
date?: Date // written relative to now, in the locale: "il y a 2 heures", "yesterday"; none: no time shown
|
|
629
|
+
done?: boolean // the mark in the accent; else a grey mark
|
|
630
|
+
}
|
|
631
|
+
interface TimelineProps extends ComponentProps {
|
|
632
|
+
items?: TimelineItem[]
|
|
633
|
+
vertical?: boolean // true by default; false: the events side by side
|
|
634
|
+
renderer?: (item: TimelineItem) => Component // what is beside the mark (the mark and the line stay)
|
|
635
|
+
click?: EventCallback<EvClick> // a click anywhere in an event: ev.context is its TimelineItem
|
|
636
|
+
}
|
|
637
|
+
interface TimelineEvents extends ComponentEvents { click: EvClick }
|
|
638
|
+
class Timeline extends Component<TimelineProps, TimelineEvents> {
|
|
639
|
+
setItems(items: TimelineItem[]): void // the relative times are written here, not updated after
|
|
640
|
+
}
|
|
641
|
+
// the line to the next mark: in the accent when the next event is done, dashed after an event not done
|
|
642
|
+
// CSS vars: --timeline-done-color --timeline-done-icon-color --timeline-todo-color --timeline-todo-icon-color
|
|
643
|
+
// --timeline-title-color --timeline-text-color --timeline-time-color
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
```ts
|
|
647
|
+
new Timeline({
|
|
648
|
+
items: [
|
|
649
|
+
{ icon: branch, title: 'New branch', text: 'fix-notifications, from master', date: created, done: true },
|
|
650
|
+
{ icon: review, title: 'Code review', text: 'Waiting for a reviewer' },
|
|
651
|
+
],
|
|
652
|
+
click: ev => this.openStep(ev.context as TimelineItem),
|
|
653
|
+
})
|
|
654
|
+
```
|
|
655
|
+
|
|
617
656
|
---
|
|
618
657
|
|
|
619
658
|
## Input components
|
|
@@ -701,9 +740,14 @@ new SimpleTextArea({ flex: 1, placeholder: 'Your message', resize: false })
|
|
|
701
740
|
|
|
702
741
|
```ts
|
|
703
742
|
interface CheckboxProps extends ComponentProps {
|
|
704
|
-
label
|
|
743
|
+
label?: string; icon?: string // no label: the icon alone (give it a tooltip)
|
|
744
|
+
checked?: boolean; value?: boolean|number|string; name?: string
|
|
705
745
|
change?: EventCallback<EvChange>
|
|
706
746
|
}
|
|
747
|
+
// cls: "left" (label on the left), "button" ("button outline" rounded), "danger" (checked in red, its box or its
|
|
748
|
+
// button: "button danger"); without the box, the input is still reached by Tab (--checkbox-button-*, -danger-*)
|
|
749
|
+
// A toggle button is a Checkbox with cls "button": getCheck() tells whether it is pressed, change when it changes
|
|
750
|
+
// new Checkbox( { cls: "button", icon: icon_bold, tooltip: "Bold", change: ( ev ) => this.setBold( ev.value ) } )
|
|
707
751
|
// in a Form: without value (or a boolean one), a boolean; with one, the value when checked, null when not
|
|
708
752
|
class Checkbox extends Component {
|
|
709
753
|
getCheck(): boolean; setCheck(ck: boolean): void
|
|
@@ -727,6 +771,8 @@ class Radio extends Component {
|
|
|
727
771
|
setLabel(text: string): void
|
|
728
772
|
checkValue(vname: string): void // check the radio whose value === vname, in the group of its form (else of the page)
|
|
729
773
|
}
|
|
774
|
+
// cls: "button" (a button; "button outline" rounded), "danger" (checked in red, its round or its button: "button
|
|
775
|
+
// danger"). Without the round, the input is still reached by Tab.
|
|
730
776
|
```
|
|
731
777
|
|
|
732
778
|
### `Switch`
|
|
@@ -806,6 +852,8 @@ class AutoComplete extends TextEdit {
|
|
|
806
852
|
getSelection(): ListboxID // id of the picked item, undefined for a free text
|
|
807
853
|
setSelection(item: ListItem): void; showDropDown(): void
|
|
808
854
|
}
|
|
855
|
+
// Keys: ↓ (or ↑), the list closed, opens it: the suggestions found last, or else a search of the text as it is
|
|
856
|
+
// (even empty: minChars is for typing). The list open: ↑ ↓ choose, Enter picks, Escape closes.
|
|
809
857
|
// Use Combobox for a fixed, known list; AutoComplete when suggestions come from a search (server).
|
|
810
858
|
// In a Form the value is the field content (text or id, per itemValue).
|
|
811
859
|
```
|
|
@@ -1082,6 +1130,8 @@ class Button extends Component {
|
|
|
1082
1130
|
setText(text: string|UnsafeHtml): void
|
|
1083
1131
|
setIcon(icon: string): void
|
|
1084
1132
|
}
|
|
1133
|
+
// A toggle button (pressed / not pressed) is not a Button: it is a Checkbox with cls "button" (see Checkbox),
|
|
1134
|
+
// and one choice among several pressed buttons, Radios with cls "button".
|
|
1085
1135
|
```
|
|
1086
1136
|
|
|
1087
1137
|
### `Tabs`
|
|
@@ -1306,20 +1356,27 @@ class VSplitBox extends VBox // panels stacked, resizable height:
|
|
|
1306
1356
|
|
|
1307
1357
|
// Low level: only place sizers by hand when a SplitBox does not fit (popup edges, header cells...).
|
|
1308
1358
|
class CSizer extends Component // base splitter; events start, resize { size, width, height }, stop
|
|
1309
|
-
class HSizer extends CSizer
|
|
1310
|
-
|
|
1359
|
+
class HSizer extends CSizer { // horizontal splitter, between two children of a HBox
|
|
1360
|
+
constructor(side: "before" | "after" | boolean = "after") // the child it resizes; a boolean (older code): true after, false before
|
|
1361
|
+
}
|
|
1362
|
+
class VSizer extends CSizer { // vertical splitter, between two children of a VBox; same constructor
|
|
1363
|
+
constructor(side: "before" | "after" | boolean = "after")
|
|
1364
|
+
}
|
|
1365
|
+
// new HBox( { content: [ list, new HSizer( "before" ), new VBox( { flex: 1 } ) ] } ) // the list is resized
|
|
1311
1366
|
// A pointercancel (a touch taken by the browser) ends the resize as a pointerup does: stop is fired.
|
|
1312
1367
|
// The columns of a Gridview or a Spreadsheet are 16px wide at least (the min-width of a cell).
|
|
1313
1368
|
class ScrollView extends Component { getViewport(): Component }
|
|
1314
1369
|
class Viewport extends Component
|
|
1315
1370
|
class Canvas extends Component { getContext(): CanvasRenderingContext2D; paint(): void }
|
|
1316
|
-
class Video extends Box { // class x4video
|
|
1371
|
+
class Video extends Box { // class x4video; props: src? (a file, url), stream? (wins over src), autoplay?, muted?, playsInline?
|
|
1317
1372
|
play(): Promise<boolean> // false: could not start (a click of the user wanted, a source not playable); no source: waits for one
|
|
1318
1373
|
pause(); mute(set: boolean)
|
|
1319
1374
|
get player(): HTMLVideoElement; get duration(): number; get paused(): boolean
|
|
1320
1375
|
get volume(): number; get videoWidth(): number; get videoHeight(): number
|
|
1376
|
+
get src(): string; set src(url: string)
|
|
1321
1377
|
set stream(stream: MediaStream)
|
|
1322
1378
|
}
|
|
1379
|
+
// The image fills the box of the Video, fitted (object-fit: contain): the box keeps its size, whatever the size of the stream.
|
|
1323
1380
|
class Calendar extends VBox { // props: date?, minDate?, maxDate? (days out of them cannot be chosen), change ("change": the day clicked)
|
|
1324
1381
|
getDate(): Date; setDate(date: Date): void // copies: the calendar keeps its own date
|
|
1325
1382
|
}
|
|
@@ -1453,6 +1510,7 @@ component.addShortcut(keys, callback, options?) // a view: while the foc
|
|
|
1453
1510
|
Application.instance().addShortcut(keys, callback, options?) // the whole application, wherever the focus is
|
|
1454
1511
|
|
|
1455
1512
|
shortcutText(keys: string): string // for a tooltip or a menu: "Ctrl+Z", "⌘Z" on a Mac
|
|
1513
|
+
isMac: boolean // true on a Mac, an iPhone or an iPad: Mod is Cmd there
|
|
1456
1514
|
|
|
1457
1515
|
class Shortcuts { // the list itself; rarely used directly
|
|
1458
1516
|
add(keys: string | string[], callback: (ev: KeyboardEvent) => void, options?: ShortcutOptions): void
|
|
@@ -1741,6 +1799,8 @@ class X4PDFBuilder {
|
|
|
1741
1799
|
page(width: number, height: number, callback: (ctx: PageContext) => void): void // points; without size 612 x 792
|
|
1742
1800
|
build(): Uint8Array
|
|
1743
1801
|
buildAsync(): Promise<Uint8Array> // streams compressed (FlateDecode)
|
|
1802
|
+
buildBlob(): Blob // the file, application/pdf (build)
|
|
1803
|
+
buildURL(): string // an object url of it: iframe src, link, window.open; URL.revokeObjectURL frees it
|
|
1744
1804
|
}
|
|
1745
1805
|
export interface PageContext { // origin bottom left, y goes up
|
|
1746
1806
|
text(str, x, y, size, opts?: { align?: 'left'|'center'|'right'; width?; color?; bold? }): void
|
|
@@ -1799,6 +1859,13 @@ createPainter(c2d: CanvasRenderingContext2D, w: number, h: number): CanvasEx /
|
|
|
1799
1859
|
initTooltips(): void // initialize the global tooltip system
|
|
1800
1860
|
```
|
|
1801
1861
|
|
|
1862
|
+
JSX, in `.tsx` files: in tsconfig.json `"jsx": "react"` and `"jsxFactory": "x4_react.create_element"` (esbuild reads
|
|
1863
|
+
them), and `"files": [ "node_modules/x4js/src/x4tsx.d.ts" ]` for the JSX types (`ref`); each `.tsx` file imports `x4_react`.
|
|
1864
|
+
`<X a={1}>{kids}</X>` is `new X( { a: 1, content: kids } )`; a tag in lower case is an html element (its attributes set,
|
|
1865
|
+
an attribute `on...` an event); arrays in braces are flattened, `null`, `undefined` and `false` give nothing (`setContent`
|
|
1866
|
+
does not flatten nested arrays: build such a content in JSX); `ref={[owner, "name"]}` keeps the element in
|
|
1867
|
+
`owner.refs.name`, the owner a Box.
|
|
1868
|
+
|
|
1802
1869
|
No tooltip is shown until the application calls `initTooltips()` once (the prop `tooltip` only sets an attribute).
|
|
1803
1870
|
`initTooltips()` shows the text of the attribute `tooltip` (the prop `tooltip`), or the one `getTip()`
|
|
1804
1871
|
of a `"tip-handler"` gives, **as html**. A text that comes from a user is escaped by the application
|
|
@@ -2039,6 +2106,6 @@ Optional keys: `external`, `define`, `esbuild`, `dev` (`host`, `port`, `https`,
|
|
|
2039
2106
|
- `Gridview` is backed by a `DataStore`; `Spreadsheet` is backed by a `Store` (different, cell-based).
|
|
2040
2107
|
- `DataRecordID` is currently typed `any`; `strictNullChecks` is intentionally off in the recommended `tsconfig.json`.
|
|
2041
2108
|
- `MonacoEditor` requires `MonacoEditor.start()` (async) before use; register extra typings with `MonacoEditor.addTypelib(name, code)`.
|
|
2042
|
-
- `x4_react.create_element` is the JSX pragma
|
|
2109
|
+
- `x4_react.create_element` is the JSX pragma (the setup: JSX factory & helpers).
|
|
2043
2110
|
- A DOM handler of an ancestor is called for the events of its descendants, as in the browser. Not for the events that do not bubble: `focus`, `blur`, `mouseenter`, `mouseleave` (use `focusin` / `focusout`, `mouseover` / `mouseout`), nor for those of x4: `created`, `removed`, `resized`.
|
|
2044
2111
|
- Several handlers of one event on one node are called in the order they were added. One that returns `true` stops those that follow it.
|
package/package.json
CHANGED
|
@@ -63,7 +63,7 @@ export interface AutoCompleteProps extends Omit<TextInputProps,"type"|"change">,
|
|
|
63
63
|
/** delay between the last key and the search, in ms (default 250) */
|
|
64
64
|
delay?: number;
|
|
65
65
|
|
|
66
|
-
/** number of chars needed to start a search (default 1) */
|
|
66
|
+
/** number of chars typed needed to start a search (default 1); ↓ searches whatever the length */
|
|
67
67
|
minChars?: number;
|
|
68
68
|
|
|
69
69
|
/** custom renderer for the suggestions */
|
|
@@ -84,6 +84,10 @@ const render_id_item = ( item: ListItem ) => {
|
|
|
84
84
|
/**
|
|
85
85
|
* Text field with suggestions asked on demand (search event).
|
|
86
86
|
*
|
|
87
|
+
* Keys: ↓ (or ↑), the list closed, opens it: the suggestions found last, or else a search of
|
|
88
|
+
* the text as it is, whatever its length (minChars is for typing). The list open: ↑ ↓ choose,
|
|
89
|
+
* Enter picks, Escape closes.
|
|
90
|
+
*
|
|
87
91
|
* @example
|
|
88
92
|
* new AutoComplete( {
|
|
89
93
|
* label: "Customer",
|
|
@@ -268,7 +272,7 @@ export class AutoComplete extends TextEdit<AutoCompleteEvents> {
|
|
|
268
272
|
private _on_focusout( ev: FocusEvent ) {
|
|
269
273
|
// focus is moving inside the control
|
|
270
274
|
const to = ev.relatedTarget as Node;
|
|
271
|
-
if( to && this.
|
|
275
|
+
if( to && this.contains( to ) ) {
|
|
272
276
|
return;
|
|
273
277
|
}
|
|
274
278
|
|
|
@@ -123,8 +123,8 @@ function split_content( panels: Component[], horz: boolean ) {
|
|
|
123
123
|
els.forEach( ( p, i ) => {
|
|
124
124
|
if( i>0 ) {
|
|
125
125
|
const prev = els[i-1];
|
|
126
|
-
const
|
|
127
|
-
res.push( horz ? new HSizer(
|
|
126
|
+
const side = prev.hasClass( "x4flex" ) && !p.hasClass( "x4flex" ) ? "after" : "before";
|
|
127
|
+
res.push( horz ? new HSizer( side ) : new VSizer( side ) );
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
res.push( p );
|
|
@@ -165,11 +165,12 @@
|
|
|
165
165
|
color: var( --ol-button-color-active );
|
|
166
166
|
fill: var( --ol-button-color-active );
|
|
167
167
|
|
|
168
|
+
// in the colour of the text: the background stays light, not the accent of a filled button
|
|
168
169
|
#icon {
|
|
169
|
-
color: var( --button-
|
|
170
|
-
fill: var( --button-
|
|
170
|
+
color: var( --ol-button-color-active );
|
|
171
|
+
fill: var( --ol-button-color-active );
|
|
171
172
|
}
|
|
172
|
-
}
|
|
173
|
+
}
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
.x4button.danger:not([disabled]) {
|
|
@@ -264,7 +264,7 @@ export class Chart extends SvgComponent<ChartProps,ChartEvents> {
|
|
|
264
264
|
|
|
265
265
|
this.addDOMEvent( "click", ( e ) => {
|
|
266
266
|
const el = ( e.target as Element ).closest( "[data-index]" );
|
|
267
|
-
if( el && this.
|
|
267
|
+
if( el && this.contains( el ) ) {
|
|
268
268
|
const serie = parseInt( el.getAttribute( "data-serie" ) );
|
|
269
269
|
const index = parseInt( el.getAttribute( "data-index" ) );
|
|
270
270
|
this.fire( "click", { serie, index, value: this._series[serie]?.values[index] } );
|
|
@@ -25,6 +25,16 @@
|
|
|
25
25
|
--checkbox-check-border: var( --border );
|
|
26
26
|
--checkbox-check-border-hover: var( --border-hover );
|
|
27
27
|
--checkbox-check-color: var( --accent-color );
|
|
28
|
+
--checkbox-focus: var( --accent-background );
|
|
29
|
+
|
|
30
|
+
--checkbox-button-border: var( --border );
|
|
31
|
+
--checkbox-button-color: var( --text-ternary );
|
|
32
|
+
--checkbox-button-color-checked: var( --accent-background-active );
|
|
33
|
+
--checkbox-button-background-checked: var( --background-secondary );
|
|
34
|
+
--checkbox-button-border-checked: var( --accent-background );
|
|
35
|
+
|
|
36
|
+
--checkbox-danger-color: var( --alert-background );
|
|
37
|
+
--checkbox-danger-background: var( --alert-background-light );
|
|
28
38
|
}
|
|
29
39
|
|
|
30
40
|
.x4checkbox {
|
|
@@ -136,8 +146,64 @@
|
|
|
136
146
|
order: 2;
|
|
137
147
|
}
|
|
138
148
|
|
|
139
|
-
|
|
149
|
+
// its label has no id: the element, before the box
|
|
150
|
+
&.left label {
|
|
140
151
|
order: 0;
|
|
141
152
|
text-align: left;
|
|
142
153
|
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// button: a button, its background and its border marked when checked (no bold: its width
|
|
157
|
+
// would change with its state). The box hidden, its input still in the page (not display: none),
|
|
158
|
+
// so that Tab and the space bar reach it; the focus of the keyboard shown around it
|
|
159
|
+
.x4checkbox.button {
|
|
160
|
+
// the colour of its icon and its text
|
|
161
|
+
--checkbox-ink: var( --checkbox-button-color );
|
|
162
|
+
|
|
163
|
+
gap: 0;
|
|
164
|
+
margin: 0;
|
|
165
|
+
padding: 8px;
|
|
166
|
+
border: 1px solid var( --checkbox-button-border );
|
|
167
|
+
|
|
168
|
+
&.outline {
|
|
169
|
+
border-radius: 22px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.inner {
|
|
173
|
+
width: 0;
|
|
174
|
+
height: 0;
|
|
175
|
+
overflow: hidden;
|
|
176
|
+
opacity: 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
&:has(input:focus-visible) {
|
|
180
|
+
outline: 1px dotted var( --checkbox-focus );
|
|
181
|
+
outline-offset: 2px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
label {
|
|
185
|
+
--label-color: var( --checkbox-ink );
|
|
186
|
+
|
|
187
|
+
#icon {
|
|
188
|
+
color: var( --checkbox-ink );
|
|
189
|
+
fill: var( --checkbox-ink );
|
|
190
|
+
width: 24px;
|
|
191
|
+
height: 24px;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
&:has(input:checked) {
|
|
196
|
+
--checkbox-ink: var( --checkbox-button-color-checked );
|
|
197
|
+
background-color: var( --checkbox-button-background-checked );
|
|
198
|
+
border-color: var( --checkbox-button-border-checked );
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// danger: checked in the red of the alerts, its box or its button
|
|
203
|
+
.x4checkbox.danger {
|
|
204
|
+
--checkbox-background-check: var( --checkbox-danger-color );
|
|
205
|
+
--checkbox-background-check-hover: var( --checkbox-danger-color );
|
|
206
|
+
--checkbox-button-color-checked: var( --checkbox-danger-color );
|
|
207
|
+
--checkbox-button-background-checked: var( --checkbox-danger-background );
|
|
208
|
+
--checkbox-button-border-checked: var( --checkbox-danger-color );
|
|
143
209
|
}
|
|
@@ -21,7 +21,8 @@ interface CheckBoxEvents extends ComponentEvents {
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
interface CheckboxProps extends ComponentProps {
|
|
24
|
-
label
|
|
24
|
+
label?: string; // The text label for the checkbox; none: its icon alone (with a tooltip)
|
|
25
|
+
icon?: string; // an icon before its label
|
|
25
26
|
checked?: boolean; // Optional boolean indicating if the checkbox is checked by default.
|
|
26
27
|
value?: boolean | number | string; // Optional value associated with the checkbox.
|
|
27
28
|
name?: string;
|
|
@@ -30,7 +31,13 @@ interface CheckboxProps extends ComponentProps {
|
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Checkbox component that can be checked or unchecked.
|
|
33
|
-
*
|
|
34
|
+
* Its look, by its class (cls): "left" (its label on the left), "button" (a button, "button
|
|
35
|
+
* outline" rounded), "danger" (checked in red, its box or its button). Without the box, its
|
|
36
|
+
* input is still reached by Tab.
|
|
37
|
+
*
|
|
38
|
+
* A toggle button is a Checkbox with the class "button":
|
|
39
|
+
* new Checkbox( { cls: "button", icon: icon_bold, tooltip: "Bold", change: ( ev ) => setBold( ev.value ) } )
|
|
40
|
+
*
|
|
34
41
|
* @cssvar
|
|
35
42
|
* ```
|
|
36
43
|
* --checkbox-background-check
|
|
@@ -41,6 +48,14 @@ interface CheckboxProps extends ComponentProps {
|
|
|
41
48
|
* --checkbox-check-border
|
|
42
49
|
* --checkbox-check-border-hover
|
|
43
50
|
* --checkbox-check-color
|
|
51
|
+
* --checkbox-focus
|
|
52
|
+
* --checkbox-button-border
|
|
53
|
+
* --checkbox-button-color
|
|
54
|
+
* --checkbox-button-color-checked
|
|
55
|
+
* --checkbox-button-background-checked
|
|
56
|
+
* --checkbox-button-border-checked
|
|
57
|
+
* --checkbox-danger-color
|
|
58
|
+
* --checkbox-danger-background
|
|
44
59
|
* ```
|
|
45
60
|
*/
|
|
46
61
|
|
|
@@ -80,11 +95,12 @@ export class Checkbox extends Component<CheckboxProps,CheckBoxEvents> {
|
|
|
80
95
|
})
|
|
81
96
|
]
|
|
82
97
|
}),
|
|
83
|
-
new Label( {
|
|
98
|
+
new Label( {
|
|
84
99
|
tag: 'label',
|
|
85
|
-
text: props.label,
|
|
86
|
-
|
|
87
|
-
|
|
100
|
+
text: props.label,
|
|
101
|
+
icon: props.icon,
|
|
102
|
+
labelFor: inputId,
|
|
103
|
+
id: undefined
|
|
88
104
|
} ),
|
|
89
105
|
])
|
|
90
106
|
|
|
@@ -72,7 +72,10 @@
|
|
|
72
72
|
&> #label {
|
|
73
73
|
|
|
74
74
|
&> .x4label {
|
|
75
|
-
|
|
75
|
+
// its text on the right of its width (labelWidth), its colon against the gap of the box:
|
|
76
|
+
// in a form, under the one of a TextEdit
|
|
77
|
+
justify-content: end;
|
|
78
|
+
padding: 0;
|
|
76
79
|
border-bottom: 1px solid transparent;
|
|
77
80
|
|
|
78
81
|
height: 100%;
|
|
@@ -45,6 +45,7 @@ export * from "./tag/tag"
|
|
|
45
45
|
export * from "./textarea/textarea"
|
|
46
46
|
export * from "./textedit/textedit"
|
|
47
47
|
export * from "./tickline/tickline"
|
|
48
|
+
export * from "./timeline/timeline"
|
|
48
49
|
export * from "./tooltips/tooltips"
|
|
49
50
|
export * from "./treeview/treeview"
|
|
50
51
|
export * from "./video/video"
|
|
@@ -404,7 +404,7 @@ export class DockingView extends Component<DockingViewProps, DockingViewEvents>
|
|
|
404
404
|
this.fills.forEach( fill => fill( ) );
|
|
405
405
|
this.placeFloats( );
|
|
406
406
|
|
|
407
|
-
if( focused instanceof HTMLElement && this.
|
|
407
|
+
if( focused instanceof HTMLElement && this.contains( focused ) ) {
|
|
408
408
|
focused.focus( );
|
|
409
409
|
}
|
|
410
410
|
}
|
|
@@ -73,6 +73,11 @@
|
|
|
73
73
|
|
|
74
74
|
background-color: var( --gridview-background );
|
|
75
75
|
border: 1px solid var( --gridview-border );
|
|
76
|
+
|
|
77
|
+
// disabled: faded as a Box (a Gridview is not one)
|
|
78
|
+
&[disabled] {
|
|
79
|
+
opacity: var( --box-disabled-opacity );
|
|
80
|
+
}
|
|
76
81
|
color: var( --gridview-cell-color );
|
|
77
82
|
|
|
78
83
|
.cell {
|
|
@@ -159,6 +164,7 @@
|
|
|
159
164
|
}
|
|
160
165
|
|
|
161
166
|
.percent {
|
|
167
|
+
align-self: center; // a Box stretches (top of the cell): centered in the row
|
|
162
168
|
width: 100%;
|
|
163
169
|
border: 1px solid var( --border );
|
|
164
170
|
height: 8px;
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
--input-sel-background: var( --accent-background );
|
|
22
22
|
--input-sel-color: var( --accent-color );
|
|
23
|
-
--input-placeholder: var( --disabled-
|
|
23
|
+
--input-placeholder: var( --disabled-color-dark ); // a text colour: seen on the background of the field, in both themes
|
|
24
24
|
--input-error: var( --alert-background );
|
|
25
25
|
|
|
26
26
|
--input-color-disabled: var( --disabled-color-dark );
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
|
|
15
15
|
**/
|
|
16
16
|
|
|
17
|
-
import { Component, ComponentEvent, ComponentEvents,
|
|
17
|
+
import { Component, ComponentEvent, ComponentEvents, ComponentProps, EvChange, EvClick, EvContextMenu, EvDblClick, EvSelectionChange } from '../../core/component';
|
|
18
18
|
import { EventCallback } from '../../core/core_events';
|
|
19
19
|
import { kbNav, isArray, UnsafeHtml, asap } from '../../core/core_tools';
|
|
20
20
|
|
|
@@ -283,47 +283,38 @@ private _view: Viewport;
|
|
|
283
283
|
*/
|
|
284
284
|
|
|
285
285
|
private _on_click( ev: MouseEvent ) {
|
|
286
|
-
|
|
287
|
-
let on_check = false;
|
|
286
|
+
const target = ev.target as Element;
|
|
288
287
|
|
|
289
|
-
|
|
290
|
-
|
|
288
|
+
// avoid trapping child click
|
|
289
|
+
if( Component.closest( target, "input" ) ) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
291
292
|
|
|
292
|
-
|
|
293
|
-
|
|
293
|
+
// an item of this list, not of a list around it
|
|
294
|
+
const c = Component.closest( target, ".x4item" );
|
|
295
|
+
if( c && this.contains( c ) ) {
|
|
296
|
+
// the case changes alone; a double click on it is ignored
|
|
297
|
+
if( Component.closest( target, ".x4check" ) ) {
|
|
298
|
+
if( ev.type == 'click' ) {
|
|
299
|
+
this._toggleCheck( c );
|
|
300
|
+
}
|
|
294
301
|
return;
|
|
295
302
|
}
|
|
296
303
|
|
|
297
|
-
|
|
298
|
-
|
|
304
|
+
const id = c.getInternalData( "id" );
|
|
305
|
+
const fev: EvClick = { context:id };
|
|
306
|
+
if (ev.type == 'click') {
|
|
307
|
+
this.fire('click', fev );
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
this.fire('dblClick', fev );
|
|
299
311
|
}
|
|
300
312
|
|
|
301
|
-
if(
|
|
302
|
-
|
|
303
|
-
if( on_check ) {
|
|
304
|
-
if( ev.type == 'click' ) {
|
|
305
|
-
this._toggleCheck( c );
|
|
306
|
-
}
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const id = c.getInternalData( "id" );
|
|
311
|
-
const fev: EvClick = { context:id };
|
|
312
|
-
if (ev.type == 'click') {
|
|
313
|
-
this.fire('click', fev );
|
|
314
|
-
}
|
|
315
|
-
else {
|
|
316
|
-
this.fire('dblClick', fev );
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (!fev.defaultPrevented) {
|
|
320
|
-
this._selectItem( id, c, ev.ctrlKey ? 'toggle' : 'single', ev.type == 'click' && this.props.reselect );
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
return;
|
|
313
|
+
if (!fev.defaultPrevented) {
|
|
314
|
+
this._selectItem( id, c, ev.ctrlKey ? 'toggle' : 'single', ev.type == 'click' && this.props.reselect );
|
|
324
315
|
}
|
|
325
316
|
|
|
326
|
-
|
|
317
|
+
return;
|
|
327
318
|
}
|
|
328
319
|
|
|
329
320
|
if (ev.type == 'click') {
|
|
@@ -344,19 +335,14 @@ private _view: Viewport;
|
|
|
344
335
|
|
|
345
336
|
ev.preventDefault();
|
|
346
337
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const
|
|
350
|
-
if( c && c.hasClass("x4item") ) {
|
|
351
|
-
const id = c.getInternalData( "id" );
|
|
352
|
-
|
|
353
|
-
this._selectItem(id, c, 'single' );
|
|
354
|
-
this.fire('contextMenu', {uievent: ev, context: id } );
|
|
355
|
-
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
338
|
+
const c = Component.closest( ev.target as Element, ".x4item" );
|
|
339
|
+
if( c && this.contains( c ) ) {
|
|
340
|
+
const id = c.getInternalData( "id" );
|
|
358
341
|
|
|
359
|
-
|
|
342
|
+
this._selectItem(id, c, 'single' );
|
|
343
|
+
this.fire('contextMenu', {uievent: ev, context: id } );
|
|
344
|
+
|
|
345
|
+
return;
|
|
360
346
|
}
|
|
361
347
|
|
|
362
348
|
this.fire('contextMenu', { uievent:ev, context: null } );
|
|
@@ -46,7 +46,9 @@
|
|
|
46
46
|
|
|
47
47
|
& .x4form {
|
|
48
48
|
min-width: 400px;
|
|
49
|
-
|
|
49
|
+
|
|
50
|
+
// the icon of the box only, not those of its message
|
|
51
|
+
> .x4hbox > .x4icon {
|
|
50
52
|
color: var( --msgbox-icon-color );
|
|
51
53
|
height: 96px;
|
|
52
54
|
}
|
|
@@ -57,7 +59,7 @@
|
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
&.question .x4form .x4icon {
|
|
62
|
+
&.question .x4form > .x4hbox > .x4icon {
|
|
61
63
|
color: var( --msgbox-question-icon-color );
|
|
62
64
|
}
|
|
63
65
|
}
|
|
@@ -68,7 +70,9 @@
|
|
|
68
70
|
|
|
69
71
|
& .x4form {
|
|
70
72
|
min-width: 400px;
|
|
71
|
-
|
|
73
|
+
|
|
74
|
+
// the icon of the box only, not those of its editor (the stars of a Rating...)
|
|
75
|
+
> .x4hbox > .x4icon {
|
|
72
76
|
color: var( --msgbox-question-icon-color );
|
|
73
77
|
height: 64px;
|
|
74
78
|
}
|
|
@@ -141,7 +145,8 @@
|
|
|
141
145
|
}
|
|
142
146
|
}
|
|
143
147
|
|
|
144
|
-
|
|
148
|
+
// the spinner of the box only, not the icons of the lines of its log
|
|
149
|
+
> .x4hbox > .x4icon {
|
|
145
150
|
height: 64px;
|
|
146
151
|
animation: rotating 3s linear infinite;
|
|
147
152
|
fill: var(--accent-background);
|