x4js 2.0.13 → 2.0.14

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 (258) hide show
  1. package/.vscode/launch.json +14 -0
  2. package/README.md +5 -0
  3. package/{lib/src/demo → demo}/main.scss +3 -1
  4. package/{lib/src/demo/main.tsx → demo/main.ts} +37 -36
  5. package/demo/package.json +26 -0
  6. package/demo/scss.d.ts +4 -0
  7. package/demo/svg.d.ts +1 -0
  8. package/demo/tsconfig.json +14 -0
  9. package/lib/README.txt +5 -0
  10. package/lib/cjs/x4.css +1 -1
  11. package/lib/cjs/x4.js +2 -1
  12. package/lib/esm/x4.css +1 -1
  13. package/lib/esm/x4.mjs +2 -1
  14. package/lib/src/components/boxes/boxes.module.scss +17 -0
  15. package/lib/src/components/boxes/boxes.ts +162 -13
  16. package/lib/src/components/breadcrumb/breadcrumb.scss +56 -28
  17. package/lib/src/components/breadcrumb/breadcrumb.ts +93 -84
  18. package/lib/src/components/btngroup/btngroup.module.scss +12 -0
  19. package/lib/src/components/btngroup/btngroup.ts +41 -8
  20. package/lib/src/components/button/button.module.scss +23 -5
  21. package/lib/src/components/button/button.ts +72 -4
  22. package/lib/src/components/canvas/canvas.module.scss +25 -0
  23. package/lib/src/components/canvas/canvas.ts +189 -0
  24. package/lib/src/components/canvas/canvas_ex.ts +269 -0
  25. package/lib/src/components/checkbox/checkbox.ts +18 -4
  26. package/lib/src/components/combobox/combobox.module.scss +24 -15
  27. package/lib/src/components/combobox/combobox.ts +107 -24
  28. package/lib/src/components/components.ts +7 -0
  29. package/lib/src/components/dialog/dialog.module.scss +37 -3
  30. package/lib/src/components/dialog/dialog.ts +149 -31
  31. package/lib/src/components/filedrop/cloud-arrow-up.svg +1 -0
  32. package/lib/src/components/filedrop/filedrop.module.scss +70 -0
  33. package/lib/src/components/filedrop/filedrop.ts +131 -0
  34. package/lib/src/components/form/form.module.scss +4 -0
  35. package/lib/src/components/form/form.ts +137 -6
  36. package/lib/src/components/gridview/arrow-down-light.svg +1 -0
  37. package/lib/src/components/gridview/arrow-up-light.svg +1 -0
  38. package/lib/src/components/gridview/gridview.module.scss +324 -0
  39. package/lib/src/components/gridview/gridview.ts +1175 -0
  40. package/lib/src/components/icon/icon.module.scss +1 -1
  41. package/lib/src/components/icon/icon.ts +4 -1
  42. package/lib/src/components/image/image.module.scss +8 -1
  43. package/lib/src/components/image/image.ts +105 -6
  44. package/lib/src/components/input/input.module.scss +8 -3
  45. package/lib/src/components/input/input.ts +137 -14
  46. package/lib/src/components/keyboard/arrow-up.svg +1 -0
  47. package/lib/src/components/keyboard/delete-left.svg +1 -0
  48. package/lib/src/components/keyboard/eye-slash.svg +1 -0
  49. package/lib/src/components/keyboard/keyboard.module.scss +134 -0
  50. package/lib/src/components/keyboard/keyboard.ts +525 -0
  51. package/lib/src/components/label/label.module.scss +22 -4
  52. package/lib/src/components/label/label.ts +33 -0
  53. package/lib/src/components/link/link.ts +81 -78
  54. package/lib/src/components/listbox/listbox.module.scss +61 -3
  55. package/lib/src/components/listbox/listbox.ts +164 -56
  56. package/lib/src/components/menu/menu.module.scss +10 -1
  57. package/lib/src/components/menu/menu.ts +4 -1
  58. package/lib/src/components/messages/messages.module.scss +44 -0
  59. package/lib/src/components/messages/messages.ts +164 -18
  60. package/lib/src/components/messages/pen-field.svg +1 -0
  61. package/lib/src/components/normalize.scss +5 -0
  62. package/lib/src/components/notification/notification.module.scss +4 -2
  63. package/lib/src/components/notification/notification.ts +2 -4
  64. package/lib/src/components/panel/panel.module.scss +12 -0
  65. package/lib/src/components/popup/popup.module.scss +4 -2
  66. package/lib/src/components/popup/popup.ts +136 -92
  67. package/lib/src/components/propgrid/folder-closed.svg +1 -0
  68. package/lib/src/components/propgrid/folder-open.svg +1 -0
  69. package/lib/src/components/propgrid/progrid.module.scss +108 -0
  70. package/lib/src/components/propgrid/propgrid.ts +271 -0
  71. package/lib/src/components/propgrid/updown.svg +4 -0
  72. package/lib/src/components/radio/radio.module.scss +147 -0
  73. package/lib/src/components/radio/radio.svg +4 -0
  74. package/lib/src/components/radio/radio.ts +142 -0
  75. package/lib/src/components/select/select.module.scss +9 -0
  76. package/lib/src/components/select/select.ts +134 -0
  77. package/lib/src/components/shared.scss +47 -0
  78. package/lib/src/components/sizers/sizer.ts +9 -2
  79. package/lib/src/components/slider/slider.module.scss +77 -30
  80. package/lib/src/components/slider/slider.ts +72 -22
  81. package/lib/src/components/tabs/tabs.module.scss +1 -2
  82. package/lib/src/components/tabs/tabs.ts +43 -12
  83. package/lib/src/components/textarea/textarea.module.scss +6 -2
  84. package/lib/src/components/textarea/textarea.ts +73 -8
  85. package/lib/src/components/textedit/textedit.module.scss +3 -1
  86. package/lib/src/components/textedit/textedit.ts +31 -4
  87. package/lib/src/components/themes.scss +7 -0
  88. package/lib/src/components/tickline/tickline.module.scss +26 -0
  89. package/lib/src/components/tickline/tickline.ts +82 -0
  90. package/lib/src/components/tooltips/comments-question.svg +1 -0
  91. package/lib/src/components/tooltips/tooltips.scss +30 -9
  92. package/lib/src/components/tooltips/tooltips.ts +10 -5
  93. package/lib/src/components/treeview/treeview.module.scss +129 -60
  94. package/lib/src/components/treeview/treeview.ts +47 -12
  95. package/lib/src/components/viewport/viewport.module.scss +7 -0
  96. package/lib/src/core/component.ts +102 -32
  97. package/lib/src/core/core_application.ts +222 -2
  98. package/lib/src/core/core_colors.ts +2 -2
  99. package/lib/src/{components/grid/datastore.ts → core/core_data.ts} +261 -250
  100. package/lib/src/core/core_dragdrop.ts +3 -3
  101. package/lib/src/core/core_element.ts +13 -1
  102. package/lib/src/core/core_events.ts +28 -0
  103. package/lib/src/core/core_i18n.ts +18 -2
  104. package/lib/src/core/core_react.ts +79 -0
  105. package/lib/src/core/core_router.ts +23 -7
  106. package/lib/src/core/core_styles.ts +5 -5
  107. package/lib/src/core/core_svg.ts +173 -12
  108. package/lib/src/core/core_tools.ts +305 -87
  109. package/lib/src/x4tsx.d.ts +25 -0
  110. package/lib/styles/x4.css +1 -1
  111. package/lib/types/x4js.d.ts +767 -92
  112. package/package.json +4 -4
  113. package/scripts/build.mjs +378 -0
  114. package/scripts/prepack.mjs +346 -0
  115. package/src/components/base.scss +25 -0
  116. package/src/components/boxes/boxes.module.scss +54 -0
  117. package/src/components/boxes/boxes.ts +278 -0
  118. package/src/components/breadcrumb/breadcrumb.scss +56 -0
  119. package/src/components/breadcrumb/breadcrumb.ts +93 -0
  120. package/src/components/breadcrumb/chevron-right.svg +1 -0
  121. package/src/components/btngroup/btngroup.module.scss +41 -0
  122. package/src/components/btngroup/btngroup.ts +153 -0
  123. package/src/components/button/button.module.scss +173 -0
  124. package/src/components/button/button.ts +185 -0
  125. package/src/components/calendar/calendar-check-sharp-light.svg +1 -0
  126. package/src/components/calendar/calendar.module.scss +163 -0
  127. package/src/components/calendar/calendar.ts +327 -0
  128. package/src/components/calendar/chevron-left-sharp-light.svg +1 -0
  129. package/src/components/calendar/chevron-right-sharp-light.svg +1 -0
  130. package/src/components/canvas/canvas.module.scss +25 -0
  131. package/src/components/canvas/canvas.ts +189 -0
  132. package/src/components/canvas/canvas_ex.ts +269 -0
  133. package/src/components/checkbox/check.svg +4 -0
  134. package/src/components/checkbox/checkbox.module.scss +142 -0
  135. package/src/components/checkbox/checkbox.ts +140 -0
  136. package/src/components/colorinput/colorinput.module.scss +65 -0
  137. package/src/components/colorinput/colorinput.ts +91 -0
  138. package/src/components/colorinput/crosshairs-simple-sharp-light.svg +1 -0
  139. package/src/components/colorpicker/colorpicker.module.scss +133 -0
  140. package/src/components/colorpicker/colorpicker.ts +482 -0
  141. package/src/components/combobox/combobox.module.scss +133 -0
  142. package/src/components/combobox/combobox.ts +275 -0
  143. package/src/components/combobox/updown.svg +4 -0
  144. package/src/components/components.ts +41 -0
  145. package/src/components/dialog/dialog.module.scss +105 -0
  146. package/src/components/dialog/dialog.ts +212 -0
  147. package/src/components/dialog/xmark-sharp-light.svg +1 -0
  148. package/src/components/filedrop/cloud-arrow-up.svg +1 -0
  149. package/src/components/filedrop/filedrop.module.scss +70 -0
  150. package/src/components/filedrop/filedrop.ts +131 -0
  151. package/src/components/form/form.module.scss +38 -0
  152. package/src/components/form/form.ts +172 -0
  153. package/src/components/gridview/arrow-down-light.svg +1 -0
  154. package/src/components/gridview/arrow-up-light.svg +1 -0
  155. package/src/components/gridview/gridview.module.scss +324 -0
  156. package/src/components/gridview/gridview.ts +1175 -0
  157. package/src/components/header/header.module.scss +40 -0
  158. package/src/components/header/header.ts +130 -0
  159. package/src/components/icon/icon.module.scss +30 -0
  160. package/src/components/icon/icon.ts +139 -0
  161. package/src/components/image/image.module.scss +28 -0
  162. package/src/components/image/image.ts +168 -0
  163. package/src/components/input/input.module.scss +74 -0
  164. package/src/components/input/input.ts +398 -0
  165. package/src/components/keyboard/arrow-up.svg +1 -0
  166. package/src/components/keyboard/delete-left.svg +1 -0
  167. package/src/components/keyboard/eye-slash.svg +1 -0
  168. package/src/components/keyboard/keyboard.module.scss +134 -0
  169. package/src/components/keyboard/keyboard.ts +525 -0
  170. package/src/components/label/label.module.scss +76 -0
  171. package/src/components/label/label.ts +97 -0
  172. package/src/components/link/link.ts +81 -0
  173. package/src/components/listbox/listbox.module.scss +161 -0
  174. package/src/components/listbox/listbox.ts +539 -0
  175. package/src/components/menu/caret-right-solid.svg +1 -0
  176. package/src/components/menu/menu.module.scss +117 -0
  177. package/src/components/menu/menu.ts +174 -0
  178. package/src/components/messages/circle-exclamation.svg +1 -0
  179. package/src/components/messages/messages.module.scss +92 -0
  180. package/src/components/messages/messages.ts +215 -0
  181. package/src/components/messages/pen-field.svg +1 -0
  182. package/src/components/normalize.scss +391 -0
  183. package/src/components/notification/circle-check-solid.svg +1 -0
  184. package/src/components/notification/circle-exclamation-solid.svg +1 -0
  185. package/src/components/notification/circle-notch-light.svg +1 -0
  186. package/src/components/notification/notification.module.scss +84 -0
  187. package/src/components/notification/notification.ts +107 -0
  188. package/src/components/notification/xmark-sharp-light.svg +1 -0
  189. package/src/components/panel/panel.module.scss +60 -0
  190. package/src/components/panel/panel.ts +58 -0
  191. package/src/components/popup/popup.module.scss +45 -0
  192. package/src/components/popup/popup.ts +440 -0
  193. package/src/components/progress/progress.module.scss +57 -0
  194. package/src/components/progress/progress.ts +44 -0
  195. package/src/components/propgrid/folder-closed.svg +1 -0
  196. package/src/components/propgrid/folder-open.svg +1 -0
  197. package/src/components/propgrid/progrid.module.scss +108 -0
  198. package/src/components/propgrid/propgrid.ts +271 -0
  199. package/src/components/propgrid/updown.svg +4 -0
  200. package/src/components/radio/radio.module.scss +147 -0
  201. package/src/components/radio/radio.svg +4 -0
  202. package/src/components/radio/radio.ts +142 -0
  203. package/src/components/rating/rating.module.scss +23 -0
  204. package/src/components/rating/rating.ts +131 -0
  205. package/src/components/rating/star-sharp-light.svg +1 -0
  206. package/src/components/rating/star-sharp-solid.svg +1 -0
  207. package/src/components/select/select.module.scss +9 -0
  208. package/src/components/select/select.ts +134 -0
  209. package/src/components/shared.scss +137 -0
  210. package/src/components/sizers/sizer.module.scss +90 -0
  211. package/src/components/sizers/sizer.ts +131 -0
  212. package/src/components/slider/slider.module.scss +118 -0
  213. package/src/components/slider/slider.ts +198 -0
  214. package/src/components/switch/switch.module.scss +127 -0
  215. package/src/components/switch/switch.ts +62 -0
  216. package/src/components/tabs/tabs.module.scss +45 -0
  217. package/src/components/tabs/tabs.ts +199 -0
  218. package/src/components/textarea/textarea.module.scss +63 -0
  219. package/src/components/textarea/textarea.ts +125 -0
  220. package/src/components/textedit/textedit.module.scss +116 -0
  221. package/src/components/textedit/textedit.ts +110 -0
  222. package/src/components/themes.scss +88 -0
  223. package/src/components/tickline/tickline.module.scss +26 -0
  224. package/src/components/tickline/tickline.ts +82 -0
  225. package/src/components/tooltips/circle-info-sharp-light.svg +1 -0
  226. package/src/components/tooltips/comments-question.svg +1 -0
  227. package/src/components/tooltips/tooltips.scss +72 -0
  228. package/src/components/tooltips/tooltips.ts +109 -0
  229. package/src/components/treeview/chevron-down-light.svg +1 -0
  230. package/src/components/treeview/treeview.module.scss +185 -0
  231. package/src/components/treeview/treeview.ts +445 -0
  232. package/src/components/viewport/viewport.module.scss +32 -0
  233. package/src/components/viewport/viewport.ts +41 -0
  234. package/src/core/component.ts +1072 -0
  235. package/src/core/core_application.ts +264 -0
  236. package/src/core/core_colors.ts +250 -0
  237. package/src/core/core_data.ts +1309 -0
  238. package/src/core/core_dom.ts +471 -0
  239. package/src/core/core_dragdrop.ts +201 -0
  240. package/src/core/core_element.ts +110 -0
  241. package/src/core/core_events.ts +177 -0
  242. package/src/core/core_i18n.ts +393 -0
  243. package/src/core/core_react.ts +79 -0
  244. package/src/core/core_router.ts +237 -0
  245. package/src/core/core_styles.ts +214 -0
  246. package/src/core/core_svg.ts +711 -0
  247. package/src/core/core_tools.ts +906 -0
  248. package/src/types/scss.d.ts +4 -0
  249. package/src/types/svg.d.ts +1 -0
  250. package/src/types/x4react.d.ts +9 -0
  251. package/src/x4.scss +19 -0
  252. package/src/x4tsx.d.ts +25 -0
  253. package/tsconfig.json +14 -0
  254. package/lib/src/components/grid/gridview.ts +0 -1108
  255. package/lib/src/components/grid/memdb.ts +0 -325
  256. /package/{lib/src/demo → demo}/assets/house-light.svg +0 -0
  257. /package/{lib/src/demo → demo}/assets/radio.svg +0 -0
  258. /package/{lib/src/demo → demo}/index.html +0 -0
