x4js 2.0.34 → 2.1.0-manual

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 (134) hide show
  1. package/README.md +21 -21
  2. package/package.json +39 -26
  3. package/src/components/base.scss +25 -89
  4. package/src/components/boxes/boxes.module.scss +54 -54
  5. package/src/components/boxes/boxes.ts +513 -513
  6. package/src/components/breadcrumb/breadcrumb.scss +56 -56
  7. package/src/components/breadcrumb/breadcrumb.ts +93 -93
  8. package/src/components/btngroup/btngroup.module.scss +40 -40
  9. package/src/components/btngroup/btngroup.ts +152 -152
  10. package/src/components/button/button.module.scss +172 -172
  11. package/src/components/button/button.ts +232 -232
  12. package/src/components/calendar/calendar.module.scss +162 -162
  13. package/src/components/calendar/calendar.ts +326 -326
  14. package/src/components/canvas/canvas.module.scss +24 -24
  15. package/src/components/canvas/canvas.ts +195 -195
  16. package/src/components/canvas/canvas_ex.ts +275 -275
  17. package/src/components/checkbox/check.svg +3 -3
  18. package/src/components/checkbox/checkbox.module.scss +141 -141
  19. package/src/components/checkbox/checkbox.ts +139 -139
  20. package/src/components/colorinput/colorinput.module.scss +64 -64
  21. package/src/components/colorinput/colorinput.ts +90 -90
  22. package/src/components/colorpicker/colorpicker.module.scss +132 -132
  23. package/src/components/colorpicker/colorpicker.ts +481 -481
  24. package/src/components/combobox/combobox.module.scss +145 -145
  25. package/src/components/combobox/combobox.ts +282 -282
  26. package/src/components/combobox/updown.svg +3 -3
  27. package/src/components/components.ts +45 -44
  28. package/src/components/dialog/dialog.module.scss +103 -105
  29. package/src/components/dialog/dialog.ts +233 -233
  30. package/src/components/filedrop/filedrop.module.scss +69 -69
  31. package/src/components/filedrop/filedrop.ts +130 -130
  32. package/src/components/form/form.module.scss +38 -38
  33. package/src/components/form/form.ts +172 -172
  34. package/src/components/gridview/gridview.module.scss +323 -337
  35. package/src/components/gridview/gridview.ts +1276 -1315
  36. package/src/components/header/header.module.scss +40 -40
  37. package/src/components/header/header.ts +141 -141
  38. package/src/components/icon/icon.module.scss +32 -32
  39. package/src/components/icon/icon.ts +165 -165
  40. package/src/components/image/image.module.scss +27 -27
  41. package/src/components/image/image.ts +168 -168
  42. package/src/components/input/input.module.scss +74 -74
  43. package/src/components/input/input.ts +537 -537
  44. package/src/components/keyboard/keyboard.module.scss +136 -136
  45. package/src/components/keyboard/keyboard.ts +549 -549
  46. package/src/components/label/label.module.scss +90 -91
  47. package/src/components/label/label.ts +101 -101
  48. package/src/components/link/link.module.scss +44 -44
  49. package/src/components/link/link.ts +87 -87
  50. package/src/components/listbox/listbox.module.scss +179 -179
  51. package/src/components/listbox/listbox.ts +596 -596
  52. package/src/components/menu/menu.module.scss +128 -128
  53. package/src/components/menu/menu.ts +174 -174
  54. package/src/components/messages/messages.module.scss +92 -146
  55. package/src/components/messages/messages.ts +237 -303
  56. package/src/components/normalize.scss +391 -391
  57. package/src/components/notification/notification.module.scss +83 -83
  58. package/src/components/notification/notification.ts +107 -107
  59. package/src/components/panel/panel.module.scss +66 -71
  60. package/src/components/panel/panel.ts +57 -57
  61. package/src/components/popup/popup.module.scss +51 -51
  62. package/src/components/popup/popup.ts +457 -457
  63. package/src/components/progress/progress.module.scss +56 -56
  64. package/src/components/progress/progress.ts +43 -43
  65. package/src/components/propgrid/progrid.module.scss +111 -111
  66. package/src/components/propgrid/propgrid.ts +300 -300
  67. package/src/components/propgrid/updown.svg +3 -3
  68. package/src/components/radio/radio.module.scss +163 -163
  69. package/src/components/radio/radio.svg +3 -3
  70. package/src/components/radio/radio.ts +141 -141
  71. package/src/components/rating/rating.module.scss +22 -22
  72. package/src/components/rating/rating.ts +131 -131
  73. package/src/components/select/select.module.scss +8 -8
  74. package/src/components/select/select.ts +134 -134
  75. package/src/components/shared.scss +141 -71
  76. package/src/components/sizers/sizer.module.scss +90 -107
  77. package/src/components/sizers/sizer.ts +131 -134
  78. package/src/components/slider/slider.module.scss +117 -117
  79. package/src/components/slider/slider.ts +197 -197
  80. package/src/components/spreadsheet/spreadsheet.module.scss +307 -307
  81. package/src/components/spreadsheet/spreadsheet.ts +1223 -1223
  82. package/src/components/switch/switch.module.scss +126 -126
  83. package/src/components/switch/switch.ts +61 -61
  84. package/src/components/tabs/tabs.module.scss +46 -67
  85. package/src/components/tabs/tabs.ts +229 -234
  86. package/src/components/textarea/textarea.module.scss +63 -63
  87. package/src/components/textarea/textarea.ts +131 -131
  88. package/src/components/textedit/textedit.module.scss +115 -115
  89. package/src/components/textedit/textedit.ts +122 -122
  90. package/src/components/themes.scss +90 -90
  91. package/src/components/tickline/tickline.module.scss +25 -25
  92. package/src/components/tickline/tickline.ts +81 -81
  93. package/src/components/tooltips/tooltips.scss +71 -71
  94. package/src/components/tooltips/tooltips.ts +120 -120
  95. package/src/components/treeview/treeview.module.scss +192 -192
  96. package/src/components/treeview/treeview.ts +484 -484
  97. package/src/components/viewport/viewport.module.scss +31 -31
  98. package/src/components/viewport/viewport.ts +41 -41
  99. package/src/core/component.ts +1299 -1299
  100. package/src/core/core_application.ts +361 -361
  101. package/src/core/core_colors.ts +512 -512
  102. package/src/core/core_data.ts +1297 -1297
  103. package/src/core/core_dom.ts +481 -481
  104. package/src/core/core_dragdrop.ts +225 -225
  105. package/src/core/core_element.ts +221 -221
  106. package/src/core/core_events.ts +214 -214
  107. package/src/core/core_i18n.ts +395 -395
  108. package/src/core/core_pdf.ts +454 -454
  109. package/src/core/core_react.ts +78 -78
  110. package/src/core/core_router.ts +296 -296
  111. package/src/core/core_state.ts +62 -62
  112. package/src/core/core_styles.ts +213 -213
  113. package/src/core/core_svg.ts +1042 -1042
  114. package/src/core/core_tools.ts +996 -996
  115. package/src/types/scss.d.ts +4 -4
  116. package/src/types/x4react.d.ts +8 -8
  117. package/src/x4.scss +19 -19
  118. package/src/x4.ts +36 -36
  119. package/src/x4tsx.d.ts +26 -26
  120. package/.vscode/launch.json +0 -14
  121. package/.vscode/settings.json +0 -2
  122. package/demo/assets/house-light.svg +0 -1
  123. package/demo/assets/radio.svg +0 -4
  124. package/demo/index.html +0 -12
  125. package/demo/main.scss +0 -23
  126. package/demo/main.ts +0 -324
  127. package/demo/package.json +0 -26
  128. package/demo/scss.d.ts +0 -4
  129. package/demo/svg.d.ts +0 -1
  130. package/demo/tsconfig.json +0 -14
  131. package/src/components/gridview/folder-open.svg +0 -1
  132. package/src/components/messages/spinner.svg +0 -1
  133. package/src/x4.d.ts +0 -10
  134. package/tsconfig.json +0 -11
