x4js 2.3.8 → 2.3.10
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 +1403 -0
- package/aiguide.md +589 -0
- package/package.json +3 -1
- package/src/components/textarea/textarea.ts +2 -2
- package/src/core/core_application.ts +1 -0
- package/src/core/core_dom.ts +2 -1
- package/src/x4tsx.d.ts +2 -1
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.
|