jattac.libs.web.zest-button 1.1.0 → 1.1.1

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.
package/dist/index.esm.js CHANGED
@@ -1,5 +1,5 @@
1
- import require$$0, { useState, useRef, useEffect } from 'react';
2
- import { FaSpinner } from 'react-icons/fa';
1
+ import require$$0, { useEffect, useState, useRef } from 'react';
2
+ import { FaSpinner } from 'react-icons/fa6';
3
3
 
4
4
  var jsxRuntime = {exports: {}};
5
5
 
@@ -1394,10 +1394,22 @@ const ZestButton = ({ visualOptions = {}, busyOptions = {}, successOptions = {},
1394
1394
  const { variant = "standard", size = "md", fullWidth = false, iconLeft, iconRight, } = visualOptions;
1395
1395
  const { handleInternally = true, preventRageClick = true, minBusyDurationMs = 500, } = busyOptions;
1396
1396
  const { showCheckmark = true, showFailIcon = true, autoResetAfterMs = 2000, } = successOptions;
1397
+ // Edge Case 4: Add warnings for very short durations in development
1398
+ useEffect(() => {
1399
+ if (process.env.NODE_ENV === 'development') {
1400
+ if (minBusyDurationMs > 0 && minBusyDurationMs < 100) {
1401
+ console.warn(`ZestButton: 'minBusyDurationMs' is set to ${minBusyDurationMs}ms. Very short durations can lead to visual flickering and a poor user experience. Consider a value of 100ms or more.`);
1402
+ }
1403
+ if (autoResetAfterMs > 0 && autoResetAfterMs < 100) {
1404
+ console.warn(`ZestButton: 'autoResetAfterMs' is set to ${autoResetAfterMs}ms. Very short durations can make success/fail feedback hard to perceive. Consider a value of 100ms or more.`);
1405
+ }
1406
+ }
1407
+ }, [minBusyDurationMs, autoResetAfterMs]);
1397
1408
  const [internalBusy, setInternalBusy] = useState(false);
1398
1409
  const [wasSuccessful, setWasSuccessful] = useState(false);
1399
1410
  const [wasFailed, setWasFailed] = useState(false);
1400
1411
  const buttonRef = useRef(null);
1412
+ const failTimeoutRef = useRef(null); // Bug 1: Add useRef for failTimeout
1401
1413
  const [currentChildren, setCurrentChildren] = useState(children);
1402
1414
  const [awaitingConfirm, setAwaitingConfirm] = useState(false);
1403
1415
  // ✅ interval ref for confirm countdown
@@ -1416,6 +1428,15 @@ const ZestButton = ({ visualOptions = {}, busyOptions = {}, successOptions = {},
1416
1428
  const isDisabled = disabled ||
1417
1429
  effectiveBusy ||
1418
1430
  (preventRageClick && (wasSuccessful || wasFailed));
1431
+ // Move handleClick, stopWaiting, and handleConfirmClick declarations here
1432
+ const stopWaiting = () => {
1433
+ if (confirmIntervalRef.current) {
1434
+ clearInterval(confirmIntervalRef.current);
1435
+ confirmIntervalRef.current = null;
1436
+ }
1437
+ setCurrentChildren(children);
1438
+ setAwaitingConfirm(false);
1439
+ };
1419
1440
  const handleClick = async (e) => {
1420
1441
  if (preventRageClick && internalBusy)
1421
1442
  return;
@@ -1447,45 +1468,14 @@ const ZestButton = ({ visualOptions = {}, busyOptions = {}, successOptions = {},
1447
1468
  onClick(e);
1448
1469
  }
1449
1470
  };
1450
- // auto-reset success/failure state
1451
- useEffect(() => {
1452
- if ((wasSuccessful || wasFailed) && autoResetAfterMs) {
1453
- const timeout = setTimeout(() => {
1454
- setWasSuccessful(false);
1455
- setWasFailed(false);
1456
- }, autoResetAfterMs);
1457
- return () => clearTimeout(timeout);
1458
- }
1459
- }, [wasSuccessful, wasFailed, autoResetAfterMs]);
1460
- // Enter key handler if isDefault
1461
- useEffect(() => {
1462
- if (!isDefault || isDisabled)
1463
- return;
1464
- const listener = (e) => {
1465
- const target = e.target;
1466
- if (e.key === "Enter" &&
1467
- !e.repeat &&
1468
- !e.defaultPrevented &&
1469
- !(target instanceof HTMLTextAreaElement)) {
1470
- e.preventDefault();
1471
- buttonRef.current?.click();
1472
- }
1473
- };
1474
- document.addEventListener("keydown", listener);
1475
- return () => document.removeEventListener("keydown", listener);
1476
- }, [isDefault, isDisabled]);
1477
- const stopWaiting = () => {
1478
- if (confirmIntervalRef.current) {
1479
- clearInterval(confirmIntervalRef.current);
1480
- confirmIntervalRef.current = null;
1481
- }
1482
- setCurrentChildren(children);
1483
- setAwaitingConfirm(false);
1484
- };
1485
1471
  const handleConfirmClick = async (e) => {
1486
1472
  if (!props.confirmOptions) {
1487
1473
  return handleClick(e);
1488
1474
  }
1475
+ // Edge Case 3: Add warning for missing onClick with confirmOptions
1476
+ if (props.confirmOptions && !onClick) { // Use destructured onClick
1477
+ console.warn("ZestButton: 'confirmOptions' are provided but 'onClick' handler is missing. The button will confirm but perform no action.");
1478
+ }
1489
1479
  if (awaitingConfirm) {
1490
1480
  stopWaiting();
1491
1481
  return handleClick(e);
@@ -1500,22 +1490,58 @@ const ZestButton = ({ visualOptions = {}, busyOptions = {}, successOptions = {},
1500
1490
  if (timeRemaining <= 0) {
1501
1491
  stopWaiting();
1502
1492
  setWasFailed(true); // Indicate failure for shake animation
1503
- setTimeout(() => {
1493
+ // Bug 1: Clear any existing timeout before setting a new one
1494
+ if (failTimeoutRef.current) {
1495
+ clearTimeout(failTimeoutRef.current);
1496
+ }
1497
+ failTimeoutRef.current = setTimeout(() => {
1504
1498
  setWasFailed(false);
1499
+ failTimeoutRef.current = null; // Clear ref after timeout fires
1505
1500
  }, 400); // Shake animation duration
1506
- // No need to return clearTimeout, as this timeout is for visual feedback only
1507
1501
  }
1508
1502
  else {
1509
1503
  setCurrentChildren(`${displayLabel} (${timeRemaining}s)`);
1510
1504
  }
1511
1505
  }, 1000); // Update once per second
1512
1506
  };
1507
+ // auto-reset success/failure state
1508
+ useEffect(() => {
1509
+ if ((wasSuccessful || wasFailed) && autoResetAfterMs) {
1510
+ const timeout = setTimeout(() => {
1511
+ setWasSuccessful(false);
1512
+ setWasFailed(false);
1513
+ }, autoResetAfterMs);
1514
+ return () => clearTimeout(timeout);
1515
+ }
1516
+ }, [wasSuccessful, wasFailed, autoResetAfterMs]);
1517
+ // Enter key handler if isDefault
1518
+ useEffect(() => {
1519
+ if (!isDefault || isDisabled)
1520
+ return;
1521
+ const listener = (e) => {
1522
+ const target = e.target;
1523
+ if (e.key === "Enter" &&
1524
+ !e.repeat &&
1525
+ !e.defaultPrevented &&
1526
+ !(target instanceof HTMLTextAreaElement)) {
1527
+ e.preventDefault();
1528
+ // Bug 2: Directly call handleConfirmClick to respect confirmation logic
1529
+ handleConfirmClick(e);
1530
+ }
1531
+ };
1532
+ document.addEventListener("keydown", listener);
1533
+ return () => document.removeEventListener("keydown", listener);
1534
+ }, [isDefault, isDisabled, handleConfirmClick]); // Bug 2: Add handleConfirmClick to dependencies
1513
1535
  // cleanup on unmount
1514
1536
  useEffect(() => {
1515
1537
  return () => {
1516
1538
  if (confirmIntervalRef.current) {
1517
1539
  clearInterval(confirmIntervalRef.current);
1518
1540
  }
1541
+ // Bug 1: Add cleanup for failTimeoutRef
1542
+ if (failTimeoutRef.current) {
1543
+ clearTimeout(failTimeoutRef.current);
1544
+ }
1519
1545
  };
1520
1546
  }, []);
1521
1547
  const renderLeftIcon = () => {
@@ -1526,7 +1552,7 @@ const ZestButton = ({ visualOptions = {}, busyOptions = {}, successOptions = {},
1526
1552
  return (jsxRuntimeExports.jsx("span", { className: `${styles.icon} ${styles.fadeIn}`, children: jsxRuntimeExports.jsx(AnimatedCheckmark, {}) }));
1527
1553
  }
1528
1554
  else if (wasFailed && showFailIcon) {
1529
- return (jsxRuntimeExports.jsx("span", { className: `${styles.icon} ${styles.fadeIn} ${styles.shake}`, children: jsxRuntimeExports.jsx(AnimatedX, {}) }));
1555
+ return (jsxRuntimeExports.jsx("span", { className: `${styles.icon} ${styles.fadeIn}`, children: jsxRuntimeExports.jsx(AnimatedX, {}) }));
1530
1556
  }
1531
1557
  else if (iconLeft) {
1532
1558
  return jsxRuntimeExports.jsx("span", { className: styles.icon, children: iconLeft });