x4js 2.3.29 → 2.3.31

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/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  </picture>
5
5
 
6
6
  # x4js
7
- **A TypeScript framework for people who build applications, not markup.**
7
+ **A full TypeScript framework for web and desktop applications.**
8
8
 
9
9
  ## Home page
10
10
  see [X4 home page](https://x4js.org)
package/aicontext.md CHANGED
@@ -42,7 +42,7 @@ CoreElement<E> named timers + event surface (no DOM)
42
42
  │ ├── SvgComponent → Chart
43
43
  │ ├── CSizer → HSizer / VSizer
44
44
  │ ├── FileDialog, ScrollView, Viewport, DockingView, Carousel, Flex, Space
45
- │ ├── HBox-based: ColorInput, Rating, Switch, Tag, Keyboard, TextEdit → AutoComplete, Breadcrumbs
45
+ │ ├── HBox-based: ColorInput, Rating, Switch, Tag, Keyboard, MenuBar, TextEdit → AutoComplete, Breadcrumbs
46
46
  │ └── VBox-based: Calendar, ColorPicker, FileDrop, Panel, PropertyGrid, TextArea, SettingsView, SettingItem
47
47
  └── Application<E> singleton, no DOM of its own
48
48
 
@@ -832,7 +832,10 @@ type TextEditProps = (TextInputProps | NumberProps | DateProps | TimeProps) & {
832
832
  labelWidth?: number; inputWidth?: number; inputId?: string
833
833
  inputGadgets?: Component[]; inputAttrs?: any
834
834
  }
835
- // style, tooltip, hidden, flex, stretch, height: on the box (label + field); dom_events: on the input
835
+ // style, tooltip, hidden, flex, stretch, height: on the box (label + field); dom_events: on the input,
836
+ // set when it is made, before the handlers of Input and of the class (the keys of an AutoComplete): they run
837
+ // first, and see the state before the class reacts (Enter before the suggestion is chosen). To run after them:
838
+ // textEdit.getInput( ).addDOMEvent( "keydown", ... ) once the component is made
836
839
  class TextEdit extends HBox {
837
840
  getValue(): string; setValue(value: string): void; getInput(): Input
838
841
  }
@@ -886,6 +889,7 @@ interface CheckboxProps extends ComponentProps {
886
889
  // button: "button danger"); without the box, the input is still reached by Tab (--checkbox-button-*, -danger-*)
887
890
  // A toggle button is a Checkbox with cls "button": getCheck() tells whether it is pressed, change when it changes
888
891
  // new Checkbox( { cls: "button", icon: icon_bold, tooltip: "Bold", change: ( ev ) => this.setBold( ev.value ) } )
892
+ // its icon 24px: --checkbox-button-icon-size, set by a class of the application ( .small { --checkbox-button-icon-size: 16px } )
889
893
  // in a Form: without value (or a boolean one), a boolean; with one, the value when checked, null when not
890
894
  class Checkbox extends Component {
891
895
  getCheck(): boolean; setCheck(ck: boolean): void
@@ -911,6 +915,7 @@ class Radio extends Component {
911
915
  }
912
916
  // cls: "button" (a button; "button outline" rounded), "danger" (checked in red, its round or its button: "button
913
917
  // danger"). Without the round, the input is still reached by Tab.
918
+ // its icon 24px: --radio-button-icon-size, set by a class of the application ( .small { --radio-button-icon-size: 16px } )
914
919
  ```
915
920
 
916
921
  ### `Switch`
@@ -1162,6 +1167,7 @@ interface TreeviewProps extends Omit<ComponentProps,"content"> {
1162
1167
  click?: EventCallback<EvClick>
1163
1168
  dblClick?: EventCallback<EvDblClick>
1164
1169
  selectionChange?: EventCallback<EvSelectionChange>
1170
+ contextMenu?: EventCallback<EvContextMenu> // as a Listbox: the item under the mouse selected, ev.context its id (null out of the items), no menu of the browser
1165
1171
  }
1166
1172
  // emptyMsg: as for a Listbox. Without it the text is _tr.global.empty_list ("Empty list").
1167
1173
  // There is no `renderer`: an item is drawn from its `text`, `iconId`, `cls` and `sub_cols`.
@@ -1194,6 +1200,7 @@ interface SpreadsheetColumn { // GridColumn +
1194
1200
  interface EvCellChange { row; col /* index */; colId /* store key */; value /* writable */; oldValue }
1195
1201
  class Spreadsheet extends Component {
1196
1202
  getSelection(): /* cell */; navigate(sens: kbNav): boolean; lock(lock): void
1203
+ clearSelection(notify = true): void // no cell selected; notify: selectionChange, its selection empty
1197
1204
  editCell(row, col, clear = false): boolean // cell must be visible
1198
1205
  stopEdit(commit = true): void; isEditing(): boolean
1199
1206
  }
@@ -1266,13 +1273,33 @@ class PropertyGrid extends VBox {
1266
1273
  interface MenuItem {
1267
1274
  text: string | UnsafeHtml
1268
1275
  cls?: string; icon?: string; menu?: Menu; disabled?: boolean
1276
+ checked?: boolean | (() => boolean) // a check as icon when there is no icon
1269
1277
  click?: DOMEventHandler
1278
+ shortcut?: string // "Mod+S": shown at the right (shortcutText), not added: see addShortcut
1270
1279
  }
1271
1280
  type MenuElement = MenuItem | Component | string // string "-" = title/separator
1272
1281
  interface MenuProps extends Omit<PopupProps,"content"> { items: MenuElement[] }
1273
1282
  class Menu extends Popup {} // displayAt/displayNear/displayCenter/close
1274
1283
  ```
1275
1284
 
1285
+ ### `MenuBar`
1286
+
1287
+ ```ts
1288
+ interface MenuBarItem {
1289
+ text: string; icon?: string; disabled?: boolean
1290
+ items: MenuElement[] | (() => MenuElement[]) // its Menu, built at each opening; a function is called then
1291
+ }
1292
+ interface MenuBarProps extends Omit<BoxProps,"content"> { items: MenuBarItem[] }
1293
+ class MenuBar extends HBox {}
1294
+ // a click on a title opens its menu under it, a second one closes it; while a menu is open, the mouse
1295
+ // on another title opens that one. No keyboard. For a web application: a desktop application
1296
+ // (Electron...) should use the menu of the system, not a MenuBar
1297
+ new MenuBar({ items: [
1298
+ { text: "File", items: [{ text: "Open…", shortcut: "Mod+O", click: open }, "-", { text: "Quit", click: quit }] },
1299
+ { text: "Edit", items: () => [{ text: "Undo", disabled: !canUndo(), click: undo }] },
1300
+ ] })
1301
+ ```
1302
+
1276
1303
  ### `BtnGroup`
1277
1304
 
1278
1305
  ```ts
@@ -1335,7 +1362,9 @@ class Tabs extends Box {
1335
1362
 
1336
1363
  ```ts
1337
1364
  interface PopupProps extends ComponentProps {
1338
- autoClose?: boolean | string // true, or a group name
1365
+ autoClose?: boolean | string // true, or a group name: closed by a press outside; while it is shown,
1366
+ // a wheel outside every popup is ignored (the page does not scroll);
1367
+ // a press in a popup closes the other groups opened after it, not its own
1339
1368
  sizable?: boolean; movable?: boolean
1340
1369
  }
1341
1370
  class Popup extends Box {
@@ -1343,7 +1372,8 @@ class Popup extends Box {
1343
1372
  displayNear(rc: Rect, dst?: string, src?: string, offset?: {x,y}): void
1344
1373
  displayCenter(center?: boolean): void
1345
1374
  close(): void
1346
- dismiss(after?: boolean): void
1375
+ dismiss(after?: boolean): void // closes the popups of its autoClose group; after: only those opened
1376
+ // after it (none when it is no longer open)
1347
1377
  isOpen(): boolean
1348
1378
  // show()/hide() inherited from Component; show() routes to displayCenter()
1349
1379
  }
@@ -1385,6 +1415,10 @@ class MessageBox extends Dialog {
1385
1415
  static showAsync(msg: string|UnsafeHtml, buttons?: BtnGroupItem[], title?: string,
1386
1416
  icon_type?: "error"|"question"): Promise<string>
1387
1417
  }
1418
+ // no icon_type: an error (its icon, the title _tr.global.error, the class "error"); its title bar and its buttons
1419
+ // in the colours of the alerts: a filled button in --alert-*, an outline one with a red border when focused.
1420
+ // The "!" of an error, the "?" of a question: white in the disc of the icon (--msgbox-icon-mark-color,
1421
+ // --msgbox-question-icon-mark-color)
1388
1422
  class InputBox extends Dialog {
1389
1423
  getValue(): string
1390
1424
  // static showAsync(msg, value, title?, options?: {password?, trim?}): Promise<string> (null if cancelled or closed)
@@ -1759,6 +1793,7 @@ new Button({ label: 'Paste', click: async () => {
1759
1793
  - The events go to the component that has the focus: give it a `tabindex`.
1760
1794
  - 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.
1761
1795
  - An image is written as a PNG; another format is converted. Files can be read in a `paste` event only, and cannot be written.
1796
+ - An image offered in several formats (Inkscape: a SVG, then a PNG) is read as the PNG, by `getClipboard` and `getEventClipboard`; else the first image.
1762
1797
 
1763
1798
  ---
1764
1799
 
@@ -1834,14 +1869,15 @@ by the browser, in its own locale, whatever this one.
1834
1869
  --color-primary-a0 … --color-primary-a100 /* the scale: a0 the background, a100 the text, the accent between */
1835
1870
  /* the globals the components use: --background-primary / -secondary / -ternary / -hover / -active / -input,
1836
1871
  --text-primary / -secondary / -ternary, --border / -light / -hover / -focus, --accent-background / -hover /
1837
- -focus / -active / -light, --accent-color (the text on the accent), --disabled-*, --icon-color / -active,
1838
- --alert-*, --warning-*, --success-* */
1872
+ -focus / -active / -light, --accent-color (the text on the accent), --accent-text (the accent as a text on the
1873
+ background: a button checked or focused; light in the dark theme), --disabled-*, --icon-color / -active,
1874
+ --alert-*, --warning-*, --success-*, --shadow-color (the shadow of what floats: dialog, list, notification, tooltip) */
1839
1875
  ```
1840
1876
  // The greys are mixes of a0 and a100: a theme that reverses the scale gets them light on dark.
1841
1877
  // Each component reads its own variables (the @cssvar of its class), which point to these globals:
1842
1878
  // a theme redefines the globals, or one variable of a component.
1843
1879
  // Themes: Application.setTheme(name) puts the class x4theme-<name> on <html>. "" is the light theme (by default),
1844
- // "dark" the dark theme of x4 (the greys of VS Code Dark Modern, its blues from #5796ca). An application adds its own theme in its css,
1880
+ // "dark" the dark theme of x4 (the greys of VS Code Dark Modern, its blues from #2d6fa5: the accent #245883). An application adds its own theme in its css,
1845
1881
  // html.x4theme-pink { --color-primary-a0: ...; ... }, then Application.setTheme("pink").
1846
1882
  // The font: --font-name and --font-size (14px), on :root (normalize.scss); body takes them. To change the size
1847
1883
  // of the text, set --font-size ( :root { --font-size: 13px } ), not the font-size of <html>: the components
@@ -2218,6 +2254,8 @@ class CMover {
2218
2254
  }
2219
2255
  createPainter(c2d: CanvasRenderingContext2D, w: number, h: number): CanvasEx // Canvas painter
2220
2256
  initTooltips(): void // initialize the global tooltip system
2257
+ // a tooltip: 30vw wide at most (--tooltip-max-width; none: the width of its text); it goes at a click, and when its
2258
+ // element is removed or hidden (checked every second while it is shown)
2221
2259
  ```
2222
2260
 
2223
2261
  JSX, in `.tsx` files: in tsconfig.json `"jsx": "react"` and `"jsxFactory": "x4_react.create_element"` (esbuild reads
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "x4js",
3
- "version": "2.3.29",
3
+ "version": "2.3.31",
4
+ "description": "A full TypeScript framework for web and desktop applications: components written as classes (layouts, inputs, lists, grids, dialogs, forms, charts...) and a command line to create, serve and build them.",
4
5
  "license": "MIT",
5
6
  "type": "module",
6
7
  "main": "src/x4.ts",
@@ -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 to the right: a menu item that opens a sub menu
23
+ caret: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 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="M246.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-128-128c-9.2-9.2-22.9-11.9-34.9-6.9s-19.8 16.6-19.8 29.6l0 256c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l128-128z"/></svg>`,
22
24
  // a triangle, a "!" cut in it
23
25
  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>`,
24
26
  };
@@ -47,7 +47,7 @@
47
47
  --ol-button-icon-color: var( --text-primary );
48
48
 
49
49
  --ol-button-border-focus: var( --accent-background-focus );
50
- --ol-button-color-focus: var( --accent-background );
50
+ --ol-button-color-focus: var( --accent-text );
51
51
  }
52
52
 
53
53
  .x4button {
@@ -29,9 +29,10 @@
29
29
 
30
30
  --checkbox-button-border: var( --border );
31
31
  --checkbox-button-color: var( --text-ternary );
32
- --checkbox-button-color-checked: var( --accent-background-active );
32
+ --checkbox-button-color-checked: var( --accent-text );
33
33
  --checkbox-button-background-checked: var( --background-secondary );
34
34
  --checkbox-button-border-checked: var( --accent-background );
35
+ --checkbox-button-icon-size: 24px; // set by a class of the application on a checkbox: it wins over :root
35
36
 
36
37
  --checkbox-danger-color: var( --alert-background );
37
38
  --checkbox-danger-background: var( --alert-background-light );
@@ -187,8 +188,8 @@
187
188
  #icon {
188
189
  color: var( --checkbox-ink );
189
190
  fill: var( --checkbox-ink );
190
- width: 24px;
191
- height: 24px;
191
+ width: var( --checkbox-button-icon-size );
192
+ height: var( --checkbox-button-icon-size );
192
193
  }
193
194
  }
194
195
 
@@ -54,6 +54,7 @@ interface CheckboxProps extends ComponentProps {
54
54
  * --checkbox-button-color-checked
55
55
  * --checkbox-button-background-checked
56
56
  * --checkbox-button-border-checked
57
+ * --checkbox-button-icon-size
57
58
  * --checkbox-danger-color
58
59
  * --checkbox-danger-background
59
60
  * ```
@@ -20,6 +20,7 @@
20
20
  --color-dialog-caption: var( --accent-background-active );
21
21
  --color-dialog-caption-text: var( --accent-color );
22
22
  --color-dialog-background: var( --background-primary );
23
+ --color-dialog-border: transparent; // its shadow is enough on a light background
23
24
  }
24
25
 
25
26
  .x4dialog {
@@ -28,6 +29,7 @@
28
29
 
29
30
  position: absolute !important;
30
31
  background-color: var( --color-dialog-background );
32
+ border: 1px solid var( --color-dialog-border );
31
33
  max-width: 99vw;
32
34
  outline: none;
33
35
 
@@ -55,6 +55,7 @@ interface DialogEvents extends PopupEvents {
55
55
  * --color-dialog-caption
56
56
  * --color-dialog-caption-text
57
57
  * --color-dialog-background
58
+ * --color-dialog-border
58
59
  * ```
59
60
  */
60
61
 
@@ -25,6 +25,15 @@
25
25
  --menuitem-color-active: var( --accent-color-active );
26
26
  --menuitem-background-hover: var( --accent-background-hover );
27
27
  --menuitem-color-hover: var( --accent-color-hover );
28
+ --menuitem-shortcut-color: var( --text-secondary );
29
+
30
+ --menubar-background: var( --background-primary );
31
+ --menubar-border: var( --border );
32
+
33
+ --menubaritem-color: var( --text-primary );
34
+ --menubaritem-background-hover: var( --accent-background-hover );
35
+ --menubaritem-color-hover: var( --accent-color-hover );
36
+ --menubaritem-color-disabled: var( --disabled-color-dark );
28
37
  }
29
38
 
30
39
  .x4menu {
@@ -84,6 +93,20 @@
84
93
  @extend %flex;
85
94
  }
86
95
 
96
+ // the keys of the command: after the text, which takes the width, at the right
97
+ #shortcut {
98
+ color: var(--menuitem-shortcut-color);
99
+ padding-left: 16px;
100
+ white-space: nowrap;
101
+ }
102
+
103
+ // hovered, active, disabled: in the colour of the text
104
+ &:hover, &.opened, &:active, &[disabled] {
105
+ #shortcut {
106
+ color: inherit;
107
+ }
108
+ }
109
+
87
110
  &.title {
88
111
  color: var( --text-primary );
89
112
  cursor: auto;
@@ -92,12 +115,10 @@
92
115
  font-weight: 600;
93
116
  }
94
117
 
95
- &.popup {
96
- &:after {
97
- //<todo: see that
98
- content: url("./caret-right-solid.svg");
99
- width: 6px;
100
- }
118
+ // the triangle of a sub menu: after the text, which takes the width, at the right
119
+ #sub {
120
+ width: 6px;
121
+ height: 12px;
101
122
  }
102
123
 
103
124
  &:hover:not(.title),
@@ -126,4 +147,40 @@
126
147
  border-bottom: 1px solid var(--border);
127
148
  margin-bottom: 4px;
128
149
  }
150
+ }
151
+
152
+ .x4menubar {
153
+ padding: 0 4px;
154
+
155
+ background-color: var(--menubar-background);
156
+ border-bottom: 1px solid var(--menubar-border);
157
+
158
+ .x4cmenubaritem {
159
+ @extend %hbox;
160
+
161
+ gap: 6px;
162
+ padding: 4px 10px;
163
+ color: var(--menubaritem-color);
164
+ cursor: pointer;
165
+ user-select: none;
166
+
167
+ transition: background-color 0.1s;
168
+
169
+ #icon {
170
+ width: 16px;
171
+ height: 16px;
172
+ }
173
+
174
+ // hovered, or its menu open: as an item of the menu
175
+ &:hover:not([disabled]),
176
+ &.opened {
177
+ background-color: var(--menubaritem-background-hover);
178
+ color: var(--menubaritem-color-hover);
179
+ }
180
+
181
+ &[disabled] {
182
+ color: var(--menubaritem-color-disabled);
183
+ cursor: auto;
184
+ }
185
+ }
129
186
  }
@@ -17,9 +17,11 @@
17
17
  import { Component } from "../../core/component"
18
18
  import { DOMEventHandler } from '../../core/core_dom';
19
19
  import { Timer, UnsafeHtml, isFunction, isString } from '../../core/core_tools';
20
+ import { shortcutText } from '../../core/core_shortcuts';
20
21
 
21
22
  import { Popup, PopupProps } from '../popup/popup';
22
23
  import { Icon } from '../icon/icon';
24
+ import { BoxProps, HBox } from '../boxes/boxes';
23
25
 
24
26
  import icons from "../assets/icons";
25
27
  import "./menu.module.scss"
@@ -38,6 +40,13 @@ export interface MenuItem {
38
40
  disabled?: boolean;
39
41
  checked?: boolean | ( () => boolean);
40
42
  click?: DOMEventHandler;
43
+
44
+ /**
45
+ * the keys of the command, shown at the right of the item: "Mod+S" is written
46
+ * "Ctrl+S", "⌘S" on a Mac (shortcutText). Shown only: the shortcut itself
47
+ * is added by Application.addShortcut or Component.addShortcut
48
+ */
49
+ shortcut?: string;
41
50
  }
42
51
 
43
52
  export type MenuElement = MenuItem | Component | string;
@@ -56,6 +65,7 @@ export interface MenuProps extends Omit<PopupProps,"content"> {
56
65
  * --menuitem-color-active
57
66
  * --menuitem-background-hover
58
67
  * --menuitem-color-hover
68
+ * --menuitem-shortcut-color
59
69
  * ```
60
70
  */
61
71
 
@@ -95,8 +105,11 @@ class CMenuItem extends Component {
95
105
  }
96
106
 
97
107
  this.setContent( [
98
- new Icon({ id:"icon", iconId }),
99
- new Component( { id: "text", content: itm.text } )
108
+ new Icon({ id:"icon", iconId }),
109
+ new Component( { id: "text", content: itm.text } ),
110
+ itm.shortcut ? new Component( { id: "shortcut", content: shortcutText( itm.shortcut ) } ) : null,
111
+ // a sub menu: a triangle at the right, in the colour of the text
112
+ itm.menu ? new Icon( { id: "sub", iconId: icons.caret } ) : null,
100
113
  ]);
101
114
 
102
115
  if( itm.menu ) {
@@ -205,3 +218,134 @@ export class Menu extends Popup {
205
218
  }
206
219
  }
207
220
 
221
+ /**
222
+ *
223
+ */
224
+
225
+ export interface MenuBarItem {
226
+ text: string;
227
+ icon?: string;
228
+ disabled?: boolean;
229
+
230
+ /** its menu; a function is called at each opening: it gives the items of that time */
231
+ items: MenuElement[] | ( ( ) => MenuElement[] );
232
+ }
233
+
234
+ export interface MenuBarProps extends Omit<BoxProps,"content"> {
235
+ items: MenuBarItem[];
236
+ }
237
+
238
+ /**
239
+ * a title of the bar
240
+ */
241
+
242
+ class CMenuBarItem extends Component {
243
+ static classPrefix = "x4";
244
+
245
+ constructor( itm: MenuBarItem ) {
246
+ super( { disabled: itm.disabled } );
247
+
248
+ this.setContent( [
249
+ itm.icon ? new Icon( { id: "icon", iconId: itm.icon } ) : null,
250
+ new Component( { id: "text", content: itm.text } ),
251
+ ] );
252
+ }
253
+ }
254
+
255
+ /**
256
+ * A row of titles, each one opens its menu under it.
257
+ * A click on a title opens its menu, a second one closes it. While a menu is open, the
258
+ * mouse on another title opens the menu of that one.
259
+ *
260
+ * For a web application. A desktop application (Electron...) should not use it: the menu
261
+ * of the system is preferable, where the user expects it (the top of the screen on a Mac)
262
+ * and with the keyboard of the system.
263
+ *
264
+ * @example
265
+ * new MenuBar( { items: [
266
+ * { text: "File", items: [ { text: "Open…", shortcut: "Mod+O", click: open }, "-", { text: "Quit", click: quit } ] },
267
+ * { text: "Edit", items: ( ) => [ { text: "Undo", disabled: !canUndo( ), click: undo } ] },
268
+ * ] } );
269
+ *
270
+ * @cssvar
271
+ * ```
272
+ * --menubar-background
273
+ * --menubar-border
274
+ * --menubaritem-color
275
+ * --menubaritem-background-hover
276
+ * --menubaritem-color-hover
277
+ * --menubaritem-color-disabled
278
+ * ```
279
+ */
280
+
281
+ export class MenuBar extends HBox<MenuBarProps> {
282
+ static classPrefix = "x4";
283
+
284
+ private _menu: Menu; // the menu open, null when none
285
+ private _title: CMenuBarItem; // the title of that menu
286
+ private _closed: CMenuBarItem; // the title whose menu closed last
287
+ private _closedAt = -1; // when (performance.now)
288
+
289
+ constructor( props: MenuBarProps ) {
290
+ super( props );
291
+
292
+ this.setContent( props.items.map( itm => {
293
+ const title = new CMenuBarItem( itm );
294
+
295
+ // The menu closes by itself as soon as the button goes down outside of it,
296
+ // before the title hears of it: it was the menu of this title if it closed
297
+ // since this very press began.
298
+ let wasOpen = false;
299
+
300
+ title.addDOMEvent( "pointerdown", ev => { wasOpen = this._closed===title && this._closedAt >= ev.timeStamp; } );
301
+ title.addDOMEvent( "click", ( ) => {
302
+ if( !wasOpen ) {
303
+ this._open( title, itm );
304
+ }
305
+ } );
306
+
307
+ title.addDOMEvent( "mouseenter", ( ) => {
308
+ if( this._menu && this._title!==title ) {
309
+ this._open( title, itm );
310
+ }
311
+ } );
312
+
313
+ return title;
314
+ } ) );
315
+ }
316
+
317
+ /**
318
+ * closes the menu open, opens the one of the title under it
319
+ */
320
+
321
+ private _open( title: CMenuBarItem, itm: MenuBarItem ) {
322
+ if( title.isDisabled( ) ) {
323
+ return;
324
+ }
325
+
326
+ if( this._menu ) {
327
+ this._menu.dismiss( );
328
+ }
329
+
330
+ const menu = new Menu( { items: isFunction( itm.items ) ? itm.items( ) : itm.items } );
331
+
332
+ menu.on( "closed", ( ) => {
333
+ title.removeClass( "opened" );
334
+
335
+ this._closed = title;
336
+ this._closedAt = performance.now( );
337
+
338
+ if( this._menu===menu ) {
339
+ this._menu = null;
340
+ this._title = null;
341
+ }
342
+ } );
343
+
344
+ this._menu = menu;
345
+ this._title = title;
346
+ title.addClass( "opened" );
347
+
348
+ menu.displayNear( title.getBoundingRect( ), "top left", "bottom left" );
349
+ }
350
+ }
351
+
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor"><!--!Font Awesome Free 6.6.0 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 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor"><!--!Font Awesome Free 6.6.0 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 512z"/><path class="mark" d="M256 128c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" fill="currentColor"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 240C302.3 240 288 254.3 288 272C288 285.3 277.3 296 264 296C250.7 296 240 285.3 240 272C240 227.8 275.8 192 320 192C364.2 192 400 227.8 400 272C400 319.2 364 339.2 344 346.5L344 350.3C344 363.6 333.3 374.3 320 374.3C306.7 374.3 296 363.6 296 350.3L296 342.2C296 321.7 310.8 307 326.1 302C332.5 299.9 339.3 296.5 344.3 291.7C348.6 287.5 352 281.7 352 272.1C352 254.4 337.7 240.1 320 240.1zM288 432C288 414.3 302.3 400 320 400C337.7 400 352 414.3 352 432C352 449.7 337.7 464 320 464C302.3 464 288 449.7 288 432z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" fill="currentColor"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576z"/><path class="mark" d="M320 240C302.3 240 288 254.3 288 272C288 285.3 277.3 296 264 296C250.7 296 240 285.3 240 272C240 227.8 275.8 192 320 192C364.2 192 400 227.8 400 272C400 319.2 364 339.2 344 346.5L344 350.3C344 363.6 333.3 374.3 320 374.3C306.7 374.3 296 363.6 296 350.3L296 342.2C296 321.7 310.8 307 326.1 302C332.5 299.9 339.3 296.5 344.3 291.7C348.6 287.5 352 281.7 352 272.1C352 254.4 337.7 240.1 320 240.1zM288 432C288 414.3 302.3 400 320 400C337.7 400 352 414.3 352 432C352 449.7 337.7 464 320 464C302.3 464 288 449.7 288 432z"/></svg>
@@ -22,6 +22,10 @@
22
22
  --msgbox-icon-color: var( --alert-background );
23
23
  --msgbox-question-icon-color: var( --accent-background ); // a question, an InputBox, a PromptBox
24
24
 
25
+ // the "!" and the "?" in the disc of the icon: as the text on the red, on the accent
26
+ --msgbox-icon-mark-color: var( --alert-color );
27
+ --msgbox-question-icon-mark-color: var( --accent-color );
28
+
25
29
  --msgbox-log-background: var( --background-ternary ); // the lines of a ProgressionBox
26
30
  --msgbox-log-border: var( --border );
27
31
  --msgbox-log-border-focus: var( --border-focus );
@@ -44,6 +48,22 @@
44
48
  }
45
49
  }
46
50
 
51
+ // the buttons of an error in the red of its title bar: a filled one, the focus of an outline
52
+ // one (its text stays the one of the text: a red text is not read on a dark background)
53
+ &.error {
54
+ --button-background: var( --alert-background );
55
+ --button-background-hover: var( --alert-background-active );
56
+ --button-background-focus: var( --alert-background-active );
57
+ --button-background-active: var( --alert-background-active );
58
+ --button-color: var( --alert-color );
59
+ --button-color-hover: var( --alert-color );
60
+ --button-color-focus: var( --alert-color );
61
+ --button-color-active: var( --alert-color );
62
+
63
+ --ol-button-border-focus: var( --alert-background );
64
+ --ol-button-color-focus: var( --ol-button-color );
65
+ }
66
+
47
67
  & .x4form {
48
68
  min-width: 400px;
49
69
 
@@ -51,6 +71,10 @@
51
71
  > .x4hbox > .x4icon {
52
72
  color: var( --msgbox-icon-color );
53
73
  height: 96px;
74
+
75
+ .mark {
76
+ fill: var( --msgbox-icon-mark-color );
77
+ }
54
78
  }
55
79
 
56
80
  .x4label {
@@ -61,6 +85,10 @@
61
85
 
62
86
  &.question .x4form > .x4hbox > .x4icon {
63
87
  color: var( --msgbox-question-icon-color );
88
+
89
+ .mark {
90
+ fill: var( --msgbox-question-icon-mark-color );
91
+ }
64
92
  }
65
93
  }
66
94
 
@@ -37,6 +37,8 @@ export interface MessageBoxProps extends DialogProps {
37
37
  * --msgbox-caption-color
38
38
  * --msgbox-icon-color
39
39
  * --msgbox-question-icon-color
40
+ * --msgbox-icon-mark-color
41
+ * --msgbox-question-icon-mark-color
40
42
  * --msgbox-log-background
41
43
  * --msgbox-log-border
42
44
  * --msgbox-log-border-focus
@@ -47,7 +49,11 @@ export interface MessageBoxProps extends DialogProps {
47
49
  * --msgbox-log-scrollbar
48
50
  * ```
49
51
  * --msgbox-caption-* and --msgbox-icon-color are the ones of an error; a question, an InputBox
50
- * and a PromptBox have the title bar of a Dialog and their icon in --msgbox-question-icon-color
52
+ * and a PromptBox have the title bar of a Dialog and their icon in --msgbox-question-icon-color.
53
+ * The "!" of an error and the "?" of a question are drawn in the disc of their icon, in
54
+ * --msgbox-icon-mark-color and --msgbox-question-icon-mark-color.
55
+ * The buttons of an error are in the colours of the alerts (--alert-*): a filled one, the border
56
+ * of an outline one that has the focus
51
57
  */
52
58
 
53
59
  export class MessageBox extends Dialog<DialogProps>
@@ -54,7 +54,10 @@
54
54
  color: var( --notification-icon-default );
55
55
  }
56
56
 
57
+ // a box does not shrink (.x4box): this one does, down to the frame, its close button kept in it
57
58
  .body {
59
+ flex-shrink: 1;
60
+
58
61
  .title {
59
62
  font-weight: bold;
60
63
  padding: 6px 6px 3px 6px;
@@ -64,6 +67,7 @@
64
67
  .text {
65
68
  padding: 3px 6px 6px 6px;
66
69
  color: var( --notification-text-color );
70
+ overflow-wrap: anywhere; // a long word, an address: cut at the edge
67
71
  }
68
72
  }
69
73