@@ -36,8 +36,12 @@
36
36
 
37
37
  // outline
38
38
  --ol-button-background: var( --background-primary );
39
- --ol-button-background-active: #eee;
39
+ --ol-button-background-active: #ccc;
40
40
  --ol-button-color-active: var( --text-primary );
41
+ --ol-button-background-hover: #eee;
42
+ --ol-button-color-hover: var( --text-primary );
43
+ --ol-button-color-disabled: #ccc;
44
+
41
45
  --ol-button-color: var( --text-primary );
42
46
  --ol-button-border: var( --border );
43
47
  --ol-button-icon-color: var( --text-primary );
@@ -69,7 +73,9 @@
69
73
  gap: 0.4em;
70
74
 
71
75
  &> #icon {
72
- transition: color 0.3s;
76
+ transition: color 0.3s, fill 0.3s;
77
+ fill: var( --button-color );
78
+
73
79
  width: 1em;
74
80
  height: 1em;
75
81
  //color: var( --button-icon-color );
@@ -80,12 +86,12 @@
80
86
  }
81
87
 
82
88
  &> #label {
83
- @extend .flex;
89
+ flex-grow: 1;
84
90
 
85
91
  padding: 0;
86
92
  color: inherit;
87
93
 
88
- &:empty {
94
+ &.empty {
89
95
  display: none;
90
96
  }
91
97
  }
