plotboilerplate 1.24.0 → 1.26.0

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 (222) hide show
  1. package/README.md +14 -1
  2. package/dist/index.esm.js +433 -162
  3. package/dist/index.esm.js.map +1 -1
  4. package/dist/index.esm.min.js +1 -1
  5. package/dist/index.esm.min.js.map +1 -1
  6. package/dist/plotboilerplate.browser.min.js +1 -1
  7. package/dist/plotboilerplate.browser.min.js.map +1 -1
  8. package/dist/plotboilerplate.js +12312 -12041
  9. package/dist/plotboilerplate.min.js +1 -1
  10. package/package.json +9 -5
  11. package/src/cjs/Bounds.d.ts +79 -25
  12. package/src/cjs/Bounds.js +102 -29
  13. package/src/cjs/Bounds.js.map +1 -1
  14. package/src/cjs/CircleSector.d.ts +3 -0
  15. package/src/cjs/CircleSector.js +28 -1
  16. package/src/cjs/CircleSector.js.map +1 -1
  17. package/src/cjs/PlotBoilerplate.d.ts +37 -6
  18. package/src/cjs/PlotBoilerplate.js +222 -116
  19. package/src/cjs/PlotBoilerplate.js.map +1 -1
  20. package/src/cjs/Polygon.d.ts +26 -7
  21. package/src/cjs/Polygon.js +56 -10
  22. package/src/cjs/Polygon.js.map +1 -1
  23. package/src/cjs/Triangle.js +2 -2
  24. package/src/cjs/Triangle.js.map +1 -1
  25. package/src/cjs/Vertex.d.ts +15 -3
  26. package/src/cjs/Vertex.js +18 -1
  27. package/src/cjs/Vertex.js.map +1 -1
  28. package/src/cjs/drawutilssvg.d.ts +2 -1
  29. package/src/cjs/drawutilssvg.js +9 -3
  30. package/src/cjs/drawutilssvg.js.map +1 -1
  31. package/src/cjs/interfaces/core.d.ts +28 -0
  32. package/src/cjs/interfaces/core.js +2 -0
  33. package/src/cjs/interfaces/core.js.map +1 -1
  34. package/src/cjs/utils/LissajousFigure.d.ts +34 -0
  35. package/src/cjs/utils/LissajousFigure.js +100 -0
  36. package/src/cjs/utils/LissajousFigure.js.map +1 -0
  37. package/src/cjs/utils/PerlinNoise.d.ts +19 -0
  38. package/src/cjs/utils/PerlinNoise.js +352 -0
  39. package/src/cjs/utils/PerlinNoise.js.map +1 -0
  40. package/src/cjs/utils/SVGPathUtils.d.ts +22 -0
  41. package/src/cjs/utils/SVGPathUtils.js +45 -0
  42. package/src/cjs/utils/SVGPathUtils.js.map +1 -0
  43. package/src/cjs/utils/algorithms/pixelCornersToRoundPaths.d.ts +23 -0
  44. package/src/cjs/utils/algorithms/pixelCornersToRoundPaths.js +270 -0
  45. package/src/cjs/utils/algorithms/pixelCornersToRoundPaths.js.map +1 -0
  46. package/src/cjs/utils/datastructures/Color.d.ts +147 -1
  47. package/src/cjs/utils/datastructures/Color.js +385 -41
  48. package/src/cjs/utils/datastructures/Color.js.map +1 -1
  49. package/src/cjs/utils/datastructures/ColorGradient.d.ts +79 -0
  50. package/src/cjs/utils/datastructures/ColorGradient.js +171 -0
  51. package/src/cjs/utils/datastructures/ColorGradient.js.map +1 -0
  52. package/src/cjs/utils/datastructures/DataGrid2d.d.ts +6 -0
  53. package/src/cjs/utils/datastructures/DataGrid2dArrayMatrix.d.ts +88 -0
  54. package/src/cjs/utils/datastructures/DataGrid2dArrayMatrix.js +155 -0
  55. package/src/cjs/utils/datastructures/DataGrid2dArrayMatrix.js.map +1 -0
  56. package/src/cjs/utils/datastructures/DataGrid2dListAdapter.d.ts +1 -7
  57. package/src/cjs/utils/datastructures/DataGrid2dListAdapter.js +3 -3
  58. package/src/cjs/utils/datastructures/DataGrid2dListAdapter.js.map +1 -1
  59. package/src/cjs/utils/datastructures/GeometryMesh.d.ts +49 -0
  60. package/src/cjs/utils/datastructures/GeometryMesh.js +58 -0
  61. package/src/cjs/utils/datastructures/GeometryMesh.js.map +1 -0
  62. package/src/cjs/utils/detectDarkMode.js +16 -7
  63. package/src/cjs/utils/detectDarkMode.js.map +1 -1
  64. package/src/cjs/utils/dom/components/ColorGradientPicker.d.ts +246 -0
  65. package/src/cjs/utils/dom/components/ColorGradientPicker.js +667 -0
  66. package/src/cjs/utils/dom/components/ColorGradientPicker.js.map +1 -0
  67. package/src/cjs/utils/dom/components/ColorGradientPickerJSX.d.ts +129 -0
  68. package/src/cjs/utils/dom/components/ColorGradientPickerJSX.js +520 -0
  69. package/src/cjs/utils/dom/components/ColorGradientPickerJSX.js.map +1 -0
  70. package/src/cjs/utils/dom/components/ColorGradientSelector.d.ts +118 -0
  71. package/src/cjs/utils/dom/components/ColorGradientSelector.js +327 -0
  72. package/src/cjs/utils/dom/components/ColorGradientSelector.js.map +1 -0
  73. package/src/cjs/utils/dom/components/Test.d.ts +7 -0
  74. package/src/cjs/utils/dom/components/Test.js +30 -0
  75. package/src/cjs/utils/dom/components/Test.js.map +1 -0
  76. package/src/cjs/utils/dom/guiSizeToggler.js +1 -1
  77. package/src/cjs/utils/dom/guiSizeToggler.js.map +1 -1
  78. package/src/cjs/utils/helpers/BezierPathInteractionHelper.d.ts +9 -3
  79. package/src/cjs/utils/helpers/BezierPathInteractionHelper.js +20 -13
  80. package/src/cjs/utils/helpers/BezierPathInteractionHelper.js.map +1 -1
  81. package/src/cjs/utils/helpers/CircleHelper.d.ts +10 -1
  82. package/src/cjs/utils/helpers/CircleHelper.js +13 -2
  83. package/src/cjs/utils/helpers/CircleHelper.js.map +1 -1
  84. package/src/cjs/utils/helpers/CircleSectorHelper.d.ts +2 -2
  85. package/src/cjs/utils/helpers/CircleSectorHelper.js +1 -0
  86. package/src/cjs/utils/helpers/CircleSectorHelper.js.map +1 -1
  87. package/src/cjs/utils/helpers/TriangleHelper.d.ts +57 -0
  88. package/src/cjs/utils/helpers/TriangleHelper.js +83 -0
  89. package/src/cjs/utils/helpers/TriangleHelper.js.map +1 -0
  90. package/src/cjs/utils/helpers/VEllipseHelper.d.ts +6 -4
  91. package/src/cjs/utils/helpers/VEllipseHelper.js +4 -2
  92. package/src/cjs/utils/helpers/VEllipseHelper.js.map +1 -1
  93. package/src/cjs/utils/helpers/VEllipseSectorHelper.d.ts +3 -2
  94. package/src/cjs/utils/helpers/VEllipseSectorHelper.js +1 -0
  95. package/src/cjs/utils/helpers/VEllipseSectorHelper.js.map +1 -1
  96. package/src/cjs/utils/parsers/LinearColorGradientParser.d.ts +78 -0
  97. package/src/cjs/utils/parsers/LinearColorGradientParser.js +224 -0
  98. package/src/cjs/utils/parsers/LinearColorGradientParser.js.map +1 -0
  99. package/src/cjs/utils/parsers/svg/types.d.ts +8 -8
  100. package/src/createDemoDrawables.js +1 -0
  101. package/src/esm/Bounds.d.ts +79 -25
  102. package/src/esm/Bounds.js +102 -29
  103. package/src/esm/Bounds.js.map +1 -1
  104. package/src/esm/CircleSector.d.ts +3 -0
  105. package/src/esm/CircleSector.js +28 -1
  106. package/src/esm/CircleSector.js.map +1 -1
  107. package/src/esm/PlotBoilerplate.d.ts +37 -6
  108. package/src/esm/PlotBoilerplate.js +222 -116
  109. package/src/esm/PlotBoilerplate.js.map +1 -1
  110. package/src/esm/Polygon.d.ts +26 -7
  111. package/src/esm/Polygon.js +53 -10
  112. package/src/esm/Polygon.js.map +1 -1
  113. package/src/esm/Triangle.js +2 -2
  114. package/src/esm/Triangle.js.map +1 -1
  115. package/src/esm/Vertex.d.ts +15 -3
  116. package/src/esm/Vertex.js +18 -1
  117. package/src/esm/Vertex.js.map +1 -1
  118. package/src/esm/drawutilssvg.d.ts +2 -1
  119. package/src/esm/drawutilssvg.js +9 -3
  120. package/src/esm/drawutilssvg.js.map +1 -1
  121. package/src/esm/interfaces/core.d.ts +28 -0
  122. package/src/esm/interfaces/core.js +2 -0
  123. package/src/esm/interfaces/core.js.map +1 -1
  124. package/src/esm/utils/LissajousFigure.d.ts +34 -0
  125. package/src/esm/utils/LissajousFigure.js +95 -0
  126. package/src/esm/utils/LissajousFigure.js.map +1 -0
  127. package/src/esm/utils/PerlinNoise.d.ts +19 -0
  128. package/src/esm/utils/PerlinNoise.js +346 -0
  129. package/src/esm/utils/PerlinNoise.js.map +1 -0
  130. package/src/esm/utils/SVGPathUtils.d.ts +22 -0
  131. package/src/esm/utils/SVGPathUtils.js +42 -0
  132. package/src/esm/utils/SVGPathUtils.js.map +1 -0
  133. package/src/esm/utils/algorithms/pixelCornersToRoundPaths.d.ts +23 -0
  134. package/src/esm/utils/algorithms/pixelCornersToRoundPaths.js +266 -0
  135. package/src/esm/utils/algorithms/pixelCornersToRoundPaths.js.map +1 -0
  136. package/src/esm/utils/datastructures/Color.d.ts +147 -1
  137. package/src/esm/utils/datastructures/Color.js +386 -42
  138. package/src/esm/utils/datastructures/Color.js.map +1 -1
  139. package/src/esm/utils/datastructures/ColorGradient.d.ts +79 -0
  140. package/src/esm/utils/datastructures/ColorGradient.js +166 -0
  141. package/src/esm/utils/datastructures/ColorGradient.js.map +1 -0
  142. package/src/esm/utils/datastructures/DataGrid2d.d.ts +6 -0
  143. package/src/esm/utils/datastructures/DataGrid2dArrayMatrix.d.ts +88 -0
  144. package/src/esm/utils/datastructures/DataGrid2dArrayMatrix.js +150 -0
  145. package/src/esm/utils/datastructures/DataGrid2dArrayMatrix.js.map +1 -0
  146. package/src/esm/utils/datastructures/DataGrid2dListAdapter.d.ts +1 -7
  147. package/src/esm/utils/datastructures/DataGrid2dListAdapter.js +3 -3
  148. package/src/esm/utils/datastructures/DataGrid2dListAdapter.js.map +1 -1
  149. package/src/esm/utils/datastructures/GeometryMesh.d.ts +49 -0
  150. package/src/esm/utils/datastructures/GeometryMesh.js +53 -0
  151. package/src/esm/utils/datastructures/GeometryMesh.js.map +1 -0
  152. package/src/esm/utils/detectDarkMode.js +16 -7
  153. package/src/esm/utils/detectDarkMode.js.map +1 -1
  154. package/src/esm/utils/dom/components/ColorGradientPicker.d.ts +246 -0
  155. package/src/esm/utils/dom/components/ColorGradientPicker.js +743 -0
  156. package/src/esm/utils/dom/components/ColorGradientPicker.js.map +1 -0
  157. package/src/esm/utils/dom/components/ColorGradientPickerJSX.d.ts +129 -0
  158. package/src/esm/utils/dom/components/ColorGradientPickerJSX.js +582 -0
  159. package/src/esm/utils/dom/components/ColorGradientPickerJSX.js.map +1 -0
  160. package/src/esm/utils/dom/components/ColorGradientSelector.d.ts +118 -0
  161. package/src/esm/utils/dom/components/ColorGradientSelector.js +339 -0
  162. package/src/esm/utils/dom/components/ColorGradientSelector.js.map +1 -0
  163. package/src/esm/utils/dom/components/Test.d.ts +7 -0
  164. package/src/esm/utils/dom/components/Test.js +26 -0
  165. package/src/esm/utils/dom/components/Test.js.map +1 -0
  166. package/src/esm/utils/dom/guiSizeToggler.js +1 -1
  167. package/src/esm/utils/dom/guiSizeToggler.js.map +1 -1
  168. package/src/esm/utils/helpers/BezierPathInteractionHelper.d.ts +9 -3
  169. package/src/esm/utils/helpers/BezierPathInteractionHelper.js +20 -13
  170. package/src/esm/utils/helpers/BezierPathInteractionHelper.js.map +1 -1
  171. package/src/esm/utils/helpers/CircleHelper.d.ts +10 -1
  172. package/src/esm/utils/helpers/CircleHelper.js +13 -2
  173. package/src/esm/utils/helpers/CircleHelper.js.map +1 -1
  174. package/src/esm/utils/helpers/CircleSectorHelper.d.ts +2 -2
  175. package/src/esm/utils/helpers/CircleSectorHelper.js +1 -0
  176. package/src/esm/utils/helpers/CircleSectorHelper.js.map +1 -1
  177. package/src/esm/utils/helpers/TriangleHelper.d.ts +57 -0
  178. package/src/esm/utils/helpers/TriangleHelper.js +78 -0
  179. package/src/esm/utils/helpers/TriangleHelper.js.map +1 -0
  180. package/src/esm/utils/helpers/VEllipseHelper.d.ts +6 -4
  181. package/src/esm/utils/helpers/VEllipseHelper.js +4 -2
  182. package/src/esm/utils/helpers/VEllipseHelper.js.map +1 -1
  183. package/src/esm/utils/helpers/VEllipseSectorHelper.d.ts +3 -2
  184. package/src/esm/utils/helpers/VEllipseSectorHelper.js +1 -0
  185. package/src/esm/utils/helpers/VEllipseSectorHelper.js.map +1 -1
  186. package/src/esm/utils/parsers/LinearColorGradientParser.d.ts +78 -0
  187. package/src/esm/utils/parsers/LinearColorGradientParser.js +217 -0
  188. package/src/esm/utils/parsers/LinearColorGradientParser.js.map +1 -0
  189. package/src/esm/utils/parsers/svg/types.d.ts +8 -8
  190. package/src/external.d.ts +9 -0
  191. package/src/ts/Bounds.ts +107 -22
  192. package/src/ts/CircleSector.ts +39 -6
  193. package/src/ts/PlotBoilerplate.ts +233 -105
  194. package/src/ts/Polygon.ts +68 -14
  195. package/src/ts/Triangle.ts +2 -2
  196. package/src/ts/Vertex.ts +21 -3
  197. package/src/ts/drawutilssvg.ts +8 -3
  198. package/src/ts/interfaces/core.ts +31 -0
  199. package/src/ts/utils/LissajousFigure.ts +110 -0
  200. package/src/ts/utils/PerlinNoise.ts +368 -0
  201. package/src/ts/utils/SVGPathUtils.ts +55 -0
  202. package/src/ts/utils/algorithms/pixelCornersToRoundPaths.ts +379 -0
  203. package/src/ts/utils/datastructures/Color.ts +423 -44
  204. package/src/ts/utils/datastructures/ColorGradient.ts +196 -0
  205. package/src/ts/utils/datastructures/DataGrid2d.ts +7 -0
  206. package/src/ts/utils/datastructures/DataGrid2dArrayMatrix.ts +182 -0
  207. package/src/ts/utils/datastructures/DataGrid2dListAdapter.ts +4 -11
  208. package/src/ts/utils/datastructures/GeometryMesh.ts +79 -0
  209. package/src/ts/utils/detectDarkMode.ts +17 -8
  210. package/src/ts/utils/dom/components/ColorGradientPicker.tsx +889 -0
  211. package/src/ts/utils/dom/components/ColorGradientSelector.tsx +452 -0
  212. package/src/ts/utils/dom/components/Test.tsx +44 -0
  213. package/src/ts/utils/dom/components/_ColorGradientPicker_raw.ts_ +392 -0
  214. package/src/ts/utils/dom/guiSizeToggler.ts +1 -1
  215. package/src/ts/utils/helpers/BezierPathInteractionHelper.ts +25 -15
  216. package/src/ts/utils/helpers/CircleHelper.ts +17 -3
  217. package/src/ts/utils/helpers/CircleSectorHelper.ts +4 -2
  218. package/src/ts/utils/helpers/TriangleHelper.ts +102 -0
  219. package/src/ts/utils/helpers/VEllipseHelper.ts +6 -4
  220. package/src/ts/utils/helpers/VEllipseSectorHelper.ts +3 -2
  221. package/src/ts/utils/parsers/LinearColorGradientParser.ts +297 -0
  222. package/src/ts/utils/parsers/svg/types.ts +8 -8
