flet-box 1.0.3 → 1.0.4

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 (42) hide show
  1. package/package.json +3 -2
  2. package/src/animations/AnimatedBox.js +32 -1
  3. package/src/animations/AnimatedText.js +7 -3
  4. package/src/animations/MatrixRain.js +37 -12
  5. package/src/animations/ParallaxBox.js +16 -3
  6. package/src/animations/animate.js +22 -3
  7. package/src/core/runApp.js +13 -1
  8. package/src/index.d.ts +191 -78
  9. package/src/navigations/AppBar.js +8 -2
  10. package/src/navigations/BottomNavigation.js +14 -2
  11. package/src/navigations/DrawerItem.js +16 -3
  12. package/src/navigations/Scaffold.js +8 -1
  13. package/src/navigations/Tabs.js +34 -6
  14. package/src/tools/dimensions.js +11 -3
  15. package/src/tools/useState.js +115 -68
  16. package/src/utils/getWidgetProps.js +5 -3
  17. package/src/widget-factory/assignProps.js +7 -10
  18. package/src/widget-factory/processProps.js +7 -2
  19. package/src/widget-factory/reactiveValue.js +147 -0
  20. package/src/widget-factory/warnings.js +15 -0
  21. package/src/widget-factory/widgetFactory.js +27 -0
  22. package/src/widgets/Accordion.js +6 -5
  23. package/src/widgets/Audio.js +30 -8
  24. package/src/widgets/BottomSheet.js +24 -1
  25. package/src/widgets/Chart.js +4 -1
  26. package/src/widgets/Checkbox.js +7 -1
  27. package/src/widgets/CircularChart.js +4 -1
  28. package/src/widgets/CodeViewer.js +26 -8
  29. package/src/widgets/DataTable.js +11 -2
  30. package/src/widgets/Dropdown.js +5 -1
  31. package/src/widgets/Image.js +8 -1
  32. package/src/widgets/Input.js +27 -6
  33. package/src/widgets/Inspector.js +16 -10
  34. package/src/widgets/ListView.js +7 -1
  35. package/src/widgets/Markdown.js +53 -8
  36. package/src/widgets/Modal.js +24 -1
  37. package/src/widgets/QRCode.js +9 -2
  38. package/src/widgets/Radio.js +5 -1
  39. package/src/widgets/Slider.js +3 -0
  40. package/src/widgets/SnackBar.js +20 -2
  41. package/src/widgets/Switch.js +5 -1
  42. package/src/widgets/TreeView.js +3 -1
