flet-box 1.0.3 → 1.0.5

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 (172) hide show
  1. package/README.md +10 -0
  2. package/bin/cli.js +15 -0
  3. package/bin/commands/checkApi.js +761 -0
  4. package/bin/commands/create.js +77 -5
  5. package/bin/utils/dtsProps.js +191 -0
  6. package/bin/utils/moduleExports.js +165 -0
  7. package/bin/utils/neutralise.js +64 -0
  8. package/bin/utils/templates.js +366 -27
  9. package/docs/CONTRIBUTING.md +156 -0
  10. package/docs/README.md +68 -0
  11. package/docs/arquitectura.md +164 -0
  12. package/docs/cli/README.md +542 -0
  13. package/docs/guides/README.md +33 -0
  14. package/docs/guides/app-templates.md +197 -0
  15. package/docs/guides/compatibility.md +95 -0
  16. package/docs/guides/embedding.md +284 -0
  17. package/docs/guides/first-app.md +208 -0
  18. package/docs/guides/frontend-services.md +171 -0
  19. package/docs/guides/minification-and-protection.md +260 -0
  20. package/docs/guides/mobile-and-platforms.md +439 -0
  21. package/docs/guides/online-updates.md +196 -0
  22. package/docs/guides/router.md +282 -0
  23. package/docs/guides/state.md +283 -0
  24. package/docs/guides/syntax-philosophy.md +150 -0
  25. package/docs/guides/utilities.md +241 -0
  26. package/docs/guides/widget-structure.md +163 -0
  27. package/docs/server/README.md +68 -0
  28. package/docs/server/START_HERE.md +121 -0
  29. package/docs/server/api.md +225 -0
  30. package/docs/server/data-services.md +300 -0
  31. package/docs/server/deployment.md +113 -0
  32. package/docs/server/security.md +196 -0
  33. package/docs/services/README.md +79 -0
  34. package/docs/services/RamStore.md +84 -0
  35. package/docs/services/Session.md +84 -0
  36. package/docs/services/Storage.md +88 -0
  37. package/docs/services/http.md +86 -0
  38. package/docs/tools/README.md +138 -0
  39. package/docs/tools/animation.md +87 -0
  40. package/docs/tools/async-ids-log.md +80 -0
  41. package/docs/tools/device.md +132 -0
  42. package/docs/tools/introspection.md +67 -0
  43. package/docs/tools/lists.md +96 -0
  44. package/docs/tools/markdown.md +79 -0
  45. package/docs/tools/reactivity.md +288 -0
  46. package/docs/tools/state.md +103 -0
  47. package/docs/tools/styling.md +92 -0
  48. package/docs/tools/text-and-time.md +89 -0
  49. package/docs/tools/theme.md +102 -0
  50. package/docs/tools/validation.md +75 -0
  51. package/docs/tools/visual-effects.md +84 -0
  52. package/docs/widget/Accordion.md +144 -0
  53. package/docs/widget/AdMob.md +144 -0
  54. package/docs/widget/AdSense.md +133 -0
  55. package/docs/widget/AdaptiveScaffold.md +105 -0
  56. package/docs/widget/AlertDialog.md +103 -0
  57. package/docs/widget/AnimatedBox.md +130 -0
  58. package/docs/widget/AnimatedText.md +123 -0
  59. package/docs/widget/AppBar.md +118 -0
  60. package/docs/widget/Audio.md +135 -0
  61. package/docs/widget/Avatar.md +119 -0
  62. package/docs/widget/Badge.md +128 -0
  63. package/docs/widget/BottomNavigation.md +116 -0
  64. package/docs/widget/BottomSheet.md +125 -0
  65. package/docs/widget/Button.md +137 -0
  66. package/docs/widget/COMMON_PROPS.md +150 -0
  67. package/docs/widget/Card.md +125 -0
  68. package/docs/widget/Carousel.md +151 -0
  69. package/docs/widget/Chart.md +151 -0
  70. package/docs/widget/Checkbox.md +118 -0
  71. package/docs/widget/Chip.md +126 -0
  72. package/docs/widget/CircularBar.md +168 -0
  73. package/docs/widget/CircularChart.md +164 -0
  74. package/docs/widget/CodeViewer.md +130 -0
  75. package/docs/widget/CollapsibleSideBar.md +112 -0
  76. package/docs/widget/Column.md +129 -0
  77. package/docs/widget/Container.md +136 -0
  78. package/docs/widget/DataTable.md +150 -0
  79. package/docs/widget/Divider.md +109 -0
  80. package/docs/widget/DraggBox.md +129 -0
  81. package/docs/widget/Drawer.md +135 -0
  82. package/docs/widget/DrawerItem.md +98 -0
  83. package/docs/widget/Dropdown.md +139 -0
  84. package/docs/widget/DroppBox.md +152 -0
  85. package/docs/widget/FloatingActionButton.md +120 -0
  86. package/docs/widget/GridView.md +140 -0
  87. package/docs/widget/Icon.md +117 -0
  88. package/docs/widget/Image.md +125 -0
  89. package/docs/widget/Input.md +151 -0
  90. package/docs/widget/Inspector.md +92 -0
  91. package/docs/widget/InstallButton.md +128 -0
  92. package/docs/widget/ListTile.md +132 -0
  93. package/docs/widget/ListView.md +138 -0
  94. package/docs/widget/Markdown.md +175 -0
  95. package/docs/widget/MatrixRain.md +122 -0
  96. package/docs/widget/Modal.md +129 -0
  97. package/docs/widget/Pagination.md +135 -0
  98. package/docs/widget/ParallaxBox.md +128 -0
  99. package/docs/widget/ProgressBar.md +130 -0
  100. package/docs/widget/QRCode.md +117 -0
  101. package/docs/widget/README.md +188 -0
  102. package/docs/widget/Radio.md +123 -0
  103. package/docs/widget/Rating.md +131 -0
  104. package/docs/widget/Row.md +131 -0
  105. package/docs/widget/START_HERE.md +146 -0
  106. package/docs/widget/Scaffold.md +152 -0
  107. package/docs/widget/Skeleton.md +130 -0
  108. package/docs/widget/Slider.md +146 -0
  109. package/docs/widget/SnackBar.md +102 -0
  110. package/docs/widget/Stack.md +128 -0
  111. package/docs/widget/Stepper.md +145 -0
  112. package/docs/widget/Switch.md +118 -0
  113. package/docs/widget/Tabs.md +124 -0
  114. package/docs/widget/Text.md +133 -0
  115. package/docs/widget/Tooltip.md +145 -0
  116. package/docs/widget/TreeView.md +176 -0
  117. package/docs/widget/Video.md +127 -0
  118. package/docs/widget/_template.md +61 -0
  119. package/package.json +4 -2
  120. package/src/animations/AnimatedBox.js +32 -1
  121. package/src/animations/AnimatedText.js +7 -3
  122. package/src/animations/MatrixRain.js +37 -12
  123. package/src/animations/ParallaxBox.js +16 -3
  124. package/src/animations/animate.js +187 -58
  125. package/src/animations/index.js +1 -0
  126. package/src/core/runApp.js +13 -1
  127. package/src/create-index.sh +1 -1
  128. package/src/index.d.ts +227 -91
  129. package/src/index.js +1 -0
  130. package/src/navigations/AdaptiveScaffold.js +11 -2
  131. package/src/navigations/AppBar.js +8 -2
  132. package/src/navigations/BottomNavigation.js +24 -5
  133. package/src/navigations/DrawerItem.js +16 -3
  134. package/src/navigations/Scaffold.js +8 -1
  135. package/src/navigations/Tabs.js +40 -8
  136. package/src/tools/dimensions.js +11 -3
  137. package/src/tools/useState.js +139 -101
  138. package/src/utils/getWidgetProps.js +5 -3
  139. package/src/widget-factory/assignProps.js +30 -11
  140. package/src/widget-factory/processProps.js +17 -5
  141. package/src/widget-factory/reactiveValue.js +172 -0
  142. package/src/widget-factory/translateProps.js +777 -901
  143. package/src/widget-factory/warnings.js +15 -0
  144. package/src/widget-factory/widgetFactory.js +27 -0
  145. package/src/widgets/Accordion.js +6 -5
  146. package/src/widgets/AlertDialog.js +7 -5
  147. package/src/widgets/Audio.js +30 -8
  148. package/src/widgets/BottomSheet.js +24 -1
  149. package/src/widgets/Chart.js +4 -1
  150. package/src/widgets/Checkbox.js +16 -8
  151. package/src/widgets/CircularBar.js +12 -1
  152. package/src/widgets/CircularChart.js +4 -1
  153. package/src/widgets/CodeViewer.js +26 -8
  154. package/src/widgets/DataTable.js +11 -2
  155. package/src/widgets/Dropdown.js +5 -1
  156. package/src/widgets/FloatingActionButton.js +4 -7
  157. package/src/widgets/Image.js +7 -2
  158. package/src/widgets/Input.js +119 -6
  159. package/src/widgets/Inspector.js +16 -10
  160. package/src/widgets/ListView.js +80 -30
  161. package/src/widgets/Markdown.js +53 -8
  162. package/src/widgets/Modal.js +24 -1
  163. package/src/widgets/Pagination.js +6 -3
  164. package/src/widgets/ProgressBar.js +16 -13
  165. package/src/widgets/QRCode.js +9 -2
  166. package/src/widgets/Radio.js +11 -8
  167. package/src/widgets/Slider.js +26 -4
  168. package/src/widgets/SnackBar.js +20 -2
  169. package/src/widgets/Stack.js +36 -3
  170. package/src/widgets/Stepper.js +4 -3
  171. package/src/widgets/Switch.js +8 -5
  172. package/src/widgets/TreeView.js +3 -2
