x4js 2.3.7 → 2.3.9

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 ADDED
@@ -0,0 +1,1403 @@
1
+ # x4js — AI Context
2
+
3
+ > Regenerated from the TypeDoc API documentation of the repository https://github.com/rlibre/x4 (commit `70fd5c0`).
4
+ > This file is a compact API reference intended for AI assistants. Signatures are exact; prose is condensed.
5
+
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ x4js is a TypeScript framework for Web or Desktop applications. Single entry point:
11
+
12
+ ```ts
13
+ import { ... } from 'x4js' // src/x4.ts
14
+ ```
15
+
16
+ **Active repository**: https://github.com/rlibre/x4
17
+ **Archived repository**: https://github.com/rlibre/x4js (read-only since March 2026)
18
+ **License**: MIT
19
+ **Quick start**: `npx x4js create demo` (list the templates with `npx x4js templates`)
20
+
21
+ ---
22
+
23
+ ## Class hierarchy
24
+
25
+ ```
26
+ CoreElement<E> named timers + event surface (no DOM)
27
+ ├── Component<P,E> base DOM component
28
+ │ ├── Box<P,E>
29
+ │ │ ├── HBox / VBox
30
+ │ │ ├── StackBox → AssistBox
31
+ │ │ ├── GridBox / MasonryBox
32
+ │ │ ├── Form
33
+ │ │ ├── Popup → Dialog → MessageBox / InputBox / PromptBox / ProgressionBox
34
+ │ │ │ → Menu, DropdownList, Notification
35
+ │ │ ├── BtnGroup, Tabs, Video, Saturation
36
+ │ ├── Button, Input, Icon, Label, SimpleText, Checkbox, Radio, Link
37
+ │ ├── Listbox, Gridview, Treeview, Select, Combobox, Slider, Gauge
38
+ │ ├── Progress, TickLine, Canvas, Image, SvgComponent, Spreadsheet, MonacoEditor
39
+ │ ├── CSizer → HSizer / VSizer
40
+ │ ├── FileDialog, ScrollView, Viewport, Flex, Space
41
+ │ ├── HBox-based: ColorInput, Rating, Switch, Keyboard, TextEdit, Breadcrumbs
42
+ │ └── VBox-based: Calendar, ColorPicker, FileDrop, Panel, PropertyGrid, TextArea
43
+ └── Application<E> singleton, no DOM of its own
44
+
45
+ EventSource<E> standalone event registry
46
+ ├── DataStore<T>
47
+ └── Router
48
+
49
+ CoreElement-based (non-DOM): DataProxy, DataView, Store
50
+ Standalone: Rect, Timer, Color, DataModel, StateManager, Stylesheet, ComputedStyle,
51
+ SvgBuilder, X4PDFBuilder, x4_react, CMover, UnsafeHtml (extends String)
52
+ ```
53
+
54
+ ---
55
+
56
+ ## `CoreElement<E extends EventMap>`
57
+
58
+ Base class without DOM. Named timers + event surface. (`src/core/core_element.ts`)
59
+
60
+ ```ts
61
+ // Timers (named — starting a timer with an existing name stops the previous one)
62
+ setTimeout(name: string, ms: number, callback: () => void): void
63
+ clearTimeout(name: string): void
64
+ setInterval(name: string, ms: number, callback: () => void): void
65
+ clearInterval(name: string): void
66
+ clearTimeouts(): void // stops ALL timers of the instance
67
+
68
+ // Events
69
+ on<K extends keyof E>(name: K, listener: (ev: E[K]) => void): { off(): void }
70
+ off<K extends keyof E>(name: K, listener: (ev: E[K]) => void): void
71
+ fire<K extends keyof E>(name: K, ev: E[K]): void
72
+ ```
73
+
74
+ `on()` returns `{ off() }` for easy unsubscription.
75
+
76
+ ---
77
+
78
+ ## `EventSource<E extends EventMap>`
79
+
80
+ Standalone reusable event registry, used by `DataStore`, `Router`. (`src/core/core_events.ts`)
81
+
82
+ ```ts
83
+ new EventSource<E>(source?: unknown)
84
+ addListener<K>(name: K, callback: (ev: E[K]) => void, capturing?: boolean): () => void
85
+ removeListener<K>(name: K, callback: (ev: E[K]) => void): void
86
+ fire<K>(name: K, ev: E[K]): void
87
+ ```
88
+
89
+ - `capturing = true` inserts at the head of the listener list.
90
+ - `fire()` automatically injects `source`, `type`, `stopPropagation()`, `preventDefault()` when absent.
91
+
92
+ ### `CoreEvent`
93
+
94
+ ```ts
95
+ interface CoreEvent {
96
+ readonly type?: string
97
+ readonly source?: CoreElement
98
+ readonly context?: any
99
+ propagationStopped?: boolean
100
+ defaultPrevented?: boolean
101
+ stopPropagation?(): void
102
+ preventDefault?(): void
103
+ }
104
+ ```
105
+
106
+ ### `EventMap`
107
+
108
+ Empty interface to extend in order to type events.
109
+
110
+ ```ts
111
+ interface MyEvents extends EventMap {
112
+ loaded: { ok: boolean }
113
+ tick: number
114
+ }
115
+ ```
116
+
117
+ ---
118
+
119
+ ## `Component<P extends ComponentProps, E extends ComponentEvents>`
120
+
121
+ Base DOM component. Creates an `HTMLElement`, generates CSS classes from the class hierarchy. (`src/core/component.ts`)
122
+
123
+ ### `ComponentProps`
124
+
125
+ ```ts
126
+ interface ComponentProps {
127
+ tag?: string // HTML tag (default "div")
128
+ ns?: string // SVG/MathML namespace
129
+ cls?: string // extra CSS classes
130
+ id?: string
131
+ style?: Partial<CSSStyleDeclaration>
132
+ attrs?: Record<string, string | number | boolean>
133
+ content?: ComponentContent
134
+ dom_events?: GlobalDOMEvents
135
+ width?: string | number
136
+ height?: string | number
137
+ flex?: number | boolean // true → "x4flex"; number → flex-grow
138
+ disabled?: boolean
139
+ hidden?: boolean
140
+ tooltip?: string
141
+ existingDOM?: HTMLElement // wrap an existing DOM node
142
+ }
143
+
144
+ type ComponentContent =
145
+ Component | Component[] | string | string[] | UnsafeHtml | UnsafeHtml[] | number | boolean
146
+ ```
147
+
148
+ ### Properties
149
+
150
+ ```ts
151
+ readonly dom: Element
152
+ readonly props: P
153
+ protected readonly clsprefix: string
154
+ ```
155
+
156
+ ### CSS classes
157
+
158
+ ```ts
159
+ hasClass(cls: string): boolean
160
+ addClass(cls: string): void // space-separated allowed
161
+ removeClass(cls: string): void // "*" clears all
162
+ removeClassEx(re: RegExp): void
163
+ toggleClass(cls: string): void
164
+ setClass(cls: string, set?: boolean): this
165
+ ```
166
+
167
+ ### HTML attributes & ARIA
168
+
169
+ ```ts
170
+ setAttributes(attrs: Record<string, string|number|boolean>): this
171
+ setAttribute(name: string, value: string|number|boolean): void // null/undefined/false → removeAttribute
172
+ getAttribute(name: string): string
173
+ getData(name: string): string // reads data-<name>
174
+ getIntData(name: string): number
175
+ setData(name: string, value: string): void
176
+ setAria(name: keyof AriaAttributes, value: string|number|boolean): this
177
+ ```
178
+
179
+ ### Internal data (non-DOM)
180
+
181
+ ```ts
182
+ setInternalData<T>(name: string|symbol, value: T): this
183
+ getInternalData<T>(name: string|symbol): T
184
+ ```
185
+
186
+ ### Styles
187
+
188
+ ```ts
189
+ setStyle(style: Partial<CSSStyleDeclaration>): this // numbers auto-suffixed "px" except unitless
190
+ setStyleValue<K>(name: K, value): this
191
+ getStyleValue<K>(name: K): CSSStyleDeclaration[K]
192
+ setWidth(w: number|string): void
193
+ setHeight(h: number|string): void
194
+ setStyleVariable(name: string, value: string): void // CSS custom property
195
+ getStyleVariable(name: string): string
196
+ getComputedStyle(): CSSStyleDeclaration
197
+ ```
198
+
199
+ ### DOM events / content / tree
200
+
201
+ ```ts
202
+ addDOMEvent<K extends keyof GlobalDOMEvents>(name: K, listener, prepend?: boolean): void
203
+ setDOMEvents(events: GlobalDOMEvents): void
204
+
205
+ setContent(content: ComponentContent): void
206
+ appendContent(content: ComponentContent): void
207
+ prependContent(content: ComponentContent): void
208
+ clearContent(): void
209
+ removeChild(child: Component): void
210
+
211
+ query<T extends Component>(selector: string): T
212
+ queryAll(selector: string): Component[]
213
+ firstChild<T>(): T; lastChild<T>(): T
214
+ nextElement<T>(): T; prevElement<T>(): T
215
+ parentElement<T>(cls?: Constructor<T>): T
216
+ childCount(): number
217
+ enumChildComponents(recursive: boolean): Component[]
218
+ enumChildNodes(recursive: boolean): Node[]
219
+ visitChildren(cb: (el: Component) => boolean): void
220
+ static parentElement<T>(dom: Node, cls?: Constructor<T>): T
221
+ ```
222
+
223
+ ### Visibility / state / focus / geometry
224
+
225
+ ```ts
226
+ show(vis?: boolean): this // toggles "x4hidden" class (not display:none)
227
+ hide(): this
228
+ enable(ena?: boolean): this // sets "disabled" + propagates to child <input>/<button>
229
+ disable(): this
230
+ isDisabled(): string | null
231
+ isVisible(): boolean
232
+ focus(): this
233
+ hasFocus(): boolean
234
+ getBoundingRect(): Rect
235
+ scrollIntoView(arg?: boolean | ScrollIntoViewOptions): void
236
+ setCapture(pointerId: number): void; releaseCapture(pointerId: number): void
237
+ animate(keyframes: Keyframe[], duration: number): void
238
+ onGlobalEvent(cb: (ev: EvMessage) => void): void // auto-removed on DOM removal
239
+ queryInterface<T>(name: string): T // "form-element" → IFormElement; "tab-handler" → ITabHandler
240
+ ```
241
+
242
+ ### Exported helpers
243
+
244
+ ```ts
245
+ componentFromDOM<T extends Component>(node: Element): T
246
+ wrapDOM(el: HTMLElement): Component
247
+ makeUniqueComponentId(): string // "x4-<n>"
248
+ ```
249
+
250
+ ### Component events (`component.ts`)
251
+
252
+ ```ts
253
+ interface EvClick extends ComponentEvent { repeat?: number }
254
+ interface EvChange extends ComponentEvent { readonly value: any }
255
+ interface EvFocus extends ComponentEvent { readonly focus_out: boolean }
256
+ interface EvSelectionChange extends ComponentEvent { readonly selection: (number|string|any)[]; readonly empty: boolean }
257
+ interface EvContextMenu extends ComponentEvent { uievent: MouseEvent }
258
+ interface EvDrag extends ComponentEvent { element: unknown; data: any }
259
+ interface EvError extends ComponentEvent { code: number; message: string }
260
+ interface EvDblClick extends ComponentEvent {}
261
+ interface EvBtnClick extends CoreEvent { button: string }
262
+
263
+ // Spacers
264
+ class Flex extends Component // CSS "x4flex"
265
+ class Space extends Component // new Space(width?: number|string, cls?: string)
266
+ ```
267
+
268
+ ---
269
+
270
+ ## `Application<E extends ApplicationEvents>`
271
+
272
+ Singleton. Extends `CoreElement` (no DOM of its own). (`src/core/core_application.ts`)
273
+
274
+ ```ts
275
+ interface AppProps { mountPoint?: string } // CSS selector, default "body"
276
+
277
+ constructor(props?: AppProps) // auto-mounts the mainview on page load
278
+ setMainView(view: Component): void
279
+ getMainView(): Component
280
+ static instance<P extends Application>(): P
281
+ setEnv(name: string, value: any): void
282
+ getEnv(name: string, def_value?: any): any
283
+ static fireGlobal(msg: string, params?: any): void
284
+ focusNext(next: boolean): boolean
285
+ setupSocketMessaging(path?: string, looseCallback?: () => void): void
286
+ getStorage(name: string): string
287
+ setStorage(name: string, value: string|number): void
288
+ getStorageJSON(name: string): any
289
+ setStorageJSON(name: string, value: any): void
290
+ readonly static process: { getMaxTouchPoints(): number }
291
+ ```
292
+
293
+ ---
294
+
295
+ ## Layout containers (`boxes`)
296
+
297
+ ```ts
298
+ // Box: generic (x4box) | HBox: horizontal flex (x4hbox) | VBox: vertical flex (x4vbox)
299
+ class Box<P, E> extends Component<P, E>
300
+ class HBox<P, E> extends Box<P, E>
301
+ class VBox<P, E> extends Box<P, E>
302
+ interface BoxProps extends ComponentProps { tag?: string }
303
+ ```
304
+
305
+ ### `Box.refs` — typed directory of the children to reach again later
306
+
307
+ `Box` owns a `protected refs: Record<string, Component>`. Declare its shape in the subclass, then assign each child
308
+ to `refs` while building the content: the assignment evaluates to the component, so it is written inline in the
309
+ content array. Do not keep such children in separate private fields, and do not use `refs.x` for a child that was
310
+ not assigned.
311
+
312
+ ```ts
313
+ class Panel extends VBox {
314
+ declare protected refs: { title: Label; save: Button }
315
+
316
+ constructor(props: BoxProps) {
317
+ super(props)
318
+ this.setContent([
319
+ this.refs.title = new Label({ text: "" }),
320
+ this.refs.save = new Button({ label: "Save", click: () => this.save() }),
321
+ ])
322
+ }
323
+
324
+ setTitle(text: string) { this.refs.title.setText(text) }
325
+ }
326
+ ```
327
+
328
+ In JSX, the same directory is filled with `ref={[owner, "name"]}` (the owner must be a `Box`).
329
+
330
+ ### `StackBox` — one page visible at a time (lazily created)
331
+
332
+ ```ts
333
+ interface StackBoxProps extends Omit<ComponentProps,"content"> {
334
+ default: string
335
+ items: StackItem[] // { name, content: Component | (() => Component), title? }
336
+ pageChange?: EventCallback<EvSelectionChange>
337
+ }
338
+ class StackBox extends Box {
339
+ select(name: string): Component
340
+ addItem(item: StackItem): void
341
+ removeItem(name: string): void
342
+ getPage(name: string): Component | ContentBuilder
343
+ getPageCount(): number
344
+ enumPageNames(): string[]
345
+ getItem(name: string): StackItem
346
+ getCurPage(): string
347
+ }
348
+ ```
349
+
350
+ ### `AssistBox` — sequential navigation (wizard/carousel)
351
+
352
+ ```ts
353
+ class AssistBox extends StackBox {
354
+ selectNextPage(nxt?: boolean): void // true=next, false=previous
355
+ isFirstPage(): boolean
356
+ isLastPage(): boolean
357
+ }
358
+ ```
359
+
360
+ ### `GridBox`
361
+
362
+ ```ts
363
+ interface GridBoxProps extends Omit<BoxProps,"content"> {
364
+ rows?: string | number | string[]
365
+ columns?: string | number | string[]
366
+ items?: { row: number; col: number; item: Component }[]
367
+ }
368
+ class GridBox extends Box {
369
+ setRows(r); setCols(r); setRowCount(n); setColCount(n)
370
+ setTemplate(t: string[]): void
371
+ setItems(items): void
372
+ }
373
+ ```
374
+
375
+ ### `MasonryBox` — Pinterest-like layout
376
+
377
+ ```ts
378
+ class MasonryBox extends Box {
379
+ setItems(items: Component[]): void
380
+ resizeAllItems(): void
381
+ resizeItem(item: Component): void
382
+ }
383
+ ```
384
+
385
+ ---
386
+
387
+ ## Display components
388
+
389
+ ### `Label`
390
+
391
+ ```ts
392
+ interface LabelProps extends ComponentProps {
393
+ text?: string | UnsafeHtml
394
+ icon?: string
395
+ labelFor?: string
396
+ align?: "left" | "center" | "right"
397
+ }
398
+ class Label extends Component { setText(text: string|UnsafeHtml): void; setIcon(icon: string): void }
399
+ ```
400
+
401
+ ### `SimpleText`
402
+
403
+ ```ts
404
+ interface SimpleTextProps extends ComponentProps {
405
+ text: string | UnsafeHtml // required
406
+ align?: "left" | "center" | "right"
407
+ }
408
+ class SimpleText extends Component { setText(text: string|UnsafeHtml): void }
409
+ ```
410
+
411
+ ### `Link`
412
+
413
+ ```ts
414
+ interface LinkProps extends ComponentProps {
415
+ href: string // required
416
+ text?: string | UnsafeHtml
417
+ icon?: string
418
+ align?: "left" | "center" | "right"
419
+ click?: EventCallback<EvClick>
420
+ }
421
+ interface LinkEvents extends ComponentEvents { click: EvClick }
422
+ class Link extends Component { setText(text: string|UnsafeHtml): void }
423
+ ```
424
+
425
+ ### `Icon`
426
+
427
+ ```ts
428
+ interface IconProps extends ComponentProps { iconId?: string }
429
+ class Icon extends Component { setIcon(iconId: string): void }
430
+ // iconId resolution: "var:name" → CSS var | "data:image/svg+xml,<svg..." → inline
431
+ // | "<svg..." → raw inline | "*.svg" → async load + cache | else → <img src>
432
+ export const svgLoader: SvgLoader // shared SVG cache
433
+ ```
434
+
435
+ ### `Image`
436
+
437
+ ```ts
438
+ interface ImageProps extends ComponentProps {
439
+ src: string // required
440
+ alt?: string
441
+ fit?: "fill" | "contain" | "cover" | "scale-down"
442
+ position?: string
443
+ lazy?: boolean
444
+ candrop?: boolean // accept drag&drop of a file
445
+ accept?: string
446
+ draggable?: boolean
447
+ change?: EventCallback<EvDropChange>
448
+ clear?: EventCallback<CoreEvent>
449
+ }
450
+ class Image extends Component {
451
+ setImage(src: string): void
452
+ setBase64(mime: string, base64: string): void
453
+ clear(): void
454
+ }
455
+ ```
456
+
457
+ ### `Progress`
458
+
459
+ ```ts
460
+ interface ProgressProps extends ComponentProps { /* min/max/value */ }
461
+ class Progress extends Component { setValue(value: number): void }
462
+ ```
463
+
464
+ ### `Gauge`
465
+
466
+ ```ts
467
+ interface GaugeProps extends ComponentProps { min?: number; max?: number; colors?: string[] }
468
+ class Gauge extends Component {
469
+ setRange(min: number, max: number): void
470
+ setPos(pos: number): void
471
+ setColorStops(colors: string[]): void
472
+ }
473
+ ```
474
+
475
+ ### `Rating`
476
+
477
+ ```ts
478
+ interface RatingProps extends ComponentProps {
479
+ value?: number; steps?: number; icon?: string; name?: string
480
+ change?: EventCallback<EvChange>
481
+ }
482
+ class Rating extends HBox {
483
+ getValue(): number; setValue(v: number): void
484
+ setSteps(n: number): void; setShape(icon: string): void
485
+ }
486
+ ```
487
+
488
+ ---
489
+
490
+ ## Input components
491
+
492
+ ### `Input` — multi-type native `<input>`
493
+
494
+ ```ts
495
+ type InputProps = TextInputProps | CheckboxProps | RadioProps | RangeProps
496
+ | DateProps | NumberProps | FileProps | TimeProps
497
+
498
+ interface BaseProps extends ComponentProps {
499
+ name?: string // required for Form.getValues/setValues
500
+ autofocus?: boolean; required?: boolean; readonly?: boolean; placeholder?: string
501
+ focus?: EventCallback<EvFocus>; change?: EventCallback<EvChange>
502
+ }
503
+ // TextInputProps: type?: "text"|"email"|"password"; value?: string|number; maxlength?, minlength?,
504
+ // pattern?, spellcheck?, trim?
505
+ // NumberProps: type: "number"; value?: string|number; min?, max?, step?
506
+ // RangeProps: type: "range"; min: number; max: number; step?; value?: number
507
+ // DateProps: type: "date"; value?: string|Date
508
+ // TimeProps: type: "time"; value?: string
509
+ // FileProps: type: "file"; accept: string|string[]
510
+
511
+ class Input extends Component<InputProps> {
512
+ getValue(): string; setValue(value: string): void
513
+ getNumValue(defNan?: number): number
514
+ setNumValue(value: number, ndec?: number): void // ndec: -1=auto, -2=step, ≥0=fixed
515
+ getCheck(): boolean; setCheck(ck: boolean): void // checkbox/radio
516
+ setReadOnly(ro: boolean): void
517
+ selectAll(): void; select(start: number, length?: number): void
518
+ getSelection(): { start: number; length: number }
519
+ isValid(): boolean
520
+ }
521
+ ```
522
+
523
+ ### `TextEdit` — Label + Input inline (HBox)
524
+
525
+ by default, TextEdit.getValue() result is timed
526
+
527
+ ```ts
528
+ type TextEditProps = (TextInputProps | NumberProps | DateProps | TimeProps) & {
529
+ label: string | UnsafeHtml
530
+ labelWidth?: number; inputWidth?: number; inputId?: string
531
+ inputGadgets?: Component[]; inputAttrs?: any
532
+ }
533
+ class TextEdit extends HBox {
534
+ getValue(): string; setValue(value: string): void; getInput(): Input
535
+ }
536
+ ```
537
+
538
+ ### `TextArea`
539
+
540
+ ```ts
541
+ class TextArea extends VBox { getText(): string; setText(text: string): void }
542
+ ```
543
+
544
+ ### `Checkbox`
545
+
546
+ ```ts
547
+ interface CheckboxProps extends ComponentProps {
548
+ label: string; checked?: boolean; value?: boolean|number|string; name?: string
549
+ change?: EventCallback<EvChange>
550
+ }
551
+ class Checkbox extends Component {
552
+ getCheck(): boolean; setCheck(ck: boolean): void
553
+ setLabel(text: string): void; toggle(): void
554
+ readonly _input: Input
555
+ }
556
+ ```
557
+
558
+ ### `Radio`
559
+
560
+ ```ts
561
+ interface RadioProps extends ComponentProps { /* label, value, name, group */ }
562
+ class Radio extends Component {
563
+ getValue(): string; setCheck(ck: boolean): void
564
+ setLabel(text: string): void
565
+ checkValue(vname: string): void // check the radio whose value === vname (group)
566
+ }
567
+ ```
568
+
569
+ ### `Switch`
570
+
571
+ ```ts
572
+ interface SwitchProps extends ComponentProps { /* label, checked, name */ }
573
+ class Switch extends HBox {}
574
+ ```
575
+
576
+ ### `Slider`
577
+
578
+ ```ts
579
+ interface SliderProps extends ComponentProps { /* min, max, value, step */ }
580
+ class Slider extends Component {
581
+ setMin(min: number): void; setMax(max: number): void; setValue(v: number): void
582
+ }
583
+ ```
584
+
585
+ ### `Select` — simple native select
586
+
587
+ ```ts
588
+ interface SelectProps extends ComponentProps {
589
+ items: ListItem[] // required
590
+ value: string // required
591
+ multiple?: boolean; name?: string
592
+ change?: EventCallback<EvChange>; focus?: EventCallback<EvFocus>
593
+ }
594
+ class Select extends Component { getValue(): string; setValue(value: string): void; setItems(items: ListItem[]): void }
595
+ ```
596
+
597
+ ### `Combobox` — input + dropdown list
598
+
599
+ ```ts
600
+ interface ComboboxProps extends ComponentProps { /* items, value, renderer... */ }
601
+ class Combobox extends Component {
602
+ getValue(): string; setValue(value: string): void
603
+ getInput(): Input
604
+ getSelection(): ListboxID
605
+ selectItem(index: number): void
606
+ setItems(items): void
607
+ showDropDown(): void
608
+ }
609
+ ```
610
+
611
+ ### `ColorInput`
612
+
613
+ ```ts
614
+ class ColorInput extends HBox {} // queryInterface("form-element")
615
+ ```
616
+
617
+ ### `FileDrop` / `FileDialog`
618
+
619
+ ```ts
620
+ class FileDrop extends VBox {} // drag&drop file zone, fires FileDropEvents
621
+ class FileDialog extends Component {} // programmatic file picker
622
+ ```
623
+
624
+ ---
625
+
626
+ ## Lists / grids / trees
627
+
628
+ ### `Listbox`
629
+
630
+ ```ts
631
+ interface ListItem {
632
+ id: ListboxID // number | string
633
+ text: string | UnsafeHtml
634
+ iconId?: string; data?: any; cls?: string; checked?: boolean
635
+ }
636
+ interface ListboxProps extends Omit<ComponentProps,"content"> {
637
+ items?: ListItem[]
638
+ renderer?: (item: ListItem) => Component
639
+ title?: string; icon?: string
640
+ header?: Header; footer?: Component
641
+ checkable?: true; multisel?: true
642
+ dblClick?: EventCallback<EvDblClick>
643
+ selectionChange?: EventCallback<EvSelectionChange>
644
+ contextMenu?: EventCallback<EvContextMenu>
645
+ }
646
+ class Listbox extends Component {
647
+ setItems(items: ListItem[], keepSel?: boolean): void
648
+ appendItem(item: ListItem, prepend?: boolean, select?: boolean): void
649
+ updateItem(id: any, item: ListItem): void
650
+ getItem(id: ListboxID): ListItem
651
+ select(ids: ListboxID | ListboxID[], notify?: boolean): void
652
+ clearSelection(fireEvent?: boolean): void
653
+ getSelection(): ListboxID[]
654
+ filter(filter: string | RegExp): void
655
+ navigate(sens: kbNav): boolean
656
+ ensureSelectionVisible(): void
657
+ renderItem(item: ListItem): Component // override to customize
658
+ defaultRenderer(item: ListItem): Component
659
+ preventFocus: boolean
660
+ }
661
+ ```
662
+
663
+ ### `Gridview` — data grid backed by a DataStore
664
+
665
+ ```ts
666
+ interface GridviewProps extends ComponentProps {
667
+ store: DataStore // required
668
+ columns: GridColumn[] // required
669
+ footer?: boolean; emptyMsg?: string
670
+ sort?: object
671
+ click?: EventCallback<EvClick>
672
+ dblClick?: EventCallback<EvDblClick>
673
+ contextMenu?: EventCallback<EvContextMenu>
674
+ selectionChange?: EventCallback<EvSelectionChange>
675
+ }
676
+ class Gridview extends Component {
677
+ setStore(store: DataStore): void
678
+ setColumns(columns: GridColumn[]): void
679
+ setColTitle(col_name: string, title: string): void
680
+ sortCol(colIdx: number, ascending: boolean): void
681
+ getView(): DataView
682
+ getSelection(): DataRecord[]
683
+ getFirstSel(): DataRecord
684
+ selectItem(id, ensureVisible?: boolean): void
685
+ clearSelection(): void
686
+ navigate(sens: kbNav): boolean
687
+ lock(lock: boolean): void
688
+ }
689
+ ```
690
+
691
+ ### `Treeview`
692
+
693
+ ```ts
694
+ interface TreeItem extends ListItem { children?: TreeItem[]; open?: boolean }
695
+ interface TreeviewProps extends ComponentProps { /* items, renderer... */ }
696
+ class Treeview extends Component {
697
+ setItems(items: TreeItem[]): void
698
+ getSelection(): ListboxID
699
+ selectItem(id): void
700
+ clearSelection(): void
701
+ navigate(sens: kbNav): boolean // see kbTreeNav enum
702
+ }
703
+ ```
704
+
705
+ ### `Spreadsheet` — editable grid backed by a `Store`
706
+
707
+ ```ts
708
+ interface SpreadsheetProps extends ComponentProps {
709
+ store: Store // required
710
+ columns: SpreadsheetColumn[] // required
711
+ footer?: boolean
712
+ rowClassifier?: RowClassifier
713
+ click?, dblClick?, contextMenu?, selectionChange?
714
+ }
715
+ class Spreadsheet extends Component {
716
+ getSelection(): /* cell */; navigate(sens: kbNav): boolean; lock(lock): void
717
+ }
718
+
719
+ // Store: in-memory cell store (extends CoreElement)
720
+ class Store extends CoreElement {
721
+ setData(row, col, data): void; getData(row, col): any; hasData(row, col?): boolean
722
+ removeRow(row_num): void; getRowCount(): number; setMaxRowCount(rows): void
723
+ clear(): void; lock(): void; unlock(): void
724
+ }
725
+ ```
726
+
727
+ ### `PropertyGrid`
728
+
729
+ ```ts
730
+ interface PropertyProps extends ComponentProps { groups: PropertyGroup[] }
731
+ class PropertyGrid extends VBox {
732
+ setItems(groups: PropertyGroup[]): void
733
+ setPropValue(name: string, value): void
734
+ }
735
+ ```
736
+
737
+ ---
738
+
739
+ ## Menus / button groups / tabs
740
+
741
+ ### `Menu`
742
+
743
+ ```ts
744
+ interface MenuItem {
745
+ text: string | UnsafeHtml
746
+ cls?: string; icon?: string; menu?: Menu; disabled?: boolean
747
+ click?: DOMEventHandler
748
+ }
749
+ type MenuElement = MenuItem | Component | string // string "-" = title/separator
750
+ interface MenuProps extends Omit<PopupProps,"content"> { items: MenuElement[] }
751
+ class Menu extends Popup {} // displayAt/displayNear/displayCenter/close
752
+ ```
753
+
754
+ ### `BtnGroup`
755
+
756
+ ```ts
757
+ type BtnGroupItem = predefined | Button | Label | Input
758
+ // predefined strings: "ok","cancel","yes","no","retry","abort" + options after a dot:
759
+ // "ok.outline.default.disabled" ; "-"/">>" = Flex spacer ; "~" = Space 1em
760
+ interface BtnGroupProps extends Omit<ComponentProps,"content"> {
761
+ items: BtnGroupItem[]
762
+ align?: "left"|"center"|"right"; vertical?: boolean; reverse?: boolean
763
+ btnclick?: EventCallback<EvBtnClick>
764
+ }
765
+ class BtnGroup extends Box {
766
+ setButtons(btns: BtnGroupItem[]): void
767
+ getButton(id: string): Button
768
+ }
769
+ ```
770
+
771
+ ### `Button`
772
+
773
+ ```ts
774
+ interface ButtonProps extends ComponentProps {
775
+ label?: string | UnsafeHtml
776
+ icon?: string
777
+ tabindex?: number | boolean
778
+ autorepeat?: number | boolean // true=200ms; number=interval (first fire after 500ms)
779
+ click?: EventCallback<EvClick>
780
+ }
781
+ class Button extends Component {
782
+ click(): void // simulate a click
783
+ setText(text: string|UnsafeHtml): void
784
+ setIcon(icon: string): void
785
+ }
786
+ ```
787
+
788
+ ### `Tabs`
789
+
790
+ ```ts
791
+ interface TabItem { name: string; title: string; content: Component | (() => Component); icon?: string; cls?: string }
792
+ interface TabsProps extends Omit<ComponentProps,"content"> { default: string; items: TabItem[]; vertical?: boolean }
793
+ class Tabs extends Box {
794
+ selectTab(name: string): void
795
+ getTab(name: string): Component | ContentBuilder
796
+ getCurTab(): string
797
+ addTab(item: TabItem): void
798
+ removeTab(name: string): void
799
+ enumTabs(): string[]
800
+ }
801
+ ```
802
+
803
+ ---
804
+
805
+ ## Popup / Dialog
806
+
807
+ ### `Popup`
808
+
809
+ ```ts
810
+ interface PopupProps extends ComponentProps {
811
+ autoClose?: boolean | string // true, or a group name
812
+ sizable?: boolean; movable?: boolean
813
+ }
814
+ class Popup extends Box {
815
+ displayAt(x: number, y: number): void
816
+ displayNear(rc: Rect, dst?: string, src?: string, offset?: {x,y}): void
817
+ displayCenter(center?: boolean): void
818
+ close(): void
819
+ dismiss(after?: boolean): void
820
+ isOpen(): boolean
821
+ // show()/hide() inherited from Component; show() routes to displayCenter()
822
+ }
823
+ ```
824
+
825
+ ### `Dialog`
826
+
827
+ ```ts
828
+ interface DialogProps extends PopupProps {
829
+ title: string // required
830
+ buttons: BtnGroupItem[] // required
831
+ icon?: string; form?: Form
832
+ closable?: boolean | string; modal?: boolean
833
+ btnclick?: EventCallback<EvBtnClick>
834
+ }
835
+ class Dialog extends Popup {
836
+ getForm(): Form
837
+ getValues(): FormValues
838
+ validate(): FormValues
839
+ getButton(name: string): Button
840
+ getBtnBar(): BtnGroup
841
+ setTitle(title: string): void
842
+ showAsync(): Promise<string> // resolves with the clicked button name
843
+ }
844
+ ```
845
+
846
+ ### `MessageBox` / `InputBox` / `PromptBox` / `ProgressionBox`
847
+
848
+ ```ts
849
+ class MessageBox extends Dialog {
850
+ static showAsync(msg: string|UnsafeHtml, buttons?: BtnGroupItem[], title?: string,
851
+ icon_type?: "error"|"question"): Promise<string>
852
+ }
853
+ class InputBox extends Dialog {
854
+ getValue(): string
855
+ // static showAsync(msg, value, title?, options?: {password?, trim?}): Promise<string> (null if cancelled)
856
+ }
857
+ class PromptBox extends Dialog {
858
+ static showAsync(msg: string|UnsafeHtml, editor: Component, title?: string): Promise<string>
859
+ }
860
+ class ProgressionBox extends Dialog {
861
+ constructor(title: string)
862
+ addText(text: string|UnsafeHtml, perc: number): void
863
+ addError(text: string|UnsafeHtml, perc: number): void
864
+ clear(): void
865
+ done(): void // hide after 5s if no error, else show OK button
866
+ }
867
+ ```
868
+
869
+ ### `Notification`
870
+
871
+ ```ts
872
+ class Notification extends Popup { display(time_in_s?: number): void }
873
+ ```
874
+
875
+ ---
876
+
877
+ ## Forms
878
+
879
+ ```ts
880
+ type FormValues = Record<string, any>
881
+ interface FormProps extends BoxProps { autoComplete?: boolean }
882
+ class Form extends Box {
883
+ setValues(values: FormValues): void
884
+ getValues<T extends FormValues>(): T
885
+ setAutoComplete(on?: boolean): void
886
+ setValidator(validator: (values: FormValues, is_valid: boolean) => boolean): void
887
+ validate(): FormValues // null if invalid
888
+ }
889
+ // Collects all children (recursive) that have a "name" attribute and implement queryInterface("form-element").
890
+ ```
891
+
892
+ ---
893
+
894
+ ## Sizers / viewport / misc
895
+
896
+ ```ts
897
+ class CSizer extends Component // base splitter
898
+ class HSizer extends CSizer // horizontal splitter
899
+ class VSizer extends CSizer // vertical splitter
900
+ class ScrollView extends Component { getViewport(): Component }
901
+ class Viewport extends Component
902
+ class Canvas extends Component { getContext(): CanvasRenderingContext2D; paint(): void }
903
+ class Video extends Box {
904
+ play(); pause(); mute(set: boolean)
905
+ get player(): HTMLVideoElement; get duration(): number; get paused(): boolean
906
+ get volume(): number; get videoWidth(): number; get videoHeight(): number
907
+ set stream(stream: MediaStream)
908
+ }
909
+ class Calendar extends VBox { getDate(): Date; setDate(date: Date): void }
910
+ class ColorPicker extends VBox // queryInterface("form-element")
911
+ class Panel extends VBox { setTitle(title: string): void }
912
+ class Breadcrumbs extends HBox { setItems(elements): void }
913
+ class Keyboard extends HBox // virtual keyboard
914
+ class TickLine extends Component
915
+ class Header extends HBox // resizable column header; updates target style "--{name}-width"
916
+ ```
917
+
918
+ ---
919
+
920
+ ## Data module (`core_data`)
921
+
922
+ ### `DataModel`
923
+
924
+ ```ts
925
+ namespace data { // field decorators
926
+ id(): FieldDecorator
927
+ string(props?: MetaData); int(props?); float(props?); bool(props?); date(props?)
928
+ calc(props?: MetaData) // computed field (getter)
929
+ array(ctor: ModelConstructor, props?: MetaData)
930
+ any(props?: MetaData)
931
+ field(data: MetaData) // generic
932
+ }
933
+ type FieldType = "string"|"int"|"float"|"date"|"bool"|"array"|"object"|"any"|"calc"
934
+ interface MetaData { type?: FieldType; required?: boolean; prec?: number; calc?: (rec)=>any; model?: DataModel }
935
+ interface FieldInfo extends MetaData { name: string }
936
+ type DataRecord<T> = Partial<T> & object
937
+ type DataRecordID = any
938
+
939
+ class DataModel<T = any> {
940
+ getFields(): FieldInfo[]
941
+ getFieldIndex(name: string): number
942
+ getID(rec: DataRecord): any
943
+ getRaw(name: string|number, rec: DataRecord): any
944
+ getField(name: string, rec: DataRecord): string
945
+ serialize<T>(input: DataRecord): T
946
+ unSerialize(data: any, id?: DataRecordID): DataRecord<T>
947
+ validate(record: DataRecord): Error[]
948
+ addField(...fields: FieldInfo[]): void
949
+ }
950
+ ```
951
+
952
+ ### `DataStore<T>` (extends EventSource)
953
+
954
+ ```ts
955
+ interface DataStoreProps { model: DataModel; data?: any[]; url?: string; autoload?: false; solver?: (data:any)=>DataRecord[] }
956
+ // Event "data_change" → { change_type: 'create'|'update'|'delete'|'data'|'change', id? }
957
+ class DataStore<T> extends EventSource {
958
+ setData(records: any[]): void; setRawData(records: DataRecord[]): void
959
+ append(rec); appendRaw(rec); update(rec): boolean; delete(id): boolean
960
+ getById(id): DataRecord<T>; getByIndex(index): DataRecord<T>; indexOfId(id): number // binary search
961
+ get count(): number; get fields(): FieldInfo[]
962
+ forEach(cb); find(cb): DataRecord; getMaxId(): number
963
+ load(url?): Promise<void>; reload(): Promise<any>; export(): DataRecord<T>[]
964
+ createIndex(filter): DataIndex; createView(opts?): DataView; moveTo(other): void
965
+ changed(): void; getModel(): DataModel<T>
966
+ }
967
+ ```
968
+
969
+ ### `DataView` (extends CoreElement)
970
+
971
+ ```ts
972
+ interface FilterInfo {
973
+ op: "<"|">"|"<="|"="|">="|"<>"|"empty-result"|FilterFunc
974
+ field?: string; value?: string|RegExp; caseSensitive?: boolean
975
+ }
976
+ interface SortProp { field: string; ascending: boolean; numeric?: boolean; callback?: SortCallback }
977
+ // Event "view_change" → { change_type: "change"|"filter"|"sort" }
978
+ class DataView extends CoreElement {
979
+ filter(filter?: FilterInfo): number // returns count of filtered items
980
+ sort(props: SortProp[]): void
981
+ getCount(): number
982
+ getById(id): DataRecord; getByIndex(index): DataRecord; getIdByIndex(index): any
983
+ getRecId(rec): any; indexOfId(id): number
984
+ getStore(): DataStore; getModel(): DataModel
985
+ forEach(cb): void; changed(): void
986
+ }
987
+ ```
988
+
989
+ ### `DataProxy` (extends CoreElement)
990
+
991
+ ```ts
992
+ interface DataProxyProps { url: string; params?: string[]; solver?: DataSolver }
993
+ class DataProxy extends CoreElement { load(url?): Promise<void> } // Event "change" → { value: DataRecord[] }
994
+ ```
995
+
996
+ ---
997
+
998
+ ## Router (`core_router`, extends EventSource)
999
+
1000
+ ```ts
1001
+ class Router extends EventSource {
1002
+ constructor(useHash?: boolean) // default true (# URLs)
1003
+ get(uri: string|RegExp, handler: (params: any, path: string) => void): void
1004
+ init(): void // trigger the current route
1005
+ navigate(uri: string, notify?: boolean, replace?: boolean): boolean
1006
+ // Events: "change" → { value: string } ; "error" → { code: 404, message: string }
1007
+ }
1008
+ parseRoute(str: string|RegExp, loose?: boolean): Segment
1009
+ ```
1010
+
1011
+ ---
1012
+
1013
+ ## i18n (`core_i18n`)
1014
+
1015
+ Two built-in languages (`fr` default, and `en`).
1016
+
1017
+ ```ts
1018
+ export let _tr // current translation object (proxy)
1019
+ createLanguage(name: string, base: string): void
1020
+ addTranslation(name: string, ...parts: any[]): void
1021
+ selectLanguage(name: string): typeof _tr
1022
+ getCurrentLanguage(): string
1023
+ getAvailableLanguages(): string[]
1024
+ isLanguage(name: string): boolean
1025
+
1026
+ // _tr.global keys: ok cancel yes no abort retry ignore error today open new delete close save
1027
+ // search search_tip required_field invalid_format invalid_email invalid_number invalid_date
1028
+ // empty_list err_403 property value copy cut paste filedrop
1029
+ // diff_date_seconds/minutes/hours, date_input_formats, date_format, date_time_format,
1030
+ // day_short[] day_long[] month_short[] month_long[], keyboard.{next,numeric,alpha}
1031
+ ```
1032
+
1033
+ ---
1034
+
1035
+ ## Utilities (`core_tools`)
1036
+
1037
+ ```ts
1038
+ class UnsafeHtml extends String // raw, unescaped HTML marker
1039
+ unsafeHtml(x: string): UnsafeHtml
1040
+ unsafe(strings, ...values): UnsafeHtml // template tag: unsafe`<b>${x}</b>`
1041
+ sanitizeHtml(input: string): string // escape &<>"' for safe text insertion
1042
+ safeText(v: string | UnsafeHtml): string // UnsafeHtml passes through; plain string is escaped
1043
+
1044
+ class Rect {
1045
+ left; top; width; height; get right(): number; get bottom(): number
1046
+ // new Rect() | new Rect(l,t,w,h) | new Rect(otherRect)
1047
+ contains(pt): boolean; touches(rc): boolean
1048
+ normalize(): this; inflate(dx, dy?): Rect; scale(scale): Rect
1049
+ }
1050
+ interface Point { x: number; y: number }
1051
+ interface Size { w: number; h: number }
1052
+ interface IRect { left; top; height; width: number }
1053
+
1054
+ class Timer { setTimeout; clearTimeout; setInterval; clearInterval; clearAllTimeouts }
1055
+
1056
+ class Color {
1057
+ // new Color(value) | new Color(r,g,b,a?)
1058
+ setValue(value): this; setRgb(r,g,b,a): this; setHsv(h,s,v,a?): this; setAlpha(a): this
1059
+ getAlpha(): number; lighten(percent): this; isInvalid(): boolean
1060
+ toHexString(): string; toRgbString(withAlpha?): string; toNumber(): number
1061
+ toRgb(): Rgb; toHsv(): Hsv
1062
+ }
1063
+
1064
+ enum kbNav { first, prev, pgdn, pgup, next, last, left, right }
1065
+ enum kbTreeNav { first, prev, next, last, parent, child, expand, collapse, toggle }
1066
+
1067
+ // Type guards
1068
+ isString(v): v is string; isNumber(v): v is number; isArray(v): v is any[]
1069
+ isFunction(v): v is Function; isDate(v): v is Date; isPlainObject(v): v is Record<string,any>
1070
+
1071
+ // Geometry / strings
1072
+ centerRect(inner: IRect, outer: IRect, margin?): IRect
1073
+ sprintf(format, ...args): string // replaces {0} {1}...
1074
+ pad(what, size, ch?): string // >0 padEnd, <0 padStart
1075
+ pascalCase(s): string; camelCase(s): string; clamp<T>(v, min, max): T
1076
+ measureText(str, size): number
1077
+
1078
+ // Dates
1079
+ date_format(date, options?): string
1080
+ date_diff(date1, date2, options?): string
1081
+ date_to_sql(date, withHours): string; date_sql_utc(date): Date
1082
+ date_hash(date): number; date_clone(date): Date; date_calc_weeknum(date): number
1083
+ parseIntlDate(value, fmts?): Date; formatIntlDate(date, fmt?, utc?): string
1084
+ calcAge(birth, ref?): number
1085
+
1086
+ // DOM / system
1087
+ asap(cb): number // requestAnimationFrame
1088
+ oneshot(cb, ms?): number // setTimeout
1089
+ addEvent(node, name, handler, prepend?): void
1090
+ dispatchEvent(ev): void
1091
+ getFocusableElements(root): Element[]
1092
+ getScrollbarSize(): number; getGlobalZoom(): number
1093
+ getSystemMetrics(part: "scrollbar"|"zoom"): number
1094
+ setWaitCursor(wait: boolean): void; beep(): void
1095
+ isUnitLess(name): boolean; isFeatureAvailable(name: "eyedropper"): boolean
1096
+
1097
+ // Class decorator
1098
+ class_ns(ns: string): ClassDecorator
1099
+ const x4_class_ns_sym: symbol
1100
+ const unitless // set of unitless CSS props
1101
+
1102
+ // System interfaces
1103
+ interface IFormElement { getRawValue(): any; setRawValue(v): void; isValid(): boolean }
1104
+ interface ITabHandler { focusNext(next: boolean): boolean }
1105
+ interface ITipHandler { getTip(): string }
1106
+
1107
+ // Other variables
1108
+ const dragManager; const svgLoader; const linearColorStops; const simpleColorStop
1109
+ const unbubbleEvents // set of DOM event names that do NOT bubble through the x4 tree
1110
+ const makeUniqueComponentId // () => string, "x4-<n>" (exported as a value)
1111
+ ```
1112
+
1113
+ ### `formatIntlDate` specifiers
1114
+
1115
+ ```
1116
+ d day (no zero) D day (2 digits) j short day (mon) J long day (monday)
1117
+ w week number W week (2 digits) m month (no zero) M month (2 digits)
1118
+ o short month O long month y/Y year h 24h hour
1119
+ H hour (2 digits) i minutes I minutes (2 dig) s seconds
1120
+ S seconds (2 dig) a am/pm l ms L ms (3 digits)
1121
+ {free text} any other char inserted as-is
1122
+ ```
1123
+
1124
+ ---
1125
+
1126
+ ## SVG (`core_svg`)
1127
+
1128
+ ```ts
1129
+ class SvgComponent extends Component { addItems(...) } // <svg> container
1130
+ interface SvgProps extends ComponentProps { viewbox?: string; svg?: SvgBuilder }
1131
+ class SvgBuilder extends SvgGroup {
1132
+ // shapes: rect, circle, ellipse, path, text, icon, group
1133
+ // transforms: translate, rotate, scale, transform, add_translation, add_rotation, add_scale
1134
+ // style: fill, no_fill, stroke, strokeWidth, strokeCap, strokeOpacity, setStyle, addClass, clip, addClip
1135
+ // gradients: linear_gradient, addPattern
1136
+ }
1137
+ // Items: SvgGroup, SvgPath (moveTo/lineTo/curveTo/arc/closePath), SvgText (font/fontSize/textAlign),
1138
+ // SvgShape, SvgIcon, SvgGradient (addStop)
1139
+ ```
1140
+
1141
+ ---
1142
+
1143
+ ## PDF (`core_pdf`)
1144
+
1145
+ ```ts
1146
+ class X4PDFBuilder {
1147
+ page(width: number, height: number, callback): void
1148
+ build(): Uint8Array
1149
+ }
1150
+ ```
1151
+
1152
+ ---
1153
+
1154
+ ## Code editor (`MonacoEditor`)
1155
+
1156
+ Monaco-based code editor component. Monaco is part of the framework surface, so exclude it from `tsc` compilation (see Default project notes).
1157
+
1158
+ ```ts
1159
+ interface MonacoEditorProps extends ComponentProps {
1160
+ language: "typescript" | "javascript" | "json" | "css" | "html"
1161
+ theme?: string
1162
+ content?: string
1163
+ options?: Monaco.editor.IEditorOptions & Monaco.editor.IGlobalEditorOptions
1164
+ }
1165
+ class MonacoEditor extends Component<MonacoEditorProps> {
1166
+ constructor(props: MonacoEditorProps)
1167
+ static start(): Promise<void> // load/initialize Monaco
1168
+ static addTypelib(name: string, code: string): void // register a .d.ts for IntelliSense
1169
+ static basePath: string
1170
+ static monaco: typeof Monaco
1171
+ }
1172
+ ```
1173
+
1174
+ ---
1175
+
1176
+ ## JSX factory & helpers
1177
+
1178
+ ```ts
1179
+ class x4_react {
1180
+ static create_element(tag: string, props: any, ...content: any[]): Component // JSX pragma
1181
+ }
1182
+ class CMover {
1183
+ constructor(x: Component, ref?: Component) // pointer-driven move helper
1184
+ }
1185
+ createPainter(c2d: CanvasRenderingContext2D, w: number, h: number): CanvasEx // Canvas painter
1186
+ initTooltips(): void // initialize the global tooltip system
1187
+ ```
1188
+
1189
+ ---
1190
+
1191
+ ## State (`core_state`) — Experimental
1192
+
1193
+ ```ts
1194
+ class StateManager {
1195
+ constructor(initialState)
1196
+ getState(path: string, defaultValue?): any
1197
+ setState(path: string, value, context?): void
1198
+ }
1199
+ // NOTE: marked @Experimental in the sources.
1200
+ ```
1201
+
1202
+ ---
1203
+
1204
+ ## Common patterns
1205
+
1206
+ ### Minimal custom component
1207
+
1208
+ ```ts
1209
+ import { Component, ComponentProps, ComponentEvents, EvClick } from 'x4js'
1210
+
1211
+ interface MyProps extends ComponentProps { label: string; click?: (ev: EvClick) => void }
1212
+ interface MyEvents extends ComponentEvents { click: EvClick }
1213
+
1214
+ class MyButton extends Component<MyProps, MyEvents> {
1215
+ constructor(props: MyProps) {
1216
+ super({ ...props, tag: 'button' })
1217
+ this.mapPropEvents(props, 'click')
1218
+ this.addDOMEvent('click', () => this.fire('click', {}))
1219
+ this.setContent(props.label)
1220
+ }
1221
+ }
1222
+ ```
1223
+
1224
+ ### Application
1225
+
1226
+ ```ts
1227
+ import { Application, Component } from 'x4js'
1228
+
1229
+ class App extends Application {
1230
+ constructor() {
1231
+ super({ }) // do NOT specify mountPoint: the app mounts directly into <body>
1232
+ this.setMainView(new Component({ content: 'Hello x4js' }))
1233
+ }
1234
+ }
1235
+ new App()
1236
+ // Access from anywhere: Application.instance()
1237
+ ```
1238
+
1239
+ ### Async Dialog
1240
+
1241
+ ```ts
1242
+ const btn = await MessageBox.showAsync('Confirm?', ['ok.outline.default', 'cancel.outline'])
1243
+ if (btn === 'ok') { /* ... */ }
1244
+
1245
+ const dlg = new Dialog({
1246
+ title: 'My dialog',
1247
+ modal: true,
1248
+ movable: true,
1249
+ form: new Form({ content: [ new TextEdit({ label: 'Name', name: 'name', value: '' }) ] }),
1250
+ buttons: ['ok.outline.default', 'cancel.outline'],
1251
+ })
1252
+ if (await dlg.showAsync() === 'ok') {
1253
+ const values = dlg.getValues() // { name: "..." }
1254
+ }
1255
+ ```
1256
+
1257
+ ### DataStore + DataView + Gridview
1258
+
1259
+ ```ts
1260
+ import { DataModel, DataStore, Gridview, data } from 'x4js'
1261
+
1262
+ class PersonModel extends DataModel {
1263
+ @data.id() id: number
1264
+ @data.string() name: string
1265
+ @data.int() age: number
1266
+ }
1267
+
1268
+ const store = new DataStore({ model: new PersonModel(), data: [...] })
1269
+ const grid = new Gridview({
1270
+ store,
1271
+ columns: [ { id: 'name', title: 'Name', width: 200 }, { id: 'age', title: 'Age', width: 80 } ],
1272
+ })
1273
+
1274
+ /* not needed because grid will reflect data changes
1275
+ const view = store.createView({ filter: { op: '>=', field: 'age', value: '18' } })
1276
+ view.on('view_change', () => { })
1277
+ */
1278
+ ```
1279
+
1280
+ ### Router
1281
+
1282
+ ```ts
1283
+ import { Router } from 'x4js'
1284
+
1285
+ const router = new Router(true) // hash mode
1286
+ router.get('/home', () => showHome())
1287
+ router.get('/detail/:id', (p) => showDetail(p.id))
1288
+ router.addListener('error', () => router.navigate('/home'))
1289
+ router.init()
1290
+ ```
1291
+
1292
+ ### Timers in a component
1293
+
1294
+ ```ts
1295
+ class Poller extends Component {
1296
+ constructor(props) { super(props); this.setInterval('poll', 5000, () => this.refresh()) }
1297
+ refresh() { /* ... */ }
1298
+ // stop: this.clearInterval('poll') or this.clearTimeouts()
1299
+ }
1300
+ ```
1301
+
1302
+ ---
1303
+
1304
+ ## Default project
1305
+
1306
+ When creating a new x4js project, use these files as a base.
1307
+
1308
+ ### `package.json`
1309
+
1310
+ ```json
1311
+ {
1312
+ "name": "min demo",
1313
+ "version": "1.0",
1314
+ "scripts": {
1315
+ "dev": "x4js dev",
1316
+ "build": "x4js build"
1317
+ },
1318
+ "dependencies": {
1319
+ "@fontsource/montserrat": "^5.1.0",
1320
+ "@types/node": "^25.9.1",
1321
+ "x4js": "latest"
1322
+ }
1323
+ }
1324
+ ```
1325
+
1326
+ `x4js dev` runs the dev server, `x4js build` writes `outdir` (add `--debug` for a debug build).
1327
+ Both read `x4.config.json` (change the file with `--config <file>`).
1328
+
1329
+ ### `x4.config.json`
1330
+
1331
+ ```json
1332
+ {
1333
+ "entryPoints": ["src/main.ts"],
1334
+ "outdir": "./bin",
1335
+ "copy": [
1336
+ { "from": "./src/assets", "to": "assets" },
1337
+ { "from": "./src/index.html", "to": "index.html" }
1338
+ ]
1339
+ }
1340
+ ```
1341
+
1342
+ Optional keys: `external`, `define`, `esbuild`, `dev` (`host`, `port`, `https`, `tls`).
1343
+ `copy[].to` is relative to `outdir`.
1344
+
1345
+ ### `tsconfig.json`
1346
+
1347
+ ```jsonc
1348
+ {
1349
+ "compilerOptions": {
1350
+ "target": "ES2022",
1351
+ "lib": ["DOM", "ES2022",],
1352
+ "module": "es2022",
1353
+ "strict": true,
1354
+ "strictNullChecks": false,
1355
+ "moduleResolution": "bundler",
1356
+ "experimentalDecorators": true,
1357
+ "paths": {
1358
+ }
1359
+ },
1360
+ "include": [ "src/**/*" ],
1361
+ "exclude": [ "node_modules", "bin", "**/monaco-editor/**" ]
1362
+ }
1363
+ ```
1364
+
1365
+ ### `src/index.html`
1366
+
1367
+ ```html
1368
+ <!DOCTYPE html>
1369
+ <html>
1370
+ <head>
1371
+ <meta charset="utf8">
1372
+ <link rel="stylesheet" href="main.css"></style>
1373
+ <script src="main.js"></script>
1374
+ </head>
1375
+ </html>
1376
+ ```
1377
+
1378
+ ### `src/main.scss`
1379
+
1380
+ ```scss
1381
+ ```
1382
+
1383
+ ---
1384
+
1385
+ ## Important stupid notes for IA
1386
+
1387
+ - as possible, never use DOM function directly, search in x4, it's probably done.
1388
+
1389
+ - `Component` stores its instance on its DOM node. Use `componentFromDOM(el)` to retrieve a component from an `HTMLElement`.
1390
+ - Numbers passed to `setStyle`/`setStyleValue` are auto-suffixed with `"px"` except for unitless properties (see the `unitless` set: `zIndex`, `opacity`, `flexGrow`...).
1391
+ - `show(false)` / `hide()` toggle the `"x4hidden"` CSS class, not `display:none`.
1392
+ - `enable(false)` / `disable()` propagate the `disabled` state to child `<input>` and `<button>` elements.
1393
+ - `Application` is a strict singleton — one instance per page (an assertion fails otherwise).
1394
+ - `Popup.show()` routes to `displayCenter()`. To position, call `displayAt()` or `displayNear()` instead.
1395
+ - `BtnGroup` accepts predefined strings (`"ok"`, `"cancel"`...) with options after a dot: `"ok.outline.default.disabled"`.
1396
+ - `Form.getValues()` collects all recursive children with a `name` attribute implementing `queryInterface("form-element")`.
1397
+ - The default language is `fr`. Call `selectLanguage('en')` to switch to English.
1398
+ - `Gridview` is backed by a `DataStore`; `Spreadsheet` is backed by a `Store` (different, cell-based).
1399
+ - `DataRecordID` is currently typed `any`; `strictNullChecks` is intentionally off in the recommended `tsconfig.json`.
1400
+ - `StateManager` (`core_state`) is marked **Experimental** — verify before relying on it.
1401
+ - `MonacoEditor` requires `MonacoEditor.start()` (async) before use; register extra typings with `MonacoEditor.addTypelib(name, code)`.
1402
+ - `x4_react.create_element` is the JSX pragma;
1403
+ - `unbubbleEvents` is the set of DOM events that do not bubble through the x4 component tree.