@digilogiclabs/saas-factory-ui 2.3.0 → 2.4.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.
- package/dist/index.d.mts +20 -3
- package/dist/index.d.ts +20 -3
- package/dist/index.js +137 -65
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +137 -65
- package/dist/index.mjs.map +1 -1
- package/dist/web/index.d.mts +20 -3
- package/dist/web/index.d.ts +20 -3
- package/dist/web/index.js +243 -169
- package/dist/web/index.js.map +1 -1
- package/dist/web/index.mjs +186 -112
- package/dist/web/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -4626,7 +4626,9 @@ declare function TiltCard({ accent, accentSecondary, topLabel, icon, title, subt
|
|
|
4626
4626
|
* - Three trigger modes: built-in button / click anywhere / external
|
|
4627
4627
|
* imperative handle via `rollRef.current()`
|
|
4628
4628
|
* - Optional roll history strip
|
|
4629
|
-
* -
|
|
4629
|
+
* - Deliberately tumbles even under `prefers-reduced-motion` — a dice
|
|
4630
|
+
* roll is a safe animation and skipping it leaves no feedback at all
|
|
4631
|
+
* (see the note inside `roll()`)
|
|
4630
4632
|
* - Zero Tailwind dependency — all styles inline
|
|
4631
4633
|
*
|
|
4632
4634
|
* Ported to TypeScript and generalized for saas-factory-ui from a
|
|
@@ -5728,6 +5730,14 @@ interface RoundRobinSchedulerProps {
|
|
|
5728
5730
|
darkMode?: boolean;
|
|
5729
5731
|
/** Minimum teams required to generate a schedule. Default 3. */
|
|
5730
5732
|
minTeams?: number;
|
|
5733
|
+
/** Maximum roster size. Adds/imports beyond this are ignored. Unlimited
|
|
5734
|
+
* by default — consumers persisting the roster against a capped schema
|
|
5735
|
+
* should set this to the schema cap. */
|
|
5736
|
+
maxTeams?: number;
|
|
5737
|
+
/** Max characters per team name. Longer typed/imported names are
|
|
5738
|
+
* truncated (matching native input maxLength paste behavior).
|
|
5739
|
+
* Unlimited by default. */
|
|
5740
|
+
nameMaxLength?: number;
|
|
5731
5741
|
/** Initial roster. Uncontrolled. */
|
|
5732
5742
|
defaultTeams?: string[];
|
|
5733
5743
|
/**
|
|
@@ -5753,7 +5763,7 @@ interface RoundRobinSchedulerProps {
|
|
|
5753
5763
|
* roster from a spreadsheet. Default `true`. */
|
|
5754
5764
|
enableImport?: boolean;
|
|
5755
5765
|
}
|
|
5756
|
-
declare function RoundRobinScheduler({ theme, darkMode, minTeams, defaultTeams, resumeRounds, onTeamsChange, onGenerate, labels, ariaLabel, className, enableImport, }: RoundRobinSchedulerProps): react_jsx_runtime.JSX.Element;
|
|
5766
|
+
declare function RoundRobinScheduler({ theme, darkMode, minTeams, maxTeams, nameMaxLength, defaultTeams, resumeRounds, onTeamsChange, onGenerate, labels, ariaLabel, className, enableImport, }: RoundRobinSchedulerProps): react_jsx_runtime.JSX.Element;
|
|
5757
5767
|
declare namespace RoundRobinScheduler {
|
|
5758
5768
|
var displayName: string;
|
|
5759
5769
|
}
|
|
@@ -5812,6 +5822,13 @@ interface ScoreboardProps {
|
|
|
5812
5822
|
onRoundAdvance?: (snapshot: ScoreboardRoundSnapshot) => void;
|
|
5813
5823
|
/** Called when the user presses "New Game". */
|
|
5814
5824
|
onNewGame?: () => void;
|
|
5825
|
+
/** Called when the user presses "Reset" (scores zeroed, round counter and
|
|
5826
|
+
* history cleared, names kept). Consumers persisting round/history via
|
|
5827
|
+
* `onRoundAdvance` MUST clear that state here or their saved copy
|
|
5828
|
+
* diverges from the screen. */
|
|
5829
|
+
onReset?: () => void;
|
|
5830
|
+
/** Max characters accepted in a team-name input. Unlimited by default. */
|
|
5831
|
+
nameMaxLength?: number;
|
|
5815
5832
|
/** Localized / customized labels. */
|
|
5816
5833
|
labels?: ScoreboardLabels;
|
|
5817
5834
|
/** Accessible label on the root region. */
|
|
@@ -5819,7 +5836,7 @@ interface ScoreboardProps {
|
|
|
5819
5836
|
/** CSS class on root element. */
|
|
5820
5837
|
className?: string;
|
|
5821
5838
|
}
|
|
5822
|
-
declare function Scoreboard({ theme, darkMode, initialTeamCount, minTeams, maxTeams, scoreButtons, defaultTeams, initialRound, initialHistory, onTeamsChange, onRoundAdvance, onNewGame, labels, ariaLabel, className, }: ScoreboardProps): react_jsx_runtime.JSX.Element;
|
|
5839
|
+
declare function Scoreboard({ theme, darkMode, initialTeamCount, minTeams, maxTeams, scoreButtons, defaultTeams, initialRound, initialHistory, onTeamsChange, onRoundAdvance, onNewGame, onReset, nameMaxLength, labels, ariaLabel, className, }: ScoreboardProps): react_jsx_runtime.JSX.Element;
|
|
5823
5840
|
declare namespace Scoreboard {
|
|
5824
5841
|
var displayName: string;
|
|
5825
5842
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4626,7 +4626,9 @@ declare function TiltCard({ accent, accentSecondary, topLabel, icon, title, subt
|
|
|
4626
4626
|
* - Three trigger modes: built-in button / click anywhere / external
|
|
4627
4627
|
* imperative handle via `rollRef.current()`
|
|
4628
4628
|
* - Optional roll history strip
|
|
4629
|
-
* -
|
|
4629
|
+
* - Deliberately tumbles even under `prefers-reduced-motion` — a dice
|
|
4630
|
+
* roll is a safe animation and skipping it leaves no feedback at all
|
|
4631
|
+
* (see the note inside `roll()`)
|
|
4630
4632
|
* - Zero Tailwind dependency — all styles inline
|
|
4631
4633
|
*
|
|
4632
4634
|
* Ported to TypeScript and generalized for saas-factory-ui from a
|
|
@@ -5728,6 +5730,14 @@ interface RoundRobinSchedulerProps {
|
|
|
5728
5730
|
darkMode?: boolean;
|
|
5729
5731
|
/** Minimum teams required to generate a schedule. Default 3. */
|
|
5730
5732
|
minTeams?: number;
|
|
5733
|
+
/** Maximum roster size. Adds/imports beyond this are ignored. Unlimited
|
|
5734
|
+
* by default — consumers persisting the roster against a capped schema
|
|
5735
|
+
* should set this to the schema cap. */
|
|
5736
|
+
maxTeams?: number;
|
|
5737
|
+
/** Max characters per team name. Longer typed/imported names are
|
|
5738
|
+
* truncated (matching native input maxLength paste behavior).
|
|
5739
|
+
* Unlimited by default. */
|
|
5740
|
+
nameMaxLength?: number;
|
|
5731
5741
|
/** Initial roster. Uncontrolled. */
|
|
5732
5742
|
defaultTeams?: string[];
|
|
5733
5743
|
/**
|
|
@@ -5753,7 +5763,7 @@ interface RoundRobinSchedulerProps {
|
|
|
5753
5763
|
* roster from a spreadsheet. Default `true`. */
|
|
5754
5764
|
enableImport?: boolean;
|
|
5755
5765
|
}
|
|
5756
|
-
declare function RoundRobinScheduler({ theme, darkMode, minTeams, defaultTeams, resumeRounds, onTeamsChange, onGenerate, labels, ariaLabel, className, enableImport, }: RoundRobinSchedulerProps): react_jsx_runtime.JSX.Element;
|
|
5766
|
+
declare function RoundRobinScheduler({ theme, darkMode, minTeams, maxTeams, nameMaxLength, defaultTeams, resumeRounds, onTeamsChange, onGenerate, labels, ariaLabel, className, enableImport, }: RoundRobinSchedulerProps): react_jsx_runtime.JSX.Element;
|
|
5757
5767
|
declare namespace RoundRobinScheduler {
|
|
5758
5768
|
var displayName: string;
|
|
5759
5769
|
}
|
|
@@ -5812,6 +5822,13 @@ interface ScoreboardProps {
|
|
|
5812
5822
|
onRoundAdvance?: (snapshot: ScoreboardRoundSnapshot) => void;
|
|
5813
5823
|
/** Called when the user presses "New Game". */
|
|
5814
5824
|
onNewGame?: () => void;
|
|
5825
|
+
/** Called when the user presses "Reset" (scores zeroed, round counter and
|
|
5826
|
+
* history cleared, names kept). Consumers persisting round/history via
|
|
5827
|
+
* `onRoundAdvance` MUST clear that state here or their saved copy
|
|
5828
|
+
* diverges from the screen. */
|
|
5829
|
+
onReset?: () => void;
|
|
5830
|
+
/** Max characters accepted in a team-name input. Unlimited by default. */
|
|
5831
|
+
nameMaxLength?: number;
|
|
5815
5832
|
/** Localized / customized labels. */
|
|
5816
5833
|
labels?: ScoreboardLabels;
|
|
5817
5834
|
/** Accessible label on the root region. */
|
|
@@ -5819,7 +5836,7 @@ interface ScoreboardProps {
|
|
|
5819
5836
|
/** CSS class on root element. */
|
|
5820
5837
|
className?: string;
|
|
5821
5838
|
}
|
|
5822
|
-
declare function Scoreboard({ theme, darkMode, initialTeamCount, minTeams, maxTeams, scoreButtons, defaultTeams, initialRound, initialHistory, onTeamsChange, onRoundAdvance, onNewGame, labels, ariaLabel, className, }: ScoreboardProps): react_jsx_runtime.JSX.Element;
|
|
5839
|
+
declare function Scoreboard({ theme, darkMode, initialTeamCount, minTeams, maxTeams, scoreButtons, defaultTeams, initialRound, initialHistory, onTeamsChange, onRoundAdvance, onNewGame, onReset, nameMaxLength, labels, ariaLabel, className, }: ScoreboardProps): react_jsx_runtime.JSX.Element;
|
|
5823
5840
|
declare namespace Scoreboard {
|
|
5824
5841
|
var displayName: string;
|
|
5825
5842
|
}
|
package/dist/index.js
CHANGED
|
@@ -30719,7 +30719,6 @@ function DiceRoller({
|
|
|
30719
30719
|
}) {
|
|
30720
30720
|
const clampedCount = Math.max(1, Math.min(MAX_COUNT, count2));
|
|
30721
30721
|
const dark = useDarkMode(darkMode);
|
|
30722
|
-
const reducedMotion = usePrefersReducedMotion();
|
|
30723
30722
|
const [rolling, setRolling] = (0, import_react68.useState)(false);
|
|
30724
30723
|
const [results, setResults] = (0, import_react68.useState)(null);
|
|
30725
30724
|
const [history, setHistory] = (0, import_react68.useState)([]);
|
|
@@ -30856,6 +30855,14 @@ function DiceRoller({
|
|
|
30856
30855
|
[]
|
|
30857
30856
|
);
|
|
30858
30857
|
(0, import_react68.useEffect)(() => {
|
|
30858
|
+
if (timersRef.current.length > 0) {
|
|
30859
|
+
timersRef.current.forEach((t) => clearTimeout(t));
|
|
30860
|
+
timersRef.current = [];
|
|
30861
|
+
}
|
|
30862
|
+
if (rollingRef.current) {
|
|
30863
|
+
rollingRef.current = false;
|
|
30864
|
+
setRolling(false);
|
|
30865
|
+
}
|
|
30859
30866
|
const rawW = arenaRef.current?.offsetWidth ?? 0;
|
|
30860
30867
|
const rawH = arenaRef.current?.offsetHeight ?? 0;
|
|
30861
30868
|
const w = rawW > 20 ? rawW : arena.w > 20 ? arena.w : 400;
|
|
@@ -31012,7 +31019,6 @@ function DiceRoller({
|
|
|
31012
31019
|
defaultPositions,
|
|
31013
31020
|
effectiveSize,
|
|
31014
31021
|
half,
|
|
31015
|
-
reducedMotion,
|
|
31016
31022
|
onRoll,
|
|
31017
31023
|
writeCubeRotation,
|
|
31018
31024
|
writeDiePosition,
|
|
@@ -33025,6 +33031,9 @@ function CoinFlipper({
|
|
|
33025
33031
|
}, [flipping, onFlip, historyMax]);
|
|
33026
33032
|
(0, import_react70.useEffect)(() => {
|
|
33027
33033
|
if (flipRef) flipRef.current = flip;
|
|
33034
|
+
return () => {
|
|
33035
|
+
if (flipRef && flipRef.current === flip) flipRef.current = null;
|
|
33036
|
+
};
|
|
33028
33037
|
}, [flip, flipRef]);
|
|
33029
33038
|
(0, import_react70.useEffect)(() => {
|
|
33030
33039
|
if (autoFlip) {
|
|
@@ -33916,6 +33925,8 @@ function RandomPlayerPicker({
|
|
|
33916
33925
|
const particleTimer = (0, import_react71.useRef)(null);
|
|
33917
33926
|
const popTimer = (0, import_react71.useRef)(null);
|
|
33918
33927
|
const tickInterval = (0, import_react71.useRef)(null);
|
|
33928
|
+
const pendingPopIdxRef = (0, import_react71.useRef)(null);
|
|
33929
|
+
const removeWinnersRef = (0, import_react71.useRef)(removeWinners);
|
|
33919
33930
|
const tickAudioRef = (0, import_react71.useRef)(null);
|
|
33920
33931
|
const winAudioRef = (0, import_react71.useRef)(null);
|
|
33921
33932
|
const containerRef = (0, import_react71.useRef)(null);
|
|
@@ -33954,10 +33965,27 @@ function RandomPlayerPicker({
|
|
|
33954
33965
|
winAudioRef.current = null;
|
|
33955
33966
|
};
|
|
33956
33967
|
}, [winSoundUrl, soundVolume]);
|
|
33968
|
+
(0, import_react71.useEffect)(() => {
|
|
33969
|
+
removeWinnersRef.current = removeWinners;
|
|
33970
|
+
}, [removeWinners]);
|
|
33957
33971
|
(0, import_react71.useEffect)(() => {
|
|
33958
33972
|
const ids = participants.map((p) => p.id).join("|");
|
|
33959
33973
|
const structuralChange = ids !== prevIdsRef.current;
|
|
33960
33974
|
if (structuralChange) {
|
|
33975
|
+
if (winnerTimer.current) {
|
|
33976
|
+
clearTimeout(winnerTimer.current);
|
|
33977
|
+
winnerTimer.current = null;
|
|
33978
|
+
}
|
|
33979
|
+
if (tickInterval.current) {
|
|
33980
|
+
clearTimeout(tickInterval.current);
|
|
33981
|
+
tickInterval.current = null;
|
|
33982
|
+
}
|
|
33983
|
+
if (popTimer.current) {
|
|
33984
|
+
clearTimeout(popTimer.current);
|
|
33985
|
+
popTimer.current = null;
|
|
33986
|
+
}
|
|
33987
|
+
pendingPopIdxRef.current = null;
|
|
33988
|
+
setSpinning(false);
|
|
33961
33989
|
setPool(participants);
|
|
33962
33990
|
setHistory([]);
|
|
33963
33991
|
setWinner(null);
|
|
@@ -34014,33 +34042,50 @@ function RandomPlayerPicker({
|
|
|
34014
34042
|
[dark]
|
|
34015
34043
|
);
|
|
34016
34044
|
const spin = (0, import_react71.useCallback)(() => {
|
|
34017
|
-
if (spinning
|
|
34045
|
+
if (spinning) return;
|
|
34046
|
+
let activePool = pool;
|
|
34047
|
+
if (popTimer.current) {
|
|
34048
|
+
clearTimeout(popTimer.current);
|
|
34049
|
+
popTimer.current = null;
|
|
34050
|
+
const pendingIdx = pendingPopIdxRef.current;
|
|
34051
|
+
pendingPopIdxRef.current = null;
|
|
34052
|
+
if (pendingIdx != null && pendingIdx >= 0 && pendingIdx < pool.length && removeWinnersRef.current) {
|
|
34053
|
+
activePool = pool.filter((_, i) => i !== pendingIdx);
|
|
34054
|
+
setPool(activePool);
|
|
34055
|
+
setWinner(null);
|
|
34056
|
+
}
|
|
34057
|
+
}
|
|
34058
|
+
const activeN = activePool.length;
|
|
34059
|
+
if (activeN < 2) return;
|
|
34060
|
+
const activeAnglePerWedge = 2 * Math.PI / activeN;
|
|
34061
|
+
const activeRotation = rotation;
|
|
34062
|
+
const activeReelY = reelY;
|
|
34018
34063
|
onSpinStart?.();
|
|
34019
|
-
const winnerIdx = weightedIndex(
|
|
34064
|
+
const winnerIdx = weightedIndex(activePool);
|
|
34020
34065
|
const minRev = minRevolutions + intensityCfg.revsBoost;
|
|
34021
34066
|
const maxRev = maxRevolutions + intensityCfg.revsBoost;
|
|
34022
34067
|
const minFullRevs = Math.ceil(minRev);
|
|
34023
34068
|
const maxFullRevs = Math.max(minFullRevs, Math.floor(maxRev));
|
|
34024
34069
|
const revolutions = minFullRevs + Math.floor(Math.random() * (maxFullRevs - minFullRevs + 1));
|
|
34025
|
-
const wedgeMidRad = winnerIdx *
|
|
34070
|
+
const wedgeMidRad = winnerIdx * activeAnglePerWedge + activeAnglePerWedge / 2;
|
|
34026
34071
|
const wedgeMidDeg = wedgeMidRad * 180 / Math.PI;
|
|
34027
|
-
const wheelWobble = (Math.random() - 0.5) * (
|
|
34028
|
-
const currentMod = (
|
|
34072
|
+
const wheelWobble = (Math.random() - 0.5) * (activeAnglePerWedge * 180 / Math.PI) * 0.6;
|
|
34073
|
+
const currentMod = (activeRotation % 360 + 360) % 360;
|
|
34029
34074
|
const targetMod = ((-wedgeMidDeg + wheelWobble) % 360 + 360) % 360;
|
|
34030
34075
|
let deltaMod = targetMod - currentMod;
|
|
34031
34076
|
if (deltaMod < 0) deltaMod += 360;
|
|
34032
|
-
const finalRotation =
|
|
34077
|
+
const finalRotation = activeRotation + deltaMod + revolutions * 360;
|
|
34033
34078
|
const centerOffsetRows = (reelVisibleRows - 1) / 2;
|
|
34034
34079
|
const centerOffsetPx = centerOffsetRows * reelRowHeight;
|
|
34035
34080
|
const currentCenterRow = Math.round(
|
|
34036
|
-
(
|
|
34081
|
+
(activeReelY + centerOffsetPx) / reelRowHeight
|
|
34037
34082
|
);
|
|
34038
|
-
const currentCenterMod = mod(currentCenterRow,
|
|
34039
|
-
const targetRowOffset = (winnerIdx - currentCenterMod +
|
|
34040
|
-
const reelRevRows = Math.round(revolutions *
|
|
34041
|
-
const finalReelY =
|
|
34042
|
-
const settledWinnerIdx = mode === "reel" ? reelIndexAtCenter(finalReelY, centerOffsetPx, reelRowHeight,
|
|
34043
|
-
const finalWinnerIdx = settledWinnerIdx >= 0 && settledWinnerIdx <
|
|
34083
|
+
const currentCenterMod = mod(currentCenterRow, activeN);
|
|
34084
|
+
const targetRowOffset = (winnerIdx - currentCenterMod + activeN) % activeN;
|
|
34085
|
+
const reelRevRows = Math.round(revolutions * activeN);
|
|
34086
|
+
const finalReelY = activeReelY + (reelRevRows + targetRowOffset) * reelRowHeight;
|
|
34087
|
+
const settledWinnerIdx = mode === "reel" ? reelIndexAtCenter(finalReelY, centerOffsetPx, reelRowHeight, activeN) : wheelIndexAtPointer(finalRotation, activeN);
|
|
34088
|
+
const finalWinnerIdx = settledWinnerIdx >= 0 && settledWinnerIdx < activePool.length ? settledWinnerIdx : winnerIdx;
|
|
34044
34089
|
const effectiveDuration = reducedMotion ? 250 : Math.round(spinDuration * intensityCfg.durationMult);
|
|
34045
34090
|
setSpinning(true);
|
|
34046
34091
|
setWinner(null);
|
|
@@ -34053,7 +34098,7 @@ function RandomPlayerPicker({
|
|
|
34053
34098
|
if ((onTick || tickAudioRef.current) && !reducedMotion) {
|
|
34054
34099
|
const wedges = mode === "reel" ? Math.max(1, reelRevRows + targetRowOffset) : (() => {
|
|
34055
34100
|
const totalDelta = deltaMod + revolutions * 360;
|
|
34056
|
-
const wedgeDeg =
|
|
34101
|
+
const wedgeDeg = activeAnglePerWedge * 180 / Math.PI;
|
|
34057
34102
|
return Math.max(
|
|
34058
34103
|
1,
|
|
34059
34104
|
Math.round(totalDelta / Math.max(wedgeDeg, 1))
|
|
@@ -34085,7 +34130,7 @@ function RandomPlayerPicker({
|
|
|
34085
34130
|
schedule();
|
|
34086
34131
|
}
|
|
34087
34132
|
winnerTimer.current = setTimeout(() => {
|
|
34088
|
-
const picked =
|
|
34133
|
+
const picked = activePool[finalWinnerIdx];
|
|
34089
34134
|
if (!picked) {
|
|
34090
34135
|
setSpinning(false);
|
|
34091
34136
|
return;
|
|
@@ -34131,7 +34176,11 @@ function RandomPlayerPicker({
|
|
|
34131
34176
|
particleTimer.current = setTimeout(() => setParticles([]), 2400);
|
|
34132
34177
|
}
|
|
34133
34178
|
if (removeWinners) {
|
|
34179
|
+
pendingPopIdxRef.current = finalWinnerIdx;
|
|
34134
34180
|
popTimer.current = setTimeout(() => {
|
|
34181
|
+
popTimer.current = null;
|
|
34182
|
+
pendingPopIdxRef.current = null;
|
|
34183
|
+
if (!removeWinnersRef.current) return;
|
|
34135
34184
|
setPool((p) => p.filter((_, i) => i !== finalWinnerIdx));
|
|
34136
34185
|
setWinner(null);
|
|
34137
34186
|
setRotation(0);
|
|
@@ -34142,8 +34191,6 @@ function RandomPlayerPicker({
|
|
|
34142
34191
|
}, [
|
|
34143
34192
|
pool,
|
|
34144
34193
|
spinning,
|
|
34145
|
-
n,
|
|
34146
|
-
anglePerWedge,
|
|
34147
34194
|
rotation,
|
|
34148
34195
|
reelY,
|
|
34149
34196
|
mode,
|
|
@@ -34179,6 +34226,7 @@ function RandomPlayerPicker({
|
|
|
34179
34226
|
[]
|
|
34180
34227
|
);
|
|
34181
34228
|
(0, import_react71.useEffect)(() => {
|
|
34229
|
+
if (trigger === "external") return;
|
|
34182
34230
|
const el = containerRef.current;
|
|
34183
34231
|
if (!el) return;
|
|
34184
34232
|
const h = (e) => {
|
|
@@ -34189,8 +34237,22 @@ function RandomPlayerPicker({
|
|
|
34189
34237
|
};
|
|
34190
34238
|
el.addEventListener("keydown", h);
|
|
34191
34239
|
return () => el.removeEventListener("keydown", h);
|
|
34192
|
-
}, [spin]);
|
|
34240
|
+
}, [spin, trigger]);
|
|
34193
34241
|
const reset = (0, import_react71.useCallback)(() => {
|
|
34242
|
+
if (winnerTimer.current) {
|
|
34243
|
+
clearTimeout(winnerTimer.current);
|
|
34244
|
+
winnerTimer.current = null;
|
|
34245
|
+
}
|
|
34246
|
+
if (tickInterval.current) {
|
|
34247
|
+
clearTimeout(tickInterval.current);
|
|
34248
|
+
tickInterval.current = null;
|
|
34249
|
+
}
|
|
34250
|
+
if (popTimer.current) {
|
|
34251
|
+
clearTimeout(popTimer.current);
|
|
34252
|
+
popTimer.current = null;
|
|
34253
|
+
}
|
|
34254
|
+
pendingPopIdxRef.current = null;
|
|
34255
|
+
setSpinning(false);
|
|
34194
34256
|
setPool(participants);
|
|
34195
34257
|
setHistory([]);
|
|
34196
34258
|
setWinner(null);
|
|
@@ -38643,27 +38705,28 @@ function GameTimer({
|
|
|
38643
38705
|
const now = performance.now();
|
|
38644
38706
|
const delta = now - lastTickRef.current;
|
|
38645
38707
|
lastTickRef.current = now;
|
|
38646
|
-
|
|
38647
|
-
|
|
38648
|
-
|
|
38649
|
-
|
|
38650
|
-
|
|
38651
|
-
|
|
38652
|
-
|
|
38708
|
+
const next = elapsedRef.current + delta;
|
|
38709
|
+
if (mode === "countdown" && next >= durationMs) {
|
|
38710
|
+
elapsedRef.current = durationMs;
|
|
38711
|
+
setElapsedMs(durationMs);
|
|
38712
|
+
setRunning(false);
|
|
38713
|
+
setCompleted(true);
|
|
38714
|
+
onComplete?.();
|
|
38715
|
+
return;
|
|
38716
|
+
}
|
|
38717
|
+
if (mode === "countdown") {
|
|
38718
|
+
const secLeft = Math.ceil((durationMs - next) / 1e3);
|
|
38719
|
+
if (secLeft <= warningAt && !warningFiredRef.current) {
|
|
38720
|
+
warningFiredRef.current = true;
|
|
38721
|
+
onWarning?.();
|
|
38653
38722
|
}
|
|
38654
|
-
if (
|
|
38655
|
-
|
|
38656
|
-
|
|
38657
|
-
warningFiredRef.current = true;
|
|
38658
|
-
onWarning?.();
|
|
38659
|
-
}
|
|
38660
|
-
if (secLeft <= dangerAt && !dangerFiredRef.current) {
|
|
38661
|
-
dangerFiredRef.current = true;
|
|
38662
|
-
onDanger?.();
|
|
38663
|
-
}
|
|
38723
|
+
if (secLeft <= dangerAt && !dangerFiredRef.current) {
|
|
38724
|
+
dangerFiredRef.current = true;
|
|
38725
|
+
onDanger?.();
|
|
38664
38726
|
}
|
|
38665
|
-
|
|
38666
|
-
|
|
38727
|
+
}
|
|
38728
|
+
elapsedRef.current = next;
|
|
38729
|
+
setElapsedMs(next);
|
|
38667
38730
|
rafRef.current = requestAnimationFrame(tick);
|
|
38668
38731
|
}, [mode, durationMs, warningAt, dangerAt, onComplete, onWarning, onDanger]);
|
|
38669
38732
|
(0, import_react74.useEffect)(() => {
|
|
@@ -38684,6 +38747,7 @@ function GameTimer({
|
|
|
38684
38747
|
}, []);
|
|
38685
38748
|
const reset = (0, import_react74.useCallback)(() => {
|
|
38686
38749
|
setRunning(false);
|
|
38750
|
+
elapsedRef.current = 0;
|
|
38687
38751
|
setElapsedMs(0);
|
|
38688
38752
|
setCompleted(false);
|
|
38689
38753
|
setLaps([]);
|
|
@@ -40828,6 +40892,8 @@ function RoundRobinScheduler({
|
|
|
40828
40892
|
theme: theme5 = "slate",
|
|
40829
40893
|
darkMode,
|
|
40830
40894
|
minTeams = 3,
|
|
40895
|
+
maxTeams,
|
|
40896
|
+
nameMaxLength,
|
|
40831
40897
|
defaultTeams = [],
|
|
40832
40898
|
resumeRounds,
|
|
40833
40899
|
onTeamsChange,
|
|
@@ -40850,39 +40916,41 @@ function RoundRobinScheduler({
|
|
|
40850
40916
|
(0, import_react77.useEffect)(() => {
|
|
40851
40917
|
onTeamsChange?.(teams);
|
|
40852
40918
|
}, [teams, onTeamsChange]);
|
|
40853
|
-
const
|
|
40854
|
-
|
|
40855
|
-
if (names.length === 0) return;
|
|
40856
|
-
setTeams((prev) => {
|
|
40857
|
-
const existing = new Set(prev.map((t) => t.toLowerCase()));
|
|
40858
|
-
const next = [...prev];
|
|
40859
|
-
for (const name of names) {
|
|
40860
|
-
if (!existing.has(name.toLowerCase())) {
|
|
40861
|
-
existing.add(name.toLowerCase());
|
|
40862
|
-
next.push(name);
|
|
40863
|
-
}
|
|
40864
|
-
}
|
|
40865
|
-
return next;
|
|
40866
|
-
});
|
|
40867
|
-
setInput("");
|
|
40868
|
-
setRounds([]);
|
|
40869
|
-
}, []);
|
|
40870
|
-
const importTeams = (0, import_react77.useCallback)((incoming) => {
|
|
40871
|
-
if (incoming.length === 0) return;
|
|
40872
|
-
setTeams((prev) => {
|
|
40919
|
+
const mergeNames = (0, import_react77.useCallback)(
|
|
40920
|
+
(prev, incoming) => {
|
|
40873
40921
|
const existing = new Set(prev.map((t) => t.toLowerCase()));
|
|
40874
40922
|
const next = [...prev];
|
|
40875
|
-
for (const
|
|
40923
|
+
for (const rawName of incoming) {
|
|
40924
|
+
if (maxTeams != null && next.length >= maxTeams) break;
|
|
40925
|
+
const name = nameMaxLength != null ? rawName.slice(0, nameMaxLength) : rawName;
|
|
40876
40926
|
const key = name.toLowerCase();
|
|
40877
|
-
if (!existing.has(key)) {
|
|
40927
|
+
if (name.length > 0 && !existing.has(key)) {
|
|
40878
40928
|
existing.add(key);
|
|
40879
40929
|
next.push(name);
|
|
40880
40930
|
}
|
|
40881
40931
|
}
|
|
40882
40932
|
return next;
|
|
40883
|
-
}
|
|
40884
|
-
|
|
40885
|
-
|
|
40933
|
+
},
|
|
40934
|
+
[maxTeams, nameMaxLength]
|
|
40935
|
+
);
|
|
40936
|
+
const addTeams = (0, import_react77.useCallback)(
|
|
40937
|
+
(raw) => {
|
|
40938
|
+
const names = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
40939
|
+
if (names.length === 0) return;
|
|
40940
|
+
setTeams((prev) => mergeNames(prev, names));
|
|
40941
|
+
setInput("");
|
|
40942
|
+
setRounds([]);
|
|
40943
|
+
},
|
|
40944
|
+
[mergeNames]
|
|
40945
|
+
);
|
|
40946
|
+
const importTeams = (0, import_react77.useCallback)(
|
|
40947
|
+
(incoming) => {
|
|
40948
|
+
if (incoming.length === 0) return;
|
|
40949
|
+
setTeams((prev) => mergeNames(prev, incoming));
|
|
40950
|
+
setRounds([]);
|
|
40951
|
+
},
|
|
40952
|
+
[mergeNames]
|
|
40953
|
+
);
|
|
40886
40954
|
const handleKeyDown = (0, import_react77.useCallback)(
|
|
40887
40955
|
(e) => {
|
|
40888
40956
|
if (e.key === "Enter") {
|
|
@@ -41371,6 +41439,8 @@ function Scoreboard({
|
|
|
41371
41439
|
onTeamsChange,
|
|
41372
41440
|
onRoundAdvance,
|
|
41373
41441
|
onNewGame,
|
|
41442
|
+
onReset,
|
|
41443
|
+
nameMaxLength,
|
|
41374
41444
|
labels,
|
|
41375
41445
|
ariaLabel = "Scoreboard",
|
|
41376
41446
|
className
|
|
@@ -41445,7 +41515,8 @@ function Scoreboard({
|
|
|
41445
41515
|
setRound(1);
|
|
41446
41516
|
setHistory([]);
|
|
41447
41517
|
setHistoryOpen(false);
|
|
41448
|
-
|
|
41518
|
+
onReset?.();
|
|
41519
|
+
}, [onReset]);
|
|
41449
41520
|
const newGame = (0, import_react78.useCallback)(() => {
|
|
41450
41521
|
setTeams(createDefaultTeams(seedCount, L.teamNamePrefix));
|
|
41451
41522
|
setRound(1);
|
|
@@ -41667,6 +41738,7 @@ function Scoreboard({
|
|
|
41667
41738
|
{
|
|
41668
41739
|
type: "text",
|
|
41669
41740
|
value: team.name,
|
|
41741
|
+
maxLength: nameMaxLength,
|
|
41670
41742
|
onChange: (e) => updateName(index, e.target.value),
|
|
41671
41743
|
style: nameInputStyle,
|
|
41672
41744
|
"aria-label": `Team ${index + 1} name`
|