x4js 2.3.28 → 2.3.30

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.
Files changed (43) hide show
  1. package/README.md +1 -1
  2. package/aicontext.md +385 -21
  3. package/package.json +8 -3
  4. package/src/components/assets/icons.ts +2 -0
  5. package/src/components/bargraph/bargraph.module.scss +44 -0
  6. package/src/components/bargraph/bargraph.ts +362 -0
  7. package/src/components/base.scss +71 -1
  8. package/src/components/calendar/calendar.module.scss +6 -5
  9. package/src/components/calendar/calendar.ts +14 -8
  10. package/src/components/canvas/canvas.ts +5 -4
  11. package/src/components/carousel/carousel.module.scss +165 -0
  12. package/src/components/carousel/carousel.ts +423 -0
  13. package/src/components/chart/chart.module.scss +8 -0
  14. package/src/components/chart/chart.ts +170 -12
  15. package/src/components/combobox/combobox.module.scss +2 -1
  16. package/src/components/components.ts +3 -0
  17. package/src/components/dialog/dialog.module.scss +2 -0
  18. package/src/components/dialog/dialog.ts +1 -0
  19. package/src/components/gantt/gantt.module.scss +462 -0
  20. package/src/components/gantt/gantt.ts +1254 -0
  21. package/src/components/knob/knob.module.scss +113 -0
  22. package/src/components/knob/knob.ts +355 -0
  23. package/src/components/mapview/mapview.module.scss +177 -0
  24. package/src/components/mapview/mapview.ts +764 -0
  25. package/src/components/menu/menu.module.scss +4 -6
  26. package/src/components/menu/menu.ts +4 -2
  27. package/src/components/planner/planner.module.scss +688 -0
  28. package/src/components/planner/planner.ts +1792 -0
  29. package/src/components/popup/popup.ts +29 -3
  30. package/src/components/radio/radio.module.scss +4 -1
  31. package/src/components/shared.scss +2 -1
  32. package/src/components/slider/slider.module.scss +22 -20
  33. package/src/components/switch/switch.module.scss +68 -44
  34. package/src/components/switch/switch.ts +6 -3
  35. package/src/components/tag/tag.module.scss +4 -4
  36. package/src/components/textedit/textedit.module.scss +5 -2
  37. package/src/components/themes.scss +27 -6
  38. package/src/components/tooltips/tooltips.scss +8 -0
  39. package/src/components/tooltips/tooltips.ts +31 -0
  40. package/src/core/core_i18n.ts +65 -0
  41. package/src/core/core_tools.ts +44 -0
  42. package/src/x4doc.ts +3 -0
  43. package/src/components/menu/caret-right-solid.svg +0 -1
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  </picture>
5
5
 
6
6
  # x4js
7
- **A TypeScript framework for people who build applications, not markup.**
7
+ **A full TypeScript framework for web and desktop applications.**
8
8
 
9
9
  ## Home page
