x4js 2.3.24 → 2.3.26
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 +101 -17
- package/aiguide.md +64 -15
- package/package.json +1 -1
- package/src/components/assets/icons.ts +2 -0
- package/src/components/autocomplete/autocomplete.ts +2 -2
- package/src/components/boxes/boxes.module.scss +16 -2
- package/src/components/boxes/boxes.ts +24 -11
- package/src/components/breadcrumb/breadcrumb.ts +1 -2
- package/src/components/btngroup/btngroup.ts +2 -2
- package/src/components/button/button.ts +7 -2
- package/src/components/calendar/calendar.ts +4 -2
- package/src/components/canvas/canvas.ts +1 -2
- package/src/components/chart/chart.ts +3 -3
- package/src/components/checkbox/checkbox.ts +2 -2
- package/src/components/colorinput/colorinput.ts +3 -2
- package/src/components/colorpicker/colorpicker.ts +5 -5
- package/src/components/combobox/combobox.ts +3 -3
- package/src/components/dialog/dialog.ts +8 -2
- package/src/components/dockingview/dockingview.ts +1 -2
- package/src/components/filedrop/filedrop.ts +3 -2
- package/src/components/form/form.module.scss +2 -1
- package/src/components/form/form.ts +7 -3
- package/src/components/gauge/gauge.ts +2 -2
- package/src/components/gridview/gridview.module.scss +8 -5
- package/src/components/gridview/gridview.ts +11 -13
- package/src/components/header/header.ts +2 -2
- package/src/components/icon/icon.ts +1 -2
- package/src/components/image/image.ts +1 -2
- package/src/components/input/input.ts +3 -2
- package/src/components/keyboard/keyboard.ts +2 -2
- package/src/components/label/label.ts +3 -3
- package/src/components/link/link.ts +3 -2
- package/src/components/listbox/listbox.module.scss +59 -2
- package/src/components/listbox/listbox.ts +133 -23
- package/src/components/menu/menu.ts +5 -4
- package/src/components/messages/messages.module.scss +95 -3
- package/src/components/messages/messages.ts +113 -27
- package/src/components/notification/notification.ts +6 -6
- package/src/components/panel/panel.ts +2 -2
- package/src/components/popup/popup.ts +17 -2
- package/src/components/progress/progress.ts +1 -2
- package/src/components/propgrid/progrid.module.scss +2 -3
- package/src/components/propgrid/propgrid.ts +32 -38
- package/src/components/radio/radio.ts +1 -2
- package/src/components/rating/rating.ts +2 -2
- package/src/components/select/select.ts +2 -2
- package/src/components/settingsview/settingsview.module.scss +6 -5
- package/src/components/settingsview/settingsview.ts +3 -3
- package/src/components/sizers/sizer.ts +21 -29
- package/src/components/slider/slider.ts +2 -2
- package/src/components/spreadsheet/spreadsheet.ts +8 -10
- package/src/components/switch/switch.ts +2 -2
- package/src/components/tabs/tabs.ts +4 -4
- package/src/components/tag/tag.ts +2 -2
- package/src/components/textarea/textarea.ts +3 -3
- package/src/components/textedit/textedit.ts +2 -2
- package/src/components/themes.scss +2 -0
- package/src/components/tickline/tickline.ts +3 -3
- package/src/components/tooltips/tooltips.ts +11 -2
- package/src/components/treeview/treeview.ts +3 -3
- package/src/components/video/video.ts +1 -2
- package/src/components/viewport/viewport.ts +4 -3
- package/src/core/component.ts +15 -24
- package/src/core/core_application.ts +36 -3
- package/src/core/core_events.ts +14 -2
- package/src/core/core_i18n.ts +67 -0
- package/src/core/core_svg.ts +1 -0
- package/src/core/core_tools.ts +83 -24
package/aicontext.md
CHANGED
|
@@ -91,6 +91,8 @@ fire<K>(name: K, ev: E[K]): void
|
|
|
91
91
|
|
|
92
92
|
- `capturing = true` inserts at the head of the listener list.
|
|
93
93
|
- `fire()` automatically injects `source`, `type`, `stopPropagation()`, `preventDefault()` when absent.
|
|
94
|
+
- A listener that throws: `fire()` sends its error to `console.error`, calls the next listeners, and returns
|
|
95
|
+
normally; the one that fires never gets the error of a listener. (An `async` listener rejects its own promise.)
|
|
94
96
|
|
|
95
97
|
### `CoreEvent`
|
|
96
98
|
|
|
@@ -140,7 +142,7 @@ interface ComponentProps {
|
|
|
140
142
|
flex?: number | boolean // true → "x4flex"; number → flex-grow
|
|
141
143
|
disabled?: boolean
|
|
142
144
|
hidden?: boolean
|
|
143
|
-
tooltip?: string
|
|
145
|
+
tooltip?: string // shown as html: escape a text from a user (see initTooltips)
|
|
144
146
|
existingDOM?: HTMLElement // wrap an existing DOM node
|
|
145
147
|
}
|
|
146
148
|
|
|
@@ -167,6 +169,15 @@ toggleClass(cls: string): void
|
|
|
167
169
|
setClass(cls: string, set?: boolean): this
|
|
168
170
|
```
|
|
169
171
|
|
|
172
|
+
A component gets one class for each class of its chain: its name in lower case, after the prefix that class
|
|
173
|
+
declares itself with `static classPrefix` (not the one of its parent), then `x4`. Every component of x4 declares
|
|
174
|
+
`static classPrefix = "x4"` (a Button: `x4button x4`); a class of the application declares its own, or none.
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
class Avatar extends Image { static classPrefix = "my"; } // classes: myavatar x4image x4
|
|
178
|
+
class Badge extends Label { } // classes: badge x4label x4
|
|
179
|
+
```
|
|
180
|
+
|
|
170
181
|
### HTML attributes & ARIA
|
|
171
182
|
|
|
172
183
|
```ts
|
|
@@ -220,7 +231,8 @@ childCount(): number
|
|
|
220
231
|
enumChildComponents(recursive: boolean): Component[]
|
|
221
232
|
enumChildNodes(recursive: boolean): Node[]
|
|
222
233
|
visitChildren(cb: (el: Component) => boolean): void
|
|
223
|
-
static parentElement<T>(dom: Node, cls?: Constructor<T>): T
|
|
234
|
+
static parentElement<T>(dom: Node, cls?: Constructor<T>): T // starts at the parent of dom, not at dom:
|
|
235
|
+
// test componentFromDOM(dom) first for the component of dom itself
|
|
224
236
|
```
|
|
225
237
|
|
|
226
238
|
### Visibility / state / focus / geometry
|
|
@@ -288,6 +300,11 @@ static fireGlobal(msg: string, params?: any): void
|
|
|
288
300
|
static setTheme(name: string): void // the class x4theme-<name> on <html>: "" the light theme (default, no class),
|
|
289
301
|
// "dark" the dark theme of x4, another name a theme of the application
|
|
290
302
|
static getTheme(): string // "" for the light theme, "dark", or the name given to setTheme
|
|
303
|
+
static setLocale(locale: string, currency?: string): void // forces the locale of the formats (formatNumber, formatMoney,
|
|
304
|
+
// formatDate, formatDateTime) over the one of the language; null: the language's
|
|
305
|
+
// again. currency: "EUR" by default, kept when not given
|
|
306
|
+
static getLocale(): string // the forced one, else _tr.global.locale ("fr-FR" for fr, "en-US" for en)
|
|
307
|
+
static getCurrency(): string
|
|
291
308
|
focusNext(next: boolean): boolean
|
|
292
309
|
addShortcut(keys: string | string[], callback: (ev: KeyboardEvent) => void, options?: ShortcutOptions): void // see Shortcuts
|
|
293
310
|
setupSocketMessaging(path?: string, looseCallback?: () => void): void
|
|
@@ -310,8 +327,14 @@ class VBox<P, E> extends Box<P, E>
|
|
|
310
327
|
interface BoxProps extends ComponentProps { tag?: string }
|
|
311
328
|
```
|
|
312
329
|
|
|
330
|
+
A Box is a `<div>` (another element with `tag`). Disabled (`disable()`, the prop `disabled`), its content is inert
|
|
331
|
+
(no click, no focus, no input) and faded by `--box-disabled-opacity` (0.5); a disabled Box in it is not faded twice.
|
|
332
|
+
|
|
313
333
|
### `Box.refs` — typed directory of the children to reach again later
|
|
314
334
|
|
|
335
|
+
Only a `Box` has `refs`: a class derived from `Component` (not from `Box`) has none, and `this.refs.x = ...` there
|
|
336
|
+
compiles with a `declare refs` but throws a `TypeError` at run time.
|
|
337
|
+
|
|
315
338
|
`Box` owns a `protected refs: Record<string, Component>`. Declare its shape in the subclass, then assign each child
|
|
316
339
|
to `refs` while building the content: the assignment evaluates to the component, so it is written inline in the
|
|
317
340
|
content array. Do not keep such children in separate private fields, and do not use `refs.x` for a child that was
|
|
@@ -826,29 +849,46 @@ class FileDialog extends Component {} // programmatic file picker
|
|
|
826
849
|
interface ListItem {
|
|
827
850
|
id: ListboxID // number | string
|
|
828
851
|
text: string | UnsafeHtml
|
|
829
|
-
|
|
852
|
+
sub_cols?: (string | UnsafeHtml | Component)[] // extra columns after text (9 at most, ref-c2...ref-c10, see Header);
|
|
853
|
+
// a null value makes no cell. A Treeview draws them too
|
|
854
|
+
iconId?: string; data?: any; cls?: string
|
|
855
|
+
checked?: boolean // checkable: the starting state of the case (getChecked() gives the current one)
|
|
830
856
|
}
|
|
831
857
|
interface ListboxProps extends Omit<ComponentProps,"content"> {
|
|
832
858
|
items?: ListItem[]
|
|
833
859
|
renderer?: (item: ListItem) => Component
|
|
834
860
|
title?: string; icon?: string
|
|
835
861
|
header?: Header; footer?: Component
|
|
836
|
-
checkable?:
|
|
862
|
+
checkable?: boolean // a case at the start of each line
|
|
863
|
+
multisel?: true
|
|
864
|
+
reselect?: boolean // a click on the item already selected fires selectionChange again
|
|
865
|
+
// (lists linked together: the view shows what was clicked last)
|
|
837
866
|
emptyMsg?: string // shown in place of the items while there are none
|
|
838
867
|
dblClick?: EventCallback<EvDblClick>
|
|
839
868
|
selectionChange?: EventCallback<EvSelectionChange>
|
|
840
869
|
contextMenu?: EventCallback<EvContextMenu>
|
|
870
|
+
checkChange?: EventCallback<EvCheckChange> // EvCheckChange { id: ListboxID, checked: boolean }
|
|
841
871
|
}
|
|
842
872
|
// emptyMsg: without it the text is _tr.global.empty_list ("Empty list"). It is shown by setItems
|
|
843
873
|
// (the constructor calls it) when there is no item. The listbox then has the class "empty".
|
|
874
|
+
// click: no prop; listbox.on( "click", ev => ... ), ev.context the id of the item. It is fired on the item
|
|
875
|
+
// already selected too, which fires no selectionChange.
|
|
876
|
+
// checkable: renderItem puts the case (.x4check) first in the line, with a renderer of yours too;
|
|
877
|
+
// a checked line has the class "checked". A click on the case, or Space for the last item
|
|
878
|
+
// selected, changes it and fires checkChange: no click, no selection. A double click on the
|
|
879
|
+
// case is ignored (its two clicks change it twice). setChecked fires nothing; updateItem keeps
|
|
880
|
+
// the state of the case. With a header, the columns start after the case.
|
|
844
881
|
class Listbox extends Component {
|
|
845
|
-
setItems(items: ListItem[], keepSel?: boolean): void
|
|
882
|
+
setItems(items: ListItem[], keepSel?: boolean): void // one selectionChange at the end, with the selection it ends
|
|
883
|
+
// with, when there was one before or there is one after (keepSel: the same ids too)
|
|
846
884
|
appendItem(item: ListItem, prepend?: boolean, select?: boolean): void
|
|
847
885
|
updateItem(id: any, item: ListItem): void
|
|
848
886
|
getItem(id: ListboxID): ListItem
|
|
849
887
|
select(ids: ListboxID | ListboxID[], notify?: boolean): void
|
|
850
888
|
clearSelection(fireEvent?: boolean): void
|
|
851
889
|
getSelection(): ListboxID[]
|
|
890
|
+
getChecked(): ListboxID[] // checkable: in the order of the list
|
|
891
|
+
setChecked(ids: ListboxID[]): void // these checked, the others not; no event
|
|
852
892
|
filter(filter: string | RegExp): void
|
|
853
893
|
navigate(sens: kbNav): boolean
|
|
854
894
|
ensureSelectionVisible(): void
|
|
@@ -1036,6 +1076,7 @@ interface ButtonProps extends ComponentProps {
|
|
|
1036
1076
|
menu?: MenuElement[] | (() => MenuElement[]) // a click opens this Menu under the button, in place of the click event;
|
|
1037
1077
|
// a function is called at each click; the button gets the class "with-menu"
|
|
1038
1078
|
}
|
|
1079
|
+
// its <button> is type="button": in a <form>, Enter in a field does not click it. attrs: { type: "submit" } to submit
|
|
1039
1080
|
class Button extends Component {
|
|
1040
1081
|
click(): void // simulate a click
|
|
1041
1082
|
setText(text: string|UnsafeHtml): void
|
|
@@ -1078,6 +1119,8 @@ class Popup extends Box {
|
|
|
1078
1119
|
isOpen(): boolean
|
|
1079
1120
|
// show()/hide() inherited from Component; show() routes to displayCenter()
|
|
1080
1121
|
}
|
|
1122
|
+
// A modal popup (a Dialog, a MessageBox...) keeps the element that had the focus when it opens and gives
|
|
1123
|
+
// it back the focus when it closes, unless the focus was put elsewhere meanwhile.
|
|
1081
1124
|
```
|
|
1082
1125
|
|
|
1083
1126
|
### `Dialog`
|
|
@@ -1101,6 +1144,10 @@ class Dialog extends Popup {
|
|
|
1101
1144
|
show(): this // each time: the focus on its first field
|
|
1102
1145
|
showAsync(): Promise<string> // the name of the clicked button; null when closed without a button (Escape, close box)
|
|
1103
1146
|
}
|
|
1147
|
+
// Keys: Escape closes it (or clicks `closable` when it is a button name). Enter clicks its default
|
|
1148
|
+
// button ("ok.default" in buttons), except in a textarea or an element contenteditable, where Enter is
|
|
1149
|
+
// a new line and Ctrl+Enter (Cmd+Enter) clicks it. Out of a dialog, Enter goes to the next field,
|
|
1150
|
+
// except in a textarea or a contenteditable.
|
|
1104
1151
|
```
|
|
1105
1152
|
|
|
1106
1153
|
### `MessageBox` / `InputBox` / `PromptBox` / `ProgressionBox`
|
|
@@ -1120,10 +1167,24 @@ class PromptBox extends Dialog {
|
|
|
1120
1167
|
class ProgressionBox extends Dialog {
|
|
1121
1168
|
constructor(title: string)
|
|
1122
1169
|
addText(text: string|UnsafeHtml, perc: number): void
|
|
1123
|
-
addError(text: string|UnsafeHtml, perc: number): void
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1170
|
+
addError(text: string|UnsafeHtml, perc: number): void // red, on a light red line, a red bar on its left;
|
|
1171
|
+
// the title bar in red until clear()
|
|
1172
|
+
addWarning(text: string|UnsafeHtml, perc: number): void // the same in orange
|
|
1173
|
+
addSection(title: string|UnsafeHtml, perc: number): void // a section (<details>, open): the lines that follow go
|
|
1174
|
+
// in it; a click on its title folds it; its title has the icon
|
|
1175
|
+
// of the worst of its lines. Without any, the lines go in the log
|
|
1176
|
+
setText(text: string|UnsafeHtml, perc: number): void // in place of all the lines
|
|
1177
|
+
clear(): void // no line, no error, no button OK
|
|
1178
|
+
done(): void // shows OK: after an error or a warning it waits for a click,
|
|
1179
|
+
// their counts on the left of the bar ("2 errors / 1 warning",
|
|
1180
|
+
// from _tr.global.progression), the icon of the worst;
|
|
1181
|
+
// else it counts "OK (5s)" and closes the box at 0
|
|
1182
|
+
}
|
|
1183
|
+
// ProgressionBox: its lines in a <pre> in a fixed font, a <div> each (class "line", plus "error" or "warning"),
|
|
1184
|
+
// 10 lines high, scrolled both ways (the box keeps its size), every line as wide as the longest one;
|
|
1185
|
+
// a string is text, an UnsafeHtml html. Ctrl+A in the lines selects them only (a folded section too).
|
|
1186
|
+
// Colours: --msgbox-log-background, -border, -border-focus, -error-color, -error-background,
|
|
1187
|
+
// -warning-color, -warning-background, -scrollbar (the thumb of its scroll bars; their track is its background).
|
|
1127
1188
|
```
|
|
1128
1189
|
|
|
1129
1190
|
### `Notification`
|
|
@@ -1146,7 +1207,8 @@ class Notification extends Popup { display(time_in_s?: number): void } // 0: u
|
|
|
1146
1207
|
```ts
|
|
1147
1208
|
type FormValues = Record<string, any>
|
|
1148
1209
|
interface FormProps extends BoxProps { autoComplete?: boolean }
|
|
1149
|
-
class Form extends Box {
|
|
1210
|
+
class Form<P extends FormProps = FormProps, E extends ComponentEvents = ComponentEvents> extends Box<P, E> {
|
|
1211
|
+
// a class derived from Form declares its events: class EntryForm extends Form<EntryProps, EntryEvents>
|
|
1150
1212
|
setValues(values: FormValues): void
|
|
1151
1213
|
getValues<T extends FormValues>(): T
|
|
1152
1214
|
setAutoComplete(on?: boolean): void // attribute on the <form>: fields without their own follow it, added later too
|
|
@@ -1154,6 +1216,9 @@ class Form extends Box {
|
|
|
1154
1216
|
validate(): FormValues // null if invalid; the values the validator changes are written back in their fields (any element with the name, every radio of a group)
|
|
1155
1217
|
}
|
|
1156
1218
|
// Collects all children (recursive) that have a "name" attribute and implement queryInterface("form-element").
|
|
1219
|
+
// Never sent by the browser: its "submit" is prevented (Enter in its only field would send it).
|
|
1220
|
+
// A Form is flex by default (it takes the free height of its box) and has a padding of 16px.
|
|
1221
|
+
// flex: false: the height of its content (a toolbar, a line); its padding set back by a css rule.
|
|
1157
1222
|
```
|
|
1158
1223
|
|
|
1159
1224
|
---
|
|
@@ -1240,9 +1305,11 @@ class VSplitBox extends VBox // panels stacked, resizable height:
|
|
|
1240
1305
|
// new HSplitBox( { content: [ new Treeview( { width: 250 } ), new Panel( { flex: 1 } ) ] } )
|
|
1241
1306
|
|
|
1242
1307
|
// Low level: only place sizers by hand when a SplitBox does not fit (popup edges, header cells...).
|
|
1243
|
-
class CSizer extends Component // base splitter
|
|
1308
|
+
class CSizer extends Component // base splitter; events start, resize { size, width, height }, stop
|
|
1244
1309
|
class HSizer extends CSizer // horizontal splitter
|
|
1245
1310
|
class VSizer extends CSizer // vertical splitter
|
|
1311
|
+
// A pointercancel (a touch taken by the browser) ends the resize as a pointerup does: stop is fired.
|
|
1312
|
+
// The columns of a Gridview or a Spreadsheet are 16px wide at least (the min-width of a cell).
|
|
1246
1313
|
class ScrollView extends Component { getViewport(): Component }
|
|
1247
1314
|
class Viewport extends Component
|
|
1248
1315
|
class Canvas extends Component { getContext(): CanvasRenderingContext2D; paint(): void }
|
|
@@ -1405,7 +1472,7 @@ class MyView extends VBox {
|
|
|
1405
1472
|
}
|
|
1406
1473
|
```
|
|
1407
1474
|
|
|
1408
|
-
- A component shortcut also works while the focus is nowhere, if the component is the last one with shortcuts that had the focus: of two views shown together, the one the user works in answers.
|
|
1475
|
+
- A component shortcut also works while the focus is nowhere, if the component is the last one with shortcuts that had the focus: of two views shown together, the one the user works in answers. Not while that component is hidden (in a tab not shown, for instance).
|
|
1409
1476
|
- A key typed in an `input`, a `textarea` or a `select` is not a shortcut, unless `editable` is set.
|
|
1410
1477
|
- A key that what has the focus already used (it called `preventDefault` or `stopPropagation`) is not a shortcut.
|
|
1411
1478
|
- A shortcut always takes its key (`preventDefault`). For a key that must sometimes be left alone, use a `keydown` handler instead.
|
|
@@ -1507,10 +1574,18 @@ isLanguage(name: string): boolean
|
|
|
1507
1574
|
// search search_tip required_field invalid_format invalid_email invalid_number invalid_date
|
|
1508
1575
|
// empty_list err_403 property value copy cut paste filedrop
|
|
1509
1576
|
// diff_date_seconds/minutes/hours, date_input_formats, date_format, date_time_format,
|
|
1577
|
+
// locale ("fr-FR", "en-US", "es-ES", "de-DE": the formats of numbers, amounts, dates),
|
|
1510
1578
|
// day_short[] day_long[] month_short[] month_long[], keyboard.{next,numeric,alpha},
|
|
1511
|
-
// settings.{search,nomatch}
|
|
1579
|
+
// settings.{search,nomatch}, progression.{error,errors,warning,warnings}
|
|
1580
|
+
getCurrentLocale(): string; getCurrentCurrency(): string // as Application.getLocale / getCurrency
|
|
1512
1581
|
```
|
|
1513
1582
|
|
|
1583
|
+
The language gives the texts and the locale of the formats: `selectLanguage("en")` changes both. A regional
|
|
1584
|
+
variant is a language based on another: `createLanguage("fr-ch", "fr")`, `addTranslation("fr-ch", { global: { locale: "fr-CH" } })`.
|
|
1585
|
+
`Application.setLocale(locale, currency)` forces the locale over the language's. The fields of date and the
|
|
1586
|
+
Calendar follow the patterns of the language (`date_format`...), not the locale. A field of type number is shown
|
|
1587
|
+
by the browser, in its own locale, whatever this one.
|
|
1588
|
+
|
|
1514
1589
|
---
|
|
1515
1590
|
|
|
1516
1591
|
## Theme (`themes.scss`)
|
|
@@ -1584,7 +1659,14 @@ pascalCase(s): string; camelCase(s): string; clamp<T>(v, min, max): T
|
|
|
1584
1659
|
measureText(str, size, bold?): number // width in Helvetica, from core_pdf (see PDF)
|
|
1585
1660
|
|
|
1586
1661
|
// Dates
|
|
1587
|
-
date_format(date, options?): string // options
|
|
1662
|
+
date_format(date, options?): string // options: Intl.DateTimeFormat in the locale; without: formatIntlDate(date)
|
|
1663
|
+
|
|
1664
|
+
// Formats in the current locale (Application.setLocale, else the one of the language), Intl made once by locale
|
|
1665
|
+
formatNumber(value, decimals?, grouping = true): string // "1 234,5" in fr-FR; decimals: the most shown
|
|
1666
|
+
formatMoney(value): string // "1 234,50 €": the currency of Application.getCurrency()
|
|
1667
|
+
formatDate(date): string // "22/10/2026"
|
|
1668
|
+
formatDateTime(date): string // "22/10/2026 14:05"
|
|
1669
|
+
// Gridview and Spreadsheet format their columns number, money, date, date-time with them (a formatter comes first)
|
|
1588
1670
|
date_diff(date1, date2, options?): string // "30 secondes", "5 minutes", "2 heures 5 minutes" (either order); options not read
|
|
1589
1671
|
date_to_sql(date, withHours): string; date_sql_utc(date): Date
|
|
1590
1672
|
date_hash(date): number; date_clone(date): Date
|
|
@@ -1603,9 +1685,6 @@ getSystemMetrics(part: "scrollbar"|"zoom"): number
|
|
|
1603
1685
|
setWaitCursor(wait: boolean): void; beep(): void
|
|
1604
1686
|
isUnitLess(name): boolean; isFeatureAvailable(name: "eyedropper"): boolean
|
|
1605
1687
|
|
|
1606
|
-
// Class decorator
|
|
1607
|
-
class_ns(ns: string): ClassDecorator
|
|
1608
|
-
const x4_class_ns_sym: symbol
|
|
1609
1688
|
const unitless // set of unitless CSS props
|
|
1610
1689
|
|
|
1611
1690
|
// System interfaces
|
|
@@ -1720,6 +1799,11 @@ createPainter(c2d: CanvasRenderingContext2D, w: number, h: number): CanvasEx /
|
|
|
1720
1799
|
initTooltips(): void // initialize the global tooltip system
|
|
1721
1800
|
```
|
|
1722
1801
|
|
|
1802
|
+
No tooltip is shown until the application calls `initTooltips()` once (the prop `tooltip` only sets an attribute).
|
|
1803
|
+
`initTooltips()` shows the text of the attribute `tooltip` (the prop `tooltip`), or the one `getTip()`
|
|
1804
|
+
of a `"tip-handler"` gives, **as html**. A text that comes from a user is escaped by the application
|
|
1805
|
+
with `sanitizeHtml( text )`: `tooltip: \`Open <b>${sanitizeHtml( file.name )}</b>\``.
|
|
1806
|
+
|
|
1723
1807
|
---
|
|
1724
1808
|
|
|
1725
1809
|
## State (`core_state`)
|
package/aiguide.md
CHANGED
|
@@ -20,7 +20,7 @@ Prefer:
|
|
|
20
20
|
- plain objects for application data
|
|
21
21
|
- `State` for observable application state
|
|
22
22
|
- local events for things that happened locally
|
|
23
|
-
- `
|
|
23
|
+
- a global message (`Application.fireGlobal`) for application-wide or distributed messages
|
|
24
24
|
- `DataStore` for specialized tabular data
|
|
25
25
|
- persistent `Component` objects backed by the real DOM
|
|
26
26
|
- CSS and CSS variables for presentation and theming
|
|
@@ -84,13 +84,13 @@ For a simple event payload, `context` can carry the relevant identity or value w
|
|
|
84
84
|
|
|
85
85
|
Do not put transient actions into State merely to trigger behavior.
|
|
86
86
|
|
|
87
|
-
###
|
|
87
|
+
### Global messages
|
|
88
88
|
|
|
89
|
-
Use `
|
|
89
|
+
Use a global message (`Application.fireGlobal`) for application-wide messages.
|
|
90
90
|
|
|
91
|
-
A
|
|
91
|
+
A global message may also be transported between processes, clients or a server.
|
|
92
92
|
|
|
93
|
-
Views should not care how a
|
|
93
|
+
Views should not care how a global message is transported.
|
|
94
94
|
|
|
95
95
|
In particular, do not expose WebSocket details to UI components merely because WebSocket is used as the transport.
|
|
96
96
|
|
|
@@ -295,9 +295,24 @@ Do not use events as a replacement for persistent application state.
|
|
|
295
295
|
|
|
296
296
|
---
|
|
297
297
|
|
|
298
|
-
## 9.
|
|
298
|
+
## 9. Global messages
|
|
299
299
|
|
|
300
|
-
|
|
300
|
+
A global message is sent by `Application.fireGlobal( msg, params )` and received by
|
|
301
|
+
`component.onGlobalEvent( ev => ... )`, `ev` an `EvMessage { msg, params }`. The listener of
|
|
302
|
+
`onGlobalEvent` is taken off when the component leaves the page.
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
Application.fireGlobal( "device.updated", { id: deviceId } );
|
|
306
|
+
|
|
307
|
+
this.onGlobalEvent( ( ev ) => {
|
|
308
|
+
if( ev.msg == "device.updated" ) {
|
|
309
|
+
this.refresh( ev.params.id );
|
|
310
|
+
}
|
|
311
|
+
} );
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Use a global message for what concerns the application or a distributed system, rather than a
|
|
315
|
+
local component relationship.
|
|
301
316
|
|
|
302
317
|
Global messages should normally use stable application-level names, for example:
|
|
303
318
|
|
|
@@ -318,11 +333,11 @@ The receiver can obtain the authoritative object through the application's model
|
|
|
318
333
|
|
|
319
334
|
For high-frequency realtime information such as telemetry, carrying the actual data can be appropriate.
|
|
320
335
|
|
|
321
|
-
Do not force every
|
|
336
|
+
Do not force every global message into the same payload strategy.
|
|
322
337
|
|
|
323
338
|
Most importantly:
|
|
324
339
|
|
|
325
|
-
>
|
|
340
|
+
> A global message represents the message, not its transport.
|
|
326
341
|
|
|
327
342
|
A view should normally not contain WebSocket-specific logic.
|
|
328
343
|
|
|
@@ -361,7 +376,7 @@ Model obtains or modifies domain data
|
|
|
361
376
|
State represents current application state
|
|
362
377
|
Component presents that state
|
|
363
378
|
Events communicate local occurrences
|
|
364
|
-
|
|
379
|
+
Global msg communicates application-wide occurrences (Application.fireGlobal)
|
|
365
380
|
```
|
|
366
381
|
|
|
367
382
|
Do not add additional layers unless they solve an actual problem.
|
|
@@ -500,11 +515,36 @@ Do not hide a control a component creates: configure the component through its p
|
|
|
500
515
|
|
|
501
516
|
If the props cannot express it, say so instead of working around it.
|
|
502
517
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
518
|
+
A theme is a class on `<html>`. `Application.setTheme( name )` puts `x4theme-<name>` there and
|
|
519
|
+
takes off the one before; `Application.setTheme( "" )` goes back to the light theme, the default;
|
|
520
|
+
`Application.getTheme( )` gives the name. x4 has one theme: `"dark"`, like Dark Modern of VS Code.
|
|
521
|
+
|
|
522
|
+
An application writes its own theme in its CSS, under `html.x4theme-<name>`, the way
|
|
523
|
+
`themes.scss` writes the dark one:
|
|
524
|
+
|
|
525
|
+
- the colour scale, `--color-primary-a0` (the background) to `--color-primary-a100` (the text).
|
|
526
|
+
The greys of x4 (backgrounds, texts, borders) are mixes of `a0` and `a100`: a scale whose
|
|
527
|
+
`a0` is dark and `a100` light gives them dark too;
|
|
528
|
+
- the global variables the scale does not give, when the theme wants other values:
|
|
529
|
+
`--background-*`, `--text-*`, `--border*`, `--disabled-*`, `--accent-*`, `--icon-color*`,
|
|
530
|
+
`--alert-*`, `--warning-*`, `--success-*`;
|
|
531
|
+
- `color-scheme: dark` for a dark theme: the native parts (select, scroll bars, spinners).
|
|
532
|
+
|
|
533
|
+
The variables of a component (`--listbox-*`, `--dialog-*`…, listed in the `@cssvar` of its
|
|
534
|
+
class) point to the global ones: the theme changes them all at once. Set one only when that
|
|
535
|
+
component must differ from the rest; do not recolour components one by one.
|
|
536
|
+
|
|
537
|
+
```css
|
|
538
|
+
html.x4theme-pink {
|
|
539
|
+
--color-primary-a0: #fff5f8;
|
|
540
|
+
/* … a5 to a90 … */
|
|
541
|
+
--color-primary-a100: #3a1020;
|
|
542
|
+
}
|
|
543
|
+
```
|
|
506
544
|
|
|
507
|
-
|
|
545
|
+
```ts
|
|
546
|
+
Application.setTheme( "pink" );
|
|
547
|
+
```
|
|
508
548
|
|
|
509
549
|
---
|
|
510
550
|
|
|
@@ -602,7 +642,7 @@ Component event
|
|
|
602
642
|
↓
|
|
603
643
|
Application operation
|
|
604
644
|
↓
|
|
605
|
-
State change /
|
|
645
|
+
State change / global message
|
|
606
646
|
↓
|
|
607
647
|
Existing Components update
|
|
608
648
|
```
|
|
@@ -643,6 +683,15 @@ const app_fr = { app: { open: "Ouvrir", close: "Fermer" } };
|
|
|
643
683
|
const app_en: typeof app_fr = { app: { open: "Open", close: "Close" } };
|
|
644
684
|
```
|
|
645
685
|
|
|
686
|
+
`_tr` is typed with the texts of x4 only: `_tr.app.open` does not compile. Give the
|
|
687
|
+
application its own typed view of `_tr`, once, from its first language:
|
|
688
|
+
|
|
689
|
+
```ts
|
|
690
|
+
export const tx = _tr as typeof _tr & typeof app_fr;
|
|
691
|
+
|
|
692
|
+
new Button( { label: tx.app.open } );
|
|
693
|
+
```
|
|
694
|
+
|
|
646
695
|
---
|
|
647
696
|
|
|
648
697
|
## Final rule
|
package/package.json
CHANGED
|
@@ -19,6 +19,8 @@ const x4icons = {
|
|
|
19
19
|
danger: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24V264c0 13.3-10.7 24-24 24s-24-10.7-24-24V152c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>`,
|
|
20
20
|
loading: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor"><!--!Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc.--><path d="M207.4 20.4c2.4 8.5-2.6 17.3-11.2 19.7C101.5 66.2 32 153 32 256c0 123.7 100.3 224 224 224s224-100.3 224-224c0-103-69.5-189.8-164.3-215.9c-8.5-2.4-13.5-11.2-11.2-19.7s11.2-13.5 19.7-11.2C432.5 39.1 512 138.2 512 256c0 141.4-114.6 256-256 256S0 397.4 0 256C0 138.2 79.5 39.1 187.7 9.2c8.5-2.4 17.3 2.6 19.7 11.2z"/></svg>`,
|
|
21
21
|
close_box: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" fill="currentColor"><!--!Font Awesome Pro 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc.--><path d="M192 233.4L59.5 100.9 36.9 123.5 169.4 256 36.9 388.5l22.6 22.6L192 278.6 324.5 411.1l22.6-22.6L214.6 256 347.1 123.5l-22.6-22.6L192 233.4z"/></svg>`,
|
|
22
|
+
// a triangle, a "!" cut in it
|
|
23
|
+
warning: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor"><path fill-rule="evenodd" d="M8 1L15.5 14.5H.5zM7.25 5.5h1.5V10h-1.5zM7 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0z"/></svg>`,
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
export default x4icons;
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import { Component, ComponentEvent, ComponentEvents, EvChange } from '../../core/component';
|
|
18
18
|
import { EventCallback } from '../../core/core_events';
|
|
19
|
-
import {
|
|
19
|
+
import { kbNav } from '../../core/core_tools';
|
|
20
20
|
|
|
21
21
|
import { HBox } from '../boxes/boxes';
|
|
22
22
|
import { DropdownList } from '../combobox/combobox';
|
|
@@ -96,8 +96,8 @@ const render_id_item = ( item: ListItem ) => {
|
|
|
96
96
|
* })
|
|
97
97
|
*/
|
|
98
98
|
|
|
99
|
-
@class_ns( "x4" )
|
|
100
99
|
export class AutoComplete extends TextEdit<AutoCompleteEvents> {
|
|
100
|
+
static classPrefix = "x4";
|
|
101
101
|
|
|
102
102
|
private _popup: DropdownList;
|
|
103
103
|
|
|
@@ -16,16 +16,30 @@
|
|
|
16
16
|
|
|
17
17
|
@use "../shared.scss";
|
|
18
18
|
|
|
19
|
+
:root {
|
|
20
|
+
--box-disabled-opacity: 0.5; // a disabled Box: its content faded
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
.x4box {
|
|
20
|
-
// reset fieldset
|
|
24
|
+
// reset a box built with tag: "fieldset"
|
|
21
25
|
margin: 0;
|
|
22
|
-
padding: 0;
|
|
26
|
+
padding: 0;
|
|
23
27
|
border: none;
|
|
24
28
|
|
|
25
29
|
min-width: 0;
|
|
26
30
|
min-height: 0;
|
|
27
31
|
flex-shrink: 0;
|
|
28
32
|
align-self: stretch;
|
|
33
|
+
|
|
34
|
+
// disabled: its content inert (enable) and faded
|
|
35
|
+
&[disabled] {
|
|
36
|
+
opacity: var( --box-disabled-opacity );
|
|
37
|
+
|
|
38
|
+
// a disabled Box in a disabled Box: opacity 1 for it, so faded once, not twice
|
|
39
|
+
.x4box[disabled] {
|
|
40
|
+
opacity: 1;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
29
43
|
}
|
|
30
44
|
|
|
31
45
|
.x4hbox {
|
|
@@ -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 { asap,
|
|
17
|
+
import { asap, isArray, isNumber } from '../../core/core_tools';
|
|
18
18
|
import { Component, ComponentEvents, ComponentProps, EvSelectionChange } from "../../core/component"
|
|
19
19
|
import { EventCallback } from '../../core/core_events';
|
|
20
20
|
import { HSizer, VSizer } from '../sizers/sizer';
|
|
@@ -28,6 +28,14 @@ export interface BoxProps extends ComponentProps {
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* A generic container component for grouping and laying out child components.
|
|
31
|
+
*
|
|
32
|
+
* A <div>. Disabled (disable( ), the prop disabled), its content is inert (no click, no
|
|
33
|
+
* focus, no input) and faded by --box-disabled-opacity.
|
|
34
|
+
*
|
|
35
|
+
* @cssvar
|
|
36
|
+
* ```
|
|
37
|
+
* --box-disabled-opacity
|
|
38
|
+
* ```
|
|
31
39
|
*
|
|
32
40
|
* The `refs` object is a typed directory of the children you need to reach
|
|
33
41
|
* again later. Assign a child to `refs` as you build the content: the
|
|
@@ -60,11 +68,12 @@ export interface BoxProps extends ComponentProps {
|
|
|
60
68
|
*
|
|
61
69
|
*/
|
|
62
70
|
|
|
63
|
-
@class_ns( "x4" )
|
|
64
71
|
export class Box<P extends BoxProps=BoxProps,E extends ComponentEvents=ComponentEvents> extends Component<P,E> {
|
|
72
|
+
static classPrefix = "x4";
|
|
73
|
+
|
|
65
74
|
protected refs: Record<string,Component> = {};
|
|
66
75
|
constructor( props: P ) {
|
|
67
|
-
super( { tag: "
|
|
76
|
+
super( { tag: "div", ...props } );
|
|
68
77
|
}
|
|
69
78
|
}
|
|
70
79
|
|
|
@@ -74,8 +83,9 @@ export class Box<P extends BoxProps=BoxProps,E extends ComponentEvents=Component
|
|
|
74
83
|
* Arranges child components in a horizontal line.
|
|
75
84
|
*/
|
|
76
85
|
|
|
77
|
-
@class_ns( "x4" )
|
|
78
86
|
export class HBox<P extends BoxProps=BoxProps,E extends ComponentEvents=ComponentEvents> extends Box<P,E> {
|
|
87
|
+
static classPrefix = "x4";
|
|
88
|
+
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
/**
|
|
@@ -84,8 +94,9 @@ export class HBox<P extends BoxProps=BoxProps,E extends ComponentEvents=Componen
|
|
|
84
94
|
* The CSS class for this component is automatically generated as `x4vbox`.
|
|
85
95
|
*/
|
|
86
96
|
|
|
87
|
-
@class_ns( "x4" )
|
|
88
97
|
export class VBox<P extends BoxProps=BoxProps,E extends ComponentEvents=ComponentEvents> extends Box<P,E> {
|
|
98
|
+
static classPrefix = "x4";
|
|
99
|
+
|
|
89
100
|
constructor( p: P ) {
|
|
90
101
|
super( p );
|
|
91
102
|
}
|
|
@@ -136,8 +147,9 @@ function split_content( panels: Component[], horz: boolean ) {
|
|
|
136
147
|
* })
|
|
137
148
|
*/
|
|
138
149
|
|
|
139
|
-
@class_ns( "x4" )
|
|
140
150
|
export class HSplitBox<P extends SplitBoxProps=SplitBoxProps,E extends ComponentEvents=ComponentEvents> extends HBox<P,E> {
|
|
151
|
+
static classPrefix = "x4";
|
|
152
|
+
|
|
141
153
|
constructor( props: P ) {
|
|
142
154
|
super( { ...props, content: split_content( props.content, true ) } );
|
|
143
155
|
}
|
|
@@ -149,8 +161,9 @@ export class HSplitBox<P extends SplitBoxProps=SplitBoxProps,E extends Component
|
|
|
149
161
|
* The CSS class for this component is automatically generated as `x4vsplitbox`.
|
|
150
162
|
*/
|
|
151
163
|
|
|
152
|
-
@class_ns( "x4" )
|
|
153
164
|
export class VSplitBox<P extends SplitBoxProps=SplitBoxProps,E extends ComponentEvents=ComponentEvents> extends VBox<P,E> {
|
|
165
|
+
static classPrefix = "x4";
|
|
166
|
+
|
|
154
167
|
constructor( props: P ) {
|
|
155
168
|
super( { ...props, content: split_content( props.content, false ) } );
|
|
156
169
|
}
|
|
@@ -206,8 +219,8 @@ interface StackItemEx extends StackItem {
|
|
|
206
219
|
* The CSS class for this component is automatically generated as `x4stackbox`.
|
|
207
220
|
*/
|
|
208
221
|
|
|
209
|
-
@class_ns( "x4" )
|
|
210
222
|
export class StackBox<P extends StackBoxProps = StackBoxProps, E extends StackeBoxEvents = StackeBoxEvents> extends Box<StackBoxProps,StackeBoxEvents> {
|
|
223
|
+
static classPrefix = "x4";
|
|
211
224
|
|
|
212
225
|
protected _items: StackItemEx[];
|
|
213
226
|
protected _cur: number;
|
|
@@ -371,8 +384,8 @@ export class StackBox<P extends StackBoxProps = StackBoxProps, E extends StackeB
|
|
|
371
384
|
* The CSS class for this component is automatically generated as `x4assistbox`.
|
|
372
385
|
*/
|
|
373
386
|
|
|
374
|
-
@class_ns( "x4" )
|
|
375
387
|
export class AssistBox extends StackBox {
|
|
388
|
+
static classPrefix = "x4";
|
|
376
389
|
|
|
377
390
|
/**
|
|
378
391
|
* Selects the next or previous page in the stack.
|
|
@@ -433,8 +446,8 @@ export interface GridBoxProps extends Omit<BoxProps,"content"> {
|
|
|
433
446
|
* Auto-generates CSS class: `x4gridbox`.
|
|
434
447
|
*/
|
|
435
448
|
|
|
436
|
-
@class_ns("x4")
|
|
437
449
|
export class GridBox<P extends GridBoxProps=GridBoxProps,E extends ComponentEvents=ComponentEvents> extends Box<P,E> {
|
|
450
|
+
static classPrefix = "x4";
|
|
438
451
|
|
|
439
452
|
constructor( props: P ) {
|
|
440
453
|
super( props );
|
|
@@ -543,8 +556,8 @@ interface MasonryProps extends Omit<BoxProps,"content"> {
|
|
|
543
556
|
* Auto-generates CSS class: `x4masonrybox`.
|
|
544
557
|
*/
|
|
545
558
|
|
|
546
|
-
@class_ns("x4")
|
|
547
559
|
export class MasonryBox extends Box<MasonryProps> {
|
|
560
|
+
static classPrefix = "x4";
|
|
548
561
|
|
|
549
562
|
constructor(props: MasonryProps ) {
|
|
550
563
|
super(props);
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
* that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
|
|
15
15
|
**/
|
|
16
16
|
|
|
17
|
-
import { class_ns } from '../../core/core_tools';
|
|
18
17
|
import { parseRoute, Router } from '../../core/core_router';
|
|
19
18
|
|
|
20
19
|
import { BoxProps, Button, HBox, Icon } from '../components';
|
|
@@ -64,8 +63,8 @@ interface BreadcrumbsProps extends BoxProps{
|
|
|
64
63
|
* ```
|
|
65
64
|
*/
|
|
66
65
|
|
|
67
|
-
@class_ns( "x4" )
|
|
68
66
|
export class Breadcrumbs extends HBox<BreadcrumbsProps,BreadcrumbEvents> {
|
|
67
|
+
static classPrefix = "x4";
|
|
69
68
|
|
|
70
69
|
constructor( props: BreadcrumbsProps ) {
|
|
71
70
|
super( props );
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import { Component, ComponentEvents, ComponentProps, Flex, Space } from '../../core/component';
|
|
18
18
|
import { EventCallback } from '../../core/core_events';
|
|
19
|
-
import {
|
|
19
|
+
import { isString } from '../../core/core_tools';
|
|
20
20
|
import { _tr } from '../../core/core_i18n'
|
|
21
21
|
|
|
22
22
|
import { Button } from '../button/button';
|
|
@@ -64,8 +64,8 @@ interface BtnGroupProps extends Omit<ComponentProps,"content"> {
|
|
|
64
64
|
* a packed group draws its buttons with the --ol-button-* variables of Button
|
|
65
65
|
*/
|
|
66
66
|
|
|
67
|
-
@class_ns( "x4" )
|
|
68
67
|
export class BtnGroup extends Box<BtnGroupProps,BtnGroupEvents> {
|
|
68
|
+
static classPrefix = "x4";
|
|
69
69
|
|
|
70
70
|
constructor( props: BtnGroupProps ) {
|
|
71
71
|
super( props );
|