x4js 2.3.25 → 2.3.27

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 (68) hide show
  1. package/aicontext.md +87 -13
  2. package/package.json +1 -1
  3. package/src/components/autocomplete/autocomplete.ts +3 -3
  4. package/src/components/boxes/boxes.module.scss +16 -2
  5. package/src/components/boxes/boxes.ts +24 -11
  6. package/src/components/breadcrumb/breadcrumb.ts +1 -2
  7. package/src/components/btngroup/btngroup.ts +2 -2
  8. package/src/components/button/button.ts +2 -2
  9. package/src/components/calendar/calendar.ts +5 -3
  10. package/src/components/canvas/canvas.ts +1 -2
  11. package/src/components/chart/chart.ts +4 -4
  12. package/src/components/checkbox/checkbox.ts +2 -2
  13. package/src/components/colorinput/colorinput.ts +3 -2
  14. package/src/components/colorpicker/colorpicker.ts +5 -5
  15. package/src/components/combobox/combobox.ts +3 -3
  16. package/src/components/components.ts +1 -0
  17. package/src/components/dialog/dialog.ts +2 -2
  18. package/src/components/dockingview/dockingview.ts +2 -3
  19. package/src/components/filedrop/filedrop.ts +3 -2
  20. package/src/components/form/form.ts +4 -3
  21. package/src/components/gauge/gauge.ts +2 -2
  22. package/src/components/gridview/gridview.module.scss +8 -5
  23. package/src/components/gridview/gridview.ts +11 -13
  24. package/src/components/header/header.ts +2 -2
  25. package/src/components/icon/icon.ts +1 -2
  26. package/src/components/image/image.ts +1 -2
  27. package/src/components/input/input.ts +3 -2
  28. package/src/components/keyboard/keyboard.ts +2 -2
  29. package/src/components/label/label.ts +3 -3
  30. package/src/components/link/link.ts +3 -2
  31. package/src/components/listbox/listbox.ts +33 -47
  32. package/src/components/menu/menu.ts +5 -4
  33. package/src/components/messages/messages.module.scss +29 -5
  34. package/src/components/messages/messages.ts +42 -50
  35. package/src/components/monaco/monaco.ts +1 -2
  36. package/src/components/notification/notification.ts +3 -2
  37. package/src/components/panel/panel.ts +2 -2
  38. package/src/components/popup/popup.ts +4 -4
  39. package/src/components/progress/progress.ts +1 -2
  40. package/src/components/propgrid/progrid.module.scss +2 -3
  41. package/src/components/propgrid/propgrid.ts +2 -2
  42. package/src/components/radio/radio.ts +1 -2
  43. package/src/components/rating/rating.ts +3 -3
  44. package/src/components/select/select.ts +2 -2
  45. package/src/components/settingsview/settingsview.module.scss +6 -5
  46. package/src/components/settingsview/settingsview.ts +3 -3
  47. package/src/components/sizers/sizer.ts +21 -29
  48. package/src/components/slider/slider.ts +2 -2
  49. package/src/components/spreadsheet/spreadsheet.ts +8 -10
  50. package/src/components/switch/switch.ts +2 -2
  51. package/src/components/tabs/tabs.ts +4 -4
  52. package/src/components/tag/tag.ts +2 -2
  53. package/src/components/textarea/textarea.ts +3 -3
  54. package/src/components/textedit/textedit.ts +2 -2
  55. package/src/components/themes.scss +2 -0
  56. package/src/components/tickline/tickline.ts +3 -3
  57. package/src/components/timeline/timeline.module.scss +147 -0
  58. package/src/components/timeline/timeline.ts +146 -0
  59. package/src/components/tooltips/tooltips.ts +2 -2
  60. package/src/components/treeview/treeview.ts +19 -25
  61. package/src/components/video/video.ts +1 -2
  62. package/src/components/viewport/viewport.ts +4 -3
  63. package/src/core/component.ts +46 -26
  64. package/src/core/core_application.ts +29 -1
  65. package/src/core/core_i18n.ts +39 -0
  66. package/src/core/core_shortcuts.ts +2 -1
  67. package/src/core/core_svg.ts +1 -0
  68. package/src/core/core_tools.ts +83 -24
@@ -14,7 +14,7 @@
14
14
  * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
15
  **/
16
16
 
17
- import { isArray, UnsafeHtml, isNumber, Rect, Constructor, class_ns, x4_class_ns_sym } from './core_tools';
17
+ import { isArray, UnsafeHtml, isNumber, Rect, Constructor } from './core_tools';
18
18
  import { CoreElement } from './core_element';
