x4js 2.3.10 → 2.3.12

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
@@ -7,7 +7,19 @@ see [X4 home page](https://x4js.org)
7
7
  ## API Documentation
8
8
  see [GitHib](https://rlibre.github.io/x4/index.html)
9
9
 
10
- see [AI context](./aicontext.md)
10
+ ## Using x4js with an AI assistant
11
+ Three files written for AI assistants ship with the package (in `node_modules/x4js/`). Give them to your assistant before asking it to write x4js code:
12
+
13
+ - [aicontext.md](./aicontext.md): compact API reference, with exact signatures
14
+ - [aiguide.md](./aiguide.md): how to write idiomatic x4js code
15
+ - [aicli.md](./aicli.md): the `x4js` command line and `x4.config.json`
16
+
17
+ With Claude Code, install the x4js plugin instead: its skill reads these files from your project by itself.
18
+
19
+ ```
20
+ claude plugin marketplace add rlibre/x4
21
+ claude plugin install x4js@rlibre
22
+ ```
11
23
 
12
24
  ---
13
25
 
package/aicli.md ADDED
@@ -0,0 +1,194 @@
1
+ # x4js command line
2
+
3
+ `x4js` creates, serves and builds x4js applications. It bundles TypeScript and styles with esbuild; there is nothing else to configure.
4
+
5
+ ```
6
+ x4js create <project> [--template <name>] [--no-install]
7
+ x4js templates
8
+ x4js dev [--config <file>] [--host <host>] [--port <port>] [--http|--https] [--open]
9
+ x4js build [--config <file>] [--debug]
10
+ x4js --help | --version
11
+ ```
12
+
13
+ ## Install
14
+
15
+ The command ships with the `x4js` package. In a project created by `x4js create` it is already a dependency, and the npm scripts use it:
16
+
17
+ ```json
18
+ {
19
+ "scripts": {
20
+ "dev": "x4js dev",
21
+ "build": "x4js build"
22
+ }
23
+ }
24
+ ```
25
+
26
+ ```
27
+ npm run dev
28
+ npm run build
29
+ ```
30
+
31
+ Without a project yet, run it through npx: `npx x4js create my-app`.
32
+
33
+ ### Which x4js runs
34
+
35
+ There can be two copies on a machine: the one in the project's `node_modules` and a global one (`npm install -g x4js`).
36
+
37
+ | You type | Copy used |
38
+ |---|---|
39
+ | `npm run dev`, `npx x4js dev` | the project's own copy |
40
+ | `x4js dev` | the global copy |
41
+
42
+ A global copy older than the project's can behave differently. `dev` and `build` print their version on the first line; check it when something looks wrong.
43
+
44
+ ```
45
+ x4js 2.3.11
46
+ mode dev
47
+ config C:\work\my-app\x4.config.json
48
+ outdir C:\work\my-app\bin
49
+ server http://127.0.0.1:8000
50
+ ```
51
+
52
+ ## Commands
53
+
54
+ ### `x4js create <project>`
55
+
56
+ Creates a new project in the folder `<project>` from a template.
57
+
58
+ | Option | Effect |
59
+ |---|---|
60
+ | `--template <name>`, `-t <name>` | Template to use. Default: `app`. |
61
+ | `--no-install` | Do not run `npm install` after creating the files. |
62
+
63
+ - The project name becomes the package name: lowercase letters, digits, `.`, `_` and `-` only.
64
+ - The target folder must be empty or not exist.
65
+ - Templates are downloaded from the `rlibre/x4-templates` repository on GitHub, so the command needs network access.
66
+ - The `x4js` dependency of the new project is set to the version line of the command that created it (for example `2.3`).
67
+
68
+ ### `x4js templates`
69
+
70
+ Lists the available templates with a one-line description of each.
71
+
72
+ ### `x4js dev`
73
+
74
+ Builds the application, serves the output folder and rebuilds on every change.
75
+
76
+ | Option | Effect |
77
+ |---|---|
78
+ | `--config <file>` | Config file to use. Default: `x4.config.json`. |
79
+ | `--host <host>` | Address to listen on. Default: `127.0.0.1`. |
80
+ | `--port <port>` | Port to listen on. Default: the first free port from 8000. |
81
+ | `--http`, `--https` | Force the protocol, whatever the config says. |
82
+ | `--open` | Open the browser once the server is ready. |
83
+
84
+ - The page reloads when a source or a copied file changes. A change that only touches CSS is applied without reloading.
85
+ - Build errors and warnings are printed in the terminal.
86
+ - Editing the config file restarts the build.
87
+ - HTTPS needs a certificate and a key, declared in `dev.tls` (see below).
88
+
89
+ ### `x4js build`
90
+
91
+ Writes the application to the output folder.
92
+
93
+ | Option | Effect |
94
+ |---|---|
95
+ | `--config <file>` | Config file to use. Default: `x4.config.json`. |
96
+ | `--debug` | Debug build: not minified, with source maps. |
97
+
98
+ The output folder is **emptied before each build**. Do not keep hand-written files in it: put them in the sources and list them in `copy`.
99
+
100
+ | | `dev` | `build --debug` | `build` |
101
+ |---|---|---|---|
102
+ | Minified | no | no | yes |
103
+ | Source maps | separate `.map` files | separate `.map` files | none |
104
+ | `DEBUG_MODE` | `true` | `true` | `false` |
105
+ | Live reload | yes | no | no |
106
+
107
+ ## `x4.config.json`
108
+
109
+ The file sits at the root of the project. Every key is optional; without the file, the defaults below apply.
110
+
111
+ ```json
112
+ {
113
+ "entryPoints": ["src/main.ts"],
114
+ "outdir": "./bin",
115
+ "copy": [
116
+ { "from": "./src/assets", "to": "assets" },
117
+ { "from": "./src/index.html", "to": "index.html" }
118
+ ]
119
+ }
120
+ ```
121
+
122
+ | Key | Default | Meaning |
123
+ |---|---|---|
124
+ | `entryPoints` | `["src/main.ts"]` | Files to bundle. `src/main.ts` produces `main.js` and `main.css`. |
125
+ | `outdir` | `"./bin"` | Output folder. It cannot be the project root. |
126
+ | `copy` | `[]` | Files or folders copied to the output after each successful build. `to` is relative to `outdir`. |
127
+ | `external` | `[]` | Module names left out of the bundle. |
128
+ | `define` | `{}` | Constants replaced at build time. Values are JavaScript expressions written as strings, for example `"\"prod\""`. |
129
+ | `dev` | | Dev server settings: `host`, `port`, `https`, `tls`. |
130
+ | `esbuild` | `{}` | Native esbuild options, applied last. They override everything above. |
131
+
132
+ Relative paths are resolved from the project root. Paths accept environment variables, written `$NAME` or `${NAME}`; an undefined variable is an error.
133
+
134
+ ### The HTML page
135
+
136
+ The command does not generate an HTML page. Write `src/index.html` and copy it with `copy`, as in the example above. It loads the bundle by its output name:
137
+
138
+ ```html
139
+ <!DOCTYPE html>
140
+ <html>
141
+ <head>
142
+ <meta charset="utf8">
143
+ <link rel="stylesheet" href="main.css">
144
+ <script src="main.js"></script>
145
+ </head>
146
+ </html>
147
+ ```
148
+
149
+ ### HTTPS in dev
150
+
151
+ ```json
152
+ {
153
+ "dev": {
154
+ "https": true,
155
+ "tls": { "cert": "./certs/dev.crt", "key": "./certs/dev.key" }
156
+ }
157
+ }
158
+ ```
159
+
160
+ ## What the build handles
161
+
162
+ | Import | Result |
163
+ |---|---|
164
+ | `.ts`, `.js` | Bundled into one script per entry point (browser, ES2020). |
165
+ | `.scss`, `.sass`, `.css` | Compiled and bundled into one stylesheet per entry point. |
166
+ | `.less` | Same, if the `less` package is installed in the project (`npm install -D less`). |
167
+ | `.svg` imported from a script | A `data:` URL string, usable as an icon. |
168
+ | `.png`, `.jpg`, `.jpeg`, `.ttf`, `.woff`, `.woff2` | Copied to `assets/` with a hashed name. |
169
+
170
+ Two constants are always defined:
171
+
172
+ | Constant | Value |
173
+ |---|---|
174
+ | `DEBUG_MODE` | `true` in `dev` and `build --debug`, `false` in `build`. |
175
+ | `VERSION_ID` | Build date as a number, `YYMMDD`. |
176
+
177
+ Declare them once in your project to use them from TypeScript:
178
+
179
+ ```ts
180
+ declare const DEBUG_MODE: boolean;
181
+ declare const VERSION_ID: number;
182
+ ```
183
+
184
+ ## Troubleshooting
185
+
186
+ | Symptom | Cause |
187
+ |---|---|
188
+ | `index.html` is missing from the output folder | It is not listed in `copy`, or the command that ran is an older global copy. Compare the version on the first line with the project's. |
189
+ | A file placed in the output folder disappeared | `x4js build` empties the folder. Move the file to the sources and add it to `copy`. |
190
+ | `Cannot find package 'esbuild'` | The dependencies of the `x4js` copy being run are not installed. Run `npm install` in the project. |
191
+ | `Config file not found` | The file given to `--config` does not exist. Without `--config`, a missing `x4.config.json` is not an error. |
192
+ | `Environment variable '…' is not defined` | A path in the config uses `$NAME` and the variable is not set. |
193
+ | `LESS support requires the "less" package` | A `.less` file is imported and `less` is not installed in the project. |
194
+ | No colors in the terminal, or unwanted ones | Set `FORCE_COLOR=1` to force them, `NO_COLOR=1` to remove them. |
package/aicontext.md CHANGED
@@ -17,6 +17,7 @@ import { ... } from 'x4js' // src/x4.ts
17
17
  **Archived repository**: https://github.com/rlibre/x4js (read-only since March 2026)
18
18
  **License**: MIT
19
19
  **Quick start**: `npx x4js create demo` (list the templates with `npx x4js templates`)
20
+ **Claude Code plugin**: `claude plugin marketplace add rlibre/x4` then `claude plugin install x4js@rlibre` (skill that reads these files from the project)
20
21
 
21
22
  ---
22
23
 
@@ -26,7 +27,7 @@ import { ... } from 'x4js' // src/x4.ts
26
27
  CoreElement<E> named timers + event surface (no DOM)
27
28
  ├── Component<P,E> base DOM component
28
29
  │ ├── Box<P,E>
29
- │ │ ├── HBox / VBox
30
+ │ │ ├── HBox / VBox → HSplitBox / VSplitBox
30
31
  │ │ ├── StackBox → AssistBox
31
32
  │ │ ├── GridBox / MasonryBox
32
33
  │ │ ├── Form
@@ -35,10 +36,11 @@ CoreElement<E> named timers + event surface (no DOM)
35
36
  │ │ ├── BtnGroup, Tabs, Video, Saturation
36
37
  │ ├── Button, Input, Icon, Label, SimpleText, Checkbox, Radio, Link
37
38
  │ ├── Listbox, Gridview, Treeview, Select, Combobox, Slider, Gauge
38
- │ ├── Progress, TickLine, Canvas, Image, SvgComponent, Spreadsheet, MonacoEditor
39
+ │ ├── Progress, TickLine, Canvas, Image, Spreadsheet, MonacoEditor
40
+ │ ├── SvgComponent → Chart
39
41
  │ ├── CSizer → HSizer / VSizer
40
42
  │ ├── FileDialog, ScrollView, Viewport, Flex, Space
41
- │ ├── HBox-based: ColorInput, Rating, Switch, Keyboard, TextEdit, Breadcrumbs
43
+ │ ├── HBox-based: ColorInput, Rating, Switch, Keyboard, TextEdit → AutoComplete, Breadcrumbs
42
44
  │ └── VBox-based: Calendar, ColorPicker, FileDrop, Panel, PropertyGrid, TextArea
43
45
  └── Application<E> singleton, no DOM of its own
44
46
 
@@ -608,6 +610,26 @@ class Combobox extends Component {
608
610
  }
609
611
  ```
610
612
 
613
+ ### `AutoComplete` — text field with on-demand suggestions
614
+
615
+ ```ts
616
+ interface AutoCompleteProps extends TextEditProps { // label, labelWidth, required, placeholder, value...
617
+ search: (ev: EvSearch) => void // required; ev.text, ev.setItems(items) — may be called async
618
+ change? // EvAutoCompleteChange { value, item /* null = free text */ }
619
+ strict?: boolean // only a picked item (or empty) is accepted; default false
620
+ itemValue?: "text" | "id" // what goes into the field when an item is picked; default "text"
621
+ delay?: number // ms before search, default 250
622
+ minChars?: number // default 1
623
+ renderer?: (item: ListItem) => Component
624
+ }
625
+ class AutoComplete extends TextEdit {
626
+ getSelection(): ListboxID // id of the picked item, undefined for a free text
627
+ setSelection(item: ListItem): void; showDropDown(): void
628
+ }
629
+ // Use Combobox for a fixed, known list; AutoComplete when suggestions come from a search (server).
630
+ // In a Form the value is the field content (text or id, per itemValue).
631
+ ```
632
+
611
633
  ### `ColorInput`
612
634
 
613
635
  ```ts
@@ -710,12 +732,32 @@ interface SpreadsheetProps extends ComponentProps {
710
732
  columns: SpreadsheetColumn[] // required
711
733
  footer?: boolean
712
734
  rowClassifier?: RowClassifier
735
+ enterMove?: "down" | "right" | "none" // selection move after Enter in a cell editor (default "down")
713
736
  click?, dblClick?, contextMenu?, selectionChange?
737
+ cellChange? // EvCellChange, fired before an edited value is written to the store
738
+ cellKey? // EvCellKey { key, row, col, uievent }, fired for each key before the default handling
739
+ }
740
+ interface SpreadsheetColumn { // GridColumn +
741
+ editable?: boolean | ((row, col) => boolean) // inline edition (off by default), col = column index
742
+ editor?: (row, col, value) => Component // custom editor, must implement queryInterface("form-element")
714
743
  }
744
+ interface EvCellChange { row; col /* index */; colId /* store key */; value /* writable */; oldValue }
715
745
  class Spreadsheet extends Component {
716
746
  getSelection(): /* cell */; navigate(sens: kbNav): boolean; lock(lock): void
747
+ editCell(row, col, clear = false): boolean // cell must be visible
748
+ stopEdit(commit = true): void; isEditing(): boolean
717
749
  }
718
750
 
751
+ // Inline edition: F2 / Enter / double-click / typing starts, Enter / Tab / click elsewhere validates, Escape cancels,
752
+ // Delete clears, editable checkboxes toggle on double-click or Space (a single click only selects).
753
+ // Default editor is an Input matching the column type.
754
+ // cellKey: key hook (function keys...), not fired while a cell is edited; ev.preventDefault() skips the default handling.
755
+ // cellChange: ev.preventDefault() refuses the value, ev.value can be replaced.
756
+ // Select / Combobox in a cell (the store holds the id, use `formatter` to display the text):
757
+ // { id: 2, title: "Status", width: 120, editable: true,
758
+ // formatter: ( id ) => states.find( x => x.id===id )?.text,
759
+ // editor: ( row, col, value ) => new Select( { items: states, value } ) }
760
+
719
761
  // Store: in-memory cell store (extends CoreElement)
720
762
  class Store extends CoreElement {
721
763
  setData(row, col, data): void; getData(row, col): any; hasData(row, col?): boolean
@@ -724,6 +766,28 @@ class Store extends CoreElement {
724
766
  }
725
767
  ```
726
768
 
769
+ ### `Chart` — minimal svg chart (line, area, bar, pie, donut)
770
+
771
+ ```ts
772
+ interface ChartProps extends SvgProps {
773
+ type?: "line" | "area" | "bar" | "pie" | "donut" // default "line"
774
+ series: ChartSerie[] // required; pie & donut use the first serie
775
+ labels?: string[] // default: 1, 2, 3...
776
+ xaxis?: ChartAxis; yaxis?: ChartAxis // { name?, ticks?, min?, max?, step? }, all computed by default
777
+ legend?: boolean // default: shown when 2+ named series
778
+ stacked?: boolean
779
+ formatter?: (value: number) => string // default: chartFormatUnit (1.5k, 2M...)
780
+ click? // EvChartClick { serie, index, value }
781
+ }
782
+ interface ChartSerie { name?; values: number[] /* undefined = hole */; color?; fill?; dots? }
783
+ class Chart extends SvgComponent {
784
+ setType(type): void; setSeries(series, labels?): void; setLabels(labels): void
785
+ setTicks(sens: "x" | "y", ticks: ChartTick[]): void
786
+ }
787
+ // Colors: CSS variables --chart-color-1..8 (or serie.color). Size it with width/height/flex like any component.
788
+ // Minimal by design: no dual axis, mixed types, time axis, zoom or animation — use a dedicated library for those.
789
+ ```
790
+
727
791
  ### `PropertyGrid`
728
792
 
729
793
  ```ts
@@ -894,6 +958,13 @@ class Form extends Box {
894
958
  ## Sizers / viewport / misc
895
959
 
896
960
  ```ts
961
+ // PREFERRED for resizable panels: HSplitBox / VSplitBox (sizers are inserted automatically between panels).
962
+ // The panel with `flex` takes the remaining space, its neighbour is the one resized.
963
+ class HSplitBox extends HBox // panels side by side, resizable width: { content: Component[] }
964
+ class VSplitBox extends VBox // panels stacked, resizable height: { content: Component[] }
965
+ // new HSplitBox( { content: [ new Treeview( { width: 250 } ), new Panel( { flex: 1 } ) ] } )
966
+
967
+ // Low level: only place sizers by hand when a SplitBox does not fit (popup edges, header cells...).
897
968
  class CSizer extends Component // base splitter
898
969
  class HSizer extends CSizer // horizontal splitter
899
970
  class VSizer extends CSizer // vertical splitter
@@ -1325,6 +1396,7 @@ When creating a new x4js project, use these files as a base.
1325
1396
 
1326
1397
  `x4js dev` runs the dev server, `x4js build` writes `outdir` (add `--debug` for a debug build).
1327
1398
  Both read `x4.config.json` (change the file with `--config <file>`).
1399
+ Full reference of the command line (commands, options, config keys, troubleshooting): `aicli.md`.
1328
1400
 
1329
1401
  ### `x4.config.json`
1330
1402
 
package/aiguide.md CHANGED
@@ -132,6 +132,23 @@ super();
132
132
 
133
133
  when the component expects props.
134
134
 
135
+ When writing a custom component, pass the whole props object: do not remove your own values from it before calling `super`. The base class keeps what it receives in `this.props`, so they stay readable later.
136
+
137
+ ```ts
138
+ // avoid: search and strict are lost for this.props
139
+ const { search, strict, ...base } = props;
140
+ super(base);
141
+
142
+ // prefer: override only what must not reach the parent
143
+ super({ ...props, type: "text" });
144
+ ```
145
+
146
+ Connect the event callbacks given in props with `mapPropEvents`, not with hand-written `if (props.x) this.on(...)`:
147
+
148
+ ```ts
149
+ this.mapPropEvents(props, "change", "search");
150
+ ```
151
+
135
152
  For derived application components, prefer building the component's main content explicitly after `super(props)`.
136
153
 
137
154
  Ordinary nested x4 components may naturally use their `content` property where convenient.
@@ -192,6 +209,10 @@ Component objects
192
209
  Real DOM
193
210
  ```
194
211
 
212
+ `addDOMEvent` belongs to custom components (a canvas-based widget, for example).
213
+
214
+ On standard components, use their props and events.
215
+
195
216
  ---
196
217
 
197
218
  ## 6. Objects vs JSX
@@ -327,6 +348,12 @@ export const deviceModel = new DeviceModel();
327
348
 
328
349
  A model may encapsulate server access directly.
329
350
 
351
+ Views should never call `fetch`.
352
+
353
+ All server access goes through model classes deriving from one base class that owns the single `fetch` call (timeout, network failure, HTTP status, expired session, fetch mask).
354
+
355
+ Create one model per functional domain. It normally corresponds to one URL root.
356
+
330
357
  Keep responsibilities simple:
331
358
 
332
359
  ```text id="t39sbp"
@@ -467,6 +494,18 @@ Do not introduce a JavaScript theme abstraction simply to mirror CSS variables.
467
494
 
468
495
  Presentation belongs in CSS when CSS already solves the problem.
469
496
 
497
+ CSS styles a component; it never removes behaviour.
498
+
499
+ Do not hide a control a component creates: configure the component through its props (for example `closable: false`).
500
+
501
+ If the props cannot express it, say so instead of working around it.
502
+
503
+ To retheme an application, redefine the primary colour scale (`--color-primary-a5` … `--color-primary-a90`) once per theme.
504
+
505
+ Do not recolour components one by one, and do not override the variables derived from the scale.
506
+
507
+ A dark theme gets its own scale.
508
+
470
509
  ---
471
510
 
472
511
  ## 19. Browser and TypeScript capabilities
@@ -578,6 +617,34 @@ The goal is to build the application with the smallest set of mechanisms that ke
578
617
 
579
618
  ---
580
619
 
620
+ ## 23. Translations
621
+
622
+ x4 starts in French.
623
+
624
+ Application texts are declared with `addTranslation` for each language.
625
+
626
+ Select the language before any component is built, since a component reads its texts at construction.
627
+
628
+ Call `selectLanguage` after `addTranslation`:
629
+
630
+ ```ts
631
+ addTranslation("fr", { /* ... */ });
632
+ addTranslation("en", { /* ... */ });
633
+
634
+ selectLanguage("en");
635
+
636
+ // only now build the application components
637
+ ```
638
+
639
+ Type each additional language after the first one, so the compiler reports a missing text:
640
+
641
+ ```ts
642
+ const app_fr = { app: { open: "Ouvrir", close: "Fermer" } };
643
+ const app_en: typeof app_fr = { app: { open: "Open", close: "Close" } };
644
+ ```
645
+
646
+ ---
647
+
581
648
  ## Final rule
582
649
 
583
650
  When generating x4 code:
package/cli/main.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { failure } from "./log.mjs";
4
+ import { failure, info } from "./log.mjs";
5
5
 
6
6
  const cliDir = path.dirname(fileURLToPath(import.meta.url));
7
7
  const packageFile = path.join(cliDir, "..", "package.json");
@@ -46,11 +46,13 @@ try {
46
46
  break;
47
47
  }
48
48
  case "dev": {
49
+ info("x4js", version);
49
50
  const { dev } = await import("./dev.mjs");
50
51
  await dev(argv);
51
52
  break;
52
53
  }
53
54
  case "build": {
55
+ info("x4js", version);
54
56
  const { build } = await import("./build.mjs");
55
57
  await build(argv);
56
58
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x4js",
3
- "version": "2.3.10",
3
+ "version": "2.3.12",
4
4
  "type": "module",
5
5
  "main": "src/x4.ts",
6
6
  "module": "src/x4.ts",
@@ -21,6 +21,7 @@
21
21
  "src",
22
22
  "bin",
23
23
  "cli",
24
+ "aicli.md",
24
25
  "aicontext.md",
25
26
  "aiguide.md",
26
27
  "README.md",
@@ -0,0 +1,28 @@
1
+ /**
2
+ * ___ ___ __
3
+ * \ \/ / / _
4
+ * \ / /_| |_
5
+ * / \____ _|
6
+ * /__/\__\ |_|
7
+ *
8
+ * @file autocomplete.module.scss
9
+ * @author Etienne Cochard
10
+ *
11
+ * @copyright (c) 2026 R-libre ingenierie
12
+ *
13
+ * Use of this source code is governed by an MIT-style license
14
+ * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
+ **/
16
+
17
+ // the field itself is a textedit
18
+
19
+ .x4dropdownlist.autocomplete {
20
+ .x4item {
21
+ gap: 8px;
22
+
23
+ .id {
24
+ font-weight: 500;
25
+ min-width: 4em;
26
+ }
27
+ }
28
+ }