@@ -312,6 +312,8 @@ export const Modal = (props = {}) => {
312
312
 
313
313
  let isOpen = false;
314
314
  let overlay = null;
315
+ // Pending "hide after the close animation" timer, so it can be cancelled.
316
+ let closeTimer = null;
315
317
  let modalBody = null;
316
318
 
317
319
  // Handlers
@@ -323,7 +325,11 @@ export const Modal = (props = {}) => {
323
325
  modalBody.style.opacity = "0";
324
326
  }
325
327
  if (overlay) overlay.style.opacity = "0";
326
- setTimeout(() => {
328
+ // Keep the id: `handleOpen` clears it, otherwise a reopen within 300 ms gets
329
+ // a stale timer that hides an OPEN modal, leaving it invisible and unusable.
330
+ if (closeTimer) clearTimeout(closeTimer);
331
+ closeTimer = setTimeout(() => {
332
+ closeTimer = null;
327
333
  if (overlay) overlay.style.visibility = "hidden";
328
334
  }, 300);
329
335
  if (closeOnEsc) document.removeEventListener("keydown", handleKeyDown);
@@ -333,6 +339,11 @@ export const Modal = (props = {}) => {
333
339
  const handleOpen = () => {
334
340
  if (isOpen) return;
335
341
  isOpen = true;
342
+ // Cancel any pending hide from a close that is still animating out.
343
+ if (closeTimer) {
344
+ clearTimeout(closeTimer);
345
+ closeTimer = null;
346
+ }
336
347
  if (overlay) {
337
348
  overlay.style.visibility = "visible";
338
349
  overlay.style.opacity = "1";
@@ -405,6 +416,18 @@ export const Modal = (props = {}) => {
405
416
  overlay.appendChild(modalBody);
406
417
  document.body.appendChild(overlay);
407
418
 
419
+ // The overlay lives in `document.body` and can be removed without `close()`
420
+ // ever running (a state rebuild, a route change, a plain removeChild of the
421
+ // host). `_cleanup` is not called on that path, so release the document
422
+ // listener and any pending animation timer through the lifecycle hook too.
423
+ overlay.onUnmount(() => {
424
+ if (closeTimer) {
425
+ clearTimeout(closeTimer);
426
+ closeTimer = null;
427
+ }
428
+ if (closeOnEsc) document.removeEventListener("keydown", handleKeyDown);
429
+ });
430
+
408
431
  // Public API
409
432
  const instance = {
410
433
  open: handleOpen,
@@ -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);
@@ -67,7 +67,7 @@ export const Radio = (props) => {
67
67
 
68
68
  // Handle click
69
69
  if (!disabled) {
70
- radio.onclick = (e) => {
70
+ const handleClick = (e) => {
71
71
  e.stopPropagation();
72
72
  if (internalSelected) return;
73
73
 
@@ -88,6 +88,10 @@ export const Radio = (props) => {
88
88
 
89
89
  if (onPress) onPress(true);
90
90
  };
91
+ // Routed through the factory props: `assignProps` clears `onclick` on every
92
+ // update and only re-applies handlers present in the merged props, so a
93
+ // direct assignment died on the first `update()` or reactive setter.
94
+ radio.update({ onPress: handleClick });
91
95
 
92
96
  // Hover effect
93
97
  radio.addEventListener("mouseenter", () => {
@@ -299,6 +299,9 @@ export const Slider = (props) => {
299
299
 
300
300
  function generateDefaultMarks() {
301
301
  const stepMark = (currentMax - currentMin) / 4;
302
+ // With `min === max` (or non-numeric bounds) stepMark is 0/NaN, so
303
+ // `i += stepMark` never advances and the loop hangs the tab forever.
304
+ if (!(stepMark > 0)) return [currentMin];
302
305
  const marksList = [];
303
306
  for (let i = currentMin; i <= currentMax + 0.1; i += stepMark) {
304
307
  marksList.push(Math.round(i));
@@ -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"
@@ -79,13 +79,17 @@ export const Switch = (props) => {
79
79
 
80
80
  // Handle click
81
81
  if (!disabled) {
82
- switchContainer.onclick = (e) => {
82
+ const handleClick = (e) => {
83
83
  e.stopPropagation();
84
84
  const newValue = !internalValue;
85
85
  internalValue = newValue;
86
86
  updateUI(newValue);
87
87
  if (onPress) onPress(newValue);
88
88
  };
89
+ // Routed through the factory props: `assignProps` clears `onclick` on every
90
+ // update and only re-applies handlers present in the merged props, so a
91
+ // direct assignment died on the first `update()` or reactive setter.
92
+ switchContainer.update({ onPress: handleClick });
89
93
  }
90
94
 
91
95
  // Public methods
@@ -280,7 +280,9 @@ export const TreeView = (props) => {
280
280
 
281
281
  // ========== PUBLIC METHODS ==========
282
282
 
283
- container.updateNodes = (newNodes, selectedId = null) => {
283
+ // NOTE: no `selectedId = null` default. The guard below tests `!== undefined`,
284
+ // so a `null` default made it always true and every call wiped the selection.
285
+ container.updateNodes = (newNodes, selectedId) => {
284
286
  nodes.length = 0;
285
287
  nodes.push(...newNodes);
286
288
  if (selectedId !== undefined) currentSelectedId = selectedId;