x4js 2.2.49 → 2.2.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,11 +2,12 @@
2
2
  **A TypeScript framework for people who build applications, not markup.**
3
3
 
4
4
  ## Home page
5
- see [home](https://x4js.org)
5
+ see [X4 home page](https://x4js.org)
6
6
 
7
7
  ## API Documentation
8
- see [API](https://rlibre.github.io/x4/index.html)
9
- see [AI](./aicontext.md)
8
+ see [GitHib](https://rlibre.github.io/x4/index.html)
9
+
10
+ see [AI context](./aicontext.md)
10
11
 
11
12
  ---
12
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x4js",
3
- "version": "2.2.49",
3
+ "version": "2.2.51",
4
4
  "type": "module",
5
5
  "main": "src/x4.ts",
6
6
  "module": "src/x4.ts",
@@ -19,6 +19,7 @@
19
19
  .x4box {
20
20
  min-width: 0;
21
21
  min-height: 0;
22
+ flex-shrink: 0;
22
23
  }
23
24
 
24
25
  .x4hbox {
@@ -22,7 +22,7 @@
22
22
  align-content: start;
23
23
  margin: 5px;
24
24
  gap: 4px;
25
-
25
+ overflow: hidden;
26
26
  background: var( --btngroup-background );
27
27
 
28
28
  &.align-right {
@@ -163,7 +163,7 @@ export class Saturation extends Box<BoxProps,CommonEvents> {
163
163
 
164
164
 
165
165
  /**
166
- *
166
+ * @internal
167
167
  */
168
168
 
169
169
  @class_ns( "x4" )
@@ -240,7 +240,7 @@ class HueSlider extends Box<BoxProps,CommonEvents> {
240
240
 
241
241
 
242
242
  /**
243
- *
243
+ * @internal
244
244
  */
245
245
 
246
246
  @class_ns( "x4" )
@@ -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 { Component, ComponentEvents, ComponentProps, EvSelectionChange, makeUniqueComponentId } from '../../core/component';
17
+ import { Component, ComponentEvents, ComponentProps, EvClick, EvSelectionChange, makeUniqueComponentId } from '../../core/component';
18
18
  import { class_ns, IComponentInterface, IFormElement, kbNav, safeText, sanitizeHtml, UnsafeHtml } from '../../core/core_tools';
19
19
  import { EventCallback } from '../../core/core_events';
20
20
 
@@ -31,7 +31,8 @@ import icon from "./updown.svg";
31
31
 
32
32
 
33
33
  interface DropdownEvents extends PopupEvents {
34
- selectionChange: EvSelectionChange;
34
+ //selectionChange: EvSelectionChange;
35
+ click: EvClick;
35
36
  }
36
37
 
37
38
 
@@ -57,8 +58,9 @@ export class DropdownList extends Popup<DropdownProps,DropdownEvents> {
57
58
  ev.preventDefault( );
58
59
  }, true );
59
60
 
60
- this._list.on( "selectionChange", ( ev ) => {
61
- this.fire( "selectionChange", ev );
61
+ this._list.on( "click", ( ev ) => {
62
+ //this.fire( "selectionChange", ev );
63
+ this.fire( "click", ev );
62
64
  })
63
65
  }
64
66
 
@@ -152,11 +154,11 @@ export class Combobox extends Component<ComboboxProps,ComboboxEvents> {
152
154
  }
153
155
  }
154
156
 
155
- this._popup.on( "selectionChange", ( ev ) => {
156
- const [sel] = ev.selection as ListboxID[];
157
+ this._popup.on( "click", ( ev ) => {
158
+ const sel = ev.context as ListboxID;
157
159
  if( sel!==undefined ) { // no empty sel
158
160
  _select( sel );
159
- this.fire( "selectionChange", ev );
161
+ this.fire( "selectionChange", { selection: [sel], empty: false } );
160
162
  }
161
163
  });
162
164
 
@@ -178,8 +180,11 @@ export class Combobox extends Component<ComboboxProps,ComboboxEvents> {
178
180
  switch( ev.key ) {
179
181
  case "Enter":
180
182
  case "Escape": {
181
- this._popup.show( false );
182
- break;
183
+ if( this._popup.isOpen( ) ) {
184
+ this._popup.show( false );
185
+ break;
186
+ }
187
+ return;
183
188
  }
184
189
 
185
190
  case "ArrowUp":
@@ -89,7 +89,8 @@ export class Dialog<P extends DialogProps = DialogProps, E extends DialogEvents
89
89
  this.fire("btnclick", { button: props.closable } );
90
90
  }
91
91
  else {
92
- this.close()
92
+ //<no it's ok 99% ! this.fire("btnclick", { button: props.buttons[0] } );
93
+ this.close( );
93
94
  }
94
95
  }
95
96
  }) : null,
@@ -113,6 +114,14 @@ export class Dialog<P extends DialogProps = DialogProps, E extends DialogEvents
113
114
  // todo cancel
114
115
  ev.preventDefault();
115
116
  ev.stopPropagation();
117
+
118
+ if( isString(props.closable) ) {
119
+ this.fire("btnclick", { button: props.closable } );
120
+ }
121
+ else {
122
+ //<no it's ok 99% ! this.fire("btnclick", { button: props.buttons[0] } );
123
+ this.close( );
124
+ }
116
125
  }
117
126
  else if (ev.key == 'Enter') {
118
127
  const def = this.query<Button>('button.default');
@@ -124,6 +133,10 @@ export class Dialog<P extends DialogProps = DialogProps, E extends DialogEvents
124
133
  }
125
134
  }
126
135
  })
136
+
137
+ asap( ( ) => {
138
+ this.focusNext( true );
139
+ } );
127
140
  }
128
141
 
129
142
  private focusNext( next: boolean) : boolean {
@@ -15,6 +15,9 @@
15
15
  **/
16
16
 
17
17
  :root {
18
+ --input-background: rgba(255, 255, 255, 0.6);
19
+ --input-color: black;
20
+
18
21
  --input-sel-background: var( --accent-background );
19
22
  --input-sel-color: var( --accent-color );
20
23
  --input-placeholder: var( --disabled-background );
@@ -28,6 +31,8 @@ input.x4input {
28
31
  outline: none;
29
32
 
30
33
  border: none;
34
+ background-color: var( --input-background );
35
+ color: var( --input-color );
31
36
 
32
37
  &::selection {
33
38
  background-color: var( --input-sel-background );
@@ -251,14 +251,14 @@ export class Listbox extends Component<ListboxProps,ListboxEvents> {
251
251
  while( target && target!=this.dom ) {
252
252
  const c = componentFromDOM( target );
253
253
 
254
- // avoid trapping child ckick
255
- if( c.dom.tagName==='INPUT' ) {
254
+ // avoid trapping child click
255
+ if( c && c.dom.tagName==='INPUT' ) {
256
256
  return;
257
257
  }
258
258
 
259
259
  if( c && c.hasClass("x4item") ) {
260
260
  const id = c.getInternalData( "id" );
261
- const fev: ComponentEvent = { context:id };
261
+ const fev: EvClick = { context:id };
262
262
  if (ev.type == 'click') {
263
263
  this.fire('click', fev );
264
264
  }
@@ -276,6 +276,10 @@ export class Listbox extends Component<ListboxProps,ListboxEvents> {
276
276
  target = target.parentElement;
277
277
  }
278
278
 
279
+ if (ev.type == 'click') {
280
+ this.fire('click', {} );
281
+ }
282
+
279
283
  this.clearSelection( );
280
284
 
281
285
  ev.stopImmediatePropagation();
@@ -498,30 +502,30 @@ export class Listbox extends Component<ListboxProps,ListboxEvents> {
498
502
 
499
503
  defaultRenderer( item: ListItem ): Component {
500
504
 
501
- const mk_col = ( _: any, index: number ) => {
502
- const c = item.sub_cols[index];
505
+ const mk_col = ( c: any, index: number ) => {
503
506
  if( c===undefined || c===null ) {
504
507
  return null;
505
508
  }
506
509
 
507
510
  if( c instanceof Component ) {
511
+ c.addClass( "column" );
508
512
  return c;
509
513
  }
510
514
 
511
515
  return new SimpleText( { cls: `column ref-c${index+2}`, text: c } )
512
516
  }
513
517
 
514
- let cols: Component[] = null;
518
+ const content = [
519
+ new Label( { cls: `column ref-c1`, icon: item.iconId, text: item.text } ),
520
+ ];
521
+
515
522
  if( item.sub_cols ) {
516
- cols = item.sub_cols.map( mk_col );
523
+ content.push( ...(item.sub_cols.map( mk_col )) );
517
524
  }
518
525
 
519
526
  return new HBox( {
520
527
  cls: item.cls,
521
- content: [
522
- new Label( { cls: `column ref-c1`, icon: item.iconId, text: item.text } ),
523
- ...cols,
524
- ],
528
+ content
525
529
  } )
526
530
  }
527
531
 
@@ -42,6 +42,12 @@ interface NotificationProps extends ComponentProps {
42
42
  }
43
43
 
44
44
  /**
45
+ *
46
+ * @example:
47
+ * const not = new Notification( { title: "Yooo", text: "You win !", mode: "danger" })
48
+ * not.display( 3000 )
49
+ *
50
+ *
45
51
  * @cssvar
46
52
  * ```
47
53
  * --notification-border
@@ -32,6 +32,7 @@
32
32
  }
33
33
  }
34
34
 
35
+ //<to remove
35
36
  .x4popup.center {
36
37
  left: 50%;
37
38
  top: 50%;
@@ -22,6 +22,7 @@ import { Box } from '../boxes/boxes'
22
22
 
23
23
  import "./popup.module.scss"
24
24
  import { getGlobalZoom, getScrollbarSize } from '../../core/core_tools.js';
25
+ import { Application } from '../../core/core_application.js';
25
26
 
26
27
  export interface PopupEvents extends ComponentEvents {
27
28
  closed: ComponentEvent;
@@ -66,6 +67,7 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
66
67
  }
67
68
 
68
69
  // wait for element to create it's childs
70
+ // > setContent is in ctor
69
71
  asap( ( ) => {
70
72
  if( this.props.movable===true || (this.props.sizable && this.props.movable===undefined) ) {
71
73
  const movers = this.queryAll( ".caption-element" );
@@ -136,13 +138,31 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
136
138
  }
137
139
 
138
140
  /**
139
- * s
141
+ * stupid parameter
140
142
  */
141
143
 
142
- displayCenter( center = true ) {
143
- //this.displayNear( new Rect( window.innerWidth/2, window.innerHeight/2, 0, 0 ), "center middle" );
144
- this.setClass( 'center', center );
144
+ displayCenter( ) {
145
145
  this._do_show( ); // to compute size
146
+
147
+ const fixpos = ( ) => {
148
+ console.log( "fixpos" );
149
+
150
+ const orc = this.getBoundingRect( );
151
+ this.setStyle( {
152
+ left: ((window.innerWidth - orc.width) / 2) +'px',
153
+ top: ((window.innerHeight - orc.height) / 2) +'px',
154
+ } );
155
+
156
+ console.log( orc );
157
+ }
158
+
159
+ asap( fixpos );
160
+
161
+ const tkn = Application.instance( ).on( "resize", () => {
162
+ asap( fixpos );
163
+ } )
164
+
165
+ this.addCleanup( tkn.off );
146
166
  }
147
167
 
148
168
  /**
@@ -411,51 +431,54 @@ class CMover {
411
431
 
412
432
  this.self = ref ? true : false;
413
433
 
414
- x.addDOMEvent( "pointerdown", ( e: PointerEvent ) => {
434
+ const mouseDown = ( e: PointerEvent ) => {
415
435
  if( this.self && e.target!=x.dom ) {
416
436
  return;
417
437
  }
418
438
 
419
439
  x.setCapture( e.pointerId );
420
440
 
421
- this.ref = ref ?? componentFromDOM( x.dom.parentElement );
441
+ if( !ref ) {
442
+ ref = componentFromDOM( x.dom.parentElement );
443
+ }
422
444
 
445
+ this.ref = ref;
423
446
  this.delta = {x:0,y:0};
424
- const rc = this.ref.getBoundingRect();
447
+ const rc = ref.getBoundingRect();
425
448
 
426
449
  this.delta.x = e.pageX-rc.left;
427
450
  this.delta.y = e.pageY-rc.top;
428
- });
429
-
430
- x.addDOMEvent( "pointerup", ( e: PointerEvent ) => {
431
- x.releaseCapture( e.pointerId );
432
- this.ref = null;
433
- });
451
+ }
434
452
 
435
- x.addDOMEvent( "pointermove", ( e: PointerEvent ) => {
436
- this._onMouseMove( e );
437
- });
438
- }
453
+ const mouseMove = ( e: PointerEvent ) => {
454
+ if( !this.delta ) {
455
+ return;
456
+ }
439
457
 
440
- private _onMouseMove( e: PointerEvent ) {
441
- if( !this.ref ) {
442
- return;
458
+ const ref = this.ref
459
+ const pt = { x: e.pageX-this.delta.x, y: e.pageY-this.delta.y };
460
+ const rc = ref.getBoundingRect( );
461
+
462
+ ref.setStyle( {
463
+ top: pt.y+"",
464
+ left: pt.x+"",
465
+ } );
466
+
467
+ e.preventDefault( );
468
+ e.stopPropagation( );
443
469
  }
444
470
 
445
- const pt = { x: e.pageX-this.delta.x, y: e.pageY-this.delta.y };
446
- const rc = this.ref.getBoundingRect( );
447
-
448
- let nr: any = {
449
- };
471
+ const mouseUp = ( e: PointerEvent ) => {
472
+ x.releaseCapture( e.pointerId );
473
+ this.delta = null;
474
+ }
450
475
 
451
- this.ref.setStyle( {
452
- top: pt.y+"",
453
- left: pt.x+"",
454
- } );
455
-
456
- e.preventDefault( );
457
- e.stopPropagation( );
476
+ x.addDOMEvent( "pointerdown", mouseDown );
477
+ x.addDOMEvent( "pointerup", mouseUp );
478
+ x.addDOMEvent( "pointermove", mouseMove );
458
479
  }
480
+
481
+
459
482
  }
460
483
 
461
484
 
@@ -6,6 +6,8 @@
6
6
 
7
7
  :root {
8
8
  --propertygrid-background: white;
9
+ --propertygrid-gadget-background: white;
10
+ --propertygrid-gadget-color: black;
9
11
  }
10
12
 
11
13
  .x4propertygrid {
@@ -92,10 +94,6 @@
92
94
  flex-basis: 10px;
93
95
  padding: 4px;
94
96
 
95
- &>* {
96
- width: 100%;
97
- }
98
-
99
97
  input {
100
98
  background: rgba(255,255,255,0.6);
101
99
  }
@@ -110,8 +108,16 @@
110
108
  padding-left: 34px;
111
109
  }
112
110
 
113
- .cell:has(.x4button) {
111
+ .cell:has(.button) {
114
112
  padding: 4px 40px;
115
113
  }
114
+
115
+ .gadget {
116
+ &.x4button {
117
+ --button-background: var( --propertygrid-gadget-background );
118
+ --button-color: var( --propertygrid-gadget-color );
119
+ padding: 5px;
120
+ }
121
+ }
116
122
  }
117
123
  }
@@ -44,6 +44,7 @@ export interface PropertyValue {
44
44
  step?: number; // for numbers
45
45
  min?: number;
46
46
  max?: number;
47
+ gadgets?: Component[];
47
48
  }
48
49
 
49
50
  export interface PropertyGroup {
@@ -246,6 +247,7 @@ export class PropertyGrid extends VBox {
246
247
  use_hdr = false;
247
248
  editor = new Button({
248
249
  id: item.name,
250
+ cls: "button",
249
251
  label: value as string,
250
252
  click: ( ) => {
251
253
  item.callback?.( item.name, '' );
@@ -253,17 +255,17 @@ export class PropertyGrid extends VBox {
253
255
  });
254
256
  }
255
257
  else {
256
- editor = new Input({
257
- type: 'text',
258
- id: item.name,
259
- name: item.name,
260
- value: value as string,
261
- focus: ( e: EvFocus ) => {
262
- if( e.focus_out ) {
263
- item.callback?.( item.name, (editor as Input).getValue() );
264
- }
265
- }
266
- });
258
+ editor = new Input({
259
+ type: 'text',
260
+ id: item.name,
261
+ name: item.name,
262
+ value: value as string,
263
+ focus: ( e: EvFocus ) => {
264
+ if( e.focus_out ) {
265
+ item.callback?.( item.name, (editor as Input).getValue() );
266
+ }
267
+ },
268
+ })
267
269
  }
268
270
 
269
271
  let cls = 'row';
@@ -271,12 +273,25 @@ export class PropertyGrid extends VBox {
271
273
  cls += ' ' + item.cls;
272
274
  }
273
275
 
276
+ editor.addClass( "x4flex" );
277
+
278
+ const content: Component[] = [
279
+ editor
280
+ ]
281
+
282
+ if( item.gadgets ) {
283
+ item.gadgets.forEach( g => {
284
+ g.addClass( "gadget" );
285
+ content.push( g );
286
+ } );
287
+ }
288
+
274
289
  return new HBox({
275
290
  cls,
276
291
  content: [
277
- use_hdr ? new Component({ cls: 'cell hdr', content: item.title ?? item.name, tooltip: item.desc }) : null,
278
- new Component({ cls: 'cell', tag: "label", attrs: { "labelFor": item.name }, content: editor })
279
- ]
292
+ use_hdr ? new Component({ cls: 'cell hdr', content: item.title ?? item.name, tooltip: item.desc }) : null,
293
+ new HBox({ cls: 'cell', tag: "label", attrs: { "labelFor": item.name }, content }),
294
+ ]
280
295
  });
281
296
  }
282
297
 
@@ -24,7 +24,9 @@ import "./sizer.module.scss"
24
24
  */
25
25
 
26
26
  export interface EvSizeChange extends ComponentEvent {
27
- size: number;
27
+ size: number; // compat
28
+ width: number;
29
+ height: number;
28
30
  }
29
31
 
30
32
  interface CSizerEvent extends ComponentEvents {
@@ -111,28 +113,41 @@ export class CSizer extends Component<ComponentProps,CSizerEvent> {
111
113
  let nr: any = {};
112
114
  let horz = true;
113
115
 
116
+ //const center = this._ref.hasClass("center");
117
+ //if( center ) {
118
+ // const orc = this._ref.getBoundingRect( );
119
+ // this._ref.removeClass("center");
120
+ //
121
+ // nr.left = orc.left;
122
+ // nr.top = orc.top;
123
+ //}
124
+
114
125
  if( this._type.includes("top") ) {
115
126
  nr.top = pt.y,
116
127
  nr.height = (rc.top+rc.height)-pt.y;
117
128
  horz = false;
118
129
  }
119
- else if( this._type=="vsize-next" ) {
130
+
131
+ if( this._type=="vsize-next" ) {
120
132
  nr.height = (rc.top+rc.height)-pt.y;
121
133
  horz = false;
122
134
  }
123
- else if( this._type.includes("bottom") || this._type=='vsize-prev' ) {
124
- //nr.top = rc.top;
135
+
136
+ if( this._type.includes("bottom") || this._type=='vsize-prev' ) {
125
137
  nr.height = (pt.y-rc.top);
126
138
  horz = false;
127
139
  }
128
- else if( this._type.includes("left") ) {
140
+
141
+ if( this._type.includes("left") ) {
129
142
  nr.left = pt.x;
130
143
  nr.width = ((rc.left+rc.width)-pt.x);
131
144
  }
132
- else if( this._type=="hsize-next" ) {
145
+
146
+ if( this._type=="hsize-next" ) {
133
147
  nr.width = ((rc.left+rc.width)-pt.x);
134
148
  }
135
- else if( this._type.includes("right") || this._type=='hsize-prev' ) {
149
+
150
+ if( this._type.includes("right") || this._type=='hsize-prev' ) {
136
151
  nr.width = (pt.x-rc.left);
137
152
  }
138
153
 
@@ -140,7 +155,7 @@ export class CSizer extends Component<ComponentProps,CSizerEvent> {
140
155
  //this._ref.setStyleValue( "flexGrow", 0 );
141
156
 
142
157
  const nrc = this._ref.getBoundingRect( );
143
- this.fire( "resize", { size: horz ? nrc.width : nrc.height })
158
+ this.fire( "resize", { size: horz ? nrc.width : nrc.height, width: nrc.width, height: nrc.height })
144
159
 
145
160
  e.preventDefault( );
146
161
  e.stopPropagation( );
@@ -75,12 +75,7 @@ interface TablistEvents extends ComponentEvents {
75
75
 
76
76
  /**
77
77
  * bar containing buttons
78
- *
79
- * @cssvar
80
- * ```
81
- * --tab-border-selected
82
- * --tab-border-hover
83
- * ```
78
+ * @internal
84
79
  */
85
80
 
86
81
  @class_ns( "x4" )
@@ -147,6 +142,11 @@ interface TabsProps extends Omit<ComponentProps,"content"> {
147
142
 
148
143
  /**
149
144
  *
145
+ * @cssvar
146
+ * ```
147
+ * --tab-border-selected
148
+ * --tab-border-hover
149
+ * ```
150
150
  */
151
151
 
152
152
  @class_ns( "x4" )
@@ -75,7 +75,7 @@ function showTT( text: string, rc: Rect, pt: Point ) {
75
75
  tooltip = new Tooltip( { } );
76
76
  }
77
77
 
78
- timer.setTimeout( null, 300, ( ) => {
78
+ timer.setTimeout( "update", 300, ( ) => {
79
79
  tooltip.setText( unsafeHtml(text) );
80
80
 
81
81
  let y = mouse_pos.y;
@@ -90,7 +90,7 @@ function showTT( text: string, rc: Rect, pt: Point ) {
90
90
 
91
91
  function closeTT( ) {
92
92
  tooltip.show( false );
93
- timer.clearTimeout( null );
93
+ timer.clearTimeout( "update" );
94
94
  }
95
95
 
96
96
  /**
@@ -488,8 +488,10 @@ export class Treeview extends Component<TreeviewProps,TreeviewEvents> {
488
488
  this._selitem = undefined;
489
489
  }
490
490
 
491
+ if( this._selection ) {
491
492
  this._selection = undefined;
492
493
  this.fire( "selectionChange", { selection: [], empty: true } );
494
+ }
493
495
  }
494
496
 
495
497
  /**
@@ -14,11 +14,11 @@
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, IRect } from './core_tools';
17
+ import { isArray, UnsafeHtml, isNumber, Rect, Constructor, class_ns, x4_class_ns_sym } 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';
21
- import { addEvent, DOMEventHandler, GlobalDOMEvents } from './core_dom';
21
+ import { addEvent, DOMEventHandler, GlobalDOMEvents, COMPONENT } from './core_dom';
22
22
  import { Application, EvMessage } from './core_application';
23
23
  import { makeState } from './core_state.js';
24
24
 
@@ -30,7 +30,6 @@ type ComponentAttributes = Record<string,string|number|boolean>;
30
30
  type CreateComponentCallBack = ( attrs: Record<string,string> ) => ComponentContent;
31
31
 
32
32
  const FRAGMENT = Symbol( "fragment" );
33
- const COMPONENT = Symbol( "component" );
34
33
 
35
34
  const RE_NUMBER = /^-?\d+(\.\d*)?$/;
36
35
 
@@ -152,7 +151,7 @@ export interface ComponentEvents extends EventMap {
152
151
  * ```
153
152
  */
154
153
 
155
- @class_ns( "x4" )
154
+ @class_ns("x4")
156
155
  export class Component<P extends ComponentProps = ComponentProps, E extends ComponentEvents = ComponentEvents>
157
156
  extends CoreElement<E> {
158
157
 
@@ -166,16 +165,16 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
166
165
 
167
166
  #store: Map<string|symbol,any>;
168
167
  #pstate: any;
169
-
170
- constructor( props: P ) {
168
+
169
+ constructor(props: P) {
171
170
  super( );
172
171
 
173
172
  this.props = props; // copy ?
174
173
 
175
174
  if( props.existingDOM ) {
176
175
  this.dom = props.existingDOM;
177
- }
178
- else {
176
+ }
177
+ else {
179
178
  if( props.ns ) {
180
179
  this.dom = document.createElementNS( props.ns, props.tag ?? "div" );
181
180
  }
@@ -242,8 +241,8 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
242
241
  this.addDOMEvent( "created", ( ) => {
243
242
  this.enable( false );
244
243
  } );
244
+ }
245
245
  }
246
- }
247
246
 
248
247
  (this.dom as any)[COMPONENT] = this;
249
248
  }
@@ -281,10 +280,10 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
281
280
  * Multiple classes can be provided as a space-separated string.
282
281
  * @param cls - The CSS class(es) to add.
283
282
  */
284
-
283
+
285
284
  addClass( cls: string ) {
286
285
  if( !cls ) return;
287
-
286
+
288
287
  cls = cls.trim( );
289
288
 
290
289
  if( cls.includes(' ') ) {
@@ -309,13 +308,13 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
309
308
  if( cls=='*' ) {
310
309
  this.dom.classList.value = "";
311
310
  return;
312
- }
311
+ }
313
312
 
314
313
  if( cls.indexOf(' ')>=0 ) {
315
314
  const ccs = cls.split( " " );
316
315
  this.dom.classList.remove(...ccs);
317
- }
318
- else {
316
+ }
317
+ else {
319
318
  this.dom.classList.remove(cls);
320
319
  }
321
320
  }
@@ -351,11 +350,11 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
351
350
  if( cls.indexOf(' ')>=0 ) {
352
351
  const ccs = cls.split( " " );
353
352
  ccs.forEach( toggle );
354
- }
355
- else {
353
+ }
354
+ else {
356
355
  toggle( cls );
357
356
  }
358
- }
357
+ }
359
358
 
360
359
  /**
361
360
  * Sets or removes a CSS class based on a boolean condition.
@@ -368,7 +367,7 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
368
367
  if( set ) this.addClass(cls);
369
368
  else this.removeClass( cls );
370
369
  return this;
371
- }
370
+ }
372
371
 
373
372
  // :: ATTRIBUTES ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
374
373
 
@@ -383,7 +382,7 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
383
382
  this.setAttribute( name, attrs[name] );
384
383
  }
385
384
  return this;
386
- }
385
+ }
387
386
 
388
387
  /**
389
388
  * Sets a single HTML attribute on the component's DOM element.
@@ -399,7 +398,7 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
399
398
  else {
400
399
  this.dom.setAttribute( name, ""+value );
401
400
  }
402
- }
401
+ }
403
402
 
404
403
  /**
405
404
  * Retrieves the value of an HTML attribute from the component's DOM element.
@@ -420,8 +419,8 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
420
419
  * @see Component.getIntData
421
420
  * @see Component.setInternalData
422
421
  * @see Component.getInternalData
423
- */
424
-
422
+ */
423
+
425
424
  getData( name: string ) : string {
426
425
  return this.getAttribute( "data-"+name );
427
426
  }
@@ -794,7 +793,7 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
794
793
  /**
795
794
  * Sets pointer capture on the component's DOM element for a specific pointer.
796
795
  * @param pointerId - The unique ID of the pointer.
797
- *
796
+ *
798
797
  * @example
799
798
  * control.on("pointerdown", (ev) => {
800
799
  * ev.preventDefault(); // Prevent default browser actions
@@ -858,7 +857,7 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
858
857
  /**
859
858
  * Checks if the component's DOM element is currently visible (i.e., not hidden by `display: none`).
860
859
  * @returns `true` if the component is visible, `false` otherwise.
861
- */
860
+ */
862
861
 
863
862
  isVisible( ) {
864
863
  return (this.dom as HTMLElement).offsetParent !== null;
@@ -944,7 +943,7 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
944
943
  /**
945
944
  * Returns the previous sibling element as a Component instance.
946
945
  * @returns The previous sibling component, or `null` if none exists.
947
- */
946
+ */
948
947
 
949
948
  prevElement<T extends Component = Component>( ): T {
950
949
  const nxt = this.dom.previousElementSibling;
@@ -962,7 +961,7 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
962
961
  }
963
962
 
964
963
  /**
965
- *
964
+ *
966
965
  */
967
966
 
968
967
  childCount( ) {
@@ -974,9 +973,9 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
974
973
  * @param dom - The starting DOM node from which to search upwards.
975
974
  * @param cls - Optional. The constructor of the Component type to match.
976
975
  * @returns The matching parent Component instance, or `null` if not found.
977
- */
978
-
979
-
976
+ */
977
+
978
+
980
979
  static parentElement<T extends Component>( dom: Node, cls?: Constructor<T> ): T {
981
980
 
982
981
  while( dom.parentElement ) {
@@ -991,14 +990,14 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
991
990
 
992
991
  dom = dom.parentElement;
993
992
  }
994
-
993
+
995
994
  return null;
996
995
  }
997
996
 
998
997
  /**
999
998
  * Returns the first child element as a Component instance.
1000
999
  * @returns The first child component, or `null` if none exists.
1001
- */
1000
+ */
1002
1001
 
1003
1002
  firstChild<T extends Component = Component>( ) : T {
1004
1003
  const nxt = this.dom.firstElementChild;
@@ -1008,7 +1007,7 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
1008
1007
  /**
1009
1008
  * Returns the last child element as a Component instance.
1010
1009
  * @returns The last child component, or `null` if none exists.
1011
- */
1010
+ */
1012
1011
 
1013
1012
  lastChild<T extends Component = Component>( ) : T {
1014
1013
  const nxt = this.dom.lastElementChild;
@@ -1019,19 +1018,19 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
1019
1018
  * Enumerates all child components of this component.
1020
1019
  * @param recursive - If `true`, searches all descendants; otherwise, only direct children.
1021
1020
  * @returns An array of child Component instances.
1022
- */
1021
+ */
1023
1022
 
1024
1023
  enumChildComponents( recursive: boolean ) {
1025
-
1024
+
1026
1025
  const children: Component[] = [];
1027
-
1026
+
1028
1027
  const nodes = this.enumChildNodes( recursive );
1029
1028
  nodes.forEach( ( c: Node ) => {
1030
1029
  const cc = componentFromDOM( c as HTMLElement );
1031
1030
  if( cc ) {
1032
1031
  children.push(cc);
1033
1032
  }
1034
- } );
1033
+ });
1035
1034
 
1036
1035
  return children;
1037
1036
  }
@@ -1188,7 +1187,7 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
1188
1187
  state.on( "change", ( ) => {
1189
1188
  this.setTimeout( key, 500, ( ) => {
1190
1189
  Application.instance().setStorage( key, JSON.stringify( raw ) );
1191
- });
1190
+ } );
1192
1191
  });
1193
1192
 
1194
1193
  this.#pstate[name] = state;
@@ -1214,7 +1213,7 @@ type ComponentConstructor = {
1214
1213
 
1215
1214
  export function componentFromDOM<T extends Component = Component>( node: Element ) {
1216
1215
  return node ? (node as any)[COMPONENT] as T : null;
1217
- }
1216
+ }
1218
1217
 
1219
1218
  /**
1220
1219
  * Wraps an existing HTMLElement with a new Component instance.
@@ -27,9 +27,15 @@ export interface EvMessage extends CoreEvent {
27
27
  params: any;
28
28
  }
29
29
 
30
+ interface EvWindowResize extends CoreEvent {
31
+ width: number;
32
+ height: number;
33
+ }
34
+
30
35
  export interface ApplicationEvents extends EventMap {
31
36
  global: EvMessage;
32
37
  message: EvMessage;
38
+ resize: EvWindowResize;
33
39
  }
34
40
 
35
41
  // singleton
@@ -104,6 +110,15 @@ export class Application<E extends ApplicationEvents = ApplicationEvents> extend
104
110
  else {
105
111
  window.addEventListener( "load", loaded, { once: true } );
106
112
  }
113
+
114
+ const resize = ( ) => {
115
+ this.fire( "resize", {
116
+ width: window.innerWidth,
117
+ height: window.innerHeight,
118
+ } );
119
+ }
120
+
121
+ window.addEventListener('resize', resize, { passive: true });
107
122
  }
108
123
 
109
124
  /**
@@ -14,6 +14,8 @@
14
14
  * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
15
  **/
16
16
 
17
+ export const COMPONENT = Symbol( "component" );
18
+
17
19
  /** @ignore this events must be defined on domNode (do not bubble) */
18
20
  export const unbubbleEvents = {
19
21
  mouseleave: 1, mouseenter: 1, load: 1, unload: 1, scroll: 1, focus: 1, blur: 1, rowexit: 1, beforeunload: 1, stop: 1,
@@ -33,9 +35,33 @@ let mutObserver: MutationObserver = null;
33
35
 
34
36
  const observeMutation = (mutations: MutationRecord[], observer: MutationObserver): void => {
35
37
 
38
+ // moves are handled this way
39
+ const added = new Set<Node>( );
40
+ const removed = new Set<Node>( );
41
+
42
+ for( const mutation of mutations ) {
43
+ if( mutation.type == "childList" ) {
44
+ mutation.addedNodes.forEach( n => {
45
+ if( removed.has( n ) ) {
46
+ removed.delete( n );
47
+ }
48
+ else {
49
+ added.add( n );
50
+ }
51
+ } );
52
+
53
+ mutation.removedNodes.forEach( n => {
54
+ if( added.has( n ) ) {
55
+ added.delete( n );
56
+ }
57
+ else {
58
+ removed.add( n );
59
+ }
60
+ } );
61
+ }
62
+ }
63
+
36
64
  const sendEvent = ( node: Node, code: "created" | "removed" ) => {
37
- // console.log( "notify", node, code );
38
-
39
65
  const store = event_handlers.get( node );
40
66
  if ( store && store[code] ) {
41
67
  node.dispatchEvent( new Event( code, {} ) );
@@ -53,26 +79,17 @@ const observeMutation = (mutations: MutationRecord[], observer: MutationObserver
53
79
  }
54
80
 
55
81
  if( !create ) {
82
+ const core = (node as any)[COMPONENT];
83
+ if( core && core.cleanUp ) { //<yes: dirty hack
84
+ core.cleanUp();
85
+ }
86
+
56
87
  sendEvent( node, "removed" );
57
88
  }
58
89
  }
59
90
 
60
-
61
- for (const mutation of mutations) {
62
- if( mutation.type=="childList" ) {
63
- if( mutation.addedNodes ) {
64
- mutation.addedNodes.forEach( node => {
65
- notify( node, true );
66
- } );
67
- }
68
-
69
- if( mutation.removedNodes ) {
70
- mutation.removedNodes.forEach( node => {
71
- notify( node, false );
72
- } );
73
- }
74
- }
75
- }
91
+ added.forEach( n => { if( n.isConnected ) notify( n, true ); } );
92
+ removed.forEach( n => { if( !n.isConnected ) notify( n, false ); } );
76
93
  }
77
94
 
78
95
 
@@ -144,17 +161,27 @@ export function dispatchEvent(ev: Event) {
144
161
 
145
162
  export function addEvent( node: Node, name: string, handler: DOMEventHandler, prepend = false ) {
146
163
 
164
+ if( !mutObserver ) {
165
+ mutObserver = new MutationObserver( observeMutation )
166
+ mutObserver.observe( document.body, {childList: true,subtree: true} );
167
+ }
168
+
147
169
  if( name=="removed" || name=="created" ) {
148
- if( !mutObserver ) {
149
- mutObserver = new MutationObserver( observeMutation )
150
- mutObserver.observe( document.body, {childList: true,subtree: true} );
170
+ const core = (node as any)[COMPONENT];
171
+ if( core && core.addClass ) { //<yes: dirty hack
172
+ core.addClass( "x4:mut")
151
173
  }
152
174
  }
153
175
  else if( name=="resized" ) {
154
176
  if (!sizeObserver) {
155
177
  sizeObserver = new ResizeObserver( observeSize );
156
178
  }
157
-
179
+
180
+ const core = (node as any)[COMPONENT];
181
+ if( core && core.addClass ) { //<yes: dirty hack
182
+ core.addClass( "x4:sze")
183
+ }
184
+
158
185
  sizeObserver.observe( node as Element );
159
186
  }
160
187
 
@@ -470,12 +497,3 @@ export interface GlobalDOMEvents {
470
497
  created?: ( ev: Event ) => void; // occurs when inserted in the dom
471
498
  removed?: ( ev: Event ) => void; // occurs when removed from dom
472
499
  }
473
-
474
-
475
-
476
-
477
-
478
-
479
-
480
-
481
-
@@ -17,6 +17,13 @@
17
17
  import { EventMap, EventSource } from './core_events';
18
18
 
19
19
 
20
+ interface TimerEntry {
21
+ cb: Function;
22
+ id: number;
23
+ clear: Function;
24
+ }
25
+
26
+
20
27
  /**
21
28
  * CoreElement
22
29
  *
@@ -77,10 +84,14 @@ import { EventMap, EventSource } from './core_events';
77
84
  * el.clearInterval("poll");
78
85
  * ```
79
86
  */
87
+
80
88
  export class CoreElement<E extends EventMap = EventMap> {
81
89
 
82
90
  #events: EventSource<E>;
83
- #timers: Map<string, Function>;
91
+ #timers: Map<string, TimerEntry>;
92
+ #cleanup: Function[];
93
+
94
+ /** changed to stop timers when object is down
84
95
 
85
96
  private __startTimer( name: string, ms: number, repeat: boolean, callback: ( ) => void ) {
86
97
  if (!this.#timers) {
@@ -102,6 +113,69 @@ export class CoreElement<E extends EventMap = EventMap> {
102
113
  const clear = this.#timers?.get(name);
103
114
  if (clear) { clear(); }
104
115
  }
116
+ */
117
+
118
+ private __startTimer(name: string, ms: number, repeat: boolean, callback: () => void) {
119
+ if (!this.#timers) {
120
+ this.#timers = new Map();
121
+ }
122
+ else {
123
+ this.__stopTimer(name);
124
+ }
125
+
126
+ // Weak reference only: the native timer must not keep the element alive.
127
+ // The callback itself is stored in #timers, held by the element,
128
+ // so the element remains collectable while a timer is active.
129
+ const ref = new WeakRef(this); // avoid keeping a ref on this.
130
+
131
+ let tick: () => void;
132
+
133
+ if (repeat) {
134
+ tick = () => {
135
+ const self = ref.deref();
136
+ if (!self) {
137
+ clearInterval(id);
138
+ return;
139
+ }
140
+
141
+ self.#timers.get(name)!.cb();
142
+ };
143
+
144
+ const id = setInterval( tick, ms );
145
+
146
+ this.#timers.set(name, {
147
+ cb: callback,
148
+ id,
149
+ clear( ) { clearInterval(id); }
150
+ });
151
+ }
152
+ else {
153
+ tick = () => {
154
+ const self = ref.deref();
155
+ if (!self) {
156
+ return;
157
+ }
158
+
159
+ // One-shot: remove the entry before firing so the map stays clean.
160
+ self.#timers.delete(name);
161
+ callback();
162
+ };
163
+
164
+ this.#timers.set(name, {
165
+ cb: callback,
166
+ id: setTimeout(tick, ms),
167
+ clear() { clearTimeout(this.id); }
168
+ });
169
+ }
170
+ }
171
+
172
+ private __stopTimer(name: string) {
173
+ const entry = this.#timers?.get(name);
174
+ if (entry) {
175
+ entry.clear();
176
+ this.#timers.delete(name);
177
+ }
178
+ }
105
179
 
106
180
  /**
107
181
  * Sets a timeout that executes a callback function after a specified delay.
@@ -110,19 +184,19 @@ export class CoreElement<E extends EventMap = EventMap> {
110
184
  * @param ms - The delay in milliseconds before the callback is executed.
111
185
  * @param callback - The function to execute after the delay.
112
186
  */
113
-
114
- setTimeout( name: string, ms: number, callback: () => void ) {
115
- this.__startTimer( name, ms, false, callback );
187
+
188
+ setTimeout(name: string, ms: number, callback: () => void) {
189
+ this.__startTimer(name, ms, false, callback);
116
190
  }
117
-
191
+
118
192
  /**
119
193
  * Clears a previously set timeout.
120
194
  * @param name - The name of the timeout to clear.
121
195
  * @see setTimeout
122
196
  */
123
197
 
124
- clearTimeout( name: string ) {
125
- this.__stopTimer( name );
198
+ clearTimeout(name: string) {
199
+ this.__stopTimer(name);
126
200
  }
127
201
 
128
202
  /**
@@ -133,8 +207,8 @@ export class CoreElement<E extends EventMap = EventMap> {
133
207
  * @param callback - The function to execute after the delay.
134
208
  */
135
209
 
136
- setInterval( name: string, ms: number, callback: ( ) => void ) {
137
- this.__startTimer( name, ms, true, callback );
210
+ setInterval(name: string, ms: number, callback: () => void) {
211
+ this.__startTimer(name, ms, true, callback);
138
212
  }
139
213
 
140
214
  /**
@@ -143,8 +217,8 @@ export class CoreElement<E extends EventMap = EventMap> {
143
217
  * @see setInterval
144
218
  */
145
219
 
146
- clearInterval( name: string ) {
147
- this.__stopTimer( name );
220
+ clearInterval(name: string) {
221
+ this.__stopTimer(name);
148
222
  }
149
223
 
150
224
  /**
@@ -152,12 +226,40 @@ export class CoreElement<E extends EventMap = EventMap> {
152
226
  * This stops all scheduled callbacks and removes their references.
153
227
  * @see setTimeout
154
228
  */
155
- clearTimeouts( ) {
156
- for( const [id,val] of this.#timers ) {
157
- val( );
229
+
230
+ clearTimeouts() {
231
+ if (!this.#timers) {
232
+ return;
233
+ }
234
+
235
+ // Entries no longer mutate the map in clear(), so plain iteration is safe.
236
+ for (const entry of this.#timers.values()) {
237
+ entry.clear();
158
238
  }
159
-
160
- this.#timers.clear( );
239
+
240
+ this.#timers.clear();
241
+ }
242
+
243
+ /**
244
+ * add a cleanup function to the cleanup list
245
+ * @see cleanUp
246
+ */
247
+
248
+ addCleanup( fn : Function ) {
249
+ if( !this.#cleanup ) {
250
+ this.#cleanup = [];
251
+ }
252
+
253
+ this.#cleanup.push( fn );
254
+ }
255
+
256
+ /**
257
+ * called when element is removed from dom
258
+ */
259
+
260
+ cleanUp( ) {
261
+ this.clearTimeouts( );
262
+ this.#cleanup?.forEach( x => x() );
161
263
  }
162
264
 
163
265
  // :: EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
@@ -173,17 +275,17 @@ export class CoreElement<E extends EventMap = EventMap> {
173
275
  * attach to an event
174
276
  */
175
277
 
176
- on<K extends keyof E>( name: K, listener: ( ev: E[K] ) => void ) {
177
- console.assert( listener!==undefined && listener!==null );
278
+ on<K extends keyof E>(name: K, listener: (ev: E[K]) => void) {
279
+ console.assert(listener !== undefined && listener !== null);
178
280
 
179
- if( !this.#events ) {
180
- this.#events = new EventSource( this );
281
+ if (!this.#events) {
282
+ this.#events = new EventSource(this);
181
283
  }
182
-
183
- this.#events.addListener( name, listener );
284
+
285
+ this.#events.addListener(name, listener);
184
286
  return {
185
- off: ( ) => {
186
- this.#events.removeListener( name, listener );
287
+ off: () => {
288
+ this.#events.removeListener(name, listener);
187
289
  }
188
290
  }
189
291
  }
@@ -197,11 +299,11 @@ export class CoreElement<E extends EventMap = EventMap> {
197
299
  * @see fire
198
300
  */
199
301
 
200
- off<K extends keyof E>( name: K, listener: ( ev: E[K] ) => void ) {
201
- console.assert( listener!==undefined && listener!==null );
302
+ off<K extends keyof E>(name: K, listener: (ev: E[K]) => void) {
303
+ console.assert(listener !== undefined && listener !== null);
202
304
 
203
- if( this.#events ) {
204
- this.#events.removeListener( name, listener );
305
+ if (this.#events) {
306
+ this.#events.removeListener(name, listener);
205
307
  }
206
308
  }
207
309
 
@@ -214,9 +316,9 @@ export class CoreElement<E extends EventMap = EventMap> {
214
316
  * @see off
215
317
  */
216
318
 
217
- fire<K extends keyof E>( name: K, ev: E[K] ) {
218
- if( this.#events ) {
219
- this.#events.fire( name, ev );
319
+ fire<K extends keyof E>(name: K, ev: E[K]) {
320
+ if (this.#events) {
321
+ this.#events.fire(name, ev);
220
322
  }
221
323
  }
222
324
  }
@@ -124,6 +124,7 @@ function serialize(val: PDFValue): string {
124
124
  return String(val)
125
125
  }
126
126
 
127
+ // @internal
127
128
  class Ref {
128
129
  id: number
129
130
  constructor(id: number) { this.id = id }