19
19
  import { AriaAttributes, unitless } from './core_styles';
20
20
  import { CoreEvent, EventMap } from './core_events';
@@ -31,17 +31,19 @@ const FRAGMENT = Symbol( "fragment" );
31
31
  const RE_NUMBER = /^-?\d+(\.\d*)?$/;
32
32
 
33
33
  /**
34
- * you can change css classname prefix by adding
34
+ * the css classes of a component: the name of each class of its chain, in lower case, after
35
+ * the prefix that class declares itself (not the one of its parent):
35
36
  *
36
37
  * ```
37
- * static "$cls-ns" = "<your prefix>";
38
+ * static classPrefix = "<your prefix>";
38
39
  * ```
39
- *
40
- * to your class to avoid autogenerated css class names conflicts
40
+ *
41
+ * every component of x4 declares "x4" (x4button...); the classes of an application have
42
+ * none unless they declare one
41
43
  */
42
44
 
43
45
  function genClassNames( x: any ): string[] {
44
-
46
+
45
47
  let self = Object.getPrototypeOf(x);
46
48
  if( self.constructor==Component ) {
47
49
  return ["x4-comp","x4"];
@@ -50,8 +52,8 @@ function genClassNames( x: any ): string[] {
50
52
  const classes = [];
51
53
  while (self && self.constructor !== Component ) {
52
54
  const clsname:string = self.constructor.name;
53
- const clsns: string = Object.prototype.hasOwnProperty.call(self.constructor,x4_class_ns_sym) ? self.constructor[x4_class_ns_sym] : "";
54
- classes.push( clsns+clsname.toLowerCase() );
55
+ const prefix: string = Object.prototype.hasOwnProperty.call(self.constructor,"classPrefix") ? self.constructor.classPrefix : "";
56
+ classes.push( prefix+clsname.toLowerCase() );
55
57
  self = Object.getPrototypeOf(self);
56
58
  }
57
59
 
@@ -156,9 +158,9 @@ export interface ComponentEvents extends EventMap {
156
158
  * ```
157
159
  */
158
160
 
159
- @class_ns("x4")
160
161
  export class Component<P extends ComponentProps = ComponentProps, E extends ComponentEvents = ComponentEvents>
161
162
  extends CoreElement<E> {
163
+ static classPrefix = "x4";
162
164
 
163
165
  /** The underlying DOM element of the component. */
164
166
  readonly dom: Element;
@@ -907,19 +909,6 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
907
909
  this.setAttribute( "disabled", !ena ? 'true' : null );
908
910
  this.setAttribute( "inert", !ena ? 'true' : null );
909
911
 
910
- /*
911
- boxes are now fieldset, this is native
912
- if( this.dom instanceof HTMLInputElement || this.dom instanceof HTMLButtonElement ) {
913
- this.dom.disabled = !ena;
914
- }
915
-
916
- // propagate enable state to all input children
917
- const nodes = this.enumChildComponents( true );
918
- nodes.forEach( x => {
919
- x.enable( ena );
920
- });
921
- */
922
-
923
912
  return this;
924
913
  }
925
914
 
@@ -970,7 +959,24 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
970
959
  */
971
960
 
972
961
  parentElement<T extends Component>( cls?: Constructor<T> ): T {
973
- return Component.parentElement<T>( this.dom, cls );
962
+ return Component.parentElement<T>( this.dom, cls );
963
+ }
964
+
965
+ /**
966
+ * The nearest component whose element matches a css selector: this one, or one of its ancestors.
967
+ * @returns null when there is none
968
+ */
969
+
970
+ closest<T extends Component = Component>( selector: string ): T {
971
+ return Component.closest<T>( this.dom, selector );
972
+ }
973
+
974
+ /**
975
+ * true when a node, or the element of a component, is the element of this one or inside it
976
+ */
977
+
978
+ contains( other: Component | Node ): boolean {
979
+ return this.dom.contains( other instanceof Component ? other.dom : other );
974
980
  }
975
981
 
976
982
  /**
@@ -1003,10 +1009,22 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
1003
1009
 
1004
1010
  dom = dom.parentElement;
1005
1011
  }
1006
-
1012
+
1007
1013
  return null;
1008
1014
  }
1009
1015
 
1016
+ /**
1017
+ * The nearest component whose element matches a css selector: the one of dom, or one of its ancestors.
1018
+ * @returns null when there is none
1019
+ * @example
1020
+ * // in a click handler: the item hit, wherever the click is in it
1021
+ * const item = Component.closest( ev.target as Element, ".x4timeline > .item" );
1022
+ */
1023
+
1024
+ static closest<T extends Component = Component>( dom: Element, selector: string ): T {
1025
+ return componentFromDOM<T>( dom.closest( selector ) );
1026
+ }
1027
+
1010
1028
  /**
1011
1029
  * Returns the first child element as a Component instance.
1012
1030
  * @returns The first child component, or `null` if none exists.
@@ -1294,8 +1312,9 @@ export function wrapDOM( el: HTMLElement ): Component {
1294
1312
  * Automatically generates CSS class: `x4flex`.
1295
1313
  */
1296
1314
 
1297
- @class_ns( "x4" )
1298
1315
  export class Flex extends Component {
1316
+ static classPrefix = "x4";
1317
+
1299
1318
  constructor( ) {
1300
1319
  super({})
1301
1320
  }
@@ -1311,8 +1330,9 @@ export class Flex extends Component {
1311
1330
  * ```
1312
1331
  */
1313
1332
 
1314
- @class_ns( "x4" )
1315
1333
  export class Space extends Component {
1334
+ static classPrefix = "x4";
1335
+
1316
1336
  constructor( width?: number|string, cls?: string ) {
1317
1337
  super( { width, cls } )
1318
1338
  }
@@ -19,6 +19,7 @@ import { CoreElement } from './core_element';
19
19
  import { CoreEvent, EventMap } from './core_events';
20
20
  import { asap, getFocusableElements, ITabHandler } from './core_tools';
21
21
  import { Shortcuts, ShortcutCallback, ShortcutOptions } from './core_shortcuts';
22
+ import { _setLocale, getCurrentLocale, getCurrentCurrency } from './core_i18n';
22
23
 
23
24
  const socket_sent = Symbol( 'socket' );
24
25
 
@@ -249,7 +250,34 @@ export class Application<E extends ApplicationEvents = ApplicationEvents> extend
249
250
  }
250
251
 
251
252
  /**
252
- *
253
+ * Forces the locale of the formats of numbers, amounts and dates (formatNumber, formatMoney,
254
+ * formatDate, formatDateTime), and the currency of the amounts. Without it, the locale is the one of the language (`_tr.global.locale`: "fr-FR"
255
+ * for fr, "en-US" for en...) and the currency "EUR".
256
+ * A field of type number is shown by the browser, in its own locale, whatever this one.
257
+ * @param locale - "fr-CH", "en-GB"...; null: the one of the language again
258
+ * @param currency - "CHF", "USD"...; not given: the currency does not change
259
+ * @example
260
+ * Application.setLocale( "fr-CH", "CHF" );
261
+ */
262
+
263
+ static setLocale( locale: string, currency?: string ) {
264
+ _setLocale( locale, currency );
265
+ }
266
+
267
+ /** The locale of the formats: the one forced by setLocale, else the one of the language. */
268
+
269
+ static getLocale( ): string {
270
+ return getCurrentLocale( );
271
+ }
272
+
273
+ /** The currency of the amounts: "EUR" unless setLocale gave another. */
274
+
275
+ static getCurrency( ): string {
276
+ return getCurrentCurrency( );
277
+ }
278
+
279
+ /**
280
+ *
253
281
  */
254
282
 
255
283
  private _setupKeyboard( ) {
@@ -255,6 +255,41 @@ export function getCurrentLanguage( ): string {
255
255
  return current.name;
256
256
  }
257
257
 
258
+ // :: LOCALE ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
259
+
260
+ // the formats of numbers, amounts and dates: the locale of the language (_tr.global.locale),
261
+ // unless one is forced (Application.setLocale); the currency of the amounts
262
+ let forced_locale: string = null;
263
+ let currency = "EUR";
264
+
265
+ /**
266
+ * @internal the locale forced (null: the one of the language), the currency when given
267
+ */
268
+
269
+ export function _setLocale( locale: string, cur?: string ) {
270
+ forced_locale = locale || null;
271
+ if( cur ) {
272
+ currency = cur;
273
+ }
274
+ }
275
+
276
+ /**
277
+ * the locale of the formats: the one forced by Application.setLocale, else the one of the
278
+ * language ("fr-FR" for fr)
279
+ */
280
+
281
+ export function getCurrentLocale( ): string {
282
+ return forced_locale ?? _tr.global.locale ?? "fr-FR";
283
+ }
284
+
285
+ /**
286
+ * the currency of the amounts: "EUR" unless Application.setLocale gave another
287
+ */
288
+
289
+ export function getCurrentCurrency( ): string {
290
+ return currency;
291
+ }
292
+
258
293
  /**
259
294
  *
260
295
  */
@@ -308,6 +343,7 @@ let fr = {
308
343
  date_input_formats: 'd/m/y|d.m.y|d m y|d-m-y|dmy',
309
344
  date_format: 'D/M/Y',
310
345
  date_time_format: 'D/M/Y H:I:S',
346
+ locale: 'fr-FR', // the formats of numbers, amounts, dates (Intl)
311
347
 
312
348
  day_short: [ 'dim', 'lun', 'mar', 'mer', 'jeu', 'ven', 'sam' ],
313
349
  day_long: [ 'dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi' ],
@@ -385,6 +421,7 @@ let en = {
385
421
  date_input_formats: 'm/d/y|m.d.y|m d y|m-d-y|mdy',
386
422
  date_format: 'M/D/Y',
387
423
  date_time_format: 'M/D/Y H:I:S',
424
+ locale: 'en-US', // the formats of numbers, amounts, dates (Intl)
388
425
 
389
426
  day_short: [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ],
390
427
  day_long: [ 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday' ],
@@ -462,6 +499,7 @@ let es = {
462
499
  date_input_formats: 'd/m/y|d.m.y|d m y|d-m-y|dmy',
463
500
  date_format: 'D/M/Y',
464
501
  date_time_format: 'D/M/Y H:I:S',
502
+ locale: 'es-ES', // the formats of numbers, amounts, dates (Intl)
465
503
 
466
504
  day_short: [ 'dom', 'lun', 'mar', 'mié', 'jue', 'vie', 'sáb' ],
467
505
  day_long: [ 'domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado' ],
@@ -539,6 +577,7 @@ let de = {
539
577
  date_input_formats: 'd.m.y|d/m/y|d m y|d-m-y|dmy',
540
578
  date_format: 'D.M.Y',
541
579
  date_time_format: 'D.M.Y H:I:S',
580
+ locale: 'de-DE', // the formats of numbers, amounts, dates (Intl)
542
581
 
543
582
  day_short: [ 'So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa' ],
544
583
  day_long: [ 'Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag' ],
@@ -51,7 +51,8 @@ interface Keys {
51
51
  key: string;
52
52
  }
53
53
 
54
- const isMac = /mac|iphone|ipad/i.test( navigator.platform ?? "" );
54
+ /** true on a Mac, an iPhone or an iPad: Mod is Cmd there */
55
+ export const isMac = /mac|iphone|ipad/i.test( navigator.platform ?? "" );
55
56
 
56
57
  /**
57
58
  * "Shift+Mod+K" -> shift, ctrl (cmd on a Mac), k
@@ -1013,6 +1013,7 @@ export interface SvgProps extends ComponentProps {
1013
1013
  */
1014
1014
 
1015
1015
  export class SvgComponent<P extends SvgProps = SvgProps, E extends ComponentEvents = ComponentEvents> extends Component<P,E> {
1016
+ static classPrefix = "x4";
1016
1017
 
1017
1018
  /**
1018
1019
  * Creates an instance of `SvgComponent`.
@@ -14,7 +14,7 @@
14
14
  * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
15
  **/
16
16
 
17
- import { _tr } from "./core_i18n.js";
17
+ import { _tr, _setLocale, getCurrentLocale, getCurrentCurrency } from "./core_i18n.js";
18
18
 
19
19
 
20
20
  // todo: split into 2 files
@@ -528,33 +528,103 @@ export function camelCase(text: string) {
528
528
 
529
529
  // :: DATES ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
530
530
 
531
- let cur_locale: string = 'fr-FR';
532
-
533
531
  /**
534
- * change the current locale for misc translations (date...)
535
- * @param locale
536
- * TODO: ????
532
+ * forces the locale of the formats (see Application.setLocale)
533
+ * @param locale
537
534
  */
538
535
 
539
536
  export function _date_set_locale(locale: string) {
540
- cur_locale = locale;
537
+ _setLocale( locale );
541
538
  }
542
539
 
543
540
  /**
544
- *
545
- * @param date
546
- * @param options
541
+ * a date as text: with options, the ones of Intl.DateTimeFormat in the current locale;
542
+ * without, the format of the language (_tr.global.date_format)
543
+ * @param date
544
+ * @param options
547
545
  * @example
548
546
  * let date = new Date( );
549
- * let options = { day: 'numeric', month: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric' };
547
+ * let options = { day: 'numeric', month: 'long', year: 'numeric' };
550
548
  * let text = date_format( date, options );
551
549
  */
552
550
 
553
- export function date_format(date: Date, options?: any): string {
554
- //return new Intl.DateTimeFormat(cur_locale, options).format( date );
551
+ export function date_format(date: Date, options?: Intl.DateTimeFormatOptions): string {
552
+ if( options ) {
553
+ return new Intl.DateTimeFormat( getCurrentLocale( ), options ).format( date );
554
+ }
555
+
555
556
  return formatIntlDate(date);
556
557
  }
557
558
 
559
+ // :: FORMATS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
560
+
561
+ // numbers, amounts and dates in the current locale (the one of the language, or the one
562
+ // Application.setLocale forced): the objects of Intl, made once by locale and options
563
+ const intl_formats = new Map<string, Intl.NumberFormat | Intl.DateTimeFormat>( );
564
+
565
+ function numberFormat( key: string, options: Intl.NumberFormatOptions ) {
566
+ const locale = getCurrentLocale( );
567
+ const id = locale + "|" + key;
568
+ let fmt = intl_formats.get( id ) as Intl.NumberFormat;
569
+ if( !fmt ) {
570
+ fmt = new Intl.NumberFormat( locale, options );
571
+ intl_formats.set( id, fmt );
572
+ }
573
+ return fmt;
574
+ }
575
+
576
+ function dateFormat( key: string, options: Intl.DateTimeFormatOptions ) {
577
+ const locale = getCurrentLocale( );
578
+ const id = locale + "|" + key;
579
+ let fmt = intl_formats.get( id ) as Intl.DateTimeFormat;
580
+ if( !fmt ) {
581
+ fmt = new Intl.DateTimeFormat( locale, options );
582
+ intl_formats.set( id, fmt );
583
+ }
584
+ return fmt;
585
+ }
586
+
587
+ /**
588
+ * a number as text in the current locale: "1 234,5" in fr-FR, "1,234.5" in en-US
589
+ * @param value
590
+ * @param decimals - the most decimals shown (Intl: 3 when not given)
591
+ * @param grouping - the separator of thousands, true by default
592
+ */
593
+
594
+ export function formatNumber( value: number, decimals?: number, grouping = true ): string {
595
+ const options: Intl.NumberFormatOptions = { useGrouping: grouping };
596
+ if( decimals!==undefined ) {
597
+ options.maximumFractionDigits = decimals;
598
+ }
599
+
600
+ return numberFormat( "n" + decimals + grouping, options ).format( value );
601
+ }
602
+
603
+ /**
604
+ * an amount as text in the current locale and currency: "1 234,50 €" in fr-FR
605
+ */
606
+
607
+ export function formatMoney( value: number ): string {
608
+ const currency = getCurrentCurrency( );
609
+ return numberFormat( "m" + currency, { style: "currency", currency } ).format( value );
610
+ }
611
+
612
+ /**
613
+ * a date as text in the current locale: "22/10/2026" in fr-FR, "10/22/2026" in en-US
614
+ */
615
+
616
+ export function formatDate( date: Date ): string {
617
+ return dateFormat( "d", { } ).format( date );
618
+ }
619
+
620
+ /**
621
+ * a date and its time (hours, minutes) as text in the current locale: "22/10/2026 14:05" in fr-FR
622
+ */
623
+
624
+ export function formatDateTime( date: Date ): string {
625
+ return dateFormat( "dt", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" } ).format( date );
626
+ }
627
+
558
628
  /**
559
629
  * difference between 2 dates
560
630
  * @return text
@@ -1002,17 +1072,6 @@ export function getSystemMetrics( part: "scrollbar" | "zoom" ) {
1002
1072
 
1003
1073
 
1004
1074
 
1005
- /**
1006
- *
1007
- */
1008
-
1009
- export const x4_class_ns_sym = Symbol("class-ns");
1010
- export function class_ns(ns: string) {
1011
- return function (constructor: Function) {
1012
- (constructor as any)[x4_class_ns_sym] = ns;
1013
- }
1014
- }
1015
-
1016
1075
  /**
1017
1076
  *
1018
1077
  */