@@ -1,1042 +1,1042 @@
1
- /**
2
- * ___ ___ __
3
- * \ \/ / / _
4
- * \ / /_| |_
5
- * / \____ _|
6
- * /__/\__\ |_|
7
- *
8
- * @file core_svg.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 { Component, ComponentProps } from './component';
18
- import { isUnitLess } from "./core_styles";
19
- import { DOMEventHandler, GlobalDOMEvents, addEvent } from './core_dom';
20
- import { isNumber, isString } from './core_tools';
21
-
22
- const SVG_NS = "http://www.w3.org/2000/svg";
23
-
24
- // degrees to radian
25
- function d2r( d: number ): number {
26
- return d * Math.PI / 180.0;
27
- }
28
-
29
- // polar to cartesian
30
- function p2c( x: number, y: number, r: number, deg: number ): {x: number,y: number} {
31
- const rad = d2r( deg );
32
- return {
33
- x: x + r * Math.cos( rad ),
34
- y: y + r * Math.sin( rad )
35
- };
36
- }
37
-
38
- // fix prec for numbers
39
- function num( x: number ): number {
40
- return Math.round( x * 1000 ) / 1000;
41
- }
42
-
43
- // clean values
44
- function clean( a: any, ...b: any ) {
45
- // just round number values to 3 digits
46
- b = b.map( ( v: any ) => {
47
- if( typeof v === 'number' && isFinite(v) ) {
48
- return num(v);
49
- }
50
-
51
- return v;
52
- });
53
-
54
- return String.raw( a, ...b );
55
- }
56
-
57
-
58
-
59
- /**
60
- * Represents a lightweight wrapper around an SVG DOM element that provides
61
- * convenient, chainable helpers for common SVG manipulations (attributes,
62
- * styles, transforms, classes and events).
63
- *
64
- * The class encapsulates an underlying SVGElement and
65
- * exposes methods that operate directly on that element while allowing fluent chaining.
66
- *
67
- * Key behaviors:
68
- * - Creation: instances are constructed with the local name of the SVG tag
69
- * (e.g. "rect", "circle", "g") and internally create the element in the
70
- * SVG namespace.
71
- * - Attribute management: `getAttr`, `getNumAttr`, and `setAttr` let you read
72
- * and write attributes. Passing `null` or `undefined` to `setAttr` removes
73
- * the attribute.
74
- * - Styling: `setStyle` assigns CSS properties on the element. When a numeric
75
- * value is provided, a "px" unit is appended unless the property is known
76
- * to be unitless (the implementation relies on a small helper to detect
77
- * unitless properties).
78
- * - Geometry & transforms: helpers exist for setting or appending transforms
79
- * (`transform`, `add_transformation`, `clear_transform`), and higher-level
80
- * helpers for rotate, translate and scale (both replace and append forms).
81
- * - Stroke & fill helpers: `stroke`, `strokeWidth`, `strokeCap`, `strokeOpacity`,
82
- * `fill`, and `no_fill` provide convenient setters for common paint
83
- * properties. These methods return `this` so they can be chained.
84
- * - Classes & clipping: `addClass` / `removeClass` manipulate the element's
85
- * class list (supporting space-separated lists) and `clip` sets a clip-path
86
- * reference using an id.
87
- * - Reset: `reset` removes all attributes from the element (but does not
88
- * remove the element itself).
89
- * - Events: `addDOMEvent` attaches DOM events to the underlying element.
90
- *
91
- * Notes and caveats:
92
- * - This class is not a full DOM abstraction; it intentionally focuses on a
93
- * small, ergonomic surface for building and manipulating SVG elements.
94
- * - Some behaviors depend on small utility helpers (e.g. numeric/unit detection
95
- * and event-attachment helpers) provided elsewhere in the codebase.
96
- *
97
- */
98
-
99
- class SvgItem {
100
- protected _dom : SVGElement;
101
-
102
- constructor( tag: string ) {
103
- this._dom = document.createElementNS("http://www.w3.org/2000/svg", tag );
104
- }
105
-
106
- /**
107
- * @returns the svh element dom
108
- */
109
-
110
- getDom( ) {
111
- return this._dom;
112
- }
113
-
114
- /**
115
- *
116
- */
117
-
118
- /**
119
- * Remove all attributes from the underlying DOM element.
120
- * @returns The current instance (this) to allow method chaining.
121
- */
122
- reset( ) {
123
- const attrs = this._dom.attributes;
124
- for (let i = attrs.length - 1; i >= 0; i--) {
125
- this._dom.removeAttribute(attrs[i].name);
126
- }
127
-
128
- return this;
129
- }
130
-
131
-
132
- /**
133
- * change the stroke color
134
- * @param color
135
- */
136
-
137
- stroke( color: string, width?: number ): this {
138
- this.setAttr( 'stroke', color );
139
- if( width!==undefined ) {
140
- this.setAttr( 'stroke-width', width+'px' );
141
- }
142
- return this;
143
- }
144
-
145
- /**
146
- * change the stroke width
147
- * @param width
148
- */
149
- strokeWidth( width: number ): this {
150
- this.setAttr( 'stroke-width', width+'px' );
151
- return this;
152
- }
153
-
154
- /**
155
- * change the stroke cap
156
- * @param cap
157
- */
158
-
159
- strokeCap( cap: "butt" | "round" | "sqaure" ) {
160
- return this.setAttr( "stroke-linecap", cap );
161
- }
162
-
163
- /**
164
- * change the stroke opacity attribute on the element.
165
- * @param opacity - Opacity value where 0 is fully transparent and 1 is fully opaque.
166
- * @returns The current instance to allow method chaining.
167
- */
168
-
169
- strokeOpacity( opacity: number ) {
170
- return this.setAttr( "stroke-opacity", opacity+"" );
171
- }
172
-
173
- /**
174
- * Set the shape rendering attribute to control anti-aliasing for shapes.
175
- *
176
- * When enabled, the attribute is set to "auto" to allow smoothing/anti-aliasing.
177
- * When disabled, the attribute is set to "crispEdges" to favor pixel-aligned,
178
- * non-anti-aliased rendering.
179
- *
180
- * @param set - True to enable anti-aliasing ("auto"), false to disable ("crispEdges").
181
- * @returns the current instance to allow chaining).
182
- */
183
-
184
- antiAlias( set: boolean ) {
185
- return this.setAttr( "shape-rendering", set ? "auto" : "crispEdges" );
186
- }
187
-
188
- /**
189
- * change the fill color
190
- * @param color
191
- */
192
-
193
- fill( color: string ): this {
194
- this.setAttr( 'fill', color );
195
- return this;
196
- }
197
-
198
- /**
199
- * the element is not filled
200
- */
201
-
202
- no_fill( ): this {
203
- this.setAttr( 'fill', "transparent" );
204
- return this;
205
- }
206
-
207
- /**
208
- * return the given attribute if any
209
- */
210
-
211
- getAttr( name: string ) : string {
212
- const a = this._dom.getAttribute( name ) || '';
213
- return a;
214
- }
215
-
216
- /**
217
- * return the attribute as number
218
- */
219
-
220
- getNumAttr( name: string ) {
221
- const a = this._dom.getAttribute( name )
222
- if( a=='' ) {
223
- return 0;
224
- }
225
-
226
- return parseInt( a );
227
- }
228
-
229
- /**
230
- * define a new attribute
231
- * @param name attibute name
232
- * @param value attribute value
233
- * @returns this
234
- */
235
-
236
- setAttr( name: string, value: string ) : this {
237
- if( value===null || value===undefined ) {
238
- this._dom.removeAttribute( name );
239
- }
240
- else {
241
- this._dom.setAttribute( name, value );
242
- }
243
- return this;
244
- }
245
-
246
- /**
247
- * change one style value
248
- */
249
-
250
- setStyle<K extends keyof CSSStyleDeclaration>( name: K, value: string | number ) : this {
251
- const _style = this._dom.style;
252
-
253
- if( isNumber(value) ) {
254
- let v = value+"";
255
- if( !isUnitLess(name as string) ) {
256
- v += "px";
257
- }
258
-
259
- (_style as any)[name] = v;
260
- }
261
- else {
262
- (_style as any)[name] = value;
263
- }
264
-
265
- return this;
266
- }
267
-
268
- /**
269
- * add a class
270
- * @param name class name to add
271
- */
272
-
273
- addClass( cls: string ): this {
274
- if( !cls ) return;
275
-
276
- cls = cls.trim();
277
- if( cls.indexOf(' ')>=0 ) {
278
- const ccs = cls.split( " " );
279
- this._dom.classList.add(...ccs);
280
- }
281
- else {
282
- this._dom.classList.add(cls);
283
- }
284
-
285
- return this;
286
- }
287
-
288
- /**
289
- * remove a class
290
- * @param name class name to remove
291
- */
292
-
293
- removeClass( cls: string ): this {
294
- if( !cls ) return;
295
-
296
- if( cls.indexOf(' ')>=0 ) {
297
- const ccs = cls.split( " " );
298
- this._dom.classList.remove(...ccs);
299
- }
300
- else {
301
- this._dom.classList.remove(cls);
302
- }
303
-
304
- return this;
305
- }
306
-
307
- /**
308
- *
309
- */
310
-
311
- clip( id: string ): this {
312
- this.setAttr( "clip-path", `url(#${id})` );
313
- return this;
314
- }
315
-
316
- /**
317
- * define the whole transformation
318
- */
319
-
320
- transform( tr: string ): this {
321
- this.setAttr( "transform", tr );
322
- return this;
323
- }
324
-
325
- /**
326
- * add a transformation to the current transformation
327
- */
328
-
329
- add_transformation( tr: string ): this {
330
- const t = this.getAttr( "transform" );
331
- this.setAttr( "transform", t+' '+tr );
332
- return this;
333
- }
334
-
335
- /**
336
- * remove all transformations
337
- */
338
-
339
- clear_transform( ) {
340
- this.setAttr( "transform", null );
341
- return this;
342
- }
343
-
344
- /**
345
- * rotation
346
- */
347
-
348
- rotate( deg: number, cx: number, cy: number ): this {
349
- this.transform( `rotate( ${deg} ${cx} ${cy} )` );
350
- return this;
351
- }
352
-
353
- add_rotation( deg: number, cx: number, cy: number ): this {
354
- this.add_transformation( `rotate( ${deg} ${cx} ${cy} )` );
355
- return this;
356
- }
357
-
358
- /**
359
- * translation
360
- */
361
-
362
- translate( dx: number, dy: number ): this {
363
- this.transform( `translate( ${dx} ${dy} )` );
364
- return this;
365
- }
366
-
367
- add_translation( dx: number, dy: number ): this {
368
- this.add_transformation( `translate( ${dx} ${dy} )` );
369
- return this;
370
- }
371
-
372
- /**
373
- * scaling
374
- */
375
-
376
- scale( x: number ): this {
377
- this.transform( `scale( ${x} )` );
378
- return this;
379
- }
380
-
381
- add_scale( x: number ): this {
382
- this.add_transformation( `scale( ${x} )` );
383
- return this;
384
- }
385
-
386
- /**
387
- * handle SVG DOM event
388
- */
389
-
390
- addDOMEvent<K extends keyof GlobalDOMEvents>( name: K, listener: GlobalDOMEvents[K], prepend = false ) {
391
- addEvent( this._dom, name, listener as DOMEventHandler, prepend );
392
- return this;
393
- }
394
- }
395
-
396
-
397
-
398
- /**
399
- * Represents an SVG path element, providing methods for constructing and manipulating SVG paths.
400
- * It extends `SvgItem` to inherit common SVG element functionalities.
401
- */
402
-
403
- export class SvgPath extends SvgItem {
404
- private _path: string;
405
-
406
- constructor( ) {
407
- super( 'path' );
408
- this._path = '';
409
- }
410
-
411
- private _update( ): this {
412
- this.setAttr( 'd', this._path );
413
- return this;
414
- }
415
-
416
- /**
417
- * Resets the path data and all attributes of the SVG path element.
418
- * @returns The current `SvgPath` instance for chaining.
419
- */
420
- reset( ) {
421
- this._path = "";
422
- super.reset( );
423
- return this;
424
- }
425
-
426
- /**
427
- * Moves the current drawing position to the specified coordinates without drawing a line.
428
- * This is typically the first command in a path.
429
- *
430
- * @param x - The x-coordinate to move to.
431
- * @param y - The y-coordinate to move to.
432
- * @returns this
433
- */
434
-
435
- moveTo( x: number, y: number ) : this {
436
- this._path += clean`M${x},${y}`;
437
- return this._update( );
438
- }
439
-
440
- /**
441
- * Draws a straight line from the current position to the specified coordinates.
442
- *
443
- * @param x - The x-coordinate of the end point.
444
- * @param y - The y-coordinate of the end point.
445
- * @returns this
446
- */
447
-
448
- lineTo( x: number, y: number ): this {
449
- this._path += clean`L${x},${y}`;
450
- return this._update( );
451
- }
452
-
453
- /**
454
- * Draws a cubic Bézier curve from the current point to `(x3, y3)` using `(x1, y1)`
455
- * as the control point at the beginning of the curve and `(x2, y2)` as the
456
- * control point at the end of the curve.
457
- *
458
- * @param x1 - The x-coordinate of the first control point.
459
- * @param y1 - The y-coordinate of the first control point.
460
- * @param x2 - The x-coordinate of the second control point.
461
- * @param y2 - The y-coordinate of the second control point.
462
- * @param x3 - The x-coordinate of the end point of the curve.
463
- * @param y3 - The y-coordinate of the end point of the curve.
464
- *
465
- * @returns The current `SvgPath` instance for chaining.
466
- */
467
-
468
- curveTo( x1: number, y1: number, x2: number, y2: number, x3: number, y3: number ) {
469
- this._path += clean`C${x1},${y1} ${x2},${y2} ${x3},${y3}`;
470
- return this._update( );
471
- }
472
-
473
-
474
- /**
475
- * Closes the current subpath by drawing a straight line from the current position
476
- * to the initial point of the current subpath.
477
- * @returns The current `SvgPath` instance for chaining.
478
- */
479
-
480
- closePath( ): this {
481
- this._path += 'Z';
482
- return this._update( );
483
- }
484
-
485
- /**
486
- * draw an arc
487
- * Draws an elliptical arc from the current point to a new point.
488
- *
489
- * @param x - The x-coordinate of the center of the ellipse.
490
- * @param y - The y-coordinate of the center of the ellipse.
491
- * @param r - The radius of the arc.
492
- * @param start - The start angle of the arc in degrees (0 is right, 90 is down).
493
- * @param end - The end angle of the arc in degrees.
494
- * @param clockwise - If `true`, the arc is drawn clockwise; otherwise, counter-clockwise. Defaults to `true`.
495
- *
496
- * @returns this
497
- */
498
-
499
- arc( x: number, y: number, r: number, start: number, end: number, clockwise= true ): this {
500
-
501
- const st = p2c( x, y, r, start-90 );
502
- const en = p2c( x, y, r, end-90 );
503
-
504
- const flag = ((end-start) <= 180 ? "0" : "1");
505
- this._path += clean`M${st.x},${st.y}A${r},${r} 0 ${flag} ${(clockwise ? '1' : '0')} ${en.x},${en.y}`;
506
-
507
- return this._update( );
508
- }
509
- }
510
-
511
- /**
512
- * Represents an SVG text element, providing methods for positioning and styling text.
513
- * It extends `SvgItem` to inherit common SVG element functionalities.
514
- */
515
-
516
- export class SvgText extends SvgItem {
517
- /**
518
- * Creates an instance of `SvgText`.
519
- * @param x - The x-coordinate for the text's starting position.
520
- * @param y - The y-coordinate for the text's starting position.
521
- * @param txt - The text content.
522
- */
523
- constructor( x: number, y: number, txt: string ) {
524
- super( 'text' );
525
-
526
- this.setAttr( 'x', num(x)+'' );
527
- this.setAttr( 'y', num(y)+'' );
528
-
529
- this._dom.innerHTML = txt;
530
- }
531
-
532
- /**
533
- * Sets the font family for the text.
534
- * @param font - The font family name (e.g., "Arial", "sans-serif").
535
- * @returns The current `SvgText` instance for chaining.
536
- */
537
- font( font: string ): this {
538
- return this.setAttr( 'font-family', font );
539
- }
540
-
541
- /**
542
- * Sets the font size for the text.
543
- * @param size - The font size, either as a number (e.g., 12) or a string (e.g., "1.2em").
544
- * @returns The current `SvgText` instance for chaining.
545
- */
546
- fontSize( size: number | string ): this {
547
- return this.setAttr( 'font-size', size+'' );
548
- }
549
-
550
- /**
551
- * Sets the font weight for the text.
552
- * @param weight - The font weight ("light", "normal", or "bold").
553
- * @returns The current `SvgText` instance for chaining.
554
- */
555
- fontWeight( weight: 'light' | 'normal' | 'bold' ): this {
556
- return this.setAttr( 'font-weight', weight );
557
- }
558
-
559
- /**
560
- * Sets the horizontal text alignment.
561
- * @param align - The horizontal alignment ("left", "center", or "right").
562
- * @returns The current `SvgText` instance for chaining.
563
- */
564
- textAlign( align: 'left' | 'center' | 'right' ): this {
565
-
566
- let al;
567
- switch( align ) {
568
- case 'left': al = 'start'; break;
569
- case 'center': al = 'middle'; break;
570
- case 'right': al = 'end'; break;
571
- default:
572
- // If an invalid alignment is provided, do nothing and return this for chaining.
573
- // This prevents setting an invalid attribute value.
574
- console.warn(`Invalid textAlign value: ${align}. Must be 'left', 'center', or 'right'.`);
575
- return this;
576
- }
577
-
578
- return this.setAttr( 'text-anchor', al );
579
- }
580
-
581
- /**
582
- * change the vertical alignment
583
- */
584
- verticalAlign( align: 'top' | 'center' | 'bottom' | 'baseline' ): this {
585
-
586
- let al;
587
- switch( align ) {
588
- case 'top': al = 'hanging'; break;
589
- case 'center': al = 'middle'; break;
590
- case 'bottom': al = 'baseline'; break;
591
- case 'baseline': al = 'mathematical'; break;
592
- default:
593
- // If an invalid alignment is provided, do nothing and return this for chaining.
594
- console.warn(`Invalid verticalAlign value: ${align}. Must be 'top', 'center', 'bottom', or 'baseline'.`);
595
- return this;
596
- }
597
-
598
- return this.setAttr( 'alignment-baseline', al );
599
- }
600
- }
601
-
602
- /**
603
- * Represents an SVG icon, which is essentially an SVG element embedded within another.
604
- * It extends `SvgItem` to inherit common SVG element functionalities.
605
- */
606
-
607
- export class SvgIcon extends SvgItem {
608
- /**
609
- * Creates an instance of `SvgIcon` from an SVG string.
610
- * @param svg - The SVG string, optionally prefixed with "data:image/svg+xml,".
611
- */
612
- constructor( svg: string ) {
613
- super( "svg" );
614
-
615
- if( svg.startsWith("data:image/svg+xml,") ) {
616
- svg = svg.substring( 19 );
617
- }
618
-
619
- const parser = new DOMParser();
620
- const doc = parser.parseFromString( decodeURIComponent(svg), "image/svg+xml");
621
-
622
- const parserErrorElement = doc.querySelector("parsererror");
623
- if( parserErrorElement ) {
624
- console.error( "error while parsing svg:\n"+ parserErrorElement.textContent );
625
- }
626
-
627
- const svgRoot = doc.documentElement; // The <svg> element from the string
628
- for( let i=0; i<svgRoot.attributes.length; i++) {
629
- this._dom.setAttribute( svgRoot.attributes[i].name, svgRoot.attributes[i].value );
630
- }
631
-
632
- for( let i=0; i<svgRoot.childNodes.length; i++) {
633
- const child = svgRoot.childNodes[i];
634
- if (child.nodeType === 1) {
635
- this._dom.appendChild(child);
636
- }
637
- }
638
- }
639
- }
640
-
641
-
642
-
643
- /**
644
- * Represents a generic SVG shape element.
645
- * It extends `SvgItem` to inherit common SVG element functionalities.
646
- */
647
-
648
- export class SvgShape extends SvgItem {
649
- constructor( tag: string ) {
650
- super( tag );
651
- }
652
- }
653
-
654
- /**
655
- *
656
- * Type alias for a number or a percentage string.
657
- */
658
-
659
- type number_or_perc = number | `${string}%`
660
-
661
- /**
662
- * Represents an SVG linear gradient element.
663
- * It extends `SvgItem` to inherit common SVG element functionalities.
664
- */
665
- export class SvgGradient extends SvgItem {
666
-
667
- private static g_id = 1;
668
-
669
- private _id: string;
670
- private _stops: { offset: number_or_perc, color: string } [];
671
-
672
- /**
673
- * Creates an instance of `SvgGradient`.
674
- * @param x1 - The x-coordinate of the starting point of the gradient vector.
675
- * @param y1 - The y-coordinate of the starting point of the gradient vector.
676
- * @param x2 - The x-coordinate of the ending point of the gradient vector.
677
- * @param y2 - The y-coordinate of the ending point of the gradient vector.
678
- * @returns The current `SvgGradient` instance for chaining.
679
- */
680
- constructor( x1: number_or_perc, y1: number_or_perc, x2: number_or_perc, y2: number_or_perc ) {
681
- super( 'linearGradient')
682
-
683
- this._id = 'gx-'+SvgGradient.g_id;
684
- SvgGradient.g_id++;
685
-
686
- this.setAttr( 'id', this._id );
687
- this.setAttr( 'x1', isString(x1) ? x1 : num(x1)+'' );
688
- this.setAttr( 'x2', isString(x2) ? x2 : num(x2)+'' );
689
- this.setAttr( 'y1', isString(y1) ? y1 : num(y1)+'' );
690
- this.setAttr( 'y2', isString(y2) ? y2 : num(y2)+'' );
691
-
692
- this._stops = [];
693
- }
694
-
695
- /**
696
- * Gets the URL reference to this gradient, suitable for use in `fill` or `stroke` attributes.
697
- * @returns A string in the format `url(#<gradient_id>)`.
698
- */
699
- get id( ) {
700
- return 'url(#'+this._id+')';
701
- }
702
-
703
- /**
704
- * Adds a color stop to the gradient.
705
- * @param offset - The offset of the color stop, either as a number (0-100) or a percentage string.
706
- * @param color - The color at this stop.
707
- * @returns The current `SvgGradient` instance for chaining.
708
- */
709
- addStop( offset: number_or_perc, color: string ): this {
710
- this._dom.insertAdjacentHTML( "beforeend", `<stop offset="${offset}%" stop-color="${color}"></stop>`);
711
- return this;
712
- }
713
- }
714
-
715
- /**
716
- * Represents an SVG group element (`<g>`), which can contain other SVG elements.
717
- * It extends `SvgItem` and provides methods for appending various SVG shapes and gradients.
718
- */
719
-
720
- export class SvgGroup extends SvgItem {
721
-
722
- /**
723
- * Creates an instance of `SvgGroup`.
724
- * @param tag - The SVG tag name for the group element (defaults to "g").
725
- */
726
- constructor( tag = "g" ) {
727
- super( tag )
728
- }
729
-
730
- /**
731
- * Appends an `SvgItem` to this group.
732
- *
733
- * @template K - The type of the `SvgItem` being appended.
734
- * @param item - The `SvgItem` instance to append.
735
- *
736
- * @returns The appended `SvgItem` instance.
737
- */
738
-
739
-
740
- append<K extends SvgItem>( item: K ): K {
741
- this._dom.appendChild( item.getDom() );
742
- return item;
743
- }
744
-
745
- appendItems<K extends SvgItem>( items: K[] ) {
746
- items.forEach( item => {
747
- this._dom.appendChild( item.getDom() );
748
- } );
749
- }
750
-
751
- /**
752
- * Creates and appends an `SvgPath` element to this group.
753
- * @returns The newly created `SvgPath` instance.
754
- */
755
-
756
- path( ): SvgPath {
757
- const path = new SvgPath( );
758
- return this.append( path );
759
- }
760
-
761
- /**
762
- * Creates and appends an `SvgText` element to this group.
763
- * @param x - The x-coordinate for the text.
764
- * @param y - The y-coordinate for the text.
765
- * @param txt - The text content.
766
- * @returns The newly created `SvgText` instance.
767
- */
768
- text( x: number, y: number, txt: string ) {
769
- const text = new SvgText( x, y, txt );
770
- return this.append( text );
771
- }
772
-
773
- /**
774
- * Creates and appends an SVG ellipse element to this group.
775
- *
776
- * @param x - The x-coordinate of the center of the ellipse.
777
- * @param y - The y-coordinate of the center of the ellipse.
778
- * @param r1 - The x-radius of the ellipse.
779
- * @param r2 - The y-radius of the ellipse.
780
- *
781
- * @returns The newly created `SvgShape` instance representing the ellipse.
782
- */
783
- ellipse( x: number, y: number, r1: number, r2?: number ): SvgShape {
784
- const shape = new SvgShape( 'ellipse' );
785
- shape.setAttr( 'cx', num(x)+'' );
786
- shape.setAttr( 'cy', num(y)+'' );
787
- shape.setAttr( 'rx', num(r1)+'' );
788
- shape.setAttr( 'ry', num(r2 ?? r1)+'' );
789
- return this.append( shape );
790
- }
791
-
792
- /**
793
- * Creates and appends an SVG circle element to this group.
794
- * (Internally uses an ellipse with equal x and y radii).
795
- *
796
- * @param x - The x-coordinate of the center of the circle.
797
- * @param y - The y-coordinate of the center of the circle.
798
- * @param r1 - The radius of the circle.
799
- * @returns The newly created `SvgShape` instance representing the circle.
800
- */
801
- circle( x: number, y: number, r1: number ): SvgShape {
802
- const shape = new SvgShape( 'ellipse' );
803
- shape.setAttr( 'cx', num(x)+'' );
804
- shape.setAttr( 'cy', num(y)+'' );
805
- shape.setAttr( 'rx', num(r1)+'' );
806
- shape.setAttr( 'ry', num(r1)+'' );
807
- return this.append( shape );
808
- }
809
-
810
- /**
811
- * Creates and appends an `SvgIcon` element to this group.
812
- *
813
- * @param svg - The SVG string content for the icon.
814
- * @param x - The x-coordinate for the icon's position.
815
- * @param y - The y-coordinate for the icon's position.
816
- * @param w - The width of the icon.
817
- * @param h - The height of the icon.
818
- * @returns The newly created `SvgIcon` instance.
819
- */
820
- icon( svg: string, x: number, y: number, w: number, h: number ): SvgIcon {
821
- const icon = new SvgIcon( svg );
822
- icon.setAttr( 'x', num(x)+'' );
823
- icon.setAttr( 'y', num(y)+'' );
824
- icon.setAttr( 'width', num(w)+'' );
825
- icon.setAttr( 'height', num(h)+'' );
826
- icon.setStyle( 'width', num(w)+'px' );
827
- icon.setStyle( 'height', num(h)+'px' );
828
- return this.append( icon );
829
- }
830
-
831
- /**
832
- * Creates and appends an SVG rectangle element to this group.
833
- * Handles negative height by adjusting `y` and `h` accordingly.
834
- *
835
- * @param x - The x-coordinate of the top-left corner of the rectangle.
836
- * @param y - The y-coordinate of the top-left corner of the rectangle.
837
- * @param w - The width of the rectangle.
838
- * @param h - The height of the rectangle.
839
- * @returns The newly created `SvgShape` instance representing the rectangle.
840
- */
841
- rect( x: number, y: number, w: number, h: number ): SvgShape {
842
-
843
- if( h<0 ) {
844
- y = y+h;
845
- h = -h;
846
- }
847
-
848
- const shape = new SvgShape( 'rect' );
849
- shape.setAttr( 'x', num(x)+'' );
850
- shape.setAttr( 'y', num(y)+'' );
851
- shape.setAttr( 'width', num(w)+'' );
852
- shape.setAttr( 'height', num(h)+'' );
853
- return this.append( shape );
854
- }
855
-
856
- /**
857
- * Creates and appends an SVG group element (`<g>`) to this group.
858
- * @param id - Optional. An ID for the new group.
859
- * @returns The newly created `SvgGroup` instance.
860
- */
861
- group( id?: string ) {
862
- const group = new SvgGroup( );
863
- if( id ) {
864
- group.setAttr( 'id', id );
865
- }
866
-
867
- return this.append( group );
868
- }
869
-
870
- /**
871
- *
872
- * Creates and appends an SVG linear gradient definition to this group.
873
- *
874
- * @example
875
- * ```typescript
876
- * const gradient = svgGroup.linear_gradient('0%', '0%', '0%', '100%')
877
- * .addStop(0, 'red')
878
- * .addStop(100, 'green');
879
- * svgGroup.rect(0, 0, 100, 100).fill(gradient.id);
880
- * ```
881
- * @param x1 - The x-coordinate of the starting point of the gradient vector.
882
- * @param y1 - The y-coordinate of the starting point of the gradient vector.
883
- * @param x2 - The x-coordinate of the ending point of the gradient vector.
884
- * @param y2 - The y-coordinate of the ending point of the gradient vector.
885
- * @returns The newly created `SvgGradient` instance.
886
- */
887
-
888
- linear_gradient( x1: number_or_perc, y1: number_or_perc, x2: number_or_perc, y2: number_or_perc ) {
889
- const grad = new SvgGradient( x1, y1, x2, y2 );
890
- return this.append( grad );
891
- }
892
-
893
- /**
894
- * Clears all child elements from this SVG group.
895
- *
896
- * @returns void
897
- */
898
-
899
- clear( ) {
900
- const dom = this._dom;
901
- while( dom.firstChild ) {
902
- dom.removeChild( dom.firstChild );
903
- }
904
- }
905
- }
906
-
907
-
908
-
909
-
910
-
911
- /**
912
- * A specialized `SvgGroup` that provides methods for adding SVG definitions like clip paths and patterns.
913
- * It extends `SvgGroup` to inherit common SVG element functionalities and acts as a container for definitions.
914
- */
915
- export class SvgBuilder extends SvgGroup {
916
- private static g_clip_id = 1;
917
- private static g_pat_id = 1;
918
-
919
- /**
920
- * Creates an instance of `SvgBuilder`.
921
- */
922
- constructor( ) {
923
- super( );
924
- }
925
-
926
- /**
927
- * Adds an SVG clip path definition to the builder.
928
- *
929
- * @param x - The x-coordinate of the top-left corner of the clipping rectangle.
930
- * @param y - The y-coordinate of the top-left corner of the clipping rectangle.
931
- * @param w - The width of the clipping rectangle.
932
- * @param h - The height of the clipping rectangle.
933
- * @returns An object containing the generated `id` for the clip path and the `SvgGroup` instance representing the clip path.
934
- */
935
- addClip( x: number, y: number, w: number, h: number ) {
936
- /**
937
- * Adds an SVG clip path definition to the builder.
938
- *
939
- * @param x - The x-coordinate of the top-left corner of the clipping rectangle.
940
- * @param y - The y-coordinate of the top-left corner of the clipping rectangle.
941
- * @param w - The width of the clipping rectangle.
942
- * @param h - The height of the clipping rectangle.
943
- * @returns An object containing the generated `id` for the clip path and the `SvgGroup` instance representing the clip path.
944
- */
945
- const id = 'clip-'+SvgBuilder.g_clip_id++;
946
- const clip = new SvgGroup( 'clipPath' );
947
- clip.setAttr('id', id );
948
- clip.rect( x, y, w, h );
949
-
950
- this.append(clip);
951
- return {id,clip};
952
- }
953
-
954
- /**
955
- * Adds an SVG pattern definition to the builder.
956
- *
957
- * @param x - The x-coordinate of the pattern tile's top-left corner.
958
- * @param y - The y-coordinate of the pattern tile's top-left corner.
959
- * @param w - The width of the pattern tile.
960
- * @param h - The height of the pattern tile.
961
- * @returns An object containing the generated `id` for the pattern and the `SvgGroup` instance representing the pattern.
962
- */
963
- addPattern( x: number, y: number, w: number, h: number ) {
964
- /**
965
- * Adds an SVG pattern definition to the builder.
966
- *
967
- * @param x - The x-coordinate of the pattern tile's top-left corner.
968
- * @param y - The y-coordinate of the pattern tile's top-left corner.
969
- * @param w - The width of the pattern tile.
970
- * @param h - The height of the pattern tile.
971
- * @returns An object containing the generated `id` for the pattern and the `SvgGroup` instance representing the pattern.
972
- */
973
- const id = 'pat-'+SvgBuilder.g_pat_id++;
974
-
975
- const pat = new SvgGroup( 'pattern' );
976
- pat.setAttr( 'id', id );
977
- pat.setAttr( 'x', num(x)+'' );
978
- pat.setAttr( 'y', num(y)+'' );
979
- pat.setAttr( 'width', num(w)+'' );
980
- pat.setAttr( 'height', num(h)+'' );
981
- pat.setAttr( 'patternUnits', "userSpaceOnUse" );
982
-
983
- this.append(pat);
984
- return {id,pat};
985
- }
986
- }
987
-
988
-
989
-
990
- /**
991
- * Properties for the `SvgComponent`.
992
- */
993
-
994
- export interface SvgProps extends ComponentProps {
995
- viewbox?: string;
996
- svg?: SvgBuilder;
997
- }
998
-
999
- /**
1000
- * A component that renders an SVG element, acting as a container for SVG graphics.
1001
- * It extends `Component` and provides methods for setting SVG content.
1002
- */
1003
-
1004
- export class SvgComponent<P extends SvgProps = SvgProps> extends Component<P> {
1005
-
1006
- /**
1007
- * Creates an instance of `SvgComponent`.
1008
- * @param props - The properties for the SVG component.
1009
- */
1010
- constructor( props: P ) {
1011
- super( { ...props, tag: "svg", ns: SVG_NS } );
1012
-
1013
- this.setAttribute( 'xmlns', SVG_NS );
1014
-
1015
- if( props.viewbox ) {
1016
- this.setAttribute( "viewBox", props.viewbox );
1017
- }
1018
-
1019
- if( props.svg ) {
1020
- this.dom.appendChild( props.svg.getDom() );
1021
- }
1022
- }
1023
-
1024
- /**
1025
- * Sets the entire SVG content of the component using an `SvgBuilder`.
1026
- * Any existing content will be cleared.
1027
- * @param bld - The `SvgBuilder` instance containing the SVG elements to render.
1028
- */
1029
- setSvg( bld: SvgBuilder ) {
1030
- this.clearContent( );
1031
- this.dom.appendChild( bld.getDom() );
1032
- }
1033
-
1034
- /**
1035
- * Appends one or more `SvgItem` instances directly to the SVG component's DOM.
1036
- *
1037
- * @param items - A spread array of `SvgItem` instances to append.
1038
- */
1039
- addItems( ...items: SvgItem[] ) {
1040
- items.forEach( item => this.dom.appendChild( item.getDom() ) );
1041
- }
1042
- }
1
+ /**
2
+ * ___ ___ __
3
+ * \ \/ / / _
4
+ * \ / /_| |_
5
+ * / \____ _|
6
+ * /__/\__\ |_|
7
+ *
8
+ * @file core_svg.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 { Component, ComponentProps } from './component';
18
+ import { isUnitLess } from "./core_styles";
19
+ import { DOMEventHandler, GlobalDOMEvents, addEvent } from './core_dom';
20
+ import { isNumber, isString } from './core_tools';
21
+
22
+ const SVG_NS = "http://www.w3.org/2000/svg";
23
+
24
+ // degrees to radian
25
+ function d2r( d: number ): number {
26
+ return d * Math.PI / 180.0;
27
+ }
28
+
29
+ // polar to cartesian
30
+ function p2c( x: number, y: number, r: number, deg: number ): {x: number,y: number} {
31
+ const rad = d2r( deg );
32
+ return {
33
+ x: x + r * Math.cos( rad ),
34
+ y: y + r * Math.sin( rad )
35
+ };
36
+ }
37
+
38
+ // fix prec for numbers
39
+ function num( x: number ): number {
40
+ return Math.round( x * 1000 ) / 1000;
41
+ }
42
+
43
+ // clean values
44
+ function clean( a: any, ...b: any ) {
45
+ // just round number values to 3 digits
46
+ b = b.map( ( v: any ) => {
47
+ if( typeof v === 'number' && isFinite(v) ) {
48
+ return num(v);
49
+ }
50
+
51
+ return v;
52
+ });
53
+
54
+ return String.raw( a, ...b );
55
+ }
56
+
57
+
58
+
59
+ /**
60
+ * Represents a lightweight wrapper around an SVG DOM element that provides
61
+ * convenient, chainable helpers for common SVG manipulations (attributes,
62
+ * styles, transforms, classes and events).
63
+ *
64
+ * The class encapsulates an underlying SVGElement and
65
+ * exposes methods that operate directly on that element while allowing fluent chaining.
66
+ *
67
+ * Key behaviors:
68
+ * - Creation: instances are constructed with the local name of the SVG tag
69
+ * (e.g. "rect", "circle", "g") and internally create the element in the
70
+ * SVG namespace.
71
+ * - Attribute management: `getAttr`, `getNumAttr`, and `setAttr` let you read
72
+ * and write attributes. Passing `null` or `undefined` to `setAttr` removes
73
+ * the attribute.
74
+ * - Styling: `setStyle` assigns CSS properties on the element. When a numeric
75
+ * value is provided, a "px" unit is appended unless the property is known
76
+ * to be unitless (the implementation relies on a small helper to detect
77
+ * unitless properties).
78
+ * - Geometry & transforms: helpers exist for setting or appending transforms
79
+ * (`transform`, `add_transformation`, `clear_transform`), and higher-level
80
+ * helpers for rotate, translate and scale (both replace and append forms).
81
+ * - Stroke & fill helpers: `stroke`, `strokeWidth`, `strokeCap`, `strokeOpacity`,
82
+ * `fill`, and `no_fill` provide convenient setters for common paint
83
+ * properties. These methods return `this` so they can be chained.
84
+ * - Classes & clipping: `addClass` / `removeClass` manipulate the element's
85
+ * class list (supporting space-separated lists) and `clip` sets a clip-path
86
+ * reference using an id.
87
+ * - Reset: `reset` removes all attributes from the element (but does not
88
+ * remove the element itself).
89
+ * - Events: `addDOMEvent` attaches DOM events to the underlying element.
90
+ *
91
+ * Notes and caveats:
92
+ * - This class is not a full DOM abstraction; it intentionally focuses on a
93
+ * small, ergonomic surface for building and manipulating SVG elements.
94
+ * - Some behaviors depend on small utility helpers (e.g. numeric/unit detection
95
+ * and event-attachment helpers) provided elsewhere in the codebase.
96
+ *
97
+ */
98
+
99
+ class SvgItem {
100
+ protected _dom : SVGElement;
101
+
102
+ constructor( tag: string ) {
103
+ this._dom = document.createElementNS("http://www.w3.org/2000/svg", tag );
104
+ }
105
+
106
+ /**
107
+ * @returns the svh element dom
108
+ */
109
+
110
+ getDom( ) {
111
+ return this._dom;
112
+ }
113
+
114
+ /**
115
+ *
116
+ */
117
+
118
+ /**
119
+ * Remove all attributes from the underlying DOM element.
120
+ * @returns The current instance (this) to allow method chaining.
121
+ */
122
+ reset( ) {
123
+ const attrs = this._dom.attributes;
124
+ for (let i = attrs.length - 1; i >= 0; i--) {
125
+ this._dom.removeAttribute(attrs[i].name);
126
+ }
127
+
128
+ return this;
129
+ }
130
+
131
+
132
+ /**
133
+ * change the stroke color
134
+ * @param color
135
+ */
136
+
137
+ stroke( color: string, width?: number ): this {
138
+ this.setAttr( 'stroke', color );
139
+ if( width!==undefined ) {
140
+ this.setAttr( 'stroke-width', width+'px' );
141
+ }
142
+ return this;
143
+ }
144
+
145
+ /**
146
+ * change the stroke width
147
+ * @param width
148
+ */
149
+ strokeWidth( width: number ): this {
150
+ this.setAttr( 'stroke-width', width+'px' );
151
+ return this;
152
+ }
153
+
154
+ /**
155
+ * change the stroke cap
156
+ * @param cap
157
+ */
158
+
159
+ strokeCap( cap: "butt" | "round" | "sqaure" ) {
160
+ return this.setAttr( "stroke-linecap", cap );
161
+ }
162
+
163
+ /**
164
+ * change the stroke opacity attribute on the element.
165
+ * @param opacity - Opacity value where 0 is fully transparent and 1 is fully opaque.
166
+ * @returns The current instance to allow method chaining.
167
+ */
168
+
169
+ strokeOpacity( opacity: number ) {
170
+ return this.setAttr( "stroke-opacity", opacity+"" );
171
+ }
172
+
173
+ /**
174
+ * Set the shape rendering attribute to control anti-aliasing for shapes.
175
+ *
176
+ * When enabled, the attribute is set to "auto" to allow smoothing/anti-aliasing.
177
+ * When disabled, the attribute is set to "crispEdges" to favor pixel-aligned,
178
+ * non-anti-aliased rendering.
179
+ *
180
+ * @param set - True to enable anti-aliasing ("auto"), false to disable ("crispEdges").
181
+ * @returns the current instance to allow chaining).
182
+ */
183
+
184
+ antiAlias( set: boolean ) {
185
+ return this.setAttr( "shape-rendering", set ? "auto" : "crispEdges" );
186
+ }
187
+
188
+ /**
189
+ * change the fill color
190
+ * @param color
191
+ */
192
+
193
+ fill( color: string ): this {
194
+ this.setAttr( 'fill', color );
195
+ return this;
196
+ }
197
+
198
+ /**
199
+ * the element is not filled
200
+ */
201
+
202
+ no_fill( ): this {
203
+ this.setAttr( 'fill', "transparent" );
204
+ return this;
205
+ }
206
+
207
+ /**
208
+ * return the given attribute if any
209
+ */
210
+
211
+ getAttr( name: string ) : string {
212
+ const a = this._dom.getAttribute( name ) || '';
213
+ return a;
214
+ }
215
+
216
+ /**
217
+ * return the attribute as number
218
+ */
219
+
220
+ getNumAttr( name: string ) {
221
+ const a = this._dom.getAttribute( name )
222
+ if( a=='' ) {
223
+ return 0;
224
+ }
225
+
226
+ return parseInt( a );
227
+ }
228
+
229
+ /**
230
+ * define a new attribute
231
+ * @param name attibute name
232
+ * @param value attribute value
233
+ * @returns this
234
+ */
235
+
236
+ setAttr( name: string, value: string ) : this {
237
+ if( value===null || value===undefined ) {
238
+ this._dom.removeAttribute( name );
239
+ }
240
+ else {
241
+ this._dom.setAttribute( name, value );
242
+ }
243
+ return this;
244
+ }
245
+
246
+ /**
247
+ * change one style value
248
+ */
249
+
250
+ setStyle<K extends keyof CSSStyleDeclaration>( name: K, value: string | number ) : this {
251
+ const _style = this._dom.style;
252
+
253
+ if( isNumber(value) ) {
254
+ let v = value+"";
255
+ if( !isUnitLess(name as string) ) {
256
+ v += "px";
257
+ }
258
+
259
+ (_style as any)[name] = v;
260
+ }
261
+ else {
262
+ (_style as any)[name] = value;
263
+ }
264
+
265
+ return this;
266
+ }
267
+
268
+ /**
269
+ * add a class
270
+ * @param name class name to add
271
+ */
272
+
273
+ addClass( cls: string ): this {
274
+ if( !cls ) return;
275
+
276
+ cls = cls.trim();
277
+ if( cls.indexOf(' ')>=0 ) {
278
+ const ccs = cls.split( " " );
279
+ this._dom.classList.add(...ccs);
280
+ }
281
+ else {
282
+ this._dom.classList.add(cls);
283
+ }
284
+
285
+ return this;
286
+ }
287
+
288
+ /**
289
+ * remove a class
290
+ * @param name class name to remove
291
+ */
292
+
293
+ removeClass( cls: string ): this {
294
+ if( !cls ) return;
295
+
296
+ if( cls.indexOf(' ')>=0 ) {
297
+ const ccs = cls.split( " " );
298
+ this._dom.classList.remove(...ccs);
299
+ }
300
+ else {
301
+ this._dom.classList.remove(cls);
302
+ }
303
+
304
+ return this;
305
+ }
306
+
307
+ /**
308
+ *
309
+ */
310
+
311
+ clip( id: string ): this {
312
+ this.setAttr( "clip-path", `url(#${id})` );
313
+ return this;
314
+ }
315
+
316
+ /**
317
+ * define the whole transformation
318
+ */
319
+
320
+ transform( tr: string ): this {
321
+ this.setAttr( "transform", tr );
322
+ return this;
323
+ }
324
+
325
+ /**
326
+ * add a transformation to the current transformation
327
+ */
328
+
329
+ add_transformation( tr: string ): this {
330
+ const t = this.getAttr( "transform" );
331
+ this.setAttr( "transform", t+' '+tr );
332
+ return this;
333
+ }
334
+
335
+ /**
336
+ * remove all transformations
337
+ */
338
+
339
+ clear_transform( ) {
340
+ this.setAttr( "transform", null );
341
+ return this;
342
+ }
343
+
344
+ /**
345
+ * rotation
346
+ */
347
+
348
+ rotate( deg: number, cx: number, cy: number ): this {
349
+ this.transform( `rotate( ${deg} ${cx} ${cy} )` );
350
+ return this;
351
+ }
352
+
353
+ add_rotation( deg: number, cx: number, cy: number ): this {
354
+ this.add_transformation( `rotate( ${deg} ${cx} ${cy} )` );
355
+ return this;
356
+ }
357
+
358
+ /**
359
+ * translation
360
+ */
361
+
362
+ translate( dx: number, dy: number ): this {
363
+ this.transform( `translate( ${dx} ${dy} )` );
364
+ return this;
365
+ }
366
+
367
+ add_translation( dx: number, dy: number ): this {
368
+ this.add_transformation( `translate( ${dx} ${dy} )` );
369
+ return this;
370
+ }
371
+
372
+ /**
373
+ * scaling
374
+ */
375
+
376
+ scale( x: number ): this {
377
+ this.transform( `scale( ${x} )` );
378
+ return this;
379
+ }
380
+
381
+ add_scale( x: number ): this {
382
+ this.add_transformation( `scale( ${x} )` );
383
+ return this;
384
+ }
385
+
386
+ /**
387
+ * handle SVG DOM event
388
+ */
389
+
390
+ addDOMEvent<K extends keyof GlobalDOMEvents>( name: K, listener: GlobalDOMEvents[K], prepend = false ) {
391
+ addEvent( this._dom, name, listener as DOMEventHandler, prepend );
392
+ return this;
393
+ }
394
+ }
395
+
396
+
397
+
398
+ /**
399
+ * Represents an SVG path element, providing methods for constructing and manipulating SVG paths.
400
+ * It extends `SvgItem` to inherit common SVG element functionalities.
401
+ */
402
+
403
+ export class SvgPath extends SvgItem {
404
+ private _path: string;
405
+
406
+ constructor( ) {
407
+ super( 'path' );
408
+ this._path = '';
409
+ }
410
+
411
+ private _update( ): this {
412
+ this.setAttr( 'd', this._path );
413
+ return this;
414
+ }
415
+
416
+ /**
417
+ * Resets the path data and all attributes of the SVG path element.
418
+ * @returns The current `SvgPath` instance for chaining.
419
+ */
420
+ reset( ) {
421
+ this._path = "";
422
+ super.reset( );
423
+ return this;
424
+ }
425
+
426
+ /**
427
+ * Moves the current drawing position to the specified coordinates without drawing a line.
428
+ * This is typically the first command in a path.
429
+ *
430
+ * @param x - The x-coordinate to move to.
431
+ * @param y - The y-coordinate to move to.
432
+ * @returns this
433
+ */
434
+
435
+ moveTo( x: number, y: number ) : this {
436
+ this._path += clean`M${x},${y}`;
437
+ return this._update( );
438
+ }
439
+
440
+ /**
441
+ * Draws a straight line from the current position to the specified coordinates.
442
+ *
443
+ * @param x - The x-coordinate of the end point.
444
+ * @param y - The y-coordinate of the end point.
445
+ * @returns this
446
+ */
447
+
448
+ lineTo( x: number, y: number ): this {
449
+ this._path += clean`L${x},${y}`;
450
+ return this._update( );
451
+ }
452
+
453
+ /**
454
+ * Draws a cubic Bézier curve from the current point to `(x3, y3)` using `(x1, y1)`
455
+ * as the control point at the beginning of the curve and `(x2, y2)` as the
456
+ * control point at the end of the curve.
457
+ *
458
+ * @param x1 - The x-coordinate of the first control point.
459
+ * @param y1 - The y-coordinate of the first control point.
460
+ * @param x2 - The x-coordinate of the second control point.
461
+ * @param y2 - The y-coordinate of the second control point.
462
+ * @param x3 - The x-coordinate of the end point of the curve.
463
+ * @param y3 - The y-coordinate of the end point of the curve.
464
+ *
465
+ * @returns The current `SvgPath` instance for chaining.
466
+ */
467
+
468
+ curveTo( x1: number, y1: number, x2: number, y2: number, x3: number, y3: number ) {
469
+ this._path += clean`C${x1},${y1} ${x2},${y2} ${x3},${y3}`;
470
+ return this._update( );
471
+ }
472
+
473
+
474
+ /**
475
+ * Closes the current subpath by drawing a straight line from the current position
476
+ * to the initial point of the current subpath.
477
+ * @returns The current `SvgPath` instance for chaining.
478
+ */
479
+
480
+ closePath( ): this {
481
+ this._path += 'Z';
482
+ return this._update( );
483
+ }
484
+
485
+ /**
486
+ * draw an arc
487
+ * Draws an elliptical arc from the current point to a new point.
488
+ *
489
+ * @param x - The x-coordinate of the center of the ellipse.
490
+ * @param y - The y-coordinate of the center of the ellipse.
491
+ * @param r - The radius of the arc.
492
+ * @param start - The start angle of the arc in degrees (0 is right, 90 is down).
493
+ * @param end - The end angle of the arc in degrees.
494
+ * @param clockwise - If `true`, the arc is drawn clockwise; otherwise, counter-clockwise. Defaults to `true`.
495
+ *
496
+ * @returns this
497
+ */
498
+
499
+ arc( x: number, y: number, r: number, start: number, end: number, clockwise= true ): this {
500
+
501
+ const st = p2c( x, y, r, start-90 );
502
+ const en = p2c( x, y, r, end-90 );
503
+
504
+ const flag = ((end-start) <= 180 ? "0" : "1");
505
+ this._path += clean`M${st.x},${st.y}A${r},${r} 0 ${flag} ${(clockwise ? '1' : '0')} ${en.x},${en.y}`;
506
+
507
+ return this._update( );
508
+ }
509
+ }
510
+
511
+ /**
512
+ * Represents an SVG text element, providing methods for positioning and styling text.
513
+ * It extends `SvgItem` to inherit common SVG element functionalities.
514
+ */
515
+
516
+ export class SvgText extends SvgItem {
517
+ /**
518
+ * Creates an instance of `SvgText`.
519
+ * @param x - The x-coordinate for the text's starting position.
520
+ * @param y - The y-coordinate for the text's starting position.
521
+ * @param txt - The text content.
522
+ */
523
+ constructor( x: number, y: number, txt: string ) {
524
+ super( 'text' );
525
+
526
+ this.setAttr( 'x', num(x)+'' );
527
+ this.setAttr( 'y', num(y)+'' );
528
+
529
+ this._dom.innerHTML = txt;
530
+ }
531
+
532
+ /**
533
+ * Sets the font family for the text.
534
+ * @param font - The font family name (e.g., "Arial", "sans-serif").
535
+ * @returns The current `SvgText` instance for chaining.
536
+ */
537
+ font( font: string ): this {
538
+ return this.setAttr( 'font-family', font );
539
+ }
540
+
541
+ /**
542
+ * Sets the font size for the text.
543
+ * @param size - The font size, either as a number (e.g., 12) or a string (e.g., "1.2em").
544
+ * @returns The current `SvgText` instance for chaining.
545
+ */
546
+ fontSize( size: number | string ): this {
547
+ return this.setAttr( 'font-size', size+'' );
548
+ }
549
+
550
+ /**
551
+ * Sets the font weight for the text.
552
+ * @param weight - The font weight ("light", "normal", or "bold").
553
+ * @returns The current `SvgText` instance for chaining.
554
+ */
555
+ fontWeight( weight: 'light' | 'normal' | 'bold' ): this {
556
+ return this.setAttr( 'font-weight', weight );
557
+ }
558
+
559
+ /**
560
+ * Sets the horizontal text alignment.
561
+ * @param align - The horizontal alignment ("left", "center", or "right").
562
+ * @returns The current `SvgText` instance for chaining.
563
+ */
564
+ textAlign( align: 'left' | 'center' | 'right' ): this {
565
+
566
+ let al;
567
+ switch( align ) {
568
+ case 'left': al = 'start'; break;
569
+ case 'center': al = 'middle'; break;
570
+ case 'right': al = 'end'; break;
571
+ default:
572
+ // If an invalid alignment is provided, do nothing and return this for chaining.
573
+ // This prevents setting an invalid attribute value.
574
+ console.warn(`Invalid textAlign value: ${align}. Must be 'left', 'center', or 'right'.`);
575
+ return this;
576
+ }
577
+
578
+ return this.setAttr( 'text-anchor', al );
579
+ }
580
+
581
+ /**
582
+ * change the vertical alignment
583
+ */
584
+ verticalAlign( align: 'top' | 'center' | 'bottom' | 'baseline' ): this {
585
+
586
+ let al;
587
+ switch( align ) {
588
+ case 'top': al = 'hanging'; break;
589
+ case 'center': al = 'middle'; break;
590
+ case 'bottom': al = 'baseline'; break;
591
+ case 'baseline': al = 'mathematical'; break;
592
+ default:
593
+ // If an invalid alignment is provided, do nothing and return this for chaining.
594
+ console.warn(`Invalid verticalAlign value: ${align}. Must be 'top', 'center', 'bottom', or 'baseline'.`);
595
+ return this;
596
+ }
597
+
598
+ return this.setAttr( 'alignment-baseline', al );
599
+ }
600
+ }
601
+
602
+ /**
603
+ * Represents an SVG icon, which is essentially an SVG element embedded within another.
604
+ * It extends `SvgItem` to inherit common SVG element functionalities.
605
+ */
606
+
607
+ export class SvgIcon extends SvgItem {
608
+ /**
609
+ * Creates an instance of `SvgIcon` from an SVG string.
610
+ * @param svg - The SVG string, optionally prefixed with "data:image/svg+xml,".
611
+ */
612
+ constructor( svg: string ) {
613
+ super( "svg" );
614
+
615
+ if( svg.startsWith("data:image/svg+xml,") ) {
616
+ svg = svg.substring( 19 );
617
+ }
618
+
619
+ const parser = new DOMParser();
620
+ const doc = parser.parseFromString( decodeURIComponent(svg), "image/svg+xml");
621
+
622
+ const parserErrorElement = doc.querySelector("parsererror");
623
+ if( parserErrorElement ) {
624
+ console.error( "error while parsing svg:\n"+ parserErrorElement.textContent );
625
+ }
626
+
627
+ const svgRoot = doc.documentElement; // The <svg> element from the string
628
+ for( let i=0; i<svgRoot.attributes.length; i++) {
629
+ this._dom.setAttribute( svgRoot.attributes[i].name, svgRoot.attributes[i].value );
630
+ }
631
+
632
+ for( let i=0; i<svgRoot.childNodes.length; i++) {
633
+ const child = svgRoot.childNodes[i];
634
+ if (child.nodeType === 1) {
635
+ this._dom.appendChild(child);
636
+ }
637
+ }
638
+ }
639
+ }
640
+
641
+
642
+
643
+ /**
644
+ * Represents a generic SVG shape element.
645
+ * It extends `SvgItem` to inherit common SVG element functionalities.
646
+ */
647
+
648
+ export class SvgShape extends SvgItem {
649
+ constructor( tag: string ) {
650
+ super( tag );
651
+ }
652
+ }
653
+
654
+ /**
655
+ *
656
+ * Type alias for a number or a percentage string.
657
+ */
658
+
659
+ type number_or_perc = number | `${string}%`
660
+
661
+ /**
662
+ * Represents an SVG linear gradient element.
663
+ * It extends `SvgItem` to inherit common SVG element functionalities.
664
+ */
665
+ export class SvgGradient extends SvgItem {
666
+
667
+ private static g_id = 1;
668
+
669
+ private _id: string;
670
+ private _stops: { offset: number_or_perc, color: string } [];
671
+
672
+ /**
673
+ * Creates an instance of `SvgGradient`.
674
+ * @param x1 - The x-coordinate of the starting point of the gradient vector.
675
+ * @param y1 - The y-coordinate of the starting point of the gradient vector.
676
+ * @param x2 - The x-coordinate of the ending point of the gradient vector.
677
+ * @param y2 - The y-coordinate of the ending point of the gradient vector.
678
+ * @returns The current `SvgGradient` instance for chaining.
679
+ */
680
+ constructor( x1: number_or_perc, y1: number_or_perc, x2: number_or_perc, y2: number_or_perc ) {
681
+ super( 'linearGradient')
682
+
683
+ this._id = 'gx-'+SvgGradient.g_id;
684
+ SvgGradient.g_id++;
685
+
686
+ this.setAttr( 'id', this._id );
687
+ this.setAttr( 'x1', isString(x1) ? x1 : num(x1)+'' );
688
+ this.setAttr( 'x2', isString(x2) ? x2 : num(x2)+'' );
689
+ this.setAttr( 'y1', isString(y1) ? y1 : num(y1)+'' );
690
+ this.setAttr( 'y2', isString(y2) ? y2 : num(y2)+'' );
691
+
692
+ this._stops = [];
693
+ }
694
+
695
+ /**
696
+ * Gets the URL reference to this gradient, suitable for use in `fill` or `stroke` attributes.
697
+ * @returns A string in the format `url(#<gradient_id>)`.
698
+ */
699
+ get id( ) {
700
+ return 'url(#'+this._id+')';
701
+ }
702
+
703
+ /**
704
+ * Adds a color stop to the gradient.
705
+ * @param offset - The offset of the color stop, either as a number (0-100) or a percentage string.
706
+ * @param color - The color at this stop.
707
+ * @returns The current `SvgGradient` instance for chaining.
708
+ */
709
+ addStop( offset: number_or_perc, color: string ): this {
710
+ this._dom.insertAdjacentHTML( "beforeend", `<stop offset="${offset}%" stop-color="${color}"></stop>`);
711
+ return this;
712
+ }
713
+ }
714
+
715
+ /**
716
+ * Represents an SVG group element (`<g>`), which can contain other SVG elements.
717
+ * It extends `SvgItem` and provides methods for appending various SVG shapes and gradients.
718
+ */
719
+
720
+ export class SvgGroup extends SvgItem {
721
+
722
+ /**
723
+ * Creates an instance of `SvgGroup`.
724
+ * @param tag - The SVG tag name for the group element (defaults to "g").
725
+ */
726
+ constructor( tag = "g" ) {
727
+ super( tag )
728
+ }
729
+
730
+ /**
731
+ * Appends an `SvgItem` to this group.
732
+ *
733
+ * @template K - The type of the `SvgItem` being appended.
734
+ * @param item - The `SvgItem` instance to append.
735
+ *
736
+ * @returns The appended `SvgItem` instance.
737
+ */
738
+
739
+
740
+ append<K extends SvgItem>( item: K ): K {
741
+ this._dom.appendChild( item.getDom() );
742
+ return item;
743
+ }
744
+
745
+ appendItems<K extends SvgItem>( items: K[] ) {
746
+ items.forEach( item => {
747
+ this._dom.appendChild( item.getDom() );
748
+ } );
749
+ }
750
+
751
+ /**
752
+ * Creates and appends an `SvgPath` element to this group.
753
+ * @returns The newly created `SvgPath` instance.
754
+ */
755
+
756
+ path( ): SvgPath {
757
+ const path = new SvgPath( );
758
+ return this.append( path );
759
+ }
760
+
761
+ /**
762
+ * Creates and appends an `SvgText` element to this group.
763
+ * @param x - The x-coordinate for the text.
764
+ * @param y - The y-coordinate for the text.
765
+ * @param txt - The text content.
766
+ * @returns The newly created `SvgText` instance.
767
+ */
768
+ text( x: number, y: number, txt: string ) {
769
+ const text = new SvgText( x, y, txt );
770
+ return this.append( text );
771
+ }
772
+
773
+ /**
774
+ * Creates and appends an SVG ellipse element to this group.
775
+ *
776
+ * @param x - The x-coordinate of the center of the ellipse.
777
+ * @param y - The y-coordinate of the center of the ellipse.
778
+ * @param r1 - The x-radius of the ellipse.
779
+ * @param r2 - The y-radius of the ellipse.
780
+ *
781
+ * @returns The newly created `SvgShape` instance representing the ellipse.
782
+ */
783
+ ellipse( x: number, y: number, r1: number, r2?: number ): SvgShape {
784
+ const shape = new SvgShape( 'ellipse' );
785
+ shape.setAttr( 'cx', num(x)+'' );
786
+ shape.setAttr( 'cy', num(y)+'' );
787
+ shape.setAttr( 'rx', num(r1)+'' );
788
+ shape.setAttr( 'ry', num(r2 ?? r1)+'' );
789
+ return this.append( shape );
790
+ }
791
+
792
+ /**
793
+ * Creates and appends an SVG circle element to this group.
794
+ * (Internally uses an ellipse with equal x and y radii).
795
+ *
796
+ * @param x - The x-coordinate of the center of the circle.
797
+ * @param y - The y-coordinate of the center of the circle.
798
+ * @param r1 - The radius of the circle.
799
+ * @returns The newly created `SvgShape` instance representing the circle.
800
+ */
801
+ circle( x: number, y: number, r1: number ): SvgShape {
802
+ const shape = new SvgShape( 'ellipse' );
803
+ shape.setAttr( 'cx', num(x)+'' );
804
+ shape.setAttr( 'cy', num(y)+'' );
805
+ shape.setAttr( 'rx', num(r1)+'' );
806
+ shape.setAttr( 'ry', num(r1)+'' );
807
+ return this.append( shape );
808
+ }
809
+
810
+ /**
811
+ * Creates and appends an `SvgIcon` element to this group.
812
+ *
813
+ * @param svg - The SVG string content for the icon.
814
+ * @param x - The x-coordinate for the icon's position.
815
+ * @param y - The y-coordinate for the icon's position.
816
+ * @param w - The width of the icon.
817
+ * @param h - The height of the icon.
818
+ * @returns The newly created `SvgIcon` instance.
819
+ */
820
+ icon( svg: string, x: number, y: number, w: number, h: number ): SvgIcon {
821
+ const icon = new SvgIcon( svg );
822
+ icon.setAttr( 'x', num(x)+'' );
823
+ icon.setAttr( 'y', num(y)+'' );
824
+ icon.setAttr( 'width', num(w)+'' );
825
+ icon.setAttr( 'height', num(h)+'' );
826
+ icon.setStyle( 'width', num(w)+'px' );
827
+ icon.setStyle( 'height', num(h)+'px' );
828
+ return this.append( icon );
829
+ }
830
+
831
+ /**
832
+ * Creates and appends an SVG rectangle element to this group.
833
+ * Handles negative height by adjusting `y` and `h` accordingly.
834
+ *
835
+ * @param x - The x-coordinate of the top-left corner of the rectangle.
836
+ * @param y - The y-coordinate of the top-left corner of the rectangle.
837
+ * @param w - The width of the rectangle.
838
+ * @param h - The height of the rectangle.
839
+ * @returns The newly created `SvgShape` instance representing the rectangle.
840
+ */
841
+ rect( x: number, y: number, w: number, h: number ): SvgShape {
842
+
843
+ if( h<0 ) {
844
+ y = y+h;
845
+ h = -h;
846
+ }
847
+
848
+ const shape = new SvgShape( 'rect' );
849
+ shape.setAttr( 'x', num(x)+'' );
850
+ shape.setAttr( 'y', num(y)+'' );
851
+ shape.setAttr( 'width', num(w)+'' );
852
+ shape.setAttr( 'height', num(h)+'' );
853
+ return this.append( shape );
854
+ }
855
+
856
+ /**
857
+ * Creates and appends an SVG group element (`<g>`) to this group.
858
+ * @param id - Optional. An ID for the new group.
859
+ * @returns The newly created `SvgGroup` instance.
860
+ */
861
+ group( id?: string ) {
862
+ const group = new SvgGroup( );
863
+ if( id ) {
864
+ group.setAttr( 'id', id );
865
+ }
866
+
867
+ return this.append( group );
868
+ }
869
+
870
+ /**
871
+ *
872
+ * Creates and appends an SVG linear gradient definition to this group.
873
+ *
874
+ * @example
875
+ * ```typescript
876
+ * const gradient = svgGroup.linear_gradient('0%', '0%', '0%', '100%')
877
+ * .addStop(0, 'red')
878
+ * .addStop(100, 'green');
879
+ * svgGroup.rect(0, 0, 100, 100).fill(gradient.id);
880
+ * ```
881
+ * @param x1 - The x-coordinate of the starting point of the gradient vector.
882
+ * @param y1 - The y-coordinate of the starting point of the gradient vector.
883
+ * @param x2 - The x-coordinate of the ending point of the gradient vector.
884
+ * @param y2 - The y-coordinate of the ending point of the gradient vector.
885
+ * @returns The newly created `SvgGradient` instance.
886
+ */
887
+
888
+ linear_gradient( x1: number_or_perc, y1: number_or_perc, x2: number_or_perc, y2: number_or_perc ) {
889
+ const grad = new SvgGradient( x1, y1, x2, y2 );
890
+ return this.append( grad );
891
+ }
892
+
893
+ /**
894
+ * Clears all child elements from this SVG group.
895
+ *
896
+ * @returns void
897
+ */
898
+
899
+ clear( ) {
900
+ const dom = this._dom;
901
+ while( dom.firstChild ) {
902
+ dom.removeChild( dom.firstChild );
903
+ }
904
+ }
905
+ }
906
+
907
+
908
+
909
+
910
+
911
+ /**
912
+ * A specialized `SvgGroup` that provides methods for adding SVG definitions like clip paths and patterns.
913
+ * It extends `SvgGroup` to inherit common SVG element functionalities and acts as a container for definitions.
914
+ */
915
+ export class SvgBuilder extends SvgGroup {
916
+ private static g_clip_id = 1;
917
+ private static g_pat_id = 1;
918
+
919
+ /**
920
+ * Creates an instance of `SvgBuilder`.
921
+ */
922
+ constructor( ) {
923
+ super( );
924
+ }
925
+
926
+ /**
927
+ * Adds an SVG clip path definition to the builder.
928
+ *
929
+ * @param x - The x-coordinate of the top-left corner of the clipping rectangle.
930
+ * @param y - The y-coordinate of the top-left corner of the clipping rectangle.
931
+ * @param w - The width of the clipping rectangle.
932
+ * @param h - The height of the clipping rectangle.
933
+ * @returns An object containing the generated `id` for the clip path and the `SvgGroup` instance representing the clip path.
934
+ */
935
+ addClip( x: number, y: number, w: number, h: number ) {
936
+ /**
937
+ * Adds an SVG clip path definition to the builder.
938
+ *
939
+ * @param x - The x-coordinate of the top-left corner of the clipping rectangle.
940
+ * @param y - The y-coordinate of the top-left corner of the clipping rectangle.
941
+ * @param w - The width of the clipping rectangle.
942
+ * @param h - The height of the clipping rectangle.
943
+ * @returns An object containing the generated `id` for the clip path and the `SvgGroup` instance representing the clip path.
944
+ */
945
+ const id = 'clip-'+SvgBuilder.g_clip_id++;
946
+ const clip = new SvgGroup( 'clipPath' );
947
+ clip.setAttr('id', id );
948
+ clip.rect( x, y, w, h );
949
+
950
+ this.append(clip);
951
+ return {id,clip};
952
+ }
953
+
954
+ /**
955
+ * Adds an SVG pattern definition to the builder.
956
+ *
957
+ * @param x - The x-coordinate of the pattern tile's top-left corner.
958
+ * @param y - The y-coordinate of the pattern tile's top-left corner.
959
+ * @param w - The width of the pattern tile.
960
+ * @param h - The height of the pattern tile.
961
+ * @returns An object containing the generated `id` for the pattern and the `SvgGroup` instance representing the pattern.
962
+ */
963
+ addPattern( x: number, y: number, w: number, h: number ) {
964
+ /**
965
+ * Adds an SVG pattern definition to the builder.
966
+ *
967
+ * @param x - The x-coordinate of the pattern tile's top-left corner.
968
+ * @param y - The y-coordinate of the pattern tile's top-left corner.
969
+ * @param w - The width of the pattern tile.
970
+ * @param h - The height of the pattern tile.
971
+ * @returns An object containing the generated `id` for the pattern and the `SvgGroup` instance representing the pattern.
972
+ */
973
+ const id = 'pat-'+SvgBuilder.g_pat_id++;
974
+
975
+ const pat = new SvgGroup( 'pattern' );
976
+ pat.setAttr( 'id', id );
977
+ pat.setAttr( 'x', num(x)+'' );
978
+ pat.setAttr( 'y', num(y)+'' );
979
+ pat.setAttr( 'width', num(w)+'' );
980
+ pat.setAttr( 'height', num(h)+'' );
981
+ pat.setAttr( 'patternUnits', "userSpaceOnUse" );
982
+
983
+ this.append(pat);
984
+ return {id,pat};
985
+ }
986
+ }
987
+
988
+
989
+
990
+ /**
991
+ * Properties for the `SvgComponent`.
992
+ */
993
+
994
+ export interface SvgProps extends ComponentProps {
995
+ viewbox?: string;
996
+ svg?: SvgBuilder;
997
+ }
998
+
999
+ /**
1000
+ * A component that renders an SVG element, acting as a container for SVG graphics.
1001
+ * It extends `Component` and provides methods for setting SVG content.
1002
+ */
1003
+
1004
+ export class SvgComponent<P extends SvgProps = SvgProps> extends Component<P> {
1005
+
1006
+ /**
1007
+ * Creates an instance of `SvgComponent`.
1008
+ * @param props - The properties for the SVG component.
1009
+ */
1010
+ constructor( props: P ) {
1011
+ super( { ...props, tag: "svg", ns: SVG_NS } );
1012
+
1013
+ this.setAttribute( 'xmlns', SVG_NS );
1014
+
1015
+ if( props.viewbox ) {
1016
+ this.setAttribute( "viewBox", props.viewbox );
1017
+ }
1018
+
1019
+ if( props.svg ) {
1020
+ this.dom.appendChild( props.svg.getDom() );
1021
+ }
1022
+ }
1023
+
1024
+ /**
1025
+ * Sets the entire SVG content of the component using an `SvgBuilder`.
1026
+ * Any existing content will be cleared.
1027
+ * @param bld - The `SvgBuilder` instance containing the SVG elements to render.
1028
+ */
1029
+ setSvg( bld: SvgBuilder ) {
1030
+ this.clearContent( );
1031
+ this.dom.appendChild( bld.getDom() );
1032
+ }
1033
+
1034
+ /**
1035
+ * Appends one or more `SvgItem` instances directly to the SVG component's DOM.
1036
+ *
1037
+ * @param items - A spread array of `SvgItem` instances to append.
1038
+ */
1039
+ addItems( ...items: SvgItem[] ) {
1040
+ items.forEach( item => this.dom.appendChild( item.getDom() ) );
1041
+ }
1042
+ }