@@ -126,26 +132,38 @@
126
132
 
127
133
  #icon {
128
134
  color: var( --ol-button-icon-color );
135
+ fill: var(--ol-button-icon-color);
129
136
  }
130
137
 
131
138
  &:focus{
132
139
  border-color: var( --ol-button-border-focus );
133
140
  color: var( --ol-button-color-focus );
141
+ fill: var( --ol-button-color-focus );
134
142
  #icon {
135
143
  color: var( --ol-button-color-focus );
144
+ fill: var( --ol-button-color-focus );
136
145
  }
137
146
  }
138
147
 
148
+ &:hover{
149
+ background-color: var( --ol-button-background-hover );
150
+ color: var( --ol-button-color-hover );
151
+ fill: var( --ol-button-color-hover );
152
+ }
153
+
139
154
  &:active{
140
155
  background-color: var( --ol-button-background-active );
141
156
  color: var( --ol-button-color-active );
157
+ fill: var( --ol-button-color-active );
158
+
142
159
  #icon {
143
160
  color: var( --button-icon-color );
161
+ fill: var( --button-icon-color );
144
162
  }
145
163
  }
146
164
  }
147
165
 
148
- .x4button.danger {
166
+ .x4button.danger:not([disabled]) {
149
167
  background-color: var( --alert-background );
150
168
  color: var( --alert-color );
151
169
 
@@ -38,6 +38,8 @@ interface ButtonEvents extends ComponentEvents {
38
38
  export interface ButtonProps extends ComponentProps {
39
39
  label?: string;
40
40
  icon?: string;
41
+ tabindex?: boolean | number;
42
+ autorepeat?: number | boolean;
41
43
  click?: EventCallback<EvClick>;
42
44
  }
43
45
 
@@ -47,6 +49,9 @@ export interface ButtonProps extends ComponentProps {
47
49
 
48
50
  @class_ns( "x4" )
49
51
  export class Button extends Component<ButtonProps,ButtonEvents> {
52
+
53
+ #text: Component;
54
+
50
55
  /**
51
56
  * Creates an instance of Button.
52
57
  *
@@ -59,12 +64,27 @@ export class Button extends Component<ButtonProps,ButtonEvents> {
59
64
  super( { ...props, tag: 'button', content: null } );
60
65
 
61
66
  this.mapPropEvents( props, 'click' );
62
- this.addDOMEvent('click', (e) => this._on_click(e));
67
+
68
+ if( props.autorepeat ) {
69
+ this.addDOMEvent('pointerdown', (e) => this._on_mouse(e) );
70
+ this.addDOMEvent('pointerup', (e) => this._on_mouse(e) );
71
+ }
72
+ else {
73
+ this.addDOMEvent('click', (e) => this._on_click(e));
74
+ }
75
+
76
+ this.addDOMEvent('keydown', (e) => this._on_keydown(e) );
63
77
 
64
78
  this.setContent( [
65
79
  new Icon( { id: "icon", iconId: this.props.icon } ),
66
- new Component( { id: "label", content: this.props.label } ),
80
+ this.#text = new Component( { id: "label" } ),
67
81
  ] );
82
+
83
+ this.setText( props.label );
84
+
85
+ if( props.tabindex!==false ) {
86
+ this.setAttribute( 'tabindex', props.tabindex );
87
+ }
68
88
  }
69
89
 
70
90
  /**
@@ -89,6 +109,52 @@ export class Button extends Component<ButtonProps,ButtonEvents> {
89
109
  ev.stopPropagation();
90
110
  }
91
111
 
112
+ protected _on_mouse( e: PointerEvent ) {
113
+
114
+ let count = 0;
115
+
116
+ if( e.type=='pointerdown' ) {
117
+ this.dom.setPointerCapture( e.pointerId );
118
+
119
+ const rt = this.props.autorepeat===true ? 200 : this.props.autorepeat as number;
120
+
121
+ this.setTimeout( 'repeat', 500, ( ) => {
122
+ count++;
123
+
124
+ this.fire('click', {} );
125
+ this.setInterval( 'repeat', rt, ( ) => {
126
+ this.fire('click', {} );
127
+ })
128
+ } );
129
+ }
130
+ else {
131
+ this.clearTimeout( 'repeat' );
132
+
133
+ if( !count ) {
134
+ this.fire('click', {} );
135
+ }
136
+ }
137
+ }
138
+
139
+ /**
140
+ * simulate a click
141
+ */
142
+
143
+ click( ) {
144
+ (this.dom as HTMLButtonElement).click( );
145
+ }
146
+
147
+ /**
148
+ * called on key down
149
+ */
150
+
151
+ protected _on_keydown( e: KeyboardEvent ) {
152
+ if( e.key=='Enter' ) {
153
+ this.click( );
154
+ e.preventDefault( );
155
+ }
156
+ }
157
+
92
158
  /**
93
159
  * Sets the text content of the button's label.
94
160
  *
@@ -99,9 +165,10 @@ export class Button extends Component<ButtonProps,ButtonEvents> {
99
165
  */
100
166
 
101
167
  public setText( text: string | UnsafeHtml ) {
102
- this.query( "#label" ).setContent( text );
168
+ this.#text.setContent( text );
169
+ this.#text.setClass( "empty", !text );
103
170
  }
104
-
171
+
105
172
  /**
106
173
  * Sets the icon of the button.
107
174
  *
@@ -113,5 +180,6 @@ export class Button extends Component<ButtonProps,ButtonEvents> {
113
180
  public setIcon( icon: string ) {
114
181
  this.query<Icon>( "#icon" ).setIcon( icon );
115
182
  }
183
+
116
184
  }
117
185
 
@@ -0,0 +1,25 @@
1
+ /**
2
+ * ___ ___ __
3
+ * \ \/ / / _
4
+ * \ / /_| |_
5
+ * / \____ _|
6
+ * /__/\__\ |_|.2
7
+ *
8
+ * @file canvas.module.scss
9
+ * @author Etienne Cochard
10
+ *
11
+ * @copyright (c) 2025 R-libre ingenierie
12
+ *
13
+ * Use of this source code is governed by an MIT-style license
14
+ * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
+ **/
16
+
17
+ .x4canvas {
18
+ overflow: hidden;
19
+
20
+ &> canvas {
21
+ position: absolute;
22
+ left: 0;
23
+ top: 0;
24
+ }
25
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * ___ ___ __
3
+ * \ \/ / / _
4
+ * \ / /_| |_
5
+ * / \____ _|
6
+ * /__/\__\ |_|.2
7
+ *
8
+ * @file canvas.ts
9
+ * @author Etienne Cochard
10
+ *
11
+ * @copyright (c) 2025 R-libre ingenierie
12
+ *
13
+ * Use of this source code is governed by an MIT-style license
14
+ * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
+ **/
16
+
17
+ import { class_ns } from '@core/core_tools.js';
18
+ import { Component, ComponentEvent, ComponentEvents, ComponentProps } from '../../core/component';
19
+ import { EventCallback } from "../../core/core_events"
20
+ import { CanvasEx, createPainter } from './canvas_ex.js';
21
+
22
+ import './canvas.module.scss'
23
+
24
+ export interface EvPaint extends ComponentEvent {
25
+ ctx: CanvasEx;
26
+ }
27
+
28
+ interface CanvasEventMap extends ComponentEvents {
29
+ paint: EvPaint;
30
+ }
31
+
32
+ interface CanvasProps extends ComponentProps {
33
+ paint: EventCallback<EvPaint>
34
+ clear?: boolean;
35
+ }
36
+
37
+
38
+
39
+ // ============================================================================
40
+ // [CANVAS]
41
+ // ============================================================================
42
+
43
+ /**
44
+ * Standard Canvas
45
+ */
46
+
47
+ @class_ns( "x4" )
48
+ export class Canvas extends Component<CanvasProps, CanvasEventMap> {
49
+
50
+ private m_iwidth: number = -1;
51
+ private m_iheight: number = -1;
52
+ private m_scale = 1.0;
53
+ private m_canvas: Component;
54
+
55
+ constructor(props: CanvasProps) {
56
+ super(props);
57
+
58
+ this.mapPropEvents( props, 'paint' );
59
+ this.addDOMEvent('resized', () => { this._paint(); })
60
+
61
+ this.m_iwidth = -1;
62
+ this.m_iheight = -1;
63
+ this.m_canvas = new Component({
64
+ tag: 'canvas'
65
+ });
66
+
67
+ this.setContent( this.m_canvas );
68
+ }
69
+
70
+ /**
71
+ * scale the whole canvas
72
+ */
73
+
74
+ scale(scale: number) {
75
+ this.m_scale = scale;
76
+ this.m_iwidth = -1; // force recalc
77
+ this.redraw();
78
+ }
79
+
80
+ /**
81
+ * return the internal canvas
82
+ */
83
+ get canvas(): Component {
84
+ return this.m_canvas;
85
+ }
86
+
87
+ getContext( ) {
88
+ return (this.m_canvas.dom as HTMLCanvasElement).getContext('2d') as CanvasEx;
89
+ }
90
+
91
+ /**
92
+ * redraw the canvas (force a paint)
93
+ */
94
+
95
+ private $update_rep = 0;
96
+ public redraw(wait?: number) {
97
+
98
+ if (wait !== undefined) {
99
+ if( ++this.$update_rep>=20 ) {
100
+ this.clearTimeout( 'update' );
101
+ this._paint( );
102
+ }
103
+ else {
104
+ this.setTimeout( 'update', wait, () => this._paint() );
105
+ }
106
+ }
107
+ else {
108
+ this.clearTimeout( 'update' );
109
+ this._paint();
110
+ }
111
+ }
112
+
113
+ /**
114
+ *
115
+ */
116
+
117
+ private _paint() {
118
+ this.$update_rep = 0;
119
+
120
+ let dom = this.dom;
121
+ if (!this.isVisible() ) {
122
+ return;
123
+ }
124
+
125
+ //const canvas = this.m_canvas.dom as HTMLCanvasElement;
126
+ const w = dom.clientWidth;
127
+ const h = dom.clientHeight;
128
+
129
+ const ctx = this.getContext();
130
+ if (w != this.m_iwidth || h != this.m_iheight) {
131
+ // adjustment for HDPI
132
+ let devicePixelRatio = window.devicePixelRatio || 1;
133
+ let backingStoreRatio = (<any>ctx).webkitBackingStorePixelRatio ||
134
+ (<any>ctx).mozBackingStorePixelRatio ||
135
+ (<any>ctx).msBackingStorePixelRatio ||
136
+ (<any>ctx).oBackingStorePixelRatio ||
137
+ (<any>ctx).backingStorePixelRatio || 1;
138
+
139
+ let canvas = this.canvas;
140
+
141
+ if ( this.m_scale != 1.0 ) { //devicePixelRatio !== backingStoreRatio || this.m_scale != 1.0) {
142
+ let ratio = 1; //devicePixelRatio / backingStoreRatio,
143
+ const rw = w * ratio;
144
+ const rh = h * ratio;
145
+
146
+ canvas.setAttribute('width', '' + rw);
147
+ canvas.setAttribute('height', '' + rh);
148
+ canvas.setStyleValue('width', w);
149
+ canvas.setStyleValue('height', h);
150
+
151
+ ratio *= this.m_scale;
152
+ ctx.scale(ratio, ratio);
153
+ }
154
+ else {
155
+ canvas.setAttribute('width', '' + w);
156
+ canvas.setAttribute('height', '' + h);
157
+ canvas.setStyleValue('width', w);
158
+ canvas.setStyleValue('height', h);
159
+ ctx.scale(1, 1);
160
+ }
161
+
162
+ this.m_iwidth = w;
163
+ this.m_iheight = h;
164
+ }
165
+
166
+ if (w && h) {
167
+ let cc = createPainter(ctx, w, h);
168
+ if (this.props.clear) {
169
+
170
+ cc.clearRect(0,0,w/this.m_scale,h/this.m_scale);
171
+ }
172
+
173
+ cc.save();
174
+ cc.translate(-0.5, -0.5);
175
+ this.paint(cc);
176
+ cc.restore();
177
+ }
178
+ }
179
+
180
+ protected paint(ctx: CanvasEx ) {
181
+ try {
182
+ this.fire('paint', { ctx } );
183
+ }
184
+ catch (x) {
185
+ console.assert(false, x);
186
+ }
187
+ }
188
+ }
189
+
@@ -0,0 +1,269 @@
1
+ /**
2
+ * ___ ___ __
3
+ * \ \/ / / _
4
+ * \ / /_| |_
5
+ * / \____ _|
6
+ * /__/\__\ |_|.2
7
+ *
8
+ * @file canvas_ex.ts
9
+ * @author Etienne Cochard
10
+ *
11
+ * @copyright (c) 2025 R-libre ingenierie
12
+ *
13
+ * Use of this source code is governed by an MIT-style license
14
+ * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
+ **/
16
+
17
+
18
+ export interface CanvasEx extends CanvasRenderingContext2D {
19
+ width: number;
20
+ height: number;
21
+
22
+ smoothLine(points: any[], path: CanvasPath, move: boolean): void;
23
+ smoothLineEx(_points: any[], tension: number, numOfSeg: number, path: CanvasPath, move?: boolean, close?: boolean): void;
24
+ line(x1: number, y1: number, x2: number, y2: number, color: string, lineWidth: number): void;
25
+ roundRect(x: number, y: number, width: number, height: number, radius: number): void;
26
+ calcTextSize(text: string, rounded: boolean): { width: number, height: number };
27
+ setFontSize(fs: number): void;
28
+ circle(x: number, y: number, radius: number): void;
29
+ }
30
+
31
+ export function createPainter(c2d: CanvasRenderingContext2D, w: number, h: number): CanvasEx {
32
+
33
+ let cp = c2d as CanvasEx;
34
+
35
+ cp.width = w;
36
+ cp.height = h;
37
+
38
+ cp.smoothLine = smoothLine;
39
+ cp.smoothLineEx = smoothLineEx;
40
+ cp.line = line;
41
+ cp.roundRect = roundRect;
42
+ cp.calcTextSize = calcTextSize;
43
+ cp.setFontSize = setFontSize;
44
+ cp.circle = circle;
45
+
46
+ return cp;
47
+ }
48
+
49
+ function smoothLine( this: CanvasRenderingContext2D, points: any[], path: CanvasPath = null, move = true) {
50
+ if (points.length < 2) {
51
+ return;
52
+ }
53
+
54
+ if (!path) {
55
+ path = this;
56
+ }
57
+
58
+ if (points.length == 2) {
59
+ if (move !== false) {
60
+ path.moveTo(points[0].x, points[0].y);
61
+ }
62
+ else {
63
+ path.lineTo(points[0].x, points[0].y);
64
+ }
65
+
66
+ path.lineTo(points[1].x, points[1].y);
67
+ return;
68
+ }
69
+
70
+ function midPointBtw(p1: IPoint, p2: IPoint ) {
71
+ return {
72
+ x: p1.x + (p2.x - p1.x) / 2,
73
+ y: p1.y + (p2.y - p1.y) / 2
74
+ };
75
+ }
76
+
77
+ function getQuadraticXY(t: number, sx: number, sy: number, cp1x: number, cp1y: number, ex: number, ey: number) : IPoint {
78
+ return {
79
+ x: (1 - t) * (1 - t) * sx + 2 * (1 - t) * t * cp1x + t * t * ex,
80
+ y: (1 - t) * (1 - t) * sy + 2 * (1 - t) * t * cp1y + t * t * ey
81
+ };
82
+ }
83
+
84
+ let p1 = points[0],
85
+ p2 = points[1],
86
+ p3 = p1;
87
+
88
+ path.moveTo(p1.x, p1.y);
89
+
90
+ for (let i = 1, len = points.length; i < len; i++) {
91
+ // we pick the point between pi+1 & pi+2 as the
92
+ // end point and p1 as our control point
93
+ let midPoint = midPointBtw(p1, p2);
94
+ //this.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y);
95
+ for (let i = 0; i < 8; i++) {
96
+ let { x, y } = getQuadraticXY(i / 8, p3.x, p3.y, p1.x, p1.y, midPoint.x, midPoint.y);
97
+ path.lineTo(x, y);
98
+ }
99
+
100
+ p1 = points[i];
101
+ p2 = points[i + 1];
102
+ p3 = midPoint;
103
+ }
104
+
105
+ // Draw last line as a straight line while
106
+ // we wait for the next point to be able to calculate
107
+ // the bezier control point
108
+
109
+ path.lineTo(p1.x, p1.y);
110
+ }
111
+
112
+ function smoothLineEx(this: CanvasRenderingContext2D, _points: any[], tension: number = 0.5, numOfSeg: number = 10, path: CanvasPath = null, move = true, close = false) {
113
+
114
+ let points = [];
115
+
116
+ //pts = points.slice(0);
117
+ for (let p = 0, pc = _points.length; p < pc; p++) {
118
+ points.push(_points[p].x);
119
+ points.push(_points[p].y);
120
+ }
121
+
122
+ let pts,
123
+ i = 1,
124
+ l = points.length,
125
+ rPos = 0,
126
+ rLen = (l - 2) * numOfSeg + 2 + (close ? 2 * numOfSeg : 0),
127
+ res = new Float32Array(rLen),
128
+ cache = new Float32Array((numOfSeg + 2) * 4),
129
+ cachePtr = 4;
130
+
131
+ pts = points.slice(0);
132
+
133
+ if (close) {
134
+ pts.unshift(points[l - 1]); // insert end point as first point
135
+ pts.unshift(points[l - 2]);
136
+ pts.push(points[0], points[1]); // first point as last point
137
+ }
138
+ else {
139
+ pts.unshift(points[1]); // copy 1. point and insert at beginning
140
+ pts.unshift(points[0]);
141
+ pts.push(points[l - 2], points[l - 1]); // duplicate end-points
142
+ }
143
+
144
+ // cache inner-loop calculations as they are based on t alone
145
+ cache[0] = 1; // 1,0,0,0
146
+
147
+ for (; i < numOfSeg; i++) {
148
+
149
+ var st = i / numOfSeg,
150
+ st2 = st * st,
151
+ st3 = st2 * st,
152
+ st23 = st3 * 2,
153
+ st32 = st2 * 3;
154
+
155
+ cache[cachePtr++] = st23 - st32 + 1; // c1
156
+ cache[cachePtr++] = st32 - st23; // c2
157
+ cache[cachePtr++] = st3 - 2 * st2 + st; // c3
158
+ cache[cachePtr++] = st3 - st2; // c4
159
+ }
160
+
161
+ cache[cachePtr] = 1; // 0,1,0,0
162
+
163
+ // calc. points
164
+ parse(pts, cache, l);
165
+
166
+ if (close) {
167
+ //l = points.length;
168
+ pts = [];
169
+ pts.push(points[l - 4], points[l - 3], points[l - 2], points[l - 1]); // second last and last
170
+ pts.push(points[0], points[1], points[2], points[3]); // first and second
171
+ parse(pts, cache, 4);
172
+ }
173
+
174
+ function parse(pts: number[], cache: Float32Array, l: number) {
175
+
176
+ for (var i = 2, t; i < l; i += 2) {
177
+
178
+ var pt1 = pts[i],
179
+ pt2 = pts[i + 1],
180
+ pt3 = pts[i + 2],
181
+ pt4 = pts[i + 3],
182
+
183
+ t1x = (pt3 - pts[i - 2]) * tension,
184
+ t1y = (pt4 - pts[i - 1]) * tension,
185
+ t2x = (pts[i + 4] - pt1) * tension,
186
+ t2y = (pts[i + 5] - pt2) * tension;
187
+
188
+ for (t = 0; t < numOfSeg; t++) {
189
+
190
+ var c = t << 2, //t * 4;
191
+
192
+ c1 = cache[c],
193
+ c2 = cache[c + 1],
194
+ c3 = cache[c + 2],
195
+ c4 = cache[c + 3];
196
+
197
+ res[rPos++] = c1 * pt1 + c2 * pt3 + c3 * t1x + c4 * t2x;
198
+ res[rPos++] = c1 * pt2 + c2 * pt4 + c3 * t1y + c4 * t2y;
199
+ }
200
+ }
201
+ }
202
+
203
+ // add last point
204
+ l = close ? 0 : points.length - 2;
205
+ res[rPos++] = points[l];
206
+ res[rPos] = points[l + 1];
207
+
208
+ if (!path) {
209
+ path = this;
210
+ }
211
+
212
+ // add lines to path
213
+ for (let i = 0, l = res.length; i < l; i += 2) {
214
+ if (i == 0 && move !== false) {
215
+ path.moveTo(res[i], res[i + 1]);
216
+ }
217
+ else {
218
+ path.lineTo(res[i], res[i + 1]);
219
+ }
220
+ }
221
+ }
222
+
223
+ function line(this: CanvasRenderingContext2D, x1: number, y1: number, x2: number, y2: number, color: string, lineWidth: number = 1) {
224
+ this.save();
225
+ this.beginPath();
226
+ this.moveTo(x1, y1);
227
+ this.lineTo(x2, y2);
228
+ this.lineWidth = lineWidth;
229
+ this.strokeStyle = color;
230
+ this.stroke();
231
+ this.restore();
232
+ }
233
+
234
+ function roundRect(this: CanvasRenderingContext2D, x: number, y: number, width: number, height: number, radius: number) {
235
+ //this.beginPath( );
236
+ this.moveTo(x + radius, y);
237
+ this.lineTo(x + width - radius, y);
238
+ this.quadraticCurveTo(x + width, y, x + width, y + radius);
239
+ this.lineTo(x + width, y + height - radius);
240
+ this.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
241
+ this.lineTo(x + radius, y + height);
242
+ this.quadraticCurveTo(x, y + height, x, y + height - radius);
243
+ this.lineTo(x, y + radius);
244
+ this.quadraticCurveTo(x, y, x + radius, y);
245
+ this.closePath();
246
+ }
247
+
248
+ function calcTextSize( this: CanvasRenderingContext2D, text: string, rounded = false): { width: number, height: number } {
249
+
250
+ let fh = this.measureText(text);
251
+ let lh = fh.fontBoundingBoxAscent + fh.fontBoundingBoxDescent;
252
+
253
+ if (rounded) {
254
+ return { width: Math.round(fh.width), height: Math.round(lh) };
255
+ }
256
+ else {
257
+ return { width: fh.width, height: lh };
258
+ }
259
+ }
260
+
261
+ function setFontSize( this: CanvasRenderingContext2D, fs: number) {
262
+ let fsize = Math.round(fs) + 'px';
263
+ this.font = this.font.replace(/\d+px/, fsize);
264
+ }
265
+
266
+ function circle( this: CanvasRenderingContext2D, x: number, y: number, radius: number) {
267
+ this.moveTo(x + radius, y);
268
+ this.arc(x, y, radius, 0, Math.PI * 2);
269
+ }