x4js 2.3.17 → 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
@@ -537,6 +537,23 @@ class Image extends Component {
537
537
  }
538
538
  ```
539
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
+
540
557
  ### `Progress`
541
558
 
542
559
  ```ts
@@ -619,17 +636,38 @@ class TextEdit extends HBox {
619
636
  }
620
637
  ```
621
638
 
622
- ### `TextArea`
639
+ ### `TextArea`, `SimpleTextArea` — native `<textarea>`, with or without a label
623
640
 
624
641
  ```ts
625
- interface TextAreaProps extends BaseProps { // BaseProps: see Input (name, readonly, placeholder...)
626
- label?: string; value?: string
627
- resize?: boolean | "none" | "vertical" | "horizontal" | "both"
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"
628
645
  trim?: boolean // getText() trims, unless false
629
646
  }
630
- class TextArea extends VBox { getText(): string; setText(text: string): void }
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
+ ```
660
+
661
+ ```ts
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
631
664
  ```
632
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.
633
671
  ### `Checkbox`
634
672
 
635
673
  ```ts
@@ -784,10 +822,13 @@ interface ListboxProps extends Omit<ComponentProps,"content"> {
784
822
  title?: string; icon?: string
785
823
  header?: Header; footer?: Component
786
824
  checkable?: true; multisel?: true
825
+ emptyMsg?: string // shown in place of the items while there are none
787
826
  dblClick?: EventCallback<EvDblClick>
788
827
  selectionChange?: EventCallback<EvSelectionChange>
789
828
  contextMenu?: EventCallback<EvContextMenu>
790
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".
791
832
  class Listbox extends Component {
792
833
  setItems(items: ListItem[], keepSel?: boolean): void
793
834
  appendItem(item: ListItem, prepend?: boolean, select?: boolean): void
@@ -801,7 +842,6 @@ class Listbox extends Component {
801
842
  ensureSelectionVisible(): void
802
843
  renderItem(item: ListItem): Component // override to customize
803
844
  defaultRenderer(item: ListItem): Component
804
- preventFocus: boolean
805
845
  }
806
846
  ```
807
847
 
@@ -812,38 +852,51 @@ interface GridviewProps extends ComponentProps {
812
852
  store: DataStore // required
813
853
  columns: GridColumn[] // required
814
854
  footer?: boolean; emptyMsg?: string
815
- sort?: object
816
- click?: EventCallback<EvClick>
817
- 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
818
859
  contextMenu?: EventCallback<EvContextMenu>
819
860
  selectionChange?: EventCallback<EvSelectionChange>
820
861
  }
821
862
  class Gridview extends Component {
822
863
  setStore(store: DataStore): void
823
- setColumns(columns: GridColumn[]): void
824
864
  setColTitle(col_name: string, title: string): void
825
- sortCol(colIdx: number, ascending: boolean): void
865
+ sortCol(colId: any, ascending: boolean): void // colId: the id of the column, not its index
826
866
  getView(): DataView
827
- getSelection(): DataRecord[]
828
- getFirstSel(): DataRecord
867
+ getSelection(): DataRecord[] // null when nothing is selected
868
+ getFirstSel(): DataRecord // null when nothing is selected
829
869
  selectItem(id, ensureVisible?: boolean): void
830
870
  clearSelection(): void
871
+ selectAll(): void // multisel only; keeps the id of every record: costs memory on a very large view
831
872
  navigate(sens: kbNav): boolean
832
873
  lock(lock: boolean): void
833
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.
834
878
  ```
835
879
 
836
880
  ### `Treeview`
837
881
 
838
882
  ```ts
839
883
  interface TreeItem extends ListItem { children?: TreeItem[]; open?: boolean }
840
- 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`.
841
894
  class Treeview extends Component {
842
895
  setItems(items: TreeItem[]): void
843
- getSelection(): ListboxID
844
- selectItem(id): void
896
+ getSelection(): ListboxID // one item, not an array
897
+ selectItem(id: ListboxID): void
845
898
  clearSelection(): void
846
- navigate(sens: kbNav): boolean // see kbTreeNav enum
899
+ navigate(sens: kbTreeNav): boolean // first, prev, next, last, parent, child, expand, collapse, toggle
847
900
  }
848
901
  ```
849
902
 
@@ -871,6 +924,7 @@ class Spreadsheet extends Component {
871
924
  stopEdit(commit = true): void; isEditing(): boolean
872
925
  }
873
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.
874
928
  // Inline edition: F2 / Enter / double-click / typing starts, Enter / Tab / click elsewhere validates, Escape cancels,
875
929
  // Delete clears, editable checkboxes toggle on double-click or Space (a single click only selects).
876
930
  // Default editor is an Input matching the column type.
@@ -1116,7 +1170,13 @@ class ColorPicker extends VBox { // props: color, swatches?, nullable?
1116
1170
  }
1117
1171
  class Panel extends VBox { setTitle(title: string): void }
1118
1172
  class Breadcrumbs extends HBox { setItems(elements): void }
1119
- 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
1120
1180
  class TickLine extends Component
1121
1181
  class Header extends HBox // resizable column header; updates target style "--{name}-width"
1122
1182
  ```
@@ -1189,6 +1249,8 @@ class DataView extends CoreElement {
1189
1249
  getRecId(rec): any; indexOfId(id): number
1190
1250
  getStore(): DataStore; getModel(): DataModel
1191
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
1192
1254
  }
1193
1255
  ```
1194
1256
 
@@ -1256,6 +1318,80 @@ class MyView extends VBox {
1256
1318
 
1257
1319
  ---
1258
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
+
1259
1395
  ## i18n (`core_i18n`)
1260
1396
 
1261
1397
  Two built-in languages (`fr` default, and `en`).
@@ -1351,8 +1487,9 @@ interface ITabHandler { focusNext(next: boolean): boolean }
1351
1487
  interface ITipHandler { getTip(): string }
1352
1488
 
1353
1489
  // Other variables
1354
- const dragManager; const svgLoader; const linearColorStops; const simpleColorStop
1355
- 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"
1356
1493
  const makeUniqueComponentId // () => string, "x4-<n>" (exported as a value)
1357
1494
  ```
1358
1495
 
@@ -1656,7 +1793,7 @@ Optional keys: `external`, `define`, `esbuild`, `dev` (`host`, `port`, `https`,
1656
1793
  - Numbers passed to `setStyle`/`setStyleValue` are auto-suffixed with `"px"` except for unitless properties (see the `unitless` set: `zIndex`, `opacity`, `flexGrow`...).
1657
1794
  - `show(false)` / `hide()` toggle the `"x4hidden"` CSS class, not `display:none`.
1658
1795
  - x4 sets `user-select: none` on every element (`*`): text that must be selectable needs `user-select: text` on it and on its descendants.
1659
- - `enable(false)` / `disable()` propagate the `disabled` state to child `<input>` and `<button>` elements.
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).
1660
1797
  - `Application` is a strict singleton — one instance per page (an assertion fails otherwise).
1661
1798
  - `Popup.show()` routes to `displayCenter()`. To position, call `displayAt()` or `displayNear()` instead.
1662
1799
  - `BtnGroup` accepts predefined strings (`"ok"`, `"cancel"`...) with options after a dot: `"ok.outline.default.disabled"`.
@@ -1666,4 +1803,5 @@ Optional keys: `external`, `define`, `esbuild`, `dev` (`host`, `port`, `https`,
1666
1803
  - `DataRecordID` is currently typed `any`; `strictNullChecks` is intentionally off in the recommended `tsconfig.json`.
1667
1804
  - `MonacoEditor` requires `MonacoEditor.start()` (async) before use; register extra typings with `MonacoEditor.addTypelib(name, code)`.
1668
1805
  - `x4_react.create_element` is the JSX pragma;
1669
- - `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.17",
3
+ "version": "2.3.18",
4
4
  "type": "module",
5
5
  "main": "src/x4.ts",
6
6
  "module": "src/x4.ts",