@@ -0,0 +1,889 @@
1
+ /**
2
+ * Approach for a 'simple' color gradient picker for my PlotBoilerplate library.
3
+ *
4
+ * As it turned out a bit more complex in the end – due to the fact that color gradient
5
+ * picking is not a super simple task – so I used JSX here. Thus a JSX library is required
6
+ * to get this running: NoReact.
7
+ *
8
+ *
9
+ * @author Ikaros Kappler
10
+ * @date 2025-06-27
11
+ * @modified 2025-09-10 Fixing a color select bug on MacOS.
12
+ * @version 1.0.0
13
+ */
14
+
15
+ import * as NoReact from "noreact";
16
+ import { JsxElement } from "typescript";
17
+
18
+ import { Color } from "../../datastructures/Color";
19
+ import { ColorGradient, ColorGradientItem } from "../../datastructures/ColorGradient";
20
+
21
+ export type ColorGradientChangeListener = (colorGradient: ColorGradient, source: ColorGradientPicker) => void;
22
+
23
+ export class ColorGradientPicker {
24
+ private readonly baseID: number;
25
+ private readonly elementID: string;
26
+
27
+ private container: HTMLElement;
28
+ private containerRef: NoReact.Ref<HTMLDivElement>;
29
+ private _sliderElementRefs: Array<NoReact.Ref<HTMLInputElement>> = [];
30
+ private colorInputRef: NoReact.Ref<HTMLInputElement>;
31
+ private colorIndicatorColorButtonRef: NoReact.Ref<HTMLButtonElement>;
32
+ private colorIndicatorRemoveButtonRef: NoReact.Ref<HTMLButtonElement>;
33
+ private colorInputContainerRef: NoReact.Ref<HTMLDivElement>;
34
+ private sliderMin: number = 0;
35
+ private sliderMax: number = 100;
36
+
37
+ // Only used during initialization!
38
+
39
+ private isMobileMode: boolean = false;
40
+ private css_indicatorWidth_num = 1.0;
41
+ private css_indicatorWidth = "1em";
42
+ private css_indicatorHeight = "1em";
43
+ private css_thumbWidth = "0.5em";
44
+ private css_thumbHeight = "1.333em";
45
+ private css_containerHeight = "20px";
46
+
47
+ private colorGradient: ColorGradient;
48
+
49
+ private installedChangeListeners: Array<ColorGradientChangeListener> = [];
50
+
51
+ private __mouseDownPosition: { x: number; y: number } = null;
52
+
53
+ /**
54
+ * The constructor: creates a new color gradient picker in the given container.
55
+ * If no container or ID is given then a new unbound `container` will be created (DIV).
56
+ *
57
+ * Pass a container ID or nothing – in the latter case the constructor will create
58
+ * a new DIV element.
59
+ *
60
+ * @param {string?} containerID - (optional) If you want to use an existing container (should be a DIV).
61
+ * @param {boolean?} isMobileMode - (optional) If `true` then the elements are rendered in double size.
62
+ */
63
+ constructor(containerID?: string, isMobileMode?: boolean) {
64
+ if (containerID) {
65
+ const cont = document.getElementById(containerID);
66
+ if (!cont) {
67
+ throw "Cannot create ColorGradientPicker. Component ID does not exist.";
68
+ }
69
+ this.container = cont;
70
+ } else {
71
+ this.container = document.createElement("div");
72
+ }
73
+ this.baseID = Math.floor(Math.random() * 65535);
74
+ this.elementID = `color-gradient-picker-${this.baseID}`;
75
+ this.colorGradient = ColorGradient.createDefault();
76
+
77
+ this.isMobileMode = Boolean(isMobileMode);
78
+ if (isMobileMode) {
79
+ // Double size :)
80
+ this.css_indicatorWidth_num = 2.0;
81
+ this.css_indicatorWidth = "2em";
82
+ this.css_indicatorHeight = "2em";
83
+ this.css_thumbWidth = "1.0em";
84
+ this.css_thumbHeight = "2.666em";
85
+ this.css_containerHeight = "40px";
86
+ }
87
+
88
+ document.head.appendChild(this.__createCustomStylesElement());
89
+ this.container.append(this._render());
90
+ this.__initializeDataSets();
91
+ this.__updateColorIndicator(0);
92
+ this.__updateBackgroundGradient();
93
+ } // END constructor
94
+
95
+ public setGradient(gradient: ColorGradient, fireChangeEvent?: boolean): void {
96
+ // We are lazy: just throw away everything and rebuild all.
97
+ this.__removeAllChildNodes(this.container);
98
+ this.colorGradient = gradient;
99
+ this.container.append(this._render());
100
+ this.__initializeDataSets();
101
+ this.__updateColorIndicator(0);
102
+ this.__updateBackgroundGradient();
103
+ if (fireChangeEvent) {
104
+ this.__fireChangeEvent();
105
+ }
106
+ }
107
+
108
+ // +---------------------------------------------------------------------------------
109
+ // | A helper function to remove all child nodes.
110
+ // +-------------------------------
111
+ private __removeAllChildNodes(node: Node) {
112
+ while (node.lastChild) {
113
+ node.removeChild(node.lastChild);
114
+ }
115
+ }
116
+
117
+ // +---------------------------------------------------------------------------------
118
+ // | Used during initialization: define dataset values for each slider.
119
+ // +-------------------------------
120
+ private __initializeDataSets() {
121
+ for (var i = 0; i < this._sliderElementRefs.length; i++) {
122
+ const sliderColor = this.colorGradient.values[i].color;
123
+ // console.log("Setting up slider color data set", i, sliderColor, sliderColor.cssRGB(), sliderColor.cssHEX());
124
+ this._sliderElementRefs[i].current.dataset.colorValue = sliderColor.cssRGB();
125
+ this._sliderElementRefs[i].current.dataset.colorValueHEX = sliderColor.cssHEX();
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Adds a new color gradient change listener to this ColorGradientPicker.
131
+ *
132
+ * @param {ColorGradientChangeListener} listener - The listener to add.
133
+ * @returns {boolean} True, if the listener was added and did not exist before.
134
+ */
135
+ addChangeListener(listener: ColorGradientChangeListener): boolean {
136
+ for (var i = 0; i < this.installedChangeListeners.length; i++) {
137
+ if (this.installedChangeListeners[i] === listener) {
138
+ return false;
139
+ }
140
+ }
141
+ this.installedChangeListeners.push(listener);
142
+ return true;
143
+ }
144
+
145
+ /**
146
+ *
147
+ * @param {ColorGradientChangeListener} listener The listener to remove.
148
+ * @returns {boolean} True, if the listener existed and has been removed.
149
+ */
150
+ removeChangeListener(listener: ColorGradientChangeListener): boolean {
151
+ for (var i = 0; i < this.installedChangeListeners.length; i++) {
152
+ if (this.installedChangeListeners[i] === listener) {
153
+ this.installedChangeListeners.splice(i, 1);
154
+ return true;
155
+ }
156
+ }
157
+ return false;
158
+ }
159
+
160
+ /**
161
+ * Fires a change event to all installed listeners.
162
+ */
163
+ private __fireChangeEvent() {
164
+ const newColorGradient = this.getColorGradient();
165
+ for (var i = 0; i < this.installedChangeListeners.length; i++) {
166
+ this.installedChangeListeners[i](newColorGradient, this);
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Creates a new color range input element.
172
+ *
173
+ * @private
174
+ * @param {number} baseID
175
+ * @param index
176
+ * @param sliderMin
177
+ * @param sliderMax
178
+ * @param initialValue
179
+ * @param initialColor
180
+ * @returns
181
+ */
182
+ private __createColorRangeInput(
183
+ index: number,
184
+ sliderMin: number,
185
+ sliderMax: number,
186
+ initialValue: number,
187
+ initialColor: Color
188
+ ): JsxElement {
189
+ const sliderHandler = this.__createSliderChangeHandler();
190
+ const ref: NoReact.Ref<HTMLInputElement> = NoReact.useRef<HTMLInputElement>();
191
+ // this._sliderElementRefs.push(ref);
192
+ this._sliderElementRefs.splice(index, 0, ref);
193
+ // console.log("new _sliderElementRefs", this._sliderElementRefs);
194
+
195
+ // Update all elements to the right of the new elelemt
196
+ this.__updateSliderDataSetIndices(index + 1);
197
+
198
+ return (
199
+ <input
200
+ id={`rage-slider-${this.baseID}-${index}`}
201
+ type="range"
202
+ min={sliderMin}
203
+ max={sliderMax}
204
+ value={initialValue}
205
+ style={{ pos: "absolute", l: "0px", t: "0px", w: "100%", h: "60%" }}
206
+ data-range-slider-index={`${index}`}
207
+ data-colorValue={initialColor.cssRGB()}
208
+ data-colorValueHEX={initialColor.cssHEX()}
209
+ onChange={sliderHandler}
210
+ onClick={sliderHandler}
211
+ ref={ref}
212
+ />
213
+ );
214
+ }
215
+
216
+ /**
217
+ * Get the absolute length of this range, in slider units.
218
+ * @returns
219
+ */
220
+ getRangeLength(): number {
221
+ return this.sliderMax - this.sliderMin;
222
+ }
223
+
224
+ /**
225
+ * Creates a callback function for range slider.
226
+ *
227
+ * @returns
228
+ */
229
+ private __createSliderChangeHandler = (): ((e: Event) => boolean) => {
230
+ const _self = this;
231
+ return (e: Event): boolean => {
232
+ const targetSlider: HTMLInputElement = e.target as HTMLInputElement;
233
+ // console.log("Clicked", targetSlider ? targetSlider.getAttribute("id") : null);
234
+ if (!targetSlider) {
235
+ return false;
236
+ }
237
+ const currentSliderValue: number = Number.parseFloat(targetSlider.value);
238
+ const rangeSliderIndexRaw: string | undefined = targetSlider.dataset["rangeSliderIndex"];
239
+ // console.log("rangeSliderIndexRaw", rangeSliderIndexRaw);
240
+ if (!rangeSliderIndexRaw) {
241
+ return false;
242
+ }
243
+ const rangeSliderIndex: number = Number.parseInt(rangeSliderIndexRaw);
244
+ var leftSliderValue = this.__getSliderValue(rangeSliderIndex - 1, _self.sliderMin);
245
+ var rightSliderValue = this.__getSliderValue(rangeSliderIndex + 1, _self.sliderMax);
246
+ if (leftSliderValue >= currentSliderValue) {
247
+ targetSlider.value = `${leftSliderValue}`;
248
+ _self.__updateBackgroundGradient();
249
+ _self.__updateColorIndicator(rangeSliderIndex);
250
+ return false;
251
+ } else if (rightSliderValue <= currentSliderValue) {
252
+ targetSlider.value = `${rightSliderValue}`;
253
+ _self.__updateBackgroundGradient();
254
+ _self.__updateColorIndicator(rangeSliderIndex);
255
+
256
+ return false;
257
+ } else {
258
+ _self.__updateBackgroundGradient();
259
+ _self.__updateColorIndicator(rangeSliderIndex);
260
+ return true;
261
+ }
262
+ };
263
+ };
264
+
265
+ /**
266
+ * Handles color value changes.
267
+ *
268
+ * @returns
269
+ */
270
+ __colorChangeHandler(): (_evt: Event) => boolean {
271
+ const _self = this;
272
+ return (_evt: Event): boolean => {
273
+ // console.log("__colorChangeHandler");
274
+ const activeSliderIndex_raw = this.colorInputRef.current.dataset.activeSliderIndex;
275
+ if (!activeSliderIndex_raw) {
276
+ console.warn(
277
+ "Cannot update color indicator; no active range slider set. This is likely a program error and should not happen."
278
+ );
279
+ return false;
280
+ }
281
+ const activeSliderIndex = Number.parseInt(activeSliderIndex_raw);
282
+ if (Number.isNaN(activeSliderIndex) || activeSliderIndex < 0 || activeSliderIndex >= _self._sliderElementRefs.length) {
283
+ console.warn(
284
+ "Cannot update color indicator; active index invalid or out of bounds. This is likely a program error and should not happen.",
285
+ activeSliderIndex
286
+ );
287
+ return false;
288
+ }
289
+
290
+ const newColor: string = _self.colorInputRef.current.value;
291
+ const rangeSlider: HTMLInputElement = _self._sliderElementRefs[activeSliderIndex].current;
292
+ rangeSlider.dataset.colorValue = newColor;
293
+ // rangeSlider.dataset["colorValue"] = newColor;
294
+ this.colorIndicatorColorButtonRef.current.style["background-color"] = newColor;
295
+ _self.__updateBackgroundGradient();
296
+ _self.__fireChangeEvent();
297
+
298
+ return true;
299
+ };
300
+ }
301
+
302
+ /**
303
+ * Removed the current color slider from the DOM and highlights the left neighbour.
304
+ *
305
+ * @returns {boolean} True if the element could be successfully removed.
306
+ */
307
+ private __handleRemoveColor(): boolean {
308
+ // const colorInput : HTMLInputElement | null = evt.target;
309
+ // console.log("__colorChangeHandler");
310
+ const activeSliderIndex_raw = this.colorInputRef.current.dataset.activeSliderIndex;
311
+ if (!activeSliderIndex_raw) {
312
+ console.warn("Cannot remove color indicator; no active range slider set.");
313
+ return false;
314
+ }
315
+ const activeSliderIndex = Number.parseInt(activeSliderIndex_raw);
316
+ if (Number.isNaN(activeSliderIndex) || activeSliderIndex < 0 || activeSliderIndex >= this._sliderElementRefs.length) {
317
+ console.warn("Cannot remove color indicator; active index invalid or out of bounds.", activeSliderIndex);
318
+ return false;
319
+ }
320
+
321
+ if (activeSliderIndex === 0 || activeSliderIndex + 1 === this.getRangeLength()) {
322
+ console.warn("Cannot remove color indicator; leftmost and rightmost slider must not be removed.", activeSliderIndex);
323
+ return false;
324
+ }
325
+
326
+ const sliderElementRef: NoReact.Ref<HTMLInputElement> = this._sliderElementRefs[activeSliderIndex];
327
+ sliderElementRef.current.remove();
328
+ this._sliderElementRefs.splice(activeSliderIndex, 1);
329
+
330
+ // Update all elements to the right of the removed elelemt
331
+ this.__updateSliderDataSetIndices(activeSliderIndex);
332
+
333
+ // Set left element active
334
+ this.__updateColorIndicator(activeSliderIndex - 1);
335
+ this.__updateBackgroundGradient();
336
+ this.__fireChangeEvent();
337
+ return true;
338
+ }
339
+
340
+ /**
341
+ * Once a slider element was added or removed then the following indices must be updated.
342
+ *
343
+ * @param {number} startIndex - The slider index to start updating at.
344
+ */
345
+ __updateSliderDataSetIndices(startIndex: number) {
346
+ // Update all elements to the right of the new elelemt
347
+ for (var i = startIndex; i < this._sliderElementRefs.length; i++) {
348
+ this._sliderElementRefs[i].current.setAttribute("data-range-slider-index", `${i}`);
349
+ this._sliderElementRefs[i].current.setAttribute("id", `rage-slider-${this.baseID}-${i}`);
350
+ }
351
+ }
352
+
353
+ /**
354
+ * To avoid too many sliders added on each click, check if the mouse was moved in the meantime (detect drag event).
355
+ *
356
+ * @returns
357
+ */
358
+ private __containerMouseDownHandler(): (event: MouseEvent) => void {
359
+ const _self = this;
360
+ return (event: MouseEvent) => {
361
+ _self.__mouseDownPosition = { x: event.clientX, y: event.clientY };
362
+ // console.log("__containerMouseDownHandler _self.__mouseDownPosition", _self.__mouseDownPosition);
363
+ };
364
+ }
365
+
366
+ /**
367
+ * Creates a handler for click events on the container. If the click event is far
368
+ * enough from existing sliders, then it can be added.
369
+ *
370
+ * @returns
371
+ */
372
+ private __containerClickHandler(): (evt: MouseEvent) => void {
373
+ const _self = this;
374
+ const maxDifference = _self.getRangeLength() * 0.03;
375
+ return (evt: MouseEvent): void => {
376
+ // console.log("Container click handler");
377
+ const relativeValue: number = _self.__clickEventToRelativeValue(evt);
378
+ if (relativeValue < 0 || relativeValue > 1.0) {
379
+ // Clicked somewhere outside the range
380
+ return;
381
+ }
382
+
383
+ // Check if element was moved in the meantime
384
+ if (_self.__mouseDownPosition && Math.abs(_self.__mouseDownPosition.x - evt.clientX) >= 10.0) {
385
+ // Ignore event if mouse was moved more than 10 pixels between down- and up-event
386
+ // console.log("Mouse was moved more than 10 px. Cancelling.");
387
+ _self.__mouseDownPosition = null;
388
+ return;
389
+ }
390
+
391
+ const absoluteValue: number = this.__relativeToAbsolute(relativeValue);
392
+ // console.log("click", "relativeValue", relativeValue, "absoluteValue", absoluteValue);
393
+ // Check if click position (ratio) is far enough away from any slider
394
+ const [leftSliderIndex, closestSliderValue] = _self.__locateClosestSliderValue(absoluteValue);
395
+ const diff: number = Math.abs(closestSliderValue - absoluteValue);
396
+ if (diff >= maxDifference) {
397
+ // console.log("Add slider here");
398
+ _self.__addSliderAt(absoluteValue, leftSliderIndex);
399
+ } else {
400
+ // console.debug("Don't add slider here.");
401
+ }
402
+ // Finally clear mousedown-position
403
+ _self.__mouseDownPosition = null;
404
+ };
405
+ }
406
+
407
+ /**
408
+ * Find that slider (index) that's value is closest to the given absolute value. The function will return
409
+ * the closest value and the left index, indicating the containing interval index.
410
+ *
411
+ * @param {number} absoluteValue - The value to look for.
412
+ * @returns {[number,number]} A pair of left slider index and closest value.
413
+ */
414
+ private __locateClosestSliderValue(absoluteValue: number): [number, number] {
415
+ // As the colorGradient always reflects the slider values we can just ust the color gradient itself to search.
416
+ const relativeValue: number = this.__absoluteToRelative(absoluteValue);
417
+ const colorGradientEntry: [number, ColorGradientItem] = this.colorGradient.locateClosestRatio(relativeValue);
418
+ return [colorGradientEntry[0], this.__relativeToAbsolute(colorGradientEntry[1].ratio)];
419
+ }
420
+
421
+ /**
422
+ * Convert the click event to the relative x value in [0..1].
423
+ *
424
+ * @param {MouseEvent} evt - The mouse event.
425
+ * @returns {number} The relative x value.
426
+ */
427
+ private __clickEventToRelativeValue(evt: MouseEvent): number {
428
+ const target: HTMLDivElement = evt.target as HTMLDivElement;
429
+ const rect: DOMRect = target.getBoundingClientRect();
430
+ const x: number = evt.clientX - rect.left; //x position within the element.
431
+ const width: number = rect.width; // target.clientWidth;
432
+ const relativeValue: number = x / width;
433
+ // console.log("width", width, "x", x, "relativeValue", relativeValue);
434
+ return relativeValue;
435
+ }
436
+
437
+ /**
438
+ * Adds a slider at the given interval index.
439
+ *
440
+ * @param {number} absoluteValue - The absolute new slider value.
441
+ * @param {number} leftSliderIndex - The new slider's position (interval index).
442
+ */
443
+ private __addSliderAt(absoluteValue: number, leftSliderIndex: number) {
444
+ // console.log("__addSliderAt", "absoluteValue", absoluteValue, "leftSliderIndex", leftSliderIndex);
445
+ const leftSlider: HTMLInputElement = this._sliderElementRefs[leftSliderIndex].current;
446
+ // const colorAtPosition = this.__getSliderColorAt(relativeValue);
447
+ const newColor: Color = this.__getSliderColorAt(absoluteValue);
448
+ const newSliderIndex: number = leftSliderIndex + 1;
449
+ const _newSlider: JsxElement = this.__createColorRangeInput(
450
+ newSliderIndex,
451
+ this.sliderMin,
452
+ this.sliderMax,
453
+ absoluteValue,
454
+ newColor // initialColor: string
455
+ );
456
+ const sliderRef = this._sliderElementRefs[newSliderIndex];
457
+ leftSlider.after(sliderRef.current);
458
+ sliderRef.current.dataset.colorValue = newColor.cssRGB();
459
+ sliderRef.current.dataset.colorValueHEX = newColor.cssHEX();
460
+ // No visible change, but let's reflect this in the output gradient anyway
461
+ this.__updateBackgroundGradient();
462
+ // Highlight the newly added range slider
463
+ this.__updateColorIndicator(newSliderIndex);
464
+ this.__fireChangeEvent();
465
+ }
466
+
467
+ /**
468
+ * Get the slider color at the given absolute value. That value must be inside the [MIN_VALUE, MAX_VALUE] interval.
469
+ * @param absoluteValue
470
+ * @returns
471
+ */
472
+ private __getSliderColorAt(absoluteValue: number): Color {
473
+ // console.log("__getSliderColorAt", "absoluteValue", absoluteValue);
474
+ // Locate interval
475
+ const leftIndex: number = this.__locateIntervalAt(absoluteValue);
476
+ const leftSliderValue: number = this.__getSliderValue(leftIndex, NaN);
477
+ const rightSliderValue: number = this.__getSliderValue(leftIndex + 1, NaN);
478
+ if (Number.isNaN(leftSliderValue) || Number.isNaN(rightSliderValue)) {
479
+ console.warn(
480
+ `[Warn] Failed to determine left/right slider values at indices ${leftIndex} or ${rightSliderValue}. Cannot proceed for absolute value ${absoluteValue}.`
481
+ );
482
+ return null;
483
+ }
484
+ const leftColorString: string = this.__getSliderColorString(leftIndex, null);
485
+ const rightColorString: string = this.__getSliderColorString(leftIndex + 1, null);
486
+ if (!leftColorString || !rightColorString) {
487
+ console.warn(
488
+ `[Warn] Failed to determine left/right color string values at indices ${leftIndex} or ${rightSliderValue}. Cannot proceed for absolute value ${absoluteValue}.`
489
+ );
490
+ return null;
491
+ }
492
+ const positionInsideInterval: number = (absoluteValue - leftSliderValue) / (rightSliderValue - leftSliderValue);
493
+ // console.log("leftColorString", leftColorString, "rightColorString", rightColorString);
494
+ const leftColorObject: Color = Color.parse(leftColorString);
495
+ const rightColorObject: Color = Color.parse(rightColorString);
496
+ const newColor: Color = leftColorObject.interpolate(rightColorObject, positionInsideInterval);
497
+ return newColor;
498
+ }
499
+
500
+ /**
501
+ * Locate the slider interval that contains the given (absolute) slider value. The given value must be
502
+ * somewhere between MIN_VALUE and MAX_VALUE (usually between 0 and 100 in the default configuraion.).
503
+ *
504
+ * @param {number} absoluteValue - The absolute value to search for.
505
+ * @returns {number} The left slider inder the containing interval is starting with – or -1 if out of bounds.
506
+ */
507
+ private __locateIntervalAt(absoluteValue: number): number {
508
+ for (var i = 0; i + 1 < this._sliderElementRefs.length; i++) {
509
+ if (
510
+ this.__getSliderValue(i, this.sliderMin) <= absoluteValue &&
511
+ absoluteValue < this.__getSliderValue(i + 1, this.sliderMax)
512
+ ) {
513
+ return i;
514
+ }
515
+ }
516
+ return -1;
517
+ }
518
+
519
+ /**
520
+ * Converts a relative value in [0..1] to [min..max].
521
+ * @param relativeValue
522
+ */
523
+ private __relativeToAbsolute(relativeValue: number): number {
524
+ return this.sliderMin + (this.sliderMax - this.sliderMin) * relativeValue;
525
+ }
526
+
527
+ /**
528
+ * Converts a relative value in [0..1] to [min..max].
529
+ * @param relativeValue
530
+ */
531
+ private __absoluteToRelative(absolute: number): number {
532
+ return (absolute - this.sliderMin) / (this.sliderMax - this.sliderMin);
533
+ }
534
+
535
+ /**
536
+ * Get the value of the n-th rangel slider.
537
+ *
538
+ * @param {number} sliderIndex
539
+ * @param {number} fallback
540
+ * @returns
541
+ */
542
+ private __getSliderValue(sliderIndex: number, fallback: number): number {
543
+ if (sliderIndex < 0 || sliderIndex >= this._sliderElementRefs.length) {
544
+ return fallback;
545
+ }
546
+ return Number.parseFloat(this._sliderElementRefs[sliderIndex].current.value);
547
+ }
548
+
549
+ /**
550
+ * Get all current slider values as an array.
551
+ * @returns
552
+ */
553
+ private __getAllSliderValues(): Array<number> {
554
+ return this._sliderElementRefs.map((ref, index: number) => this.__getSliderValue(index, NaN));
555
+ }
556
+
557
+ /**
558
+ * Updates the container's background to display the configured color gradient.
559
+ *
560
+ * @private
561
+ */
562
+ private __updateBackgroundGradient() {
563
+ // const colorGradient = this.getColorGradient();
564
+ this.colorGradient = new ColorGradient(
565
+ this.__getAllSliderValues().map((sliderValue: number, sliderIndex: number) => ({
566
+ color: this.__getSliderColor(sliderIndex, null),
567
+ ratio: this.__absoluteToRelative(sliderValue)
568
+ })),
569
+ this.colorGradient.angle
570
+ );
571
+ this.containerRef.current.style["background"] = this.colorGradient.toColorGradientString();
572
+ }
573
+
574
+ /**
575
+ * After changes this function updates the color indicator button.
576
+ *
577
+ * @param {number} rangeSliderIndex - The new active slider index.
578
+ */
579
+ private __updateColorIndicator(rangeSliderIndex: number) {
580
+ const colorValue = this.__getSliderColorHEXString(rangeSliderIndex, "#808080");
581
+ const ratio = this.__getSliderPercentage(rangeSliderIndex);
582
+
583
+ // Set left offset of color input container
584
+ this.colorInputContainerRef.current.style["left"] = `calc( ${ratio * 100}% + ${
585
+ (1.0 - ratio) * this.css_indicatorWidth_num * 0.5
586
+ }em - ${ratio * this.css_indicatorWidth_num * 0.5}em)`;
587
+
588
+ // Also set offset of colot input element (safari will open the native color dialog at this position)
589
+ this.colorInputRef.current.style["left"] = `calc( ${ratio * 100}% + ${
590
+ (1.0 - ratio) * this.css_indicatorWidth_num * 0.5
591
+ }em - ${ratio * this.css_indicatorWidth_num * 0.5}em)`;
592
+ this.colorIndicatorColorButtonRef.current.style["background-color"] = colorValue;
593
+ this.colorInputRef.current.value = colorValue;
594
+ // console.log("Setting new color value", this.colorInputRef.current.value, colorValue);
595
+ this.colorInputRef.current.dataset.activeSliderIndex = `${rangeSliderIndex}`;
596
+
597
+ if (rangeSliderIndex <= 0 || rangeSliderIndex + 1 >= this._sliderElementRefs.length) {
598
+ this.colorIndicatorRemoveButtonRef.current.style["visibility"] = "hidden";
599
+ } else {
600
+ this.colorIndicatorRemoveButtonRef.current.style["visibility"] = "visible";
601
+ }
602
+ }
603
+
604
+ /**
605
+ * Get a color gradient CSS value string from the current editor settings.
606
+ *
607
+ * @instance
608
+ * @memberof ColorGradientPicker
609
+ * @returns {ColorGradient}
610
+ */
611
+ public getColorGradient(): ColorGradient {
612
+ return this.colorGradient;
613
+ }
614
+
615
+ /**
616
+ * Get the configured color value of the n-th rangel slider.
617
+ *
618
+ * @param {number} sliderIndex
619
+ * @param {string} fallback
620
+ * @returns
621
+ */
622
+ private __getSliderColorString(sliderIndex: number, fallback: string): string {
623
+ if (sliderIndex < 0 || sliderIndex >= this._sliderElementRefs.length) {
624
+ return fallback;
625
+ }
626
+ const colorValue = this._sliderElementRefs[sliderIndex].current.dataset.colorValue;
627
+ return colorValue ? colorValue : fallback;
628
+ }
629
+
630
+ /**
631
+ * Get the configured color value of the n-th rangel slider.
632
+ *
633
+ * @param {number} sliderIndex
634
+ * @param {string} fallback
635
+ * @returns
636
+ */
637
+ private __getSliderColorHEXString(sliderIndex: number, fallback: string): string {
638
+ if (sliderIndex < 0 || sliderIndex >= this._sliderElementRefs.length) {
639
+ return fallback;
640
+ }
641
+ const colorValue = this._sliderElementRefs[sliderIndex].current.dataset.colorValueHEX;
642
+ return colorValue ? colorValue : fallback;
643
+ }
644
+
645
+ /**
646
+ * Get get color of this gradient picker at the given slider index.
647
+ *
648
+ * @param {number} sliderIndex - The index to get the slider color from. Something between 0 and _sliderElementRefs.length.
649
+ * @param {Color} fallback
650
+ * @returns
651
+ */
652
+ private __getSliderColor(sliderIndex: number, fallback: Color): Color {
653
+ if (sliderIndex < 0 || sliderIndex >= this._sliderElementRefs.length) {
654
+ return fallback;
655
+ }
656
+ const colorValueString = this._sliderElementRefs[sliderIndex].current.dataset["colorValue"];
657
+ const colorValue = Color.parse(colorValueString);
658
+ return colorValue ? colorValue : fallback;
659
+ }
660
+
661
+ /**
662
+ * Get the slider's value in a mapped range of 0.0 ... 1.0.
663
+ *
664
+ * @param sliderIndex
665
+ * @returns
666
+ */
667
+ private __getSliderPercentage(sliderIndex): number {
668
+ const sliderValue: number = this.__getSliderValue(sliderIndex, 0.0);
669
+ const percentage: number = (this.sliderMin + sliderValue) / (this.sliderMax - this.sliderMin);
670
+ return percentage;
671
+ }
672
+
673
+ /**
674
+ * Init the container contents.
675
+ *
676
+ * @private
677
+ */
678
+ private _render(): HTMLElement {
679
+ const _self = this;
680
+ // console.log("Rendering ...", NoReact);
681
+ this._sliderElementRefs = [];
682
+ this.colorIndicatorColorButtonRef = NoReact.useRef<HTMLButtonElement>();
683
+ this.colorIndicatorRemoveButtonRef = NoReact.useRef<HTMLButtonElement>();
684
+ this.colorInputRef = NoReact.useRef<HTMLInputElement>();
685
+ this.colorInputContainerRef = NoReact.useRef<HTMLInputElement>();
686
+ this.containerRef = NoReact.useRef<HTMLDivElement>();
687
+
688
+ /**
689
+ * This helper method is called when the tiny color button below the active slider is clicked. The method
690
+ * triggers the colorInput dialog to be triggered.
691
+ *
692
+ * @param {Event} evt
693
+ */
694
+ const handleIndicatorButtonClick = (evt: Event) => {
695
+ evt.preventDefault(); // Prevent other inputs to react to this event.
696
+ evt.stopPropagation();
697
+ // But fake a click on the input element
698
+ // console.log("Current selected color: " + _self.colorInputRef.current.value);
699
+ _self.colorInputRef.current.click();
700
+ };
701
+
702
+ /**
703
+ * This helper method is called when the remove-color button is clicked. It removes the
704
+ * currently active color slider – if possible (leftmost and rightmost slider cannot be removed).
705
+ * @param evt
706
+ */
707
+ const handleRemoveColorButtonClick = (evt: Event) => {
708
+ evt.preventDefault(); // Prevent other inputs to react to this event.
709
+ evt.stopPropagation();
710
+ _self.__handleRemoveColor();
711
+ };
712
+
713
+ return (
714
+ <div
715
+ id={this.elementID}
716
+ style={{
717
+ d: "flex",
718
+ fd: "column",
719
+ w: "100%",
720
+ h: this.css_containerHeight,
721
+ pos: "relative",
722
+ mb: this.isMobileMode ? "5em" : "3em"
723
+ }}
724
+ ref={this.containerRef}
725
+ onMouseDown={this.__containerMouseDownHandler()}
726
+ onClick={this.__containerClickHandler()}
727
+ >
728
+ {/* For each color gradient value create a range slider. */}
729
+ {this.colorGradient.values.map((colorGradientItem: ColorGradientItem, index: number) => {
730
+ const initialValue: number = _self.__relativeToAbsolute(colorGradientItem.ratio);
731
+ return this.__createColorRangeInput(index, this.sliderMin, this.sliderMax, initialValue, colorGradientItem.color);
732
+ })}
733
+ <div style={{ w: "100%" }}>
734
+ <input
735
+ id={`color-indicator-input-${this.baseID}`}
736
+ type="color"
737
+ style={{ v: "hidden", pos: "absolute", l: 0 }}
738
+ // data-active-slider-index=""
739
+ ref={this.colorInputRef}
740
+ onInput={this.__colorChangeHandler()}
741
+ />
742
+ <div
743
+ ref={this.colorInputContainerRef}
744
+ style={{
745
+ pos: "absolute",
746
+ b: "0px",
747
+ l: "0%",
748
+ d: "flex",
749
+ fd: "column",
750
+ transform: "translate(0%, 100%)"
751
+ }}
752
+ >
753
+ <button
754
+ id={`color-indicator-button-${this.baseID}`}
755
+ className="color-indicator-button"
756
+ style={{
757
+ backgroundColor: "grey",
758
+ borderRadius: "50%",
759
+ border: "1px solid grey",
760
+ w: this.css_indicatorWidth, // "1em",
761
+ h: this.css_indicatorHeight, // "1em",
762
+ transform: "translate(-50%, 0%)"
763
+ }}
764
+ onClick={handleIndicatorButtonClick}
765
+ ref={this.colorIndicatorColorButtonRef}
766
+ ></button>
767
+ <button
768
+ id={`color-remove-button-${this.baseID}`}
769
+ className="color-remove-button"
770
+ style={{
771
+ borderRadius: "10%",
772
+ border: "1px solid grey",
773
+ w: this.css_indicatorWidth, // "1em",
774
+ h: this.css_indicatorHeight, // "1em",
775
+ transform: "translate(-50%, 50%)",
776
+ lineHeight: "0.5em",
777
+ p: 0
778
+ }}
779
+ onClick={handleRemoveColorButtonClick}
780
+ ref={this.colorIndicatorRemoveButtonRef}
781
+ >
782
+ <span style={{ fontSize: this.isMobileMode ? "1.0em" : "0.5em" }}>🗑</span>
783
+ </button>
784
+ </div>
785
+ </div>
786
+ </div>
787
+ );
788
+ } // END function render()
789
+
790
+ /**
791
+ * Adds custom styles (global STYLE tag).
792
+ *
793
+ * @private
794
+ */
795
+ private __createCustomStylesElement = () => {
796
+ // Thanks to Ana Tudor
797
+ // https://css-tricks.com/multi-thumb-sliders-particular-two-thumb-case/
798
+ return (
799
+ <style>{`
800
+ #${this.elementID} {
801
+ margin-top: 1em;
802
+ }
803
+
804
+ #${this.elementID} input[type='range'] {
805
+
806
+ -webkit-appearance: none;
807
+
808
+ grid-column: 1;
809
+ grid-row: 2;
810
+
811
+ /* same as before */
812
+ background: none; /* get rid of white Chrome background */
813
+ color: #000;
814
+ font: inherit; /* fix too small font-size in both Chrome & Firefox */
815
+ margin: 0;
816
+ pointer-events: none; /* let clicks pass through */
817
+ }
818
+
819
+ #${this.elementID} input[type='range']::-webkit-slider-runnable-track {
820
+ -webkit-appearance: none;
821
+
822
+ background: none; /* get rid of Firefox track background */
823
+ height: 100%;
824
+ width: 100%;
825
+
826
+ pointer-events: none;
827
+ }
828
+
829
+ #${this.elementID} input[type='range']::-webkit-slider-thumb {
830
+ -webkit-appearance: none;
831
+ background: currentcolor;
832
+ border: none; /* get rid of Firefox thumb border */
833
+ border-radius: 6px; /* get rid of Firefox corner rounding */
834
+ pointer-events: auto; /* catch clicks */
835
+ width: ${this.css_thumbWidth};
836
+ height: ${this.css_thumbHeight};
837
+ }
838
+
839
+ #${this.elementID} input[type='range']:focus::-webkit-slider-thumb {
840
+ border: 2px solid white;
841
+ }
842
+
843
+ #${this.elementID} input[type='range']::-moz-range-track {
844
+ -webkit-appearance: none;
845
+ background: none; /* get rid of Firefox track background */
846
+ height: 100%;
847
+ width: 100%;
848
+ pointer-events: none;
849
+ }
850
+
851
+ #${this.elementID} input[type='range']::-moz-range-thumb {
852
+ /* -webkit-appearance: none; */
853
+ background: currentcolor;
854
+ border: none; /* get rid of Firefox thumb border */
855
+ border-radius: 6px; /* get rid of Firefox corner rounding */
856
+ pointer-events: auto; /* catch clicks */
857
+ width: ${this.css_thumbWidth};
858
+ height: ${this.css_thumbHeight};
859
+ }
860
+
861
+ #${this.elementID} input[type='range']:focus::-moz-range-thumb {
862
+ border: 2px solid white;
863
+ }
864
+
865
+ #${this.elementID} input[type='range'] {
866
+ /* same as before */
867
+ z-index: 1;
868
+ }
869
+
870
+ #${this.elementID} input[type='range']:focus {
871
+ z-index: 2;
872
+ /* outline: dotted 1px orange; */
873
+ color: darkorange;
874
+ }
875
+
876
+ body.darkmode #${this.elementID} .color-remove-button {
877
+ background-color: #1e2937;
878
+ color: grey;
879
+ }
880
+
881
+ body.darkmode #${this.elementID} .color-remove-button:hover,
882
+ body.darkmode #${this.elementID} .color-remove-button:active {
883
+ background-color: rgba(216,216,216,0.9);
884
+ }
885
+
886
+ `}</style>
887
+ );
888
+ };
889
+ }