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 +216 -36
- package/package.json +1 -1
- package/src/components/colorinput/colorinput.module.scss +2 -2
- package/src/components/colorpicker/colorpicker.module.scss +2 -2
- package/src/components/combobox/combobox.ts +5 -1
- package/src/components/dockingview/dockingview.module.scss +21 -3
- package/src/components/dockingview/dockingview.ts +192 -55
- package/src/components/dockingview/docklayout.ts +125 -36
- package/src/components/gauge/gauge.ts +4 -0
- package/src/components/gridview/gridview.ts +350 -175
- package/src/components/input/input.ts +6 -1
- package/src/components/keyboard/keyboard.ts +181 -65
- package/src/components/notification/notification.module.scss +8 -3
- package/src/components/notification/notification.ts +3 -0
- package/src/components/progress/progress.ts +7 -3
- package/src/components/spreadsheet/spreadsheet.ts +27 -1
- package/src/components/switch/switch.ts +2 -1
- package/src/components/tag/tag.ts +1 -2
- package/src/components/textarea/textarea.module.scss +6 -0
- package/src/components/textarea/textarea.ts +48 -10
- package/src/core/component.ts +16 -9
- package/src/core/core_clipboard.ts +239 -0
- package/src/core/core_data.ts +54 -23
- package/src/core/core_dom.ts +27 -5
- package/src/core/core_dragdrop.ts +11 -5
- package/src/core/core_element.ts +1 -1
- package/src/core/core_shortcuts.ts +1 -1
- package/src/x4.d.ts +1 -0
- package/src/x4.ts +1 -0
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"
|
|
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
|
|
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 { /*
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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(
|
|
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 {
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1314
|
-
const
|
|
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
|
-
- `
|
|
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
|
-
-
|
|
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
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
// props.nullable, no color:
|
|
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:
|
|
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:
|
|
150
|
+
// no color: the checkerboard that stands for what is transparent
|
|
151
151
|
&.none {
|
|
152
|
-
background:
|
|
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,
|
|
38
|
-
//
|
|
39
|
-
//
|
|
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
|