x4js 2.3.16 → 2.3.18

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/aicontext.md CHANGED
@@ -34,13 +34,13 @@ CoreElement<E> named timers + event surface (no DOM)
34
34
  │ │ ├── Popup → Dialog → MessageBox / InputBox / PromptBox / ProgressionBox
35
35
  │ │ │ → Menu, DropdownList, Notification
36
36
  │ │ ├── BtnGroup, Tabs, Video, Saturation
37
- │ ├── Button, Input, Icon, Label, SimpleText, Checkbox, Radio, Link
37
+ │ ├── Button, Input, Icon, Label, SimpleText, Checkbox, Radio, Link, SimpleTextArea
38
38
  │ ├── Listbox, Gridview, Treeview, Select, Combobox, Slider, Gauge
39
39
  │ ├── Progress, TickLine, Canvas, Image, Spreadsheet, MonacoEditor
40
40
  │ ├── SvgComponent → Chart
41
41
  │ ├── CSizer → HSizer / VSizer
42
42
  │ ├── FileDialog, ScrollView, Viewport, DockingView, Flex, Space
43
- │ ├── HBox-based: ColorInput, Rating, Switch, Keyboard, TextEdit → AutoComplete, Breadcrumbs
43
+ │ ├── HBox-based: ColorInput, Rating, Switch, Tag, Keyboard, TextEdit → AutoComplete, Breadcrumbs
44
44
  │ └── VBox-based: Calendar, ColorPicker, FileDrop, Panel, PropertyGrid, TextArea
45
45
  └── Application<E> singleton, no DOM of its own
46
46
 
@@ -228,7 +228,7 @@ static parentElement<T>(dom: Node, cls?: Constructor<T>): T
228
228
  ```ts
229
229
  show(vis?: boolean): this // toggles "x4hidden" class (not display:none)
230
230
  hide(): this
231
- enable(ena?: boolean): this // sets "disabled" + propagates to child <input>/<button>
231
+ enable(ena?: boolean): this // sets or removes "disabled" and "inert" on the component
232
232
  disable(): this
233
233
  isDisabled(): string | null
234
234
  isVisible(): boolean
@@ -265,7 +265,7 @@ interface EvDblClick extends ComponentEvent {}
265
265
  interface EvBtnClick extends CoreEvent { button: string }
266
266
 
267
267
  // Spacers
268
- class Flex extends Component // CSS "x4flex"
268
+ class Flex extends Component // new Flex(), no argument; CSS "x4flex"
269
269
  class Space extends Component // new Space(width?: number|string, cls?: string)
270
270
  ```
271
271
 
@@ -403,12 +403,14 @@ interface DockPanel {
403
403
  kind?: 'tool' | 'content' // default 'tool'
404
404
  closable?: boolean // for this panel, instead of the view's
405
405
  floating?: boolean // idem; a content never floats
406
+ sides?: DockSide[] // a tool: the bands it may be docked in, instead of the view's
406
407
  }
407
408
  interface DockingViewProps extends ComponentProps {
408
409
  panels: DockPanel[]
409
410
  layout: DockLayout // where the panels are at first
410
411
  floating?: boolean // tools can float above the rest
411
412
  closable?: boolean // tools have a button that hides them
413
+ sides?: DockSide[] // the bands tools may be docked in; left and right when missing
412
414
  persist?: string // a name: the layout is remembered under it (application storage)
413
415
  layoutChange?: EventCallback<ComponentEvent>
414
416
  }
@@ -421,12 +423,14 @@ class DockingView extends Component {
421
423
  }
422
424
 
423
425
  interface DockLayout {
424
- left: DockBand; right: DockBand // the tools
426
+ left: DockBand; right: DockBand // the tools, on the sides
427
+ top?: DockBand; bottom?: DockBand // the tools above and under the contents, between left and right
425
428
  content: DockNode // the contents; null when there is none
426
429
  floats: DockFloat[] // floating tools: { panel, x, y, w, h }
427
430
  hidden: string[] // ids of the closed panels
428
431
  }