@@ -43,9 +43,9 @@ export const ProgressBar = (props) => {
43
43
  position: "relative",
44
44
  ...(striped || indeterminate
45
45
  ? {
46
- backgroundImage: `repeating-linear-gradient(45deg, rgba(255,255,255,0.15) 0px, rgba(255,255,255,0.15) 10px, transparent 10px, transparent 20px)`,
47
- backgroundSize: "28px 28px",
48
- }
46
+ backgroundImage: `repeating-linear-gradient(45deg, rgba(255,255,255,0.15) 0px, rgba(255,255,255,0.15) 10px, transparent 10px, transparent 20px)`,
47
+ backgroundSize: "28px 28px",
48
+ }
49
49
  : {}),
50
50
  });
51
51
 
@@ -115,9 +115,15 @@ export const ProgressBar = (props) => {
115
115
  );
116
116
  }
117
117
 
118
- // Value percentage
118
+ // Value percentage.
119
+ // FIX: guardo referencia directa al span del valor (`valueElRef`) en vez de
120
+ // buscarlo con `querySelector("span:last-child")` — la versión anterior
121
+ // podía escribir el porcentaje dentro del label (si el label estaba a la
122
+ // derecha, era el último span) y el check `valueEl !== label` nunca fallaba
123
+ // porque `label` era un string.
124
+ let valueElRef = null;
119
125
  if (showValue && !indeterminate) {
120
- const valueEl = WidgetFactory({
126
+ valueElRef = WidgetFactory({
121
127
  tag: "span",
122
128
  textContent: `${Math.round(percentage)}%`,
123
129
  fontSize: "12px",
@@ -129,9 +135,9 @@ export const ProgressBar = (props) => {
129
135
  });
130
136
 
131
137
  if (valuePosition === "left" || valuePosition === "top") {
132
- children.unshift(valueEl);
138
+ children.unshift(valueElRef);
133
139
  } else {
134
- children.push(valueEl);
140
+ children.push(valueElRef);
135
141
  }
136
142
  }
137
143
 
@@ -154,11 +160,8 @@ export const ProgressBar = (props) => {
154
160
  percentage = (progressValue / max) * 100;
155
161
  fill.style.width = `${percentage}%`;
156
162
 
157
- if (showValue) {
158
- const valueEl = container.querySelector("span:last-child");
159
- if (valueEl && valueEl !== label) {
160
- valueEl.textContent = `${Math.round(percentage)}%`;
161
- }
163
+ if (valueElRef) {
164
+ valueElRef.textContent = `${Math.round(percentage)}%`;
162
165
  }
163
166
  };
164
167
 
@@ -175,4 +178,4 @@ export const ProgressBar = (props) => {
175
178
  return container;
176
179
  };
177
180
 
178
- export default ProgressBar;
181
+ export default ProgressBar;
@@ -116,7 +116,9 @@ function buildQRMatrix(text, ecLevel = "M") {
116
116
  for (let i = len - 1; i >= 0; i--) bits.push((val >> i) & 1);
117
117
  };
118
118
  pushBits(0b0100, 4); // mode indicator: byte
119
- pushBits(bytes.length, 8); // character count
119
+ // Character count: 8 bits for versions 1-9, 16 bits for versions 10-40
120
+ // (ISO/IEC 18004). Emitting 8 bits at version 10 misaligns every later bit.
121
+ pushBits(bytes.length, version < 10 ? 8 : 16);
120
122
  for (const b of bytes) pushBits(b, 8);
121
123
  // Terminator
122
124
  for (let i = 0; i < 4 && bits.length < dataCodewords * 8; i++) bits.push(0);
@@ -161,7 +163,12 @@ function buildQRMatrix(text, ecLevel = "M") {
161
163
 
162
164
  // 7. Build final bit sequence
163
165
  const finalBits = [];
164
- for (const cw of finalCW) pushBits(cw, 8);
166
+ // NOTE: `pushBits` writes into `bits` (the pre-interleave stream), so it must
167
+ // not be used here. Using it left `finalBits` empty, and the placement pass
168
+ // then wrote every data module as 0 — the symbol carried no payload at all.
169
+ for (const cw of finalCW) {
170
+ for (let i = 7; i >= 0; i--) finalBits.push((cw >> i) & 1);
171
+ }
165
172
  // Remainder bits (version-dependent, versions 2–6 need 7)
166
173
  const remainderBitsCount = version >= 2 && version <= 6 ? 7 : 0;
167
174
  for (let i = 0; i < remainderBitsCount; i++) finalBits.push(0);
@@ -13,6 +13,7 @@ export const Radio = (props) => {
13
13
  disabled = false,
14
14
  size = 20,
15
15
  name,
16
+ activeColor = colors.primary,
16
17
  ...rest
17
18
  } = props;
18
19
 
@@ -26,10 +27,8 @@ export const Radio = (props) => {
26
27
 
27
28
  const updateUI = (newSelected) => {
28
29
  if (radioRef) {
29
- radioRef.style.backgroundColor = newSelected
30
- ? colors.primary
31
- : "transparent";
32
- radioRef.style.border = `2px solid ${newSelected ? colors.primary : colors.border}`;
30
+ radioRef.style.backgroundColor = newSelected ? activeColor : "transparent";
31
+ radioRef.style.border = `2px solid ${newSelected ? activeColor : colors.border}`;
33
32
  }
34
33
  if (dotRef) {
35
34
  dotRef.style.display = newSelected ? "block" : "none";
@@ -51,9 +50,9 @@ export const Radio = (props) => {
51
50
  widgetName: "Radio",
52
51
  width: size,
53
52
  height: size,
54
- border: `2px solid ${disabled ? colors.border : internalSelected ? colors.primary : colors.border}`,
53
+ border: `2px solid ${disabled ? colors.border : internalSelected ? activeColor : colors.border}`,
55
54
  borderRadius: "50%",
56
- backgroundColor: internalSelected ? colors.primary : "transparent",
55
+ backgroundColor: internalSelected ? activeColor : "transparent",
57
56
  display: "flex",
58
57
  alignItems: "center",
59
58
  justifyContent: "center",
@@ -67,7 +66,7 @@ export const Radio = (props) => {
67
66
 
68
67
  // Handle click
69
68
  if (!disabled) {
70
- radio.onclick = (e) => {
69
+ const handleClick = (e) => {
71
70
  e.stopPropagation();
72
71
  if (internalSelected) return;
73
72
 
@@ -88,11 +87,15 @@ export const Radio = (props) => {
88
87
 
89
88
  if (onPress) onPress(true);
90
89
  };
90
+ // Routed through the factory props: `assignProps` clears `onclick` on every
91
+ // update and only re-applies handlers present in the merged props, so a
92
+ // direct assignment died on the first `update()` or reactive setter.
93
+ radio.update({ onPress: handleClick });
91
94
 
92
95
  // Hover effect
93
96
  radio.addEventListener("mouseenter", () => {
94
97
  if (!disabled && !internalSelected) {
95
- radio.style.backgroundColor = colors.primary + "20";
98
+ radio.style.backgroundColor = `color-mix(in srgb, ${activeColor} 12%, transparent)`;
96
99
  }
97
100
  });
98
101
 
@@ -50,8 +50,8 @@ export const Slider = (props) => {
50
50
  stripeColor = "rgba(255,255,255,0.25)",
51
51
  glow = false,
52
52
  // Events
53
+ /** @deprecated Alias of `onPress`. */
53
54
  onChanged,
54
- /** @deprecated Use onPress instead */
55
55
  onPress: onPressProp,
56
56
  onChangeEnd,
57
57
  // Rest
@@ -77,6 +77,13 @@ export const Slider = (props) => {
77
77
  typeof trackSizePx === "number" ? `${trackSizePx}px` : trackSizePx;
78
78
  let thumbSizeNum = typeof thumbSize === "number" ? thumbSize : 20;
79
79
 
80
+ // Positioning retries while the track still measures 0 (hidden parent, not laid
81
+ // out yet). Both handles are tracked so unmounting can stop them: an untracked
82
+ // recursive rAF outlived the widget and re-armed itself every frame, forever.
83
+ let thumbRaf = 0;
84
+ let initTimer = 0;
85
+ let disposed = false;
86
+
80
87
  injectKeyframes(
81
88
  "slider-stripes",
82
89
  "0% { background-position: 0 0; } 100% { background-position: 20px 0; }",
@@ -299,6 +306,9 @@ export const Slider = (props) => {
299
306
 
300
307
  function generateDefaultMarks() {
301
308
  const stepMark = (currentMax - currentMin) / 4;
309
+ // With `min === max` (or non-numeric bounds) stepMark is 0/NaN, so
310
+ // `i += stepMark` never advances and the loop hangs the tab forever.
311
+ if (!(stepMark > 0)) return [currentMin];
302
312
  const marksList = [];
303
313
  for (let i = currentMin; i <= currentMax + 0.1; i += stepMark) {
304
314
  marksList.push(Math.round(i));
@@ -326,10 +336,16 @@ export const Slider = (props) => {
326
336
  }
327
337
 
328
338
  function updateThumbPosition() {
329
- if (!track || !thumb) return;
339
+ if (disposed || !track || !thumb) return;
330
340
  const trackRect = track.getBoundingClientRect();
331
341
  if (trackRect.width === 0 && trackRect.height === 0) {
332
- requestAnimationFrame(updateThumbPosition);
342
+ // Not measurable yet: wait one frame for layout, keeping a single pending
343
+ // frame instead of stacking one retry per caller.
344
+ if (thumbRaf) cancelAnimationFrame(thumbRaf);
345
+ thumbRaf = requestAnimationFrame(() => {
346
+ thumbRaf = 0;
347
+ updateThumbPosition();
348
+ });
333
349
  return;
334
350
  }
335
351
  if (isHorizontal) {
@@ -503,6 +519,9 @@ export const Slider = (props) => {
503
519
 
504
520
  // ---------- Cleanup ----------
505
521
  const cleanup = () => {
522
+ disposed = true;
523
+ if (thumbRaf) cancelAnimationFrame(thumbRaf);
524
+ if (initTimer) clearTimeout(initTimer);
506
525
  if (resizeObserver) {
507
526
  resizeObserver.disconnect();
508
527
  } else {
@@ -520,7 +539,10 @@ export const Slider = (props) => {
520
539
  container.onUnmount(cleanup);
521
540
 
522
541
  // Initialize position
523
- setTimeout(() => updateThumbPosition(), 0);
542
+ initTimer = setTimeout(() => {
543
+ initTimer = 0;
544
+ updateThumbPosition();
545
+ }, 0);
524
546
  return container;
525
547
  };
526
548
 
@@ -124,9 +124,18 @@ export function SnackBar(options) {
124
124
  });
125
125
 
126
126
  let timeoutId = null;
127
+ let rafId = null;
128
+ // `close()` is final: it must fire `onClose` exactly once and must win any
129
+ // race against the pending `show()` frame, otherwise closing a toast right
130
+ // after creating it would flash it back to opacity 1 for a full animation.
131
+ let closed = false;
127
132
 
128
133
  const show = () => {
129
- requestAnimationFrame(() => {
134
+ if (closed) return;
135
+ if (rafId !== null) cancelAnimationFrame(rafId);
136
+ rafId = requestAnimationFrame(() => {
137
+ rafId = null;
138
+ if (closed) return;
130
139
  snackbar.style.opacity = "1";
131
140
  snackbar.style.transform = "translateY(0)";
132
141
  });
@@ -134,7 +143,16 @@ export function SnackBar(options) {
134
143
  };
135
144
 
136
145
  const close = () => {
137
- if (timeoutId) clearTimeout(timeoutId);
146
+ if (closed) return;
147
+ closed = true;
148
+ if (timeoutId) {
149
+ clearTimeout(timeoutId);
150
+ timeoutId = null;
151
+ }
152
+ if (rafId !== null) {
153
+ cancelAnimationFrame(rafId);
154
+ rafId = null;
155
+ }
138
156
  snackbar.style.opacity = "0";
139
157
  snackbar.style.transform =
140
158
  position === "bottom"
@@ -1,27 +1,60 @@
1
1
  /**
2
2
  * @file Stack.js
3
3
  * @description Positioning container for overlaying children (relative by default,
4
- * use `position: "stack"` for absolute centering).
4
+ * use `position: "stack"` for absolute centering). Supports a nine-point
5
+ * `alignment` that anchors its children inside the box.
5
6
  */
6
7
  import { WidgetFactory } from "../widget-factory/index.js";
7
8
 
9
+ /**
10
+ * Nine-point grid → [vertical anchor, horizontal anchor].
11
+ * `alignment` turns the stack into a column flex container, so `justifyContent`
12
+ * carries the top/center/bottom anchor and `alignItems` the left/center/right one.
13
+ */
14
+ const ALIGNMENTS = {
15
+ "top-left": ["flex-start", "flex-start"],
16
+ "top-center": ["flex-start", "center"],
17
+ "top-right": ["flex-start", "flex-end"],
18
+ "center-left": ["center", "flex-start"],
19
+ center: ["center", "center"],
20
+ "center-right": ["center", "flex-end"],
21
+ "bottom-left": ["flex-end", "flex-start"],
22
+ "bottom-center": ["flex-end", "center"],
23
+ "bottom-right": ["flex-end", "flex-end"],
24
+ };
25
+
8
26
  export const Stack = (props = {}) => {
9
27
  const {
10
28
  // Default layout
11
29
  position = "relative",
12
- display = "block",
30
+ display,
13
31
  boxSizing = "border-box",
14
32
 
33
+ // Nine-point anchor for the children
34
+ alignment,
35
+
15
36
  // Everything else
16
37
  ...rest
17
38
  } = props;
18
39
 
40
+ const preset = alignment ? ALIGNMENTS[alignment] : undefined;
41
+
42
+ // The preset sits before `...rest`, so an explicit `flexDirection`,
43
+ // `justifyContent` or `alignItems` from the caller always overrides it;
44
+ // `alignment` only fills the axes nobody claimed.
19
45
  return WidgetFactory({
20
46
  tag: "div",
21
47
  widgetName: "Stack",
22
48
  position: position,
23
- display: display,
49
+ display: display ?? (preset ? "flex" : "block"),
24
50
  boxSizing: boxSizing,
51
+ ...(preset
52
+ ? {
53
+ flexDirection: "column",
54
+ justifyContent: preset[0],
55
+ alignItems: preset[1],
56
+ }
57
+ : null),
25
58
  ...rest,
26
59
  });
27
60
  };
@@ -274,12 +274,13 @@ export const Stepper = (props) => {
274
274
  overflow: "auto",
275
275
  });
276
276
 
277
- // Navigation container
278
- navContainer = Container({ width: "100%" });
277
+ // Navigation container — only built when the stepper is allowed to drive itself.
278
+ // `updateUI()` below skips the whole nav branch while `navContainer` is null.
279
+ navContainer = showNavigation ? Container({ width: "100%" }) : null;
279
280
 
280
281
  container.appendChild(stepsWrapper);
281
282
  container.appendChild(contentContainer);
282
- container.appendChild(navContainer);
283
+ if (navContainer) container.appendChild(navContainer);
283
284
 
284
285
  // Initial render
285
286
  updateUI();
@@ -12,6 +12,7 @@ export const Switch = (props) => {
12
12
  onPress: onPressProp,
13
13
  disabled = false,
14
14
  size = "medium",
15
+ activeColor = colors.success,
15
16
  ...rest
16
17
  } = props;
17
18
 
@@ -33,9 +34,7 @@ export const Switch = (props) => {
33
34
 
34
35
  const updateUI = (newValue) => {
35
36
  if (switchRef) {
36
- switchRef.style.backgroundColor = newValue
37
- ? colors.success
38
- : colors.gray300;
37
+ switchRef.style.backgroundColor = newValue ? activeColor : colors.gray300;
39
38
  }
40
39
  if (knobRef) {
41
40
  knobRef.style.transform = newValue
@@ -63,7 +62,7 @@ export const Switch = (props) => {
63
62
  widgetName: "Switch",
64
63
  width: sz.width,
65
64
  height: sz.height,
66
- backgroundColor: internalValue ? colors.success : colors.gray300,
65
+ backgroundColor: internalValue ? activeColor : colors.gray300,
67
66
  borderRadius: sz.height / 2,
68
67
  display: "flex",
69
68
  alignItems: "center",
@@ -79,13 +78,17 @@ export const Switch = (props) => {
79
78
 
80
79
  // Handle click
81
80
  if (!disabled) {
82
- switchContainer.onclick = (e) => {
81
+ const handleClick = (e) => {
83
82
  e.stopPropagation();
84
83
  const newValue = !internalValue;
85
84
  internalValue = newValue;
86
85
  updateUI(newValue);
87
86
  if (onPress) onPress(newValue);
88
87
  };
88
+ // Routed through the factory props: `assignProps` clears `onclick` on every
89
+ // update and only re-applies handlers present in the merged props, so a
90
+ // direct assignment died on the first `update()` or reactive setter.
91
+ switchContainer.update({ onPress: handleClick });
89
92
  }
90
93
 
91
94
  // Public methods
@@ -104,7 +104,6 @@ export const TreeView = (props) => {
104
104
  typeof borderRadius === "number" ? `${borderRadius}px` : borderRadius,
105
105
  marginLeft: `${level * indent}px`,
106
106
  transition: `background-color ${transitionDuration} ease`,
107
- hoverBackgroundColor: hoverBgColor,
108
107
  });
109
108
 
110
109
  // Add hover effect
@@ -280,7 +279,9 @@ export const TreeView = (props) => {
280
279
 
281
280
  // ========== PUBLIC METHODS ==========
282
281
 
283
- container.updateNodes = (newNodes, selectedId = null) => {
282
+ // NOTE: no `selectedId = null` default. The guard below tests `!== undefined`,
283
+ // so a `null` default made it always true and every call wiped the selection.
284
+ container.updateNodes = (newNodes, selectedId) => {
284
285
  nodes.length = 0;
285
286
  nodes.push(...newNodes);
286
287
  if (selectedId !== undefined) currentSelectedId = selectedId;