10
10
  see [X4 home page](https://x4js.org)
package/aicontext.md CHANGED
@@ -35,11 +35,13 @@ CoreElement<E> named timers + event surface (no DOM)
35
35
  │ │ │ → Menu, DropdownList, Notification
36
36
  │ │ ├── BtnGroup, Tabs, Video, Saturation
37
37
  │ ├── Button, Input, Icon, Label, SimpleText, Checkbox, Radio, Link, SimpleTextArea
38
- │ ├── Listbox, Gridview, Treeview, Select, Combobox, Slider, Gauge
38
+ │ ├── Listbox, Gridview, Treeview, Select, Combobox, Slider, Knob, Gauge
39
39
  │ ├── Progress, TickLine, Timeline, Canvas, Image, Spreadsheet, MonacoEditor
40
+ │ ├── Gantt, Planner, MapView (each one from its entry: x4js/gantt, x4js/planner, x4js/mapview)
41
+ │ ├── Canvas → Bargraph
40
42
  │ ├── SvgComponent → Chart
41
43
  │ ├── CSizer → HSizer / VSizer
42
- │ ├── FileDialog, ScrollView, Viewport, DockingView, Flex, Space
44
+ │ ├── FileDialog, ScrollView, Viewport, DockingView, Carousel, Flex, Space
43
45
  │ ├── HBox-based: ColorInput, Rating, Switch, Tag, Keyboard, TextEdit → AutoComplete, Breadcrumbs
44
46
  │ └── VBox-based: Calendar, ColorPicker, FileDrop, Panel, PropertyGrid, TextArea, SettingsView, SettingItem
45
47
  └── Application<E> singleton, no DOM of its own
@@ -382,7 +384,7 @@ class StackBox extends Box {
382
384
  }
383
385
  ```
384
386
 
385
- ### `AssistBox` — sequential navigation (wizard/carousel)
387
+ ### `AssistBox` — sequential navigation (wizard)
386
388
 
387
389
  ```ts
388
390
  class AssistBox extends StackBox {
@@ -390,6 +392,52 @@ class AssistBox extends StackBox {
390
392
  isFirstPage(): boolean
391
393
  isLastPage(): boolean
392
394
  }
395
+ // one page shown, no animation nor commands: for slides that move (arrows, dots, swipe, autoplay), Carousel
396
+ ```
397
+
398
+ ### `Carousel` — slides that move: arrows, dots, swipe, autoplay
399
+
400
+ ```ts
401
+ interface CarouselImage { // a slide that is an image: loaded when it comes near the view
402
+ src: string; alt?: string
403
+ caption?: string | UnsafeHtml // over the bottom of the image
404
+ fit?: "cover" | "contain" // cover by default
405
+ }
406
+ type CarouselItem = Component | CarouselImage
407
+
408
+ interface CarouselProps extends Omit<ComponentProps, "content"> {
409
+ items?: CarouselItem[]
410
+ perView?: number // slides seen side by side, 1 by default; a move goes one slide further
411
+ index?: number // the first slide seen at the start, 0 by default
412
+ loop?: boolean // after the last, the first (and before the first, the last)
413
+ autoplay?: number // ms between two slides; it loops; paused under the pointer, with the focus in it,
414
+ // during a swipe, or while the page is hidden
415
+ arrows?: boolean; dots?: boolean // true by default; none of them when all the slides are seen
416
+ change?: EventCallback<EvChange> // value: the index; the user (arrows, dots, swipe, keys) or the autoplay, not the code
417
+ }
418
+ class Carousel extends Component {
419
+ setItems(items: CarouselItem[], index?: number): void
420
+ select(index: number): void // no change event; beyond the ends: the end
421
+ next(): void; prev(): void // no change event; they loop when loop (or autoplay)
422
+ getIndex(): number; getCount(): number
423
+ }
424
+ // Its height is given (height, flex): the slides fill it. Without loop, an arrow is hidden at its end.
425
+ // Swipe: the finger or the mouse; a fifth of a slide goes to the next, a long swipe further; across, the page scrolls;
426
+ // a swipe is not a click on what is in the slide. Keys: ← → Home End.
427
+ // The slides out of the view are inert (Tab does not go in them). Disabled: faded as a Box, its images grey.
428
+ // The texts for screen readers: _tr.global.carousel. The autoplay timer stops when it leaves the page.
429
+ // CSS vars: --carousel-gap --carousel-duration --carousel-arrow-background --carousel-arrow-color --carousel-dot
430
+ // --carousel-dot-active --carousel-radius --carousel-caption-color --carousel-caption-background
431
+ // --carousel-disabled-filter
432
+ ```
433
+
434
+ ```ts
435
+ new Carousel({
436
+ height: 300, autoplay: 5000,
437
+ items: photos.map(p => ({ src: p.url, alt: p.title, caption: p.title })),
438
+ change: ev => this.showInfo(photos[ev.value]),
439
+ })
440
+ new Carousel({ height: 180, perView: 3, items: products.map(p => new ProductCard({ product: p })) })
393
441
  ```
394
442
 
395
443
  ### `GridBox`
@@ -578,7 +626,8 @@ interface TagProps extends ComponentProps {
578
626
  }
579
627
  class Tag extends HBox {} // no method: its text and its icon are those of its props
580
628
  // cls "alert": the colors of an alert
581
- // CSS vars: --tag-background --tag-color --tag-icon-color --tag-border
629
+ // CSS vars: --tag-background --tag-color (the text on the accent) --tag-border; --tag-icon-color is not set:
630
+ // the icon has the color of the text, a class that changes --tag-color changes both
582
631
  ```
583
632
 
584
633
  ```ts
@@ -605,6 +654,41 @@ class Gauge extends Component {
605
654
  }
606
655
  ```
607
656
 
657
+ ### `Bargraph` — a level meter of segments, drawn in a canvas
658
+
659
+ ```ts
660
+ interface BargraphProps extends ComponentProps {
661
+ value?: number // min by default
662
+ min?: number; max?: number // 0 and 100 by default
663
+ vertical?: boolean // the segments go up from the bottom; else from the left
664
+ segments?: number // 20 by default
665
+ colors?: string[] // from min to max, as Gauge (linearColorStops: green to red); one: a plain bar;
666
+ // none: the accent, pale to full (--bargraph-color-start, -end)
667
+ overshoot?: number // the segments from this value have the alert color; the colors go under it
668
+ animated?: boolean // up quickly, down slowly, in a few frames; false: at once (true by default)
669
+ peak?: boolean // the highest segment reached stays lit a second, then falls
670
+ }
671
+ class Bargraph extends Canvas {
672
+ setValue(v: number): void // drawn at the next frame: call it as often as needed (60 times a second)
673
+ getValue(): number
674
+ setMin(min: number): void; setMax(max: number): void
675
+ setColorStops(colors: string[]): void; setOvershoot(overshoot: number): void // undefined: no overshoot
676
+ refresh(): void // read the css variables again and draw
677
+ }
678
+ // For performance: one canvas, one requestAnimationFrame loop for all the bargraphs (stopped while none moves),
679
+ // a drawing only when a segment changes. 200 x 12 by default (12 x 200 vertical): width / height.
680
+ // The colors are read from the css when it draws first, and again when the theme changed (compared at each
681
+ // drawing), on enable / disable, setColorStops, setOvershoot; a bargraph that does not move keeps its colors
682
+ // until then: refresh() after a change of the theme or of the variables.
683
+ // An unlit segment: its color at --bargraph-off-mix (15%) in --bargraph-off. Disabled: the lit ones in the disabled color.
684
+ // CSS vars: --bargraph-color-start --bargraph-color-end --bargraph-overshoot --bargraph-off --bargraph-off-mix --bargraph-gap
685
+ ```
686
+
687
+ ```ts
688
+ const meters = channels.map( ( ) => new Bargraph( { vertical: true, overshoot: 85, peak: true } ) )
689
+ audio.onLevels( levels => levels.forEach( ( v, i ) => meters[i].setValue( v ) ) )
690
+ ```
691
+
608
692
  ### `Rating`
609
693
 
610
694
  ```ts
@@ -653,6 +737,56 @@ new Timeline({
653
737
  })
654
738
  ```
655
739
 
740
+ ### `MapView` — a map of tiles (openstreetmap), with markers and zones (`x4js/mapview`)
741
+
742
+ ```ts
743
+ import { MapView, LatLng, MapMarker } from "x4js/mapview" // not in x4js
744
+ interface LatLng { lat: number; lng: number }
745
+ interface MapMarker extends LatLng { title?: string; icon?: string /* a pin by default */; data?: any }
746
+ type MapShape = // zones, under the markers
747
+ { type: "polygon"; points: LatLng[] }
748
+ | { type: "circle"; center: LatLng; radius: number /* meters */ }
749
+ | { type: "line"; points: LatLng[] }
750
+ // all: title?: string; color?: string (else --chart-color-1..8 in turn); width?: number (px: 4 a line, 2 a border); data?: any
751
+
752
+ interface MapViewProps extends Omit<ComponentProps, "content"> {
753
+ center?: LatLng; zoom?: number // 0,0 and 2 by default; zoom: whole levels only
754
+ minZoom?: number; maxZoom?: number // 1 and 19 by default
755
+ tiles?: string // url of a tile, {z} {x} {y}: openstreetmap by default
756
+ attribution?: string | UnsafeHtml // the credit of the tiles, always shown: the one of openstreetmap by default
757
+ markers?: MapMarker[]; shapes?: MapShape[]
758
+ move?: EventCallback<EvMapMove> // { center, zoom }: the user moved or zoomed it (not setCenter, setZoom, fitBounds)
759
+ click?: EventCallback<EvMapClick> // { latlng }: a click on the map, not on a marker nor a zone
760
+ markerClick?: EventCallback<EvMarkerClick> // { marker }
761
+ shapeClick?: EventCallback<EvShapeClick> // { shape }
762
+ }
763
+ class MapView extends Component {
764
+ getCenter(): LatLng; setCenter(center: LatLng, zoom?: number): void
765
+ getZoom(): number; setZoom(zoom: number): void
766
+ fitBounds(points: LatLng[], margin = 32): void // the highest zoom where all are seen; before the map has a size: when it gets one
767
+ setMarkers(m: MapMarker[]); addMarker(m); removeMarker(m); getMarkers(): MapMarker[]
768
+ setShapes(s: MapShape[]); addShape(s); removeShape(s); getShapes(): MapShape[]
769
+ }
770
+ // The pointer moves it; the wheel, a double click, the buttons + and −, and the keys + and − zoom it (the point under
771
+ // the pointer stays in place, animated); the arrows move it; two fingers pinch it. East and west, the world goes round.
772
+ // A marker or a zone takes the click from the map; a marker is over a zone. title: a tooltip shown as html (initTooltips;
773
+ // escape a text from a user).
774
+ // openstreetmap is for a light use: an application with many users gives the tiles of a provider (tiles, attribution).
775
+ // No Leaflet: no projection other than web mercator, no fractional zoom, no clusters, no GeoJSON.
776
+ // The dark theme turns the tiles dark (--mapview-tile-filter in themes.scss).
777
+ // CSS vars: --mapview-background --mapview-marker --mapview-tile-filter --mapview-credit-background
778
+ // --mapview-shape-opacity --mapview-shape-opacity-hover
779
+ ```
780
+
781
+ ```ts
782
+ new MapView({
783
+ height: 400, center: { lat: 48.8566, lng: 2.3522 }, zoom: 13,
784
+ markers: [ { lat: 48.8584, lng: 2.2945, title: 'Tour Eiffel' } ],
785
+ shapes: [ { type: 'circle', center: { lat: 48.8584, lng: 2.2945 }, radius: 500 } ],
786
+ markerClick: ev => this.showPlace(ev.marker),
787
+ })
788
+ ```
789
+
656
790
  ---
657
791
 
658
792
  ## Input components
@@ -702,6 +836,10 @@ type TextEditProps = (TextInputProps | NumberProps | DateProps | TimeProps) & {
702
836
  class TextEdit extends HBox {
703
837
  getValue(): string; setValue(value: string): void; getInput(): Input
704
838
  }
839
+ // the class label-top (base.scss): the label above its field, smaller, darker while the field has the focus; on a
840
+ // TextEdit / AutoComplete / Combobox / TextArea, or on a box that holds them (a Form, the main view: all the
841
+ // application). labelWidth is not used then.
842
+ // Vars: --label-top-color / -color-focus / -size / -gap / -gap-textarea, --label-top-suffix (":" after the label)
705
843
  ```
706
844
 
707
845
  ### `TextArea`, `SimpleTextArea` — native `<textarea>`, with or without a label
@@ -781,6 +919,9 @@ class Radio extends Component {
781
919
  interface SwitchProps extends ComponentProps { label: string; checked?: boolean; value?: string; name?: string }
782
920
  // with a name, a Form reads and writes it: a boolean, or with a value, the value when checked and null when not
783
921
  class Switch extends HBox {}
922
+ // cls "square": the track and the thumb with corners barely rounded; "danger": the colour of an alert when on
923
+ // its input has role="switch"; its sizes follow the size of <html> (rem), rounded to whole pixels
924
+ // CSS vars: --switch-border --switch-background --switch-background-disabled --switch-checked --switch-danger --switch-thumb
784
925
  ```
785
926
 
786
927
  ### `Slider`
@@ -798,6 +939,39 @@ class Slider extends Component {
798
939
  }
799
940
  ```
800
941
 
942
+ ### `Knob` — a rotary button, on a range or infinite
943
+
944
+ ```ts
945
+ interface KnobProps extends ComponentProps {
946
+ value?: number // min by default (0 when infinite)
947
+ min?: number; max?: number // 0 and 100 by default; on an arc of 270°, min at -135°, max at +135° (0 at the top)
948
+ step?: number // 1 by default
949
+ infinite?: boolean // it turns without end: no min nor max, no arc (ticks all around)
950
+ turn?: number // infinite: the value of a whole turn, 360 by default (the value is an angle)
951
+ size?: number // the diameter of the dial in px, 48 by default (width/height size the whole, label included)
952
+ label?: string | UnsafeHtml; icon?: string // under the dial, an x4 Label; none: no room taken
953
+ name?: string // for a Form, which reads a number
954
+ change?: EventCallback<EvChange> // when the user turns it (pointer, wheel, keys), not by the code
955
+ }
956
+ class Knob extends Component {
957
+ getValue(): number; setValue(v: number): void // on the step; on a range, kept between min and max
958
+ setMin(min: number): void; setMax(max: number): void
959
+ setLabel(text: string | UnsafeHtml): void; setIcon(icon: string): void
960
+ }
961
+ // The pointer turns the dial around its center: the knob follows the turn, not the position (no jump when
962
+ // taken; on a range it stops at min and max and comes back at once).
963
+ // The wheel turns it only while it has the focus: the wheel over the page scrolls it.
964
+ // Keys: arrows a step (Ctrl: 10), PageUp / PageDown 10 steps, Home / End min and max (range).
965
+ // infinite: a double click sets it back to its first value (props.value), with a change.
966
+ // CSS vars: --knob-track --knob-value --knob-cap-background --knob-cap-border --knob-pointer --knob-border-focus
967
+ // --knob-track-disabled --knob-value-disabled --knob-cap-background-disabled
968
+ ```
969
+
970
+ ```ts
971
+ new Knob( { label: "Volume", value: 25, change: ( ev ) => player.setVolume( ev.value ) } )
972
+ new Knob( { infinite: true, turn: 20, change: ( ev ) => list.scrollTo( ev.value ) } ) // an encoder
973
+ ```
974
+
801
975
  ### `Select` — simple native select
802
976
 
803
977
  ```ts
@@ -1043,13 +1217,13 @@ class Store extends CoreElement {
1043
1217
  }
1044
1218
  ```
1045
1219
 
1046
- ### `Chart` — minimal svg chart (line, area, bar, pie, donut)
1220
+ ### `Chart` — minimal svg chart (line, area, bar, pie, donut, radar)
1047
1221
 
1048
1222
  ```ts
1049
1223
  interface ChartProps extends SvgProps {
1050
- type?: "line" | "area" | "bar" | "pie" | "donut" // default "line"
1224
+ type?: "line" | "area" | "bar" | "pie" | "donut" | "radar" // default "line"
1051
1225
  series: ChartSerie[] // required; pie & donut use the first serie
1052
- labels?: string[] // default: 1, 2, 3...
1226
+ labels?: string[] // default: 1, 2, 3...; radar: the name of each axis
1053
1227
  xaxis?: ChartAxis; yaxis?: ChartAxis // { name?, ticks?, min?, max?, step? }, all computed by default; on x: max = number of points, step = points between labels (>= 1), min not used
1054
1228
  legend?: boolean // default: shown when 2+ named series
1055
1229
  stacked?: boolean
@@ -1062,6 +1236,10 @@ class Chart extends SvgComponent {
1062
1236
  setTicks(sens: "x" | "y", ticks: ChartTick[]): void
1063
1237
  }
1064
1238
  // Colors: CSS variables --chart-color-1..8 (or serie.color). Size it with width/height/flex like any component.
1239
+ // radar: an axis for each point (3 at least), the first at the top, clockwise; the center is yaxis.min (0 by
1240
+ // default), the outer ring yaxis.max; a ring for each tick of the y scale (yaxis.step, yaxis.ticks), their
1241
+ // values along the first axis. Each serie is a closed line, tinted inside (serie.fill: its color, "none": the
1242
+ // line only); a value missing is at the center. xaxis.name, yaxis.name and stacked are not used.
1065
1243
  // Minimal by design: no dual axis, mixed types, time axis, zoom or animation — use a dedicated library for those.
1066
1244
  ```
1067
1245
 
@@ -1157,7 +1335,8 @@ class Tabs extends Box {
1157
1335
 
1158
1336
  ```ts
1159
1337
  interface PopupProps extends ComponentProps {
1160
- autoClose?: boolean | string // true, or a group name
1338
+ autoClose?: boolean | string // true, or a group name: closed by a press outside; while it is shown,
1339
+ // a wheel outside every popup is ignored (the page does not scroll)
1161
1340
  sizable?: boolean; movable?: boolean
1162
1341
  }
1163
1342
  class Popup extends Box {
@@ -1367,7 +1546,10 @@ class VSizer extends CSizer { // vertical splitter, between two chi
1367
1546
  // The columns of a Gridview or a Spreadsheet are 16px wide at least (the min-width of a cell).
1368
1547
  class ScrollView extends Component { getViewport(): Component }
1369
1548
  class Viewport extends Component
1370
- class Canvas extends Component { getContext(): CanvasRenderingContext2D; paint(): void }
1549
+ class Canvas<P extends CanvasProps = CanvasProps, E extends CanvasEventMap = CanvasEventMap> extends Component<P, E> {
1550
+ getContext(): CanvasRenderingContext2D; redraw(wait?: number): void
1551
+ protected paint(ctx: CanvasEx): void // a derived class draws here, in px of the page (the ratio of the screen is set)
1552
+ }
1371
1553
  class Video extends Box { // class x4video; props: src? (a file, url), stream? (wins over src), autoplay?, muted?, playsInline?
1372
1554
  play(): Promise<boolean> // false: could not start (a click of the user wanted, a source not playable); no source: waits for one
1373
1555
  pause(); mute(set: boolean)
@@ -1634,7 +1816,8 @@ isLanguage(name: string): boolean
1634
1816
  // diff_date_seconds/minutes/hours, date_input_formats, date_format, date_time_format,
1635
1817
  // locale ("fr-FR", "en-US", "es-ES", "de-DE": the formats of numbers, amounts, dates),
1636
1818
  // day_short[] day_long[] month_short[] month_long[], keyboard.{next,numeric,alpha},
1637
- // settings.{search,nomatch}, progression.{error,errors,warning,warnings}
1819
+ // settings.{search,nomatch}, progression.{error,errors,warning,warnings},
1820
+ // carousel.{carousel,slide,prev,next,position}
1638
1821
  getCurrentLocale(): string; getCurrentCurrency(): string // as Application.getLocale / getCurrency
1639
1822
  ```
1640
1823
 
@@ -1653,14 +1836,18 @@ by the browser, in its own locale, whatever this one.
1653
1836
  /* the globals the components use: --background-primary / -secondary / -ternary / -hover / -active / -input,
1654
1837
  --text-primary / -secondary / -ternary, --border / -light / -hover / -focus, --accent-background / -hover /
1655
1838
  -focus / -active / -light, --accent-color (the text on the accent), --disabled-*, --icon-color / -active,
1656
- --alert-*, --warning-*, --success-* */
1839
+ --alert-*, --warning-*, --success-*, --shadow-color (the shadow of what floats: dialog, list, notification, tooltip) */
1657
1840
  ```
1658
1841
  // The greys are mixes of a0 and a100: a theme that reverses the scale gets them light on dark.
1659
1842
  // Each component reads its own variables (the @cssvar of its class), which point to these globals:
1660
1843
  // a theme redefines the globals, or one variable of a component.
1661
1844
  // Themes: Application.setTheme(name) puts the class x4theme-<name> on <html>. "" is the light theme (by default),
1662
- // "dark" the dark theme of x4 (the colours of VS Code Dark Modern). An application adds its own theme in its css,
1845
+ // "dark" the dark theme of x4 (the greys of VS Code Dark Modern, its blues from #2d6fa5: the accent #245883). An application adds its own theme in its css,
1663
1846
  // html.x4theme-pink { --color-primary-a0: ...; ... }, then Application.setTheme("pink").
1847
+ // The font: --font-name and --font-size (14px), on :root (normalize.scss); body takes them. To change the size
1848
+ // of the text, set --font-size ( :root { --font-size: 13px } ), not the font-size of <html>: the components
1849
+ // follow --font-size; the font-size of <html> is only the rem of a few inner sizes (the box of a Checkbox, the
1850
+ // round of a Radio, a Switch, a Slider), not the text.
1664
1851
 
1665
1852
  ---
1666
1853
 
@@ -1722,6 +1909,8 @@ date_format(date, options?): string // options: Intl.DateTimeFormat in the
1722
1909
  // Formats in the current locale (Application.setLocale, else the one of the language), Intl made once by locale
1723
1910
  formatNumber(value, decimals?, grouping = true): string // "1 234,5" in fr-FR; decimals: the most shown
1724
1911
  formatMoney(value): string // "1 234,50 €": the currency of Application.getCurrency()
1912
+ formatPercent(value, decimals = 0): string // 89 → "89 %" in fr-FR, "89%" in en-US: a value from 0 to 100
1913
+ formatUnit(value, unit, decimals?): string // short: "12 j", "3 h" in fr-FR, "12 days", "3 hr" in en-US; unit of Intl: "day", "hour"...
1725
1914
  formatDate(date): string // "22/10/2026"
1726
1915
  formatDateTime(date): string // "22/10/2026 14:05"
1727
1916
  // Gridview and Spreadsheet format their columns number, money, date, date-time with them (a formatter comes first)
@@ -1729,6 +1918,8 @@ date_diff(date1, date2, options?): string // "30 secondes", "5 minutes", "2 he
1729
1918
  date_to_sql(date, withHours): string; date_sql_utc(date): Date
1730
1919
  date_hash(date): number; date_clone(date): Date
1731
1920
  date_calc_weeknum(date): number // ISO 8601: weeks from monday, week 1 holds the first thursday (29/12/2025 → 1)
1921
+ date_week_start(date): Date // the monday of its week (itself on a monday), at the same time: a new date
1922
+ date_add_days(date, days): Date // days after (before when < 0), at the same time: a new date; a day of summer time counts as one
1732
1923
  parseIntlDate(value, fmts?): Date; formatIntlDate(date, fmt?, utc?): string
1733
1924
  calcAge(birth, ref?): number
1734
1925
 
@@ -1824,24 +2015,195 @@ measureText(str: string, size: number, bold?: boolean): number // Helvetica
1824
2015
 
1825
2016
  ---
1826
2017
 
2018
+ ## Components with an entry of their own
2019
+
2020
+ Some components are not in `x4js`: they are big, or seldom used, and an application that does not
2021
+ use them should not carry their code nor their CSS. Each one has an entry of its own in the package:
2022
+ import it from it, not from `x4js` (`import { Gantt } from "x4js"` fails: "has no exported member").
2023
+
2024
+ | Component | Import | More |
2025
+ |---|---|---|
2026
+ | `MonacoEditor` | `import { MonacoEditor } from "x4js/monaco"` | copy its files to the folder `monaco` of the output (below) |
2027
+ | `Gantt` | `import { Gantt } from "x4js/gantt"` | |
2028
+ | `Planner` | `import { Planner } from "x4js/planner"` | |
2029
+ | `MapView` | `import { MapView } from "x4js/mapview"` | `LatLng`, `MapMarker`, `MapShape` from it too |
2030
+
2031
+ ---
2032
+
1827
2033
  ## Code editor (`MonacoEditor`)
1828
2034
 
1829
2035
  Monaco-based code editor component. Monaco is part of the framework surface, so exclude it from `tsc` compilation (see Default project notes).
1830
2036
 
2037
+ ```jsonc
2038
+ // x4.config.json: its files (14 MB) in the folder monaco of the output, where the editor loads them
2039
+ "copy": [ { "from": "./node_modules/x4js/src/components/monaco/bin", "to": "monaco" } ]
2040
+ ```
2041
+
1831
2042
  ```ts
2043
+ import { MonacoEditor } from "x4js/monaco" // not in x4js
1832
2044
  interface MonacoEditorProps extends ComponentProps {
1833
- language: "typescript" | "javascript" | "json" | "css" | "html"
1834
- theme?: string
1835
- content?: string
2045
+ language: "typescript" | "javascript" | "json" | "css" | "html" | string
2046
+ theme?: string // a theme of Monaco: "vs", "vs-dark", "hc-black", "hc-light"
2047
+ content?: string // the text at the start
1836
2048
  options?: Monaco.editor.IEditorOptions & Monaco.editor.IGlobalEditorOptions
1837
2049
  }
1838
2050
  class MonacoEditor extends Component<MonacoEditorProps> {
1839
- constructor(props: MonacoEditorProps)
1840
- static start(): Promise<void> // load/initialize Monaco
1841
- static addTypelib(name: string, code: string): void // register a .d.ts for IntelliSense
1842
- static basePath: string
1843
- static monaco: typeof Monaco
2051
+ constructor(props: MonacoEditorProps) // the first one loads Monaco: monaco/monaco.js beside the bundle, its workers, its css
2052
+ static addTypelib(name: string, code: string): void // a .d.ts for the completion of typescript; before the first editor too
2053
+ static monaco(): Promise<typeof Monaco> // the module of Monaco, loaded
2054
+ getText(): string; setText(text: string): void
2055
+ setTheme(name: string): void
2056
+ }
2057
+ ```
2058
+
2059
+ ---
2060
+
2061
+ ## `Gantt` — tasks on a calendar (`x4js/gantt`)
2062
+
2063
+ ```ts
2064
+ import { Gantt } from "x4js/gantt" // not in x4js
2065
+ type GanttID = string | number
2066
+ type GanttScale = "hour" | "day" | "week" | "month"
2067
+ interface GanttTask {
2068
+ id: GanttID; title: string
2069
+ start?: Date; end?: Date // a date without hours is a whole day: end is its last day, included; none: the day of start
2070
+ progress?: number // 0 to 100
2071
+ milestone?: boolean // a diamond in the middle of its day
2072
+ children?: GanttTask[] // a group: from the first start of its tasks to their last end, its progress weighted by their length
2073
+ open?: boolean // a group: its tasks shown; true by default
2074
+ deps?: GanttID[] // the tasks it waits for: an arrow from the end of each one
2075
+ color?: string // its bar; a group gives it to its tasks that have none; none: the theme
2076
+ tooltip?: string; cls?: string; data?: any
2077
+ }
2078
+ interface GanttColumn {
2079
+ id: string // "title" (the tree), "start", "end", "duration", "progress", or a field of the task
2080
+ title?: string // none: for the ids above, their title in the language (_tr.global.gantt)
2081
+ width?: number; align?: "left" | "center" | "right"; formatter?: (task: GanttTask) => string | UnsafeHtml
2082
+ }
2083
+ interface EvGanttChange extends ComponentEvent { readonly task: GanttTask; readonly start: Date; readonly end: Date; readonly progress: number }
2084
+ interface EvGanttScale extends ComponentEvent { readonly scale: GanttScale }
2085
+ interface GanttProps extends Omit<ComponentProps, "content"> {
2086
+ tasks?: GanttTask[]
2087
+ columns?: GanttColumn[] // the list on the left; the titles alone by default
2088
+ scale?: GanttScale // "day" by default; ctrl+wheel changes it
2089
+ start?: Date; end?: Date // the days shown; else those of the tasks
2090
+ rowHeight?: number // 32 by default
2091
+ readonly?: boolean
2092
+ click?, dblClick?, contextMenu? // ev.context: the task
2093
+ selectionChange?: EventCallback<EvSelectionChange>
2094
+ change?: EventCallback<EvGanttChange> // told before it is written in the task: ev.preventDefault() puts the bar back
2095
+ scaleChange?: EventCallback<EvGanttScale> // the zoom took another scale
1844
2096
  }
2097
+ class Gantt extends Component<GanttProps, GanttEvents> {
2098
+ setTasks(tasks: GanttTask[]): void
2099
+ setScale(scale: GanttScale): void // its width of a day; the date at the left stays
2100
+ getScale(): GanttScale
2101
+ setReadOnly(ro: boolean): void
2102
+ selectTask(id: GanttID): void // without selectionChange; null: none
2103
+ getSelection(): GanttID
2104
+ scrollToDate(date: Date): void // in the middle of the chart
2105
+ }
2106
+ // the pointer: moves a bar, stretches it by its ends, its progress by the mark under it (by the hour in "hour", else by the day)
2107
+ // ctrl+wheel on the chart: zoom, the day under the pointer stays; the scale follows the width of a day
2108
+ // keys: up, down, home, end select; right opens a group, left closes it or goes to its group
2109
+ // readonly: no bar moved; disabled (enable(false)): faded as a Box
2110
+ // CSS vars: --gantt-background --gantt-border --gantt-border-focus --gantt-header-background --gantt-header-color
2111
+ // --gantt-line-color --gantt-major-line-color --gantt-weekend-background --gantt-today-color
2112
+ // --gantt-row-background-sel --gantt-row-color-sel --gantt-lane-background-sel --gantt-icon-color
2113
+ // --gantt-bar-background --gantt-bar-border --gantt-bar-progress --gantt-bar-label --gantt-handle-color
2114
+ // --gantt-group-background --gantt-milestone-background --gantt-link-color --gantt-link-color-sel
2115
+ ```
2116
+
2117
+ ```ts
2118
+ new Gantt({
2119
+ tasks: [
2120
+ { id: "dev", title: "Development", color: "#fb8c00", children: [
2121
+ { id: "core", title: "Core", start: new Date(2026, 10, 2), end: new Date(2026, 10, 12), progress: 30 },
2122
+ { id: "ui", title: "User interface", start: new Date(2026, 10, 6), end: new Date(2026, 10, 18), deps: ["core"] },
2123
+ ] },
2124
+ ],
2125
+ columns: [{ id: "title", width: 200 }, { id: "start" }, { id: "end" }],
2126
+ change: (ev) => save(ev.task, ev.start, ev.end, ev.progress),
2127
+ })
2128
+ ```
2129
+
2130
+ ---
2131
+
2132
+ ## `Planner` — events by day, week or month (`x4js/planner`)
2133
+
2134
+ ```ts
2135
+ import { Planner } from "x4js/planner" // not in x4js
2136
+ type PlannerID = string | number
2137
+ type PlannerView = "day" | "week" | "month"
2138
+ interface PlannerEvent {
2139
+ id: PlannerID; title: string
2140
+ start: Date
2141
+ end?: Date // a date without hours is a whole day: end is its last day, included; else the time it ends.
2142
+ // none: the day of start, or an hour after it
2143
+ text?: string // under the title
2144
+ color?: string // its border, its icon and a tint of its background; none: the accent of the theme
2145
+ icon?: string // on the right of its title, in its colour (as Icon: an svg)
2146
+ tooltip?: string; cls?: string; data?: any
2147
+ }
2148
+ interface EvPlannerChange extends ComponentEvent { readonly event: PlannerEvent; readonly start: Date; readonly end: Date }
2149
+ interface EvPlannerSelect extends ComponentEvent { readonly start: Date; readonly end: Date } // whole days: without hours, end the last one
2150
+ interface EvPlannerView extends ComponentEvent { readonly view: PlannerView; readonly date: Date }
2151
+ interface PlannerProps extends Omit<ComponentProps, "content"> {
2152
+ events?: PlannerEvent[]
2153
+ view?: PlannerView // "week" by default
2154
+ date?: Date // a day shown; today by default
2155
+ workDays?: number[] // as Date.getDay(): monday to friday by default
2156
+ workHours?: [number, number] // minutes from midnight: 8:00 to 18:00 by default
2157
+ showOffDays?: boolean // the other days, narrower; true by default
2158
+ showOffHours?: boolean // the other hours, reached by the scroll; true by default
2159
+ band?: boolean // the whole days in a band under the days; true by default; false: in the working hours
2160
+ showSubGrid?: boolean // the lines of the steps between the hours; false by default
2161
+ step?: number // the precision of the pointer, in minutes: 15 by default
2162
+ readonly?: boolean
2163
+ click?, dblClick?, contextMenu? // ev.context: the event
2164
+ selectionChange?: EventCallback<EvSelectionChange>
2165
+ change?: EventCallback<EvPlannerChange> // moved or stretched; told before it is written in the event: ev.preventDefault() puts it back
2166
+ copy?: EventCallback<EvPlannerChange> // ctrl+drag (option on a Mac): nothing is written, the application adds the copy
2167
+ select?: EventCallback<EvPlannerSelect> // a range of time or of days: the application creates the event
2168
+ viewChange?: EventCallback<EvPlannerView> // a day of the month opened: its week
2169
+ }
2170
+ class Planner extends Component<PlannerProps, PlannerEvents> {
2171
+ setEvents(events: PlannerEvent[]): void // the selection goes when its event is not in them
2172
+ setView(view: PlannerView): void; getView(): PlannerView
2173
+ setDate(date: Date): void; getDate(): Date
2174
+ next(): void; prev(): void // the day, the week, the month after or before
2175
+ getRange(): { start: Date, end: Date } // the first and the last day shown; in the month, its days
2176
+ setBand(band: boolean): void; setShowOffDays(show: boolean): void; setShowOffHours(show: boolean): void
2177
+ setShowSubGrid(show: boolean): void; setStep(step: number): void; setReadOnly(ro: boolean): void
2178
+ selectEvent(id: PlannerID): void // without selectionChange; null: none
2179
+ getSelection(): PlannerID
2180
+ }
2181
+ // the pointer: a drag on an empty place selects a range (shift, or the strip on the right of a day: over the events too);
2182
+ // a drag moves an event (a ghost, the event faded at its place), its top or its bottom stretches it, by step;
2183
+ // an event of one day stays in its day; near the top or the bottom of the view, the view scrolls
2184
+ // the events out of the view: a mark at the top or the bottom of their day, a click on it scrolls to them
2185
+ // a key on the event selected: addShortcut("Delete", ...) and getSelection()
2186
+ // readonly: nothing created, moved, stretched nor copied; disabled (enable(false)): faded as a Box
2187
+ // CSS vars: --planner-background --planner-border --planner-border-focus --planner-header-color --planner-day-color
2188
+ // --planner-off-color --planner-today-background --planner-today-color --planner-today-column
2189
+ // --planner-off-background --planner-line-color --planner-step-color --planner-hour-line-color
2190
+ // --planner-hour-color --planner-hour-color-work --planner-edge-background --planner-edge-color
2191
+ // --planner-edge-shadow --planner-edge-opacity --planner-now-color --planner-now-tag --planner-now-text
2192
+ // --planner-event-background --planner-event-color --planner-event-tint --planner-event-radius
2193
+ // --planner-event-title --planner-event-text --planner-event-selected --planner-range-background
2194
+ // --planner-range-border --planner-out-color --planner-more-color
2195
+ ```
2196
+
2197
+ ```ts
2198
+ const planner = new Planner({
2199
+ events: [
2200
+ { id: 1, title: "Design review", start: new Date(2026, 9, 26, 10), end: new Date(2026, 9, 26, 12), color: "#2e9e48" },
2201
+ { id: 2, title: "Day off", start: new Date(2026, 9, 30) },
2202
+ ],
2203
+ select: (ev) => create(ev.start, ev.end),
2204
+ change: (ev) => save(ev.event, ev.start, ev.end),
2205
+ })
2206
+ planner.addShortcut("Delete", () => remove(planner.getSelection()))
1845
2207
  ```
1846
2208
 
1847
2209
  ---
@@ -1857,6 +2219,8 @@ class CMover {
1857
2219
  }
1858
2220
  createPainter(c2d: CanvasRenderingContext2D, w: number, h: number): CanvasEx // Canvas painter
1859
2221
  initTooltips(): void // initialize the global tooltip system
2222
+ // a tooltip: 30vw wide at most (--tooltip-max-width; none: the width of its text); it goes at a click, and when its
2223
+ // element is removed or hidden (checked every second while it is shown)
1860
2224
  ```
1861
2225
 
1862
2226
  JSX, in `.tsx` files: in tsconfig.json `"jsx": "react"` and `"jsxFactory": "x4_react.create_element"` (esbuild reads
@@ -2105,7 +2469,7 @@ Optional keys: `external`, `define`, `esbuild`, `dev` (`host`, `port`, `https`,
2105
2469
  - The default language is `fr`. Call `selectLanguage('en')` to switch to English.
2106
2470
  - `Gridview` is backed by a `DataStore`; `Spreadsheet` is backed by a `Store` (different, cell-based).
2107
2471
  - `DataRecordID` is currently typed `any`; `strictNullChecks` is intentionally off in the recommended `tsconfig.json`.
2108
- - `MonacoEditor` requires `MonacoEditor.start()` (async) before use; register extra typings with `MonacoEditor.addTypelib(name, code)`.
2472
+ - `MonacoEditor` loads Monaco by itself (the first editor), from the folder `monaco` of the output where its files are copied; register extra typings with `MonacoEditor.addTypelib(name, code)`.
2109
2473
  - `x4_react.create_element` is the JSX pragma (the setup: JSX factory & helpers).
2110
2474
  - A DOM handler of an ancestor is called for the events of its descendants, as in the browser. Not for the events that do not bubble: `focus`, `blur`, `mouseenter`, `mouseleave` (use `focusin` / `focusout`, `mouseover` / `mouseout`), nor for those of x4: `created`, `removed`, `resized`.
2111
2475
  - Several handlers of one event on one node are called in the order they were added. One that returns `true` stops those that follow it.
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "x4js",
3
- "version": "2.3.28",
3
+ "version": "2.3.30",
4
+ "description": "A full TypeScript framework for web and desktop applications: components written as classes (layouts, inputs, lists, grids, dialogs, forms, charts...) and a command line to create, serve and build them.",
4
5
  "license": "MIT",
5
6
  "type": "module",
6
7
  "main": "src/x4.ts",
@@ -8,7 +9,10 @@
8
9
  "types": "src/x4.ts",
9
10
  "exports": {
10
11
  ".": "./src/x4.ts",
11
- "./monaco": "./src/components/monaco/monaco.ts"
12
+ "./monaco": "./src/components/monaco/monaco.ts",
13
+ "./gantt": "./src/components/gantt/gantt.ts",
14
+ "./planner": "./src/components/planner/planner.ts",
15
+ "./mapview": "./src/components/mapview/mapview.ts"
12
16
  },
13
17
  "bin": {
14
18
  "x4js": "./bin/x4js.mjs"
@@ -16,7 +20,8 @@
16
20
  "scripts": {
17
21
  "build-publish": "deno run --allow-all ./publish.ts",
18
22
  "build-doc": "typedoc",
19
- "build-types": "npx tsc --declaration --emitDeclarationOnly --outFile types/x4.d.ts"
23
+ "build-types": "npx tsc --declaration --emitDeclarationOnly --outFile types/x4.d.ts",
24
+ "lint": "tsc --noEmit && node demo/gen-sources.mjs && tsc --noEmit -p demo/tsconfig.local.json"
20
25
  },
21
26
  "files": [
22
27
  "src",
@@ -19,6 +19,8 @@ const x4icons = {
19
19
  danger: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24V264c0 13.3-10.7 24-24 24s-24-10.7-24-24V152c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>`,
20
20
  loading: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor"><!--!Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc.--><path d="M207.4 20.4c2.4 8.5-2.6 17.3-11.2 19.7C101.5 66.2 32 153 32 256c0 123.7 100.3 224 224 224s224-100.3 224-224c0-103-69.5-189.8-164.3-215.9c-8.5-2.4-13.5-11.2-11.2-19.7s11.2-13.5 19.7-11.2C432.5 39.1 512 138.2 512 256c0 141.4-114.6 256-256 256S0 397.4 0 256C0 138.2 79.5 39.1 187.7 9.2c8.5-2.4 17.3 2.6 19.7 11.2z"/></svg>`,
21
21
  close_box: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" fill="currentColor"><!--!Font Awesome Pro 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc.--><path d="M192 233.4L59.5 100.9 36.9 123.5 169.4 256 36.9 388.5l22.6 22.6L192 278.6 324.5 411.1l22.6-22.6L214.6 256 347.1 123.5l-22.6-22.6L192 233.4z"/></svg>`,
22
+ // a triangle to the right: a menu item that opens a sub menu
23
+ caret: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 512" fill="currentColor"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M246.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-128-128c-9.2-9.2-22.9-11.9-34.9-6.9s-19.8 16.6-19.8 29.6l0 256c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l128-128z"/></svg>`,
22
24
  // a triangle, a "!" cut in it
23
25
  warning: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor"><path fill-rule="evenodd" d="M8 1L15.5 14.5H.5zM7.25 5.5h1.5V10h-1.5zM7 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0z"/></svg>`,
24
26
  };