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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/aicontext.md +73 -6
  4. package/package.json +2 -1
  5. package/src/components/autocomplete/autocomplete.ts +6 -2
  6. package/src/components/boxes/boxes.ts +2 -2
  7. package/src/components/button/button.module.scss +4 -3
  8. package/src/components/calendar/calendar.ts +1 -1
  9. package/src/components/chart/chart.ts +1 -1
  10. package/src/components/checkbox/checkbox.module.scss +67 -1
  11. package/src/components/checkbox/checkbox.ts +22 -6
  12. package/src/components/colorinput/colorinput.module.scss +1 -0
  13. package/src/components/combobox/combobox.module.scss +4 -1
  14. package/src/components/components.ts +1 -0
  15. package/src/components/dockingview/dockingview.ts +1 -1
  16. package/src/components/gridview/gridview.module.scss +6 -0
  17. package/src/components/input/input.module.scss +1 -1
  18. package/src/components/listbox/listbox.ts +31 -45
  19. package/src/components/messages/messages.module.scss +9 -4
  20. package/src/components/monaco/monaco.ts +1 -2
  21. package/src/components/popup/popup.ts +2 -2
  22. package/src/components/propgrid/progrid.module.scss +13 -5
  23. package/src/components/radio/radio.module.scss +54 -31
  24. package/src/components/radio/radio.ts +12 -1
  25. package/src/components/rating/rating.ts +1 -1
  26. package/src/components/sizers/sizer.ts +17 -5
  27. package/src/components/spreadsheet/spreadsheet.module.scss +6 -0
  28. package/src/components/tabs/tabs.module.scss +2 -0
  29. package/src/components/tabs/tabs.ts +1 -0
  30. package/src/components/textarea/textarea.module.scss +9 -0
  31. package/src/components/textarea/textarea.ts +1 -0
  32. package/src/components/themes.scss +1 -0
  33. package/src/components/timeline/timeline.module.scss +147 -0
  34. package/src/components/timeline/timeline.ts +146 -0
  35. package/src/components/treeview/treeview.module.scss +6 -1
  36. package/src/components/treeview/treeview.ts +17 -22
  37. package/src/components/video/video.module.scss +5 -2
  38. package/src/components/video/video.ts +14 -1
  39. package/src/core/component.ts +31 -2
  40. package/src/core/core_pdf.ts +17 -0
  41. package/src/core/core_react.ts +12 -18
  42. package/src/core/core_shortcuts.ts +2 -1
  43. 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: string; checked?: boolean; value?: boolean|number|string; name?: string
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 // horizontal splitter
1310
- class VSizer extends CSizer // vertical splitter
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
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "x4js",
3
- "version": "2.3.26",
3
+ "version": "2.3.28",
4
+ "license": "MIT",
4
5
  "type": "module",
5
6
  "main": "src/x4.ts",
6
7
  "module": "src/x4.ts",
@@ -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.dom.contains( to ) ) {
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 next = prev.hasClass( "x4flex" ) && !p.hasClass( "x4flex" );
127
- res.push( horz ? new HSizer( next ) : new VSizer( next ) );
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-icon-color );
170
- fill: var( --button-icon-color );
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]) {
@@ -358,7 +358,7 @@ export class PopupCalendar extends Popup {
358
358
  let newfocus = <HTMLElement>e.target;
359
359
 
360
360
  // child of this: ok
361
- if (this.dom.contains(newfocus)) {
361
+ if (this.contains(newfocus)) {
362
362
  return;
363
363
  }
364
364
 
@@ -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.dom.contains( el ) ) {
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
- &.left #label {
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: string; // The text label for the checkbox.
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
- labelFor: inputId,
87
- id: undefined
100
+ text: props.label,
101
+ icon: props.icon,
102
+ labelFor: inputId,
103
+ id: undefined
88
104
  } ),
89
105
  ])
90
106
 
@@ -71,6 +71,7 @@
71
71
  margin: 0;
72
72
  height: 100%;
73
73
  flex-grow: 1;
74
+ min-width: 0; // narrower than its default size in a narrow box: it does not overflow
74
75
  }
75
76
 
76
77
  // the eye dropper: as high as the field, and wide enough to be aimed at
@@ -72,7 +72,10 @@
72
72
  &> #label {
73
73
 
74
74
  &> .x4label {
75
- padding: 0 6px 0 0;
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.dom.contains( focused ) ) {
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-background );
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, componentFromDOM, ComponentProps, EvChange, EvClick, EvContextMenu, EvDblClick, EvSelectionChange } from '../../core/component';
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
- let target = ev.target as HTMLElement;
287
- let on_check = false;
286
+ const target = ev.target as Element;
288
287
 
289
- while( target && target!=this.dom ) {
290
- const c = componentFromDOM( target );
288
+ // avoid trapping child click
289
+ if( Component.closest( target, "input" ) ) {
290
+ return;
291
+ }
291
292
 
292
- // avoid trapping child click
293
- if( c && c.dom.tagName==='INPUT' ) {
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
- if( c && c.hasClass("x4check") ) {
298
- on_check = true;
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( c && c.hasClass("x4item") ) {
302
- // the case changes alone; a double click on it is ignored
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
- target = target.parentElement;
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
- let target = ev.target as HTMLElement;
348
- while( target && target!=this.dom ) {
349
- const c = componentFromDOM( target );
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
- target = target.parentElement;
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
- .x4icon {
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
- .x4icon {
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
- .x4icon {
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);