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
@@ -0,0 +1,1072 @@
1
+ /**
2
+ * ___ ___ __
3
+ * \ \/ / / _
4
+ * \ / /_| |_
5
+ * / \____ _|
6
+ * /__/\__\ |_|
7
+ *
8
+ * @file component.ts
9
+ * @author Etienne Cochard
10
+ *
11
+ * @copyright (c) 2024 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 { isArray, UnsafeHtml, isNumber, Rect, Constructor, class_ns, x4_class_ns_sym, IRect } from './core_tools';
18
+ import { CoreElement } from './core_element';
19
+ import { ariaValues, unitless } from './core_styles';
20
+ import { CoreEvent, EventMap } from './core_events';
21
+ import { addEvent, DOMEventHandler, GlobalDOMEvents } from './core_dom';
22
+
23
+ interface RefType<T extends Component> {
24
+ dom: T;
25
+ }
26
+
27
+ type ComponentAttributes = Record<string,string|number|boolean>;
28
+
29
+ const FRAGMENT = Symbol( "fragment" );
30
+ const COMPONENT = Symbol( "component" );
31
+
32
+ const RE_NUMBER = /^-?\d+(\.\d*)?$/;
33
+
34
+ /**
35
+ * you can change css classname prefix by adding
36
+ *
37
+ * ```
38
+ * static "$cls-ns" = "<your prefix>";
39
+ * ```
40
+ *
41
+ * to your class to avoid autogenerated css class names conflicts
42
+ */
43
+
44
+ function genClassNames( x: any ): string[] {
45
+
46
+ let classes = [];
47
+ let self = Object.getPrototypeOf(x);
48
+
49
+ if( self.constructor==Component ) {
50
+ return ["x4-comp"];
51
+ }
52
+
53
+ while (self && self.constructor !== Component ) {
54
+ const clsname:string = self.constructor.name;
55
+ const clsns: string = self.constructor.hasOwnProperty(x4_class_ns_sym) ? self.constructor[x4_class_ns_sym] : "";
56
+ classes.push( clsns+clsname.toLowerCase() );
57
+ self = Object.getPrototypeOf(self);
58
+ }
59
+
60
+ return classes;
61
+ }
62
+
63
+ /**
64
+ *
65
+ */
66
+
67
+ export type ComponentContent = Component | Component[] | string | string[] | UnsafeHtml| UnsafeHtml[] | number | boolean;
68
+
69
+ let gen_id = 1000;
70
+
71
+ export const makeUniqueComponentId = ( ) => {
72
+ return `x4-${gen_id++}`;
73
+ }
74
+
75
+ /**
76
+ *
77
+ */
78
+
79
+ export interface ComponentProps {
80
+ tag?: string;
81
+ ns?: string;
82
+
83
+ style?: Partial<CSSStyleDeclaration>;
84
+ attrs?: Record<string,string|number|boolean>;
85
+ content?: ComponentContent;
86
+ dom_events?: GlobalDOMEvents;
87
+ cls?: string;
88
+ id?: string;
89
+ ref?: RefType<any>;
90
+
91
+ // shortcuts
92
+ width?: string | number;
93
+ height?: string | number;
94
+ disabled?: true,
95
+ hidden?: true,
96
+ flex?: boolean | number;
97
+
98
+ tooltip?: string;
99
+
100
+ // wrapper
101
+ existingDOM?: HTMLElement;
102
+
103
+ // index signature
104
+ // to avoid errors: Type 'X' has no properties in common with type 'Y'
105
+ // because all memebers here are optional.
106
+ // this allow TS to recongnize derived props as ComponentProps
107
+ //[key: string]: any;
108
+ };
109
+
110
+
111
+ /**
112
+ *
113
+ */
114
+
115
+ export interface ComponentEvent extends CoreEvent {
116
+ }
117
+
118
+ /**
119
+ *
120
+ */
121
+
122
+ export interface ComponentEvents extends EventMap {
123
+ }
124
+
125
+ /**
126
+ *
127
+ */
128
+
129
+ @class_ns( "x4" )
130
+ export class Component<P extends ComponentProps = ComponentProps, E extends ComponentEvents = ComponentEvents>
131
+ extends CoreElement<E> {
132
+
133
+ readonly dom: Element;
134
+ readonly props: P;
135
+ protected readonly clsprefix: string; // internal class name prefix (x4 internal)
136
+
137
+ #store: Map<string|Symbol,any>;
138
+
139
+ constructor( props: P ) {
140
+ super( );
141
+
142
+ this.props = props; // copy ?
143
+
144
+ if( props.existingDOM ) {
145
+ this.dom = props.existingDOM;
146
+ }
147
+ else {
148
+ if( props.ns ) {
149
+ this.dom = document.createElementNS( props.ns, props.tag ?? "div" );
150
+ }
151
+ else {
152
+ this.dom = document.createElement( props.tag ?? "div" );
153
+ }
154
+
155
+ if (props.attrs) {
156
+ this.setAttributes( props.attrs );
157
+ }
158
+
159
+ if( props.cls ) {
160
+ this.addClass( props.cls );
161
+ }
162
+
163
+ if( props.hidden ) {
164
+ this.show( false );
165
+ }
166
+
167
+ if( props.flex===true ) {
168
+ this.addClass( "x4flex" );
169
+ }
170
+ else if( props.flex!==undefined ) {
171
+ this.setStyle( {
172
+ "flexGrow": props.flex+""
173
+ });
174
+ }
175
+
176
+ if( props.id!==undefined ) {
177
+ this.setAttribute( "id", props.id );
178
+ }
179
+
180
+ // small shortcut
181
+ if( props.width!==undefined ) {
182
+ this.setStyleValue( "width", props.width );
183
+ }
184
+
185
+ if( props.height!==undefined ) {
186
+ this.setStyleValue( "height", props.height );
187
+ }
188
+
189
+ if( props.tooltip ) {
190
+ this.setAttribute( "tooltip", props.tooltip );
191
+ }
192
+
193
+ if( props.style ) {
194
+ this.setStyle( props.style );
195
+ }
196
+
197
+ if( props.content ) {
198
+ this.setContent( props.content );
199
+ }
200
+
201
+ if( props.dom_events ) {
202
+ this.setDOMEvents( props.dom_events );
203
+ }
204
+
205
+ const classes = genClassNames( this );
206
+ this.dom.classList.add( ...classes );
207
+
208
+ // need to have children for next statements
209
+ // and children way be created in caller
210
+ if( props.disabled ) {
211
+ this.addDOMEvent( "created", ( ) => {
212
+ this.enable( false );
213
+ } );
214
+ }
215
+ }
216
+
217
+ (this.dom as any)[COMPONENT] = this;
218
+ }
219
+
220
+
221
+ // :: CLASSES ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
222
+
223
+ /**
224
+ *
225
+ */
226
+
227
+ hasClass( cls: string ) {
228
+ return this.dom.classList.contains( cls );
229
+ }
230
+
231
+ /**
232
+ *
233
+ */
234
+
235
+ addClass( cls: string ) {
236
+ if( !cls ) return;
237
+
238
+ if( cls.indexOf(' ')>=0 ) {
239
+ cls = cls.trim( );
240
+ const ccs = cls.split( " " );
241
+ this.dom.classList.add(...ccs);
242
+ }
243
+ else {
244
+ this.dom.classList.add(cls);
245
+ }
246
+ }
247
+
248
+ /**
249
+ * special case: '*' mean clear class list
250
+ */
251
+
252
+ removeClass( cls: string ) {
253
+ if( !cls ) return;
254
+
255
+ if( cls=='*' ) {
256
+ this.dom.classList.value = "";
257
+ return;
258
+ }
259
+
260
+ if( cls.indexOf(' ')>=0 ) {
261
+ const ccs = cls.split( " " );
262
+ this.dom.classList.remove(...ccs);
263
+ }
264
+ else {
265
+ this.dom.classList.remove(cls);
266
+ }
267
+ }
268
+
269
+ /**
270
+ *
271
+ */
272
+
273
+ removeClassEx( re: RegExp ) {
274
+ const all = Array.from( this.dom.classList );
275
+ all.forEach( x => {
276
+ if( x.match(re) ) {
277
+ this.dom.classList.remove( x );
278
+ }
279
+ });
280
+ }
281
+
282
+ /**
283
+ *
284
+ */
285
+
286
+ toggleClass( cls: string ) {
287
+ if( !cls ) return;
288
+
289
+ const toggle = ( x: string ) => {
290
+ this.dom.classList.toggle(x);
291
+ }
292
+
293
+ if( cls.indexOf(' ')>=0 ) {
294
+ const ccs = cls.split( " " );
295
+ ccs.forEach( toggle );
296
+ }
297
+ else {
298
+ toggle( cls );
299
+ }
300
+ }
301
+
302
+ /**
303
+ *
304
+ */
305
+
306
+ setClass( cls: string, set: boolean = true ) {
307
+ if( set ) this.addClass(cls);
308
+ else this.removeClass( cls );
309
+ }
310
+
311
+ // :: ATTRIBUTES ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
312
+
313
+ /**
314
+ * attributes
315
+ */
316
+
317
+ setAttributes( attrs: ComponentAttributes ) {
318
+ for( const name in attrs ) {
319
+ this.setAttribute( name, attrs[name] );
320
+ }
321
+ }
322
+
323
+ /**
324
+ *
325
+ */
326
+
327
+ setAttribute( name: string, value: string | number | boolean ) {
328
+ if( value===null || value===undefined ) {
329
+ this.dom.removeAttribute( name );
330
+ }
331
+ else {
332
+ this.dom.setAttribute( name, ""+value );
333
+ }
334
+ }
335
+
336
+ /**
337
+ *
338
+ */
339
+
340
+ getAttribute( name: string ): string {
341
+ return this.dom.getAttribute( name );
342
+ }
343
+
344
+ /**
345
+ *
346
+ */
347
+
348
+ getData( name: string ) : string {
349
+ return this.getAttribute( "data-"+name );
350
+ }
351
+
352
+ /**
353
+ * @returns undefined if not a number
354
+ */
355
+ getIntData( name: string ) : number {
356
+ const v = parseInt( this.getAttribute( "data-"+name ) );
357
+ if( Number.isFinite(v) ) {
358
+ return v;
359
+ }
360
+
361
+ return undefined;
362
+ }
363
+
364
+ /**
365
+ *
366
+ */
367
+
368
+ setData( name: string, value: string ) {
369
+ return this.setAttribute( "data-"+name, value );
370
+ }
371
+
372
+ /**
373
+ * idem as setData but onot on dom, you can store anything
374
+ */
375
+
376
+ setInternalData( name: string|Symbol, value: any ): this {
377
+ if( !this.#store ) {
378
+ this.#store = new Map( );
379
+ }
380
+
381
+ this.#store.set( name, value );
382
+ return this;
383
+ }
384
+
385
+ getInternalData( name: string|Symbol ): any {
386
+ return this.#store?.get(name);
387
+ }
388
+
389
+
390
+ // :: DOM EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
391
+
392
+ /**
393
+ *
394
+ */
395
+
396
+ addDOMEvent<K extends keyof GlobalDOMEvents>( name: K, listener: GlobalDOMEvents[K], prepend = false ) {
397
+ addEvent( this.dom, name, listener as DOMEventHandler, prepend );
398
+ }
399
+
400
+ /**
401
+ *
402
+ */
403
+
404
+ setDOMEvents( events: GlobalDOMEvents ) {
405
+ for( const name in events ) {
406
+ this.addDOMEvent( name as any, (events as any)[name] );
407
+ }
408
+ }
409
+
410
+ // :: HILEVEL EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
411
+
412
+ /**
413
+ * tool to move named events to internal event map
414
+ * @internal
415
+ */
416
+
417
+ protected mapPropEvents<N extends keyof E>(props: P, ...elements: N[] ) {
418
+ const p = props as any;
419
+ elements.forEach( n => {
420
+ if (p.hasOwnProperty(n) && p[n]) {
421
+ this.on( n, p[n] );
422
+ }
423
+ });
424
+ }
425
+
426
+ // :: CONTENT ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
427
+
428
+ /**
429
+ * remove all content from component
430
+ */
431
+
432
+ clearContent( ) {
433
+ const d = this.dom;
434
+ while( d.firstChild ) {
435
+ d.removeChild( d.firstChild );
436
+ }
437
+ }
438
+
439
+ /**
440
+ * change the whole content of the component
441
+ * clear the content before
442
+ * @param content new content
443
+ */
444
+
445
+ setContent( content: ComponentContent ) {
446
+ this.clearContent( );
447
+ this.appendContent( content );
448
+ }
449
+
450
+ /**
451
+ * cf. appendContent
452
+ * @param content content to append
453
+ */
454
+
455
+ appendContent( content: ComponentContent ) {
456
+ const set = ( d: any, c: Component | string | UnsafeHtml | number | boolean ) => {
457
+
458
+ if (c instanceof Component ) {
459
+ d.appendChild( c.dom );
460
+ }
461
+ else if( c instanceof UnsafeHtml) {
462
+ d.insertAdjacentHTML( 'beforeend' , c.toString() );
463
+ }
464
+ else if (typeof c === "string" || typeof c === "number") {
465
+ const tnode = document.createTextNode(c.toString());
466
+ d.appendChild( tnode );
467
+ }
468
+ else if( c ) {
469
+ console.warn("Unknown type to append: ", c);
470
+ }
471
+ }
472
+
473
+ if( !isArray(content) ) {
474
+ set( this.dom, content );
475
+ }
476
+ else if( content.length<=8 ) {
477
+ for( const c of content ) {
478
+ set( this.dom, c );
479
+ }
480
+ }
481
+ else {
482
+ const fragment = document.createDocumentFragment( );
483
+ for (const child of content ) {
484
+ set( fragment, child );
485
+ }
486
+
487
+ this.dom.appendChild( fragment );
488
+ }
489
+ }
490
+
491
+ /**
492
+ * cf. appendContent
493
+ * @param content content to append
494
+ */
495
+
496
+ prependContent( content: ComponentContent ) {
497
+ const d = this.dom;
498
+ const set = ( c: Component | string | UnsafeHtml | number | boolean ) => {
499
+ if (c instanceof Component ) {
500
+ d.insertAdjacentElement( 'afterbegin', c.dom );
501
+ }
502
+ else if( c instanceof UnsafeHtml) {
503
+ d.insertAdjacentHTML( 'afterbegin', c.toString() );
504
+ }
505
+ else if (typeof c === "string" || typeof c === "number") {
506
+ d.insertAdjacentText( 'afterbegin', c.toString() );
507
+ }
508
+ else {
509
+ console.warn("Unknown type to append: ", c);
510
+ }
511
+ }
512
+
513
+ if( !isArray(content) ) {
514
+ set( content );
515
+ }
516
+ else {
517
+ const fragment = document.createDocumentFragment( );
518
+ for (const child of content ) {
519
+ set( child );
520
+ }
521
+
522
+ d.insertBefore( d.firstChild, fragment );
523
+ }
524
+ }
525
+
526
+ /**
527
+ * remove a single child
528
+ * @see clearContent
529
+ */
530
+
531
+ removeChild( child: Component ) {
532
+ this.dom.removeChild( child.dom );
533
+ }
534
+
535
+
536
+ /**
537
+ * query all elements by selector
538
+ */
539
+
540
+ queryAll( selector: string ): Component[] {
541
+ const all = this.dom.querySelectorAll( selector );
542
+ const rc = new Array( all.length );
543
+ all.forEach( (x,i) => rc[i]=wrapDOM(x as HTMLElement) );
544
+ return rc;
545
+ }
546
+
547
+ /**
548
+ *
549
+ */
550
+
551
+ query<T extends Component = Component>( selector: string ): T {
552
+ const r = this.dom.querySelector( selector );
553
+ return componentFromDOM<T>(r);
554
+ }
555
+
556
+
557
+ // :: STYLES ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
558
+
559
+
560
+ /**
561
+ *
562
+ */
563
+
564
+ setAria( name: keyof ariaValues, value: string | number | boolean ): this {
565
+ this.setAttribute( name, value );
566
+ return this;
567
+ }
568
+
569
+
570
+ /**
571
+ *
572
+ */
573
+
574
+ setStyle( style: Partial<CSSStyleDeclaration> ): this {
575
+ const _style = (this.dom as HTMLElement).style;
576
+
577
+ for( const name in style ) {
578
+
579
+ let value = style[name];
580
+ if( !unitless[name] && (isNumber(value) || RE_NUMBER.test(value)) ) {
581
+ value += "px";
582
+ }
583
+
584
+ _style[name] = value;
585
+ }
586
+
587
+ return this;
588
+ }
589
+
590
+ /**
591
+ *
592
+ */
593
+
594
+ setStyleValue<K extends keyof CSSStyleDeclaration>( name: K, value: CSSStyleDeclaration[K] | number ): this {
595
+
596
+ const _style = (this.dom as HTMLElement).style;
597
+
598
+ if( isNumber(value) ) {
599
+ let v = value+"";
600
+ if( !unitless[name as string] ) {
601
+ v += "px";
602
+ }
603
+
604
+ (_style as any)[name] = v;
605
+ }
606
+ else {
607
+ _style[name] = value;
608
+ }
609
+
610
+ return this;
611
+ }
612
+
613
+ /**
614
+ *
615
+ * @param name
616
+ * @returns
617
+ */
618
+
619
+ getStyleValue<K extends keyof CSSStyleDeclaration>( name: K ) {
620
+ const _style = (this.dom as HTMLElement).style;
621
+ return _style[name];
622
+ }
623
+
624
+ setWidth( w: number | string ) {
625
+ this.setStyleValue( "width", isNumber(w) ? w+"px" : w );
626
+ }
627
+
628
+ setHeight( h: number | string ) {
629
+ this.setStyleValue( "height", isNumber(h) ? h+"px" : h );
630
+ }
631
+
632
+ /**
633
+ *
634
+ */
635
+
636
+ setStyleVariable( name: string, value: string ) {
637
+ (this.dom as HTMLElement).style.setProperty( name, value );
638
+ }
639
+
640
+ /**
641
+ *
642
+ */
643
+
644
+ getStyleVariable( name: string ) {
645
+ const style = this.getComputedStyle( );
646
+ return style.getPropertyValue( name );
647
+ }
648
+
649
+ /**
650
+ *
651
+ * @returns
652
+ */
653
+
654
+ getComputedStyle( ) {
655
+ return getComputedStyle( this.dom );
656
+ }
657
+
658
+ /**
659
+ *
660
+ */
661
+
662
+ setCapture( pointerId: number ) {
663
+ this.dom.setPointerCapture( pointerId );
664
+ }
665
+
666
+ /**
667
+ *
668
+ */
669
+
670
+ releaseCapture( pointerId: number ) {
671
+ this.dom.releasePointerCapture( pointerId );
672
+ }
673
+
674
+ /**
675
+ *
676
+ */
677
+
678
+ getBoundingRect( ): IRect {
679
+ const rc = this.dom.getBoundingClientRect( );
680
+ return new Rect( rc.x, rc.y, rc.width, rc.height );
681
+ }
682
+
683
+ // :: MISC ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
684
+
685
+ /**
686
+ *
687
+ */
688
+
689
+ focus( ): this {
690
+ (this.dom as HTMLElement).focus( );
691
+ return this;
692
+ }
693
+
694
+ hasFocus( ) {
695
+ return document.activeElement==this.dom;
696
+ }
697
+
698
+ /**
699
+ *
700
+ */
701
+
702
+ scrollIntoView(arg?: boolean | ScrollIntoViewOptions) {
703
+ this.dom.scrollIntoView(arg);
704
+ }
705
+
706
+ /**
707
+ *
708
+ */
709
+
710
+ isVisible( ) {
711
+ return (this.dom as HTMLElement).offsetParent !== null;
712
+ }
713
+
714
+ /**
715
+ *
716
+ */
717
+
718
+ show( vis = true ): this {
719
+ this.setClass( 'x4hidden', !vis );
720
+ return this;
721
+ }
722
+
723
+ /**
724
+ *
725
+ */
726
+
727
+ hide( ): this {
728
+ this.show( false );
729
+ return this;
730
+ }
731
+
732
+ /**
733
+ * enable or disable a component (all sub HTMLElement will be also disabled)
734
+ */
735
+
736
+ enable( ena = true ): this {
737
+ this.setAttribute( "disabled", !ena ? 'true' : null );
738
+
739
+ if( this.dom instanceof HTMLInputElement || this.dom instanceof HTMLButtonElement ) {
740
+ this.dom.disabled = !ena;
741
+ }
742
+
743
+ // propagate diable state to all input children
744
+ const nodes = this.enumChildNodes( true );
745
+ nodes.forEach( x => {
746
+ if( x instanceof HTMLInputElement || x instanceof HTMLButtonElement ) {
747
+ x.disabled = !ena;
748
+ }
749
+ });
750
+
751
+ return this;
752
+ }
753
+
754
+ /**
755
+ *
756
+ */
757
+
758
+ disable( ): this {
759
+ this.enable( false );
760
+ return this;
761
+ }
762
+
763
+ /**
764
+ * check if element is marked disabled
765
+ */
766
+
767
+ isDisabled( ) {
768
+ return this.getAttribute('disabled');
769
+ }
770
+
771
+ /**
772
+ *
773
+ */
774
+
775
+ nextElement<T extends Component = Component>( ): T {
776
+ const nxt = this.dom.nextElementSibling;
777
+ return componentFromDOM<T>( nxt );
778
+ }
779
+
780
+ /**
781
+ *
782
+ * @returns
783
+ */
784
+
785
+ prevElement<T extends Component = Component>( ): T {
786
+ const nxt = this.dom.previousElementSibling;
787
+ return componentFromDOM<T>( nxt );
788
+ }
789
+
790
+ /**
791
+ * search for parent that match the given contructor
792
+ */
793
+
794
+ parentElement<T extends Component>( cls?: Constructor<T> ): T {
795
+ return Component.parentElement<T>( this.dom, cls );
796
+ }
797
+
798
+ /**
799
+ * search for parent that match the given contructor
800
+ */
801
+
802
+ static parentElement<T extends Component>( dom: Node, cls?: Constructor<T> ): T {
803
+
804
+ while( dom.parentElement ) {
805
+ const cp = componentFromDOM( dom.parentElement );
806
+ if( !cls ) {
807
+ return cp as T;
808
+ }
809
+
810
+ if( cp && cp instanceof cls ) {
811
+ return cp;
812
+ }
813
+
814
+ dom = dom.parentElement;
815
+ }
816
+
817
+ return null;
818
+ }
819
+
820
+ /**
821
+ *
822
+ * @returns
823
+ */
824
+
825
+ firstChild<T extends Component = Component>( ) : T {
826
+ const nxt = this.dom.firstElementChild;
827
+ return componentFromDOM<T>( nxt );
828
+ }
829
+
830
+ /**
831
+ *
832
+ * @returns
833
+ */
834
+
835
+ lastChild<T extends Component = Component>( ) : T {
836
+ const nxt = this.dom.lastElementChild;
837
+ return componentFromDOM( nxt );
838
+ }
839
+
840
+ /**
841
+ * renvoie la liste des Composants enfants
842
+ */
843
+
844
+ enumChildComponents( recursive: boolean ) {
845
+
846
+ let children: Component[] = [];
847
+
848
+ const nodes = this.enumChildNodes( recursive );
849
+ nodes.forEach( ( c: Node ) => {
850
+ const cc = componentFromDOM( c as HTMLElement );
851
+ if( cc ) {
852
+ children.push(cc);
853
+ }
854
+ } );
855
+
856
+ return children;
857
+ }
858
+
859
+ /**
860
+ * return children list of node (not all should be components)
861
+ */
862
+
863
+ enumChildNodes( recursive: boolean ) {
864
+ let children: Node[] = Array.from( recursive ? this.dom.querySelectorAll( '*' ) : this.dom.children );
865
+ return children;
866
+ }
867
+
868
+ /**
869
+ *
870
+ */
871
+
872
+ animate( keyframes: Keyframe[], duration: number ) {
873
+ this.dom.animate(keyframes,duration);
874
+ }
875
+
876
+
877
+ // :: TSX/REACT ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
878
+
879
+ /**
880
+ * called by the compiler when a jsx element is seen
881
+ */
882
+
883
+ static createElement( clsOrTag: string | ComponentConstructor | Symbol | Function, attrs: any, ...children: Component[] ): Component | Component[] {
884
+
885
+ let comp: Component;
886
+
887
+ // fragment
888
+ if( clsOrTag==this.createFragment || clsOrTag===FRAGMENT ) {
889
+ return children;
890
+ }
891
+
892
+ // class constructor, yes : dirty
893
+ if( clsOrTag instanceof Function ) {
894
+ attrs = attrs ?? {};
895
+ if( !attrs.children && children && children.length ) {
896
+ attrs.content = children;
897
+ }
898
+
899
+ comp = new (clsOrTag as any)( attrs ?? {} );
900
+ }
901
+ // basic tag
902
+ else {
903
+ comp = new Component( {
904
+ tag: clsOrTag,
905
+ content: children,
906
+ ...attrs,
907
+ });
908
+ }
909
+
910
+ if( children && children.length ) {
911
+ //comp.setContent( children );
912
+ }
913
+
914
+ return comp;
915
+ }
916
+
917
+ /**
918
+ *
919
+ */
920
+
921
+ static createFragment( ): Component[] {
922
+ return this.createElement( FRAGMENT, null ) as Component[];
923
+ }
924
+
925
+ // :: SPECIALS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
926
+
927
+ /**
928
+ * system interfaces:
929
+ * "form-element"
930
+ * "tab-handler"
931
+ *
932
+ * each app can create it's own interface
933
+ */
934
+
935
+ queryInterface<T>( name: string ): T {
936
+ return null;
937
+ }
938
+ }
939
+
940
+
941
+ /**
942
+ *
943
+ */
944
+
945
+ type ComponentConstructor = {
946
+ new(...params: any[]): Component;
947
+ };
948
+
949
+ /**
950
+ * get a component element from it's DOM counterpart
951
+ */
952
+
953
+ export function componentFromDOM<T extends Component = Component>( node: Element ) {
954
+ return node ? (node as any)[COMPONENT] as T : null;
955
+ }
956
+
957
+ /**
958
+ * create a component from an existing DOM
959
+ */
960
+
961
+ export function wrapDOM( el: HTMLElement ): Component {
962
+ const com = componentFromDOM(el);
963
+ if( com ) {
964
+ return com;
965
+ }
966
+
967
+ return new Component( { existingDOM: el } );
968
+ }
969
+
970
+
971
+ // :: Special components ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
972
+
973
+ // just a flexible element that push other
974
+ export class Flex extends Component {
975
+ constructor( ) {
976
+ super({})
977
+ }
978
+ }
979
+
980
+ // just a spacer element that push other
981
+ export class Space extends Component {
982
+ constructor( width?: number|string, cls?: string ) {
983
+ super( { width, cls } )
984
+ }
985
+ }
986
+
987
+
988
+ // :: HIGH LEVEL BASIC EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
989
+
990
+
991
+
992
+ /**
993
+ * Click Event
994
+ * click event do not have any additional parameters
995
+ */
996
+
997
+ export interface EvClick extends ComponentEvent {
998
+ }
999
+
1000
+ /**
1001
+ * Change Event
1002
+ * value is the the element value
1003
+ */
1004
+
1005
+ export interface EvChange extends ComponentEvent {
1006
+ readonly value: any;
1007
+ }
1008
+
1009
+ /**
1010
+ * Focus event
1011
+ */
1012
+
1013
+ export interface EvFocus extends ComponentEvent {
1014
+ readonly focus_out: boolean;
1015
+ }
1016
+
1017
+ /**
1018
+ * Selection Event
1019
+ * value is the new selection or null
1020
+ */
1021
+
1022
+ interface ISelection {
1023
+ }
1024
+
1025
+ export interface EvSelectionChange extends ComponentEvent {
1026
+ readonly selection: ISelection;
1027
+ readonly empty: boolean;
1028
+ }
1029
+
1030
+
1031
+ /**
1032
+ * ContextMenu Event
1033
+ */
1034
+
1035
+ export interface EvContextMenu extends ComponentEvent {
1036
+ uievent: UIEvent; // UI event that fire this event
1037
+ }
1038
+
1039
+ /**
1040
+ * Simple message
1041
+ */
1042
+
1043
+ export interface EvMessage extends ComponentEvent {
1044
+ readonly msg: string;
1045
+ readonly params?: any;
1046
+ }
1047
+
1048
+ /**
1049
+ * Drag/Drop event
1050
+ */
1051
+
1052
+ export interface EvDrag extends ComponentEvent {
1053
+ element: unknown;
1054
+ data: any;
1055
+ }
1056
+
1057
+ /**
1058
+ * Errors
1059
+ */
1060
+
1061
+ export interface EvError extends ComponentEvent {
1062
+ code: number;
1063
+ message: string;
1064
+ }
1065
+
1066
+ /**
1067
+ * DblClick Event
1068
+ */
1069
+
1070
+ export interface EvDblClick extends ComponentEvent {
1071
+ }
1072
+