429
- interface DockBand { size: number; root: DockNode } // size: width in px; root: null when empty
432
+ interface DockBand { size: number; root: DockNode } // size: width in px (height for top/bottom); root: null when empty
433
+ type DockSide = 'left' | 'right' | 'top' | 'bottom'
430
434
  type DockNode = DockStack | DockSplit
431
435
  interface DockStack { type: 'stack'; size: number; panels: string[]; active: string } // one tab per panel
432
436
  interface DockSplit { type: 'split'; dir: 'row' | 'column'; size: number; children: DockNode[] }
@@ -455,7 +459,8 @@ const view = new DockingView({
455
459
  ```
456
460
 
457
461
  - A closed panel is hidden, not removed: `showPanel(id)` brings it back where it was. Offer it in a menu.
458
- - Tools only go in `left`, `right` or `floats`; contents only in `content`. A panel missing from the layout is added where its kind goes.
462
+ - Tools only go in the bands or in `floats`; contents only in `content`. A panel missing from the layout is added where its kind goes.
463
+ - A tool goes in `left` or `right` only, unless `sides` says otherwise (on the view, or on one panel): `sides: ['bottom']` for what is wide and low (a log, a list of results). It is never offered a place in a band it may not be in.
459
464
  - A content alone has no title bar; with several contents, each stack has tabs.
460
465
  - What a panel shows is built once (`content`) and moved as the layout changes: never rebuild it.
461
466
  - CSS variables: `--dock-line`, `--dock-accent`, `--dock-title-background`, `--dock-title-background-hover`, `--dock-title-color`, `--dock-title-color-active`, `--dock-float-background`, `--dock-float-shadow`.
@@ -532,17 +537,35 @@ class Image extends Component {
532
537
  }
533
538
  ```
534
539
 
540
+ ### `Tag` — a short text in a rounded frame, with an icon
541
+
542
+ ```ts
543
+ interface TagProps extends ComponentProps {
544
+ label?: string | UnsafeHtml // the text: `label`, not `text` as in Label
545
+ icon?: string
546
+ }
547
+ class Tag extends HBox {} // no method: its text and its icon are those of its props
548
+ // cls "alert": the colors of an alert
549
+ // CSS vars: --tag-background --tag-color --tag-icon-color --tag-border
550
+ ```
551
+
552
+ ```ts
553
+ new Tag({ label: 'Draft' })
554
+ new Tag({ label: 'Late', icon: 'assets/clock.svg', cls: 'alert' })
555
+ ```
556
+
535
557
  ### `Progress`
536
558
 
537
559
  ```ts
538
- interface ProgressProps extends ComponentProps { /* min/max/value */ }
560
+ interface ProgressProps extends ComponentProps { value: number; min?: number /* 0 */; max?: number /* 100 */ }
561
+ // the bar is where value is between min (0%) and max (100%)
539
562
  class Progress extends Component { setValue(value: number): void }
540
563
  ```
541
564
 
542
565
  ### `Gauge`
543
566
 
544
567
  ```ts
545
- interface GaugeProps extends ComponentProps { min?: number; max?: number; colors?: string[] }
568
+ interface GaugeProps extends ComponentProps { min?: number /* 0 */; max?: number /* 100 */; colors?: string[] }
546
569
  class Gauge extends Component {
547
570
  setRange(min: number, max: number): void
548
571
  setPos(pos: number): void
@@ -613,12 +636,38 @@ class TextEdit extends HBox {
613
636
  }
614
637
  ```
615
638
 
616
- ### `TextArea`
639
+ ### `TextArea`, `SimpleTextArea` — native `<textarea>`, with or without a label
640
+
641
+ ```ts
642
+ interface SimpleTextAreaProps extends BaseProps { // BaseProps: see Input (name, readonly, required, placeholder, autofocus, focus, change)
643
+ value?: string
644
+ resize?: boolean | "none" | "vertical" | "horizontal" | "both" // false = "none"
645
+ trim?: boolean // getText() trims, unless false
646
+ }
647
+ interface TextAreaProps extends SimpleTextAreaProps { label?: string }
648
+
649
+ class SimpleTextArea extends Component { // the <textarea> alone
650
+ getText(): string; setText(text: string): void
651
+ scrollToBottom(): void
652
+ isValid(): boolean // false when it is required and empty
653
+ }
654
+ class TextArea extends VBox { // a Label above a SimpleTextArea
655
+ getText(): string; setText(text: string): void
656
+ scrollToBottom(): void
657
+ isValid(): boolean
658
+ }
659
+ ```
617
660
 
618
661
  ```ts
619
- class TextArea extends VBox { getText(): string; setText(text: string): void }
662
+ new TextArea({ label: 'Comment', name: 'comment', placeholder: 'A few words...', resize: 'vertical' }) // in a form
663
+ new SimpleTextArea({ flex: 1, placeholder: 'Your message', resize: false }) // no label
620
664
  ```
621
665
 
666
+ - `TextArea` in a form; `SimpleTextArea` where a label has no place (a message to type, a log).
667
+ - Both are form elements: with a `name`, `Form.getValues()` and `setValues()` read and write them.
668
+ - The value is read and written with `getText()` / `setText()`, not `getValue()` / `setValue()`.
669
+ - `change` is fired at each key that is typed, with the text as `ev.value`; `focus` when the focus comes and goes (`ev.focus_out`). As for an `Input`.
670
+ - `required`: an empty text is not valid. `Form.validate()` then returns `null`, or tells its validator that the form is not valid.
622
671
  ### `Checkbox`
623
672
 
624
673
  ```ts
@@ -636,7 +685,13 @@ class Checkbox extends Component {
636
685
  ### `Radio`
637
686
 
638
687
  ```ts
639
- interface RadioProps extends ComponentProps { /* label, value, name, group */ }
688
+ interface RadioProps extends ComponentProps {
689
+ label: string; icon?: string
690
+ name: string // required: the radios that have the same name are one group
691
+ value: number | string // required
692
+ checked?: boolean
693
+ change?: EventCallback<EvChange>
694
+ }
640
695
  class Radio extends Component {
641
696
  getValue(): string; setCheck(ck: boolean): void
642
697
  setLabel(text: string): void
@@ -647,14 +702,19 @@ class Radio extends Component {
647
702
  ### `Switch`
648
703
 
649
704
  ```ts
650
- interface SwitchProps extends ComponentProps { /* label, checked, name */ }
705
+ interface SwitchProps extends ComponentProps { label: string; checked?: boolean; name?: string }
706
+ // with a name, a Form reads and writes it as a boolean
651
707
  class Switch extends HBox {}
652
708
  ```
653
709
 
654
710
  ### `Slider`
655
711
 
656
712
  ```ts
657
- interface SliderProps extends ComponentProps { /* min, max, value, step */ }
713
+ interface SliderProps extends ComponentProps {
714
+ value: number; min: number; max: number // required
715
+ step?: number; vertical?: boolean
716
+ change?: EventCallback<EvChange>
717
+ }
658
718
  class Slider extends Component {
659
719
  setMin(min: number): void; setMax(max: number): void; setValue(v: number): void
660
720
  }
@@ -665,17 +725,29 @@ class Slider extends Component {
665
725
  ```ts
666
726
  interface SelectProps extends ComponentProps {
667
727
  items: ListItem[] // required
668
- value: string // required
669
- multiple?: boolean; name?: string
728
+ value?: ListboxID // the id of the item that is selected
729
+ multiple?: boolean; required?: boolean; name?: string
670
730
  change?: EventCallback<EvChange>; focus?: EventCallback<EvFocus>
671
731
  }
672
- class Select extends Component { getValue(): string; setValue(value: string): void; setItems(items: ListItem[]): void }
732
+ class Select extends Component {
733
+ getValue(): ListboxID; setValue(value: ListboxID): void
734
+ getSelValues(): ListboxID[]; setSelValues(values: ListboxID[]): void // multiple
735
+ setItems(items: ListItem[]): void
736
+ }
673
737
  ```
674
738
 
675
739
  ### `Combobox` — input + dropdown list
676
740
 
677
741
  ```ts
678
- interface ComboboxProps extends ComponentProps { /* items, value, renderer... */ }
742
+ interface ComboboxProps extends ComponentProps {
743
+ items: ListItem[] // required
744
+ value?: ListboxID // the id of the item that is selected
745
+ label?: string; labelWidth?: number | string
746
+ readonly?: boolean // true by default: the user picks, and cannot type. false to let him type
747
+ renderer?: (item: ListItem) => Component // draws an item of the list (the field still shows item.text)
748
+ required?: boolean; name?: string
749
+ selectionChange?: EventCallback<EvSelectionChange>
750
+ }
679
751
  class Combobox extends Component {
680
752
  getValue(): string; setValue(value: string): void
681
753
  getInput(): Input
@@ -750,10 +822,13 @@ interface ListboxProps extends Omit<ComponentProps,"content"> {
750
822
  title?: string; icon?: string
751
823
  header?: Header; footer?: Component
752
824
  checkable?: true; multisel?: true
825
+ emptyMsg?: string // shown in place of the items while there are none
753
826
  dblClick?: EventCallback<EvDblClick>
754
827
  selectionChange?: EventCallback<EvSelectionChange>
755
828
  contextMenu?: EventCallback<EvContextMenu>
756
829
  }
830
+ // emptyMsg: without it the text is _tr.global.empty_list ("Empty list"). It is shown by setItems
831
+ // (the constructor calls it) when there is no item. The listbox then has the class "empty".
757
832
  class Listbox extends Component {
758
833
  setItems(items: ListItem[], keepSel?: boolean): void
759
834
  appendItem(item: ListItem, prepend?: boolean, select?: boolean): void
@@ -767,7 +842,6 @@ class Listbox extends Component {
767
842
  ensureSelectionVisible(): void
768
843
  renderItem(item: ListItem): Component // override to customize
769
844
  defaultRenderer(item: ListItem): Component
770
- preventFocus: boolean
771
845
  }
772
846
  ```
773
847
 
@@ -778,38 +852,51 @@ interface GridviewProps extends ComponentProps {
778
852
  store: DataStore // required
779
853
  columns: GridColumn[] // required
780
854
  footer?: boolean; emptyMsg?: string
781
- sort?: object
782
- click?: EventCallback<EvClick>
783
- dblClick?: EventCallback<EvDblClick>
855
+ multisel?: boolean // Ctrl+click adds or removes a row, Shift+click a range, Ctrl+A all
856
+ sort?: { field_id: string; asc?: boolean } // the first sort; ascending only when asc is true
857
+ click?: EventCallback<EvClick> // a click on a row; ev.context is its record
858
+ dblClick?: EventCallback<EvDblClick> // ev.context is the record of the row
784
859
  contextMenu?: EventCallback<EvContextMenu>
785
860
  selectionChange?: EventCallback<EvSelectionChange>
786
861
  }
787
862
  class Gridview extends Component {
788
863
  setStore(store: DataStore): void
789
- setColumns(columns: GridColumn[]): void
790
864
  setColTitle(col_name: string, title: string): void
791
- sortCol(colIdx: number, ascending: boolean): void
865
+ sortCol(colId: any, ascending: boolean): void // colId: the id of the column, not its index
792
866
  getView(): DataView
793
- getSelection(): DataRecord[]
794
- getFirstSel(): DataRecord
867
+ getSelection(): DataRecord[] // null when nothing is selected
868
+ getFirstSel(): DataRecord // null when nothing is selected
795
869
  selectItem(id, ensureVisible?: boolean): void
796
870
  clearSelection(): void
871
+ selectAll(): void // multisel only; keeps the id of every record: costs memory on a very large view
797
872
  navigate(sens: kbNav): boolean
798
873
  lock(lock: boolean): void
799
874
  }
875
+ // Selection: the selected records, not their rows. It survives a sort; a record that leaves the view (filter, delete) leaves it, with a selectionChange.
876
+ // getSelection(): every selected record, or null. selectionChange: ev.selection has them all.
877
+ // Column sizer: dragging it gives the column that width (it is not flex anymore); a double click on it makes the column flex again.
800
878
  ```
801
879
 
802
880
  ### `Treeview`
803
881
 
804
882
  ```ts
805
883
  interface TreeItem extends ListItem { children?: TreeItem[]; open?: boolean }
806
- interface TreeviewProps extends ComponentProps { /* items, renderer... */ }
884
+ interface TreeviewProps extends Omit<ComponentProps,"content"> {
885
+ items: TreeItem[] // required
886
+ footer?: Component
887
+ emptyMsg?: string // shown in place of the items while there are none
888
+ click?: EventCallback<EvClick>
889
+ dblClick?: EventCallback<EvDblClick>
890
+ selectionChange?: EventCallback<EvSelectionChange>
891
+ }
892
+ // emptyMsg: as for a Listbox. Without it the text is _tr.global.empty_list ("Empty list").
893
+ // There is no `renderer`: an item is drawn from its `text`, `iconId`, `cls` and `sub_cols`.
807
894
  class Treeview extends Component {
808
895
  setItems(items: TreeItem[]): void
809
- getSelection(): ListboxID
810
- selectItem(id): void
896
+ getSelection(): ListboxID // one item, not an array
897
+ selectItem(id: ListboxID): void
811
898
  clearSelection(): void
812
- navigate(sens: kbNav): boolean // see kbTreeNav enum
899
+ navigate(sens: kbTreeNav): boolean // first, prev, next, last, parent, child, expand, collapse, toggle
813
900
  }
814
901
  ```
815
902
 
@@ -837,6 +924,7 @@ class Spreadsheet extends Component {
837
924
  stopEdit(commit = true): void; isEditing(): boolean
838
925
  }
839
926
 
927
+ // Column sizer: dragging it gives the column that width (it is not flex anymore); a double click on it makes the column flex again.
840
928
  // Inline edition: F2 / Enter / double-click / typing starts, Enter / Tab / click elsewhere validates, Escape cancels,
841
929
  // Delete clears, editable checkboxes toggle on double-click or Space (a single click only selects).
842
930
  // Default editor is an Input matching the column type.
@@ -1025,7 +1113,14 @@ class ProgressionBox extends Dialog {
1025
1113
  ### `Notification`
1026
1114
 
1027
1115
  ```ts
1028
- class Notification extends Popup { display(time_in_s?: number): void }
1116
+ interface NotificationProps extends ComponentProps {
1117
+ title: string; text: string | UnsafeHtml
1118
+ mode?: "success" | "danger"
1119
+ loading?: boolean // a turning icon
1120
+ iconId?: string
1121
+ closable?: boolean // a button that closes it
1122
+ }
1123
+ class Notification extends Popup { display(time_in_s?: number): void } // 0: until it is closed
1029
1124
  ```
1030
1125
 
1031
1126
  ---
@@ -1075,7 +1170,13 @@ class ColorPicker extends VBox { // props: color, swatches?, nullable?
1075
1170
  }
1076
1171
  class Panel extends VBox { setTitle(title: string): void }
1077
1172
  class Breadcrumbs extends HBox { setItems(elements): void }
1078
- class Keyboard extends HBox // virtual keyboard
1173
+ interface KeyboardProps extends BoxProps { locale?: string } // "fr-FR" (default), "de-CH", "pl"...
1174
+ class Keyboard extends HBox { // virtual keyboard, shown while an <input> has the focus
1175
+ setLocale(locale: string): void
1176
+ setZoom(perc: number): void
1177
+ showOn(el: Component): void
1178
+ }
1179
+ // languages: cs da de en es et fi fr ga hr hu it lt lv mt nl pl pt ro sk sl sv
1079
1180
  class TickLine extends Component
1080
1181
  class Header extends HBox // resizable column header; updates target style "--{name}-width"
1081
1182
  ```
@@ -1148,6 +1249,8 @@ class DataView extends CoreElement {
1148
1249
  getRecId(rec): any; indexOfId(id): number
1149
1250
  getStore(): DataStore; getModel(): DataModel
1150
1251
  forEach(cb): void; changed(): void
1252
+ close(): void // stops following the store; call it on a view you made (createView) and do not use anymore
1253
+ open(): void // follows the store again after a close(), filtered and sorted with its data of the moment
1151
1254
  }
1152
1255
  ```
1153
1256
 
@@ -1215,6 +1318,80 @@ class MyView extends VBox {
1215
1318
 
1216
1319
  ---
1217
1320
 
1321
+ ## Clipboard (`core_clipboard`)
1322
+
1323
+ The clipboard of the system: a text, an image, files. Never call `navigator.clipboard` or `ev.clipboardData`.
1324
+
1325
+ ```ts
1326
+ interface ClipboardContent { text?: string; image?: Blob }
1327
+ interface ClipboardRead extends ClipboardContent { files: File[] } // files: only in an event
1328
+
1329
+ // from a menu or a button: asynchronous, never fail
1330
+ setClipboard(data: ClipboardContent): Promise<boolean> // false: the system refused
1331
+ getClipboard(): Promise<ClipboardRead>
1332
+ canReadClipboard(): boolean // false: nothing to paste, disable the item
1333
+
1334
+ // in a "copy", "cut" or "paste" event
1335
+ setEventClipboard(ev: ClipboardEvent, data: ClipboardContent): void // prevents the default
1336
+ getEventClipboard(ev: ClipboardEvent): ClipboardRead // synchronous, default left as it is
1337
+ ```
1338
+
1339
+ ```ts
1340
+ this.addDOMEvent('copy', ev => setEventClipboard(ev, { text: this.getSelectedText() }))
1341
+
1342
+ this.addDOMEvent('paste', ev => {
1343
+ const data = getEventClipboard(ev)
1344
+ if (data.image) {
1345
+ ev.preventDefault()
1346
+ this.setPicture(data.image)
1347
+ }
1348
+ })
1349
+
1350
+ new Button({ label: 'Paste', click: async () => {
1351
+ const data = await getClipboard()
1352
+ if (data.text) this.insert(data.text)
1353
+ } })
1354
+ ```
1355
+
1356
+ - The keys of the user (Ctrl+C, Ctrl+V) are the `copy`, `cut` and `paste` events, not shortcuts: the browser then gives its clipboard without asking for a permission.
1357
+ - The events go to the component that has the focus: give it a `tabindex`.
1358
+ - Out of an event, the browser may refuse, and there is no system clipboard on a page that is not served in https. What the application set last is then what `getClipboard` gives: copy and paste still work inside the application.
1359
+ - An image is written as a PNG; another format is converted. Files can be read in a `paste` event only, and cannot be written.
1360
+
1361
+ ---
1362
+
1363
+ ## Drag and drop (`core_dragdrop`)
1364
+
1365
+ `dragManager` makes a component something the user drags, and another one a place where things are dropped: components of the application, or files that come from the desktop.
1366
+
1367
+ ```ts
1368
+ interface DropInfo { pt: Point; data: DataTransfer }
1369
+ type DropCallback = (command: 'enter' | 'leave' | 'drag' | 'drop', el: Component, infos: DropInfo) => void
1370
+ type DropFilterCallback = (el: Component, data: DataTransfer) => boolean // false: nothing can be dropped here
1371
+
1372
+ dragManager.registerDraggableElement(el: Component): void
1373
+ dragManager.registerDropTarget(el: Component, cb: DropCallback, filterCB?: DropFilterCallback): void
1374
+ ```
1375
+
1376
+ ```ts
1377
+ dragManager.registerDraggableElement(card)
1378
+
1379
+ dragManager.registerDropTarget(column, (cmd, el, infos) => {
1380
+ if (cmd == 'drop') {
1381
+ if (infos.data.files.length) this.upload(infos.data.files) // files from the desktop
1382
+ else this.moveCard(el, infos.pt) // el: the component that was dragged
1383
+ }
1384
+ })
1385
+ ```
1386
+
1387
+ - `registerDraggableElement`: while it is dragged, the component has the class `dragging`, and a copy of it with the class `dragged`, added to the body, follows the pointer. Nothing else is done: no data of the application goes with the drag, the target gets the component itself.
1388
+ - The callback of a target: `enter` and `leave` when the pointer comes over it and goes away, `drag` again and again while it is over it, `drop`. Only `drag` and `drop` have `infos`; `pt` is in the coordinates of the page.
1389
+ - `enter` and `leave` are found by a timer: they may come up to 300 ms late, and a `leave` also follows a `drop`. Between them the target has the class `drop-over`.
1390
+ - `el` in the callbacks is the dragged component, not the target. There is none for files that come from the desktop: read `infos.data.files`.
1391
+ - `Image` (`candrop`) and `FileDrop` already use it for files.
1392
+
1393
+ ---
1394
+
1218
1395
  ## i18n (`core_i18n`)
1219
1396
 
1220
1397
  Two built-in languages (`fr` default, and `en`).
@@ -1310,8 +1487,9 @@ interface ITabHandler { focusNext(next: boolean): boolean }
1310
1487
  interface ITipHandler { getTip(): string }
1311
1488
 
1312
1489
  // Other variables
1313
- const dragManager; const svgLoader; const linearColorStops; const simpleColorStop
1314
- const unbubbleEvents // set of DOM event names that do NOT bubble through the x4 tree
1490
+ const dragManager // see Drag and drop
1491
+ const svgLoader; const linearColorStops; const simpleColorStop
1492
+ const unbubbleEvents // the events that stay on their node: see "Important stupid notes"
1315
1493
  const makeUniqueComponentId // () => string, "x4-<n>" (exported as a value)
1316
1494
  ```
1317
1495
 
@@ -1614,7 +1792,8 @@ Optional keys: `external`, `define`, `esbuild`, `dev` (`host`, `port`, `https`,
1614
1792
  - `Component` stores its instance on its DOM node. Use `componentFromDOM(el)` to retrieve a component from an `HTMLElement`.
1615
1793
  - Numbers passed to `setStyle`/`setStyleValue` are auto-suffixed with `"px"` except for unitless properties (see the `unitless` set: `zIndex`, `opacity`, `flexGrow`...).
1616
1794
  - `show(false)` / `hide()` toggle the `"x4hidden"` CSS class, not `display:none`.
1617
- - `enable(false)` / `disable()` propagate the `disabled` state to child `<input>` and `<button>` elements.
1795
+ - x4 sets `user-select: none` on every element (`*`): text that must be selectable needs `user-select: text` on it and on its descendants.
1796
+ - `enable(false)` / `disable()` put `disabled` and `inert` on the component itself. Its children are not changed: the browser makes all that is inside an `inert` element inactive (no click, no focus).
1618
1797
  - `Application` is a strict singleton — one instance per page (an assertion fails otherwise).
1619
1798
  - `Popup.show()` routes to `displayCenter()`. To position, call `displayAt()` or `displayNear()` instead.
1620
1799
  - `BtnGroup` accepts predefined strings (`"ok"`, `"cancel"`...) with options after a dot: `"ok.outline.default.disabled"`.
@@ -1624,4 +1803,5 @@ Optional keys: `external`, `define`, `esbuild`, `dev` (`host`, `port`, `https`,
1624
1803
  - `DataRecordID` is currently typed `any`; `strictNullChecks` is intentionally off in the recommended `tsconfig.json`.
1625
1804
  - `MonacoEditor` requires `MonacoEditor.start()` (async) before use; register extra typings with `MonacoEditor.addTypelib(name, code)`.
1626
1805
  - `x4_react.create_element` is the JSX pragma;
1627
- - `unbubbleEvents` is the set of DOM events that do not bubble through the x4 component tree.
1806
+ - 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`.
1807
+ - 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,6 @@
1
1
  {
2
2
  "name": "x4js",
3
- "version": "2.3.16",
3
+ "version": "2.3.18",
4
4
  "type": "module",
5
5
  "main": "src/x4.ts",
6
6
  "module": "src/x4.ts",
@@ -50,10 +50,10 @@
50
50
  }
51
51
  }
52
52
 
53
- // props.nullable, no color: crossed out
53
+ // props.nullable, no color: the checkerboard that stands for what is transparent
54
54
  &.none {
55
55
  border: 1px solid var( --border );
56
- background: linear-gradient( to top right, transparent 45%, #d04040 45%, #d04040 55%, transparent 55% );
56
+ background: repeating-conic-gradient( #c8c8c8 0% 25%, #ffffff 0% 50% ) 0 0 / 6px 6px;
57
57
  }
58
58
  }
59
59
 
@@ -147,9 +147,9 @@
147
147
  border-color: var( --border-focus );
148
148
  }
149
149
 
150
- // no color: crossed out
150
+ // no color: the checkerboard that stands for what is transparent
151
151
  &.none {
152
- background: linear-gradient( to top right, transparent 45%, #d04040 45%, #d04040 55%, transparent 55% );
152
+ background: repeating-conic-gradient( #c8c8c8 0% 25%, #ffffff 0% 50% ) 0 0 / 6px 6px;
153
153
  }
154
154
  }
155
155
  }
@@ -87,6 +87,10 @@ interface ComboboxProps extends Omit<ComponentProps,"content"> {
87
87
  readonly?: boolean;
88
88
  required?: boolean;
89
89
  items: ListItem[];
90
+
91
+ /** draws an item of the list, instead of its text. The field still shows the text of the item that is chosen. */
92
+ renderer?: ( item: ListItem ) => Component;
93
+
90
94
  selectionChange?: EventCallback<EvSelectionChange>,
91
95
  }
92
96
 
@@ -139,7 +143,7 @@ export class Combobox extends Component<ComboboxProps,ComboboxEvents> {
139
143
  this.setAttribute( "required", true );
140
144
  }
141
145
 
142
- this._popup = new DropdownList( { items: props.items } );
146
+ this._popup = new DropdownList( { items: props.items, renderer: props.renderer } );
143
147
  const list = this._popup.getList( );
144
148
 
145
149
  const _select = ( sel: ListboxID ) => {
@@ -34,9 +34,9 @@
34
34
  // even in an HBox, which centers its children
35
35
  align-self: stretch;
36
36
 
37
- // The boxes of the layout: the band of the left, the room of the
38
- // contents, the band of the right. The floating tools are placed from
39
- // its top-left corner, which is the one of the box.
37
+ // The boxes of the layout: the band of the left, what is between the
38
+ // two, the band of the right. The floating tools are placed from its
39
+ // top-left corner, which is the one of the box.
40
40
  > .dock-frame {
41
41
  position: relative;
42
42
  display: flex;
@@ -45,6 +45,17 @@
45
45
  min-height: 0;
46
46
  }
47
47
 
48
+ // Between the bands of the left and of the right, one above the other:
49
+ // the band of the top, the room of the contents, the band of the bottom.
50
+ .dock-middle {
51
+ display: flex;
52
+ flex-direction: column;
53
+ flex: 1 1 0;
54
+ min-width: 0;
55
+ min-height: 0;
56
+ overflow: hidden;
57
+ }
58
+
48
59
  // The tools of one side. Its width is given by the layout; it only
49
60
  // gets narrower when the box has no room left.
50
61
  .dock-band {
@@ -58,6 +69,13 @@
58
69
  > .dock-stack {
59
70
  flex: 1 1 0;
60
71
  }
72
+
73
+ // the bands that lie flat: it is their height that the layout gives
74
+ &.top,
75
+ &.bottom {
76
+ min-width: 0;
77
+ min-height: 48px;
78
+ }
61
79
  }
62
80
 
63
81
  // the room of the contents: what the bands leave