@digilogiclabs/saas-factory-ui 2.7.0 → 2.8.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.
@@ -30544,6 +30544,7 @@ function DiceRoller({
30544
30544
  iconEmoji = "\u{1F3B2}",
30545
30545
  showHistory = false,
30546
30546
  historyMax = 8,
30547
+ initialHistory,
30547
30548
  rollRef,
30548
30549
  arenaHeight: arenaHeightProp,
30549
30550
  hideResults = false,
@@ -30555,7 +30556,9 @@ function DiceRoller({
30555
30556
  const dark = useDarkMode(darkMode);
30556
30557
  const [rolling, setRolling] = useState66(false);
30557
30558
  const [results, setResults] = useState66(null);
30558
- const [history, setHistory] = useState66([]);
30559
+ const [history, setHistory] = useState66(
30560
+ () => initialHistory && initialHistory.length > 0 ? initialHistory.slice(0, historyMax) : []
30561
+ );
30559
30562
  const dieWrapperRefs = useRef40([]);
30560
30563
  const cubeRefs = useRef40([]);
30561
30564
  const shadowRefs = useRef40([]);
@@ -30729,7 +30732,10 @@ function DiceRoller({
30729
30732
  defaultPositions,
30730
30733
  half
30731
30734
  ]);
30735
+ const prevSidesRef = useRef40(sides);
30732
30736
  useEffect51(() => {
30737
+ if (prevSidesRef.current === sides) return;
30738
+ prevSidesRef.current = sides;
30733
30739
  setHistory([]);
30734
30740
  }, [sides]);
30735
30741
  const roll = useCallback41(() => {
@@ -31792,32 +31798,26 @@ function BracketGenerator({
31792
31798
  const autoPlay = useCallback42(() => {
31793
31799
  if (simulating) {
31794
31800
  if (autoRef.current) clearTimeout(autoRef.current);
31801
+ autoRef.current = null;
31795
31802
  setSimulating(false);
31796
31803
  return;
31797
31804
  }
31798
31805
  setSimulating(true);
31799
- const tick = () => {
31800
- setBracketState((prev) => {
31801
- const next = prev.matches.find((m) => m.a && m.b && !m.winner);
31802
- if (!next) {
31803
- setSimulating(false);
31804
- return prev;
31805
- }
31806
- const completed = simulateMatch(next);
31807
- if (!completed) return prev;
31808
- let matches = prev.matches.map(
31809
- (m) => m.id === completed.id ? completed : m
31810
- );
31811
- matches = propagateWinner(matches, completed);
31812
- const finalMatch = matches.find((m) => m.round === prev.rounds - 1);
31813
- const champion = finalMatch?.winner || null;
31814
- if (!champion) autoRef.current = setTimeout(tick, simulateDelayMs);
31815
- else setSimulating(false);
31816
- return { ...prev, matches, champion };
31817
- });
31806
+ simulateNext();
31807
+ }, [simulating, simulateNext]);
31808
+ useEffect52(() => {
31809
+ if (!simulating) return;
31810
+ const hasNext = bracketState.matches.some((m) => m.a && m.b && !m.winner);
31811
+ if (bracketState.champion || !hasNext) {
31812
+ setSimulating(false);
31813
+ return;
31814
+ }
31815
+ autoRef.current = setTimeout(simulateNext, simulateDelayMs);
31816
+ return () => {
31817
+ if (autoRef.current) clearTimeout(autoRef.current);
31818
+ autoRef.current = null;
31818
31819
  };
31819
- tick();
31820
- }, [simulating, simulateDelayMs]);
31820
+ }, [simulating, bracketState, simulateDelayMs, simulateNext]);
31821
31821
  const advanceWinner = useCallback42(
31822
31822
  (matchId, winnerSlot) => {
31823
31823
  setBracketState((prev) => {
@@ -32811,6 +32811,8 @@ function CoinFlipper({
32811
32811
  tailsLabel = "Tails",
32812
32812
  showHistory = false,
32813
32813
  historyMax = 20,
32814
+ initialHistory,
32815
+ initialStats,
32814
32816
  flipRef,
32815
32817
  ariaLabel,
32816
32818
  className = ""
@@ -32819,8 +32821,23 @@ function CoinFlipper({
32819
32821
  const tc = typeof theme5 === "string" ? PRESETS3[theme5] || PRESETS3.gold : theme5;
32820
32822
  const [result, setResult] = useState68(null);
32821
32823
  const [flipping, setFlipping] = useState68(false);
32822
- const [history, setHistory] = useState68([]);
32823
- const [stats, setStats] = useState68({ heads: 0, tails: 0 });
32824
+ const [history, setHistory] = useState68(
32825
+ () => initialHistory && initialHistory.length > 0 ? initialHistory.slice(0, historyMax).map((result2, i) => ({ result: result2, id: -(i + 1) })) : []
32826
+ );
32827
+ const [stats, setStats] = useState68(() => {
32828
+ if (initialStats) {
32829
+ return {
32830
+ heads: Math.max(0, Math.floor(initialStats.heads)),
32831
+ tails: Math.max(0, Math.floor(initialStats.tails))
32832
+ };
32833
+ }
32834
+ if (initialHistory && initialHistory.length > 0) {
32835
+ const seeded = initialHistory.slice(0, historyMax);
32836
+ const heads = seeded.filter((r) => r === "heads").length;
32837
+ return { heads, tails: seeded.length - heads };
32838
+ }
32839
+ return { heads: 0, tails: 0 };
32840
+ });
32824
32841
  const flipIdRef = useRef42(0);
32825
32842
  const coinRef = useRef42(null);
32826
32843
  const shadowRef = useRef42(null);
@@ -33755,6 +33772,23 @@ function ReelStage({
33755
33772
  }
33756
33773
  );
33757
33774
  }
33775
+ function computeDraftSeed(participants, drawnIds, removeWinners) {
33776
+ if (!removeWinners || !drawnIds || drawnIds.length === 0) return null;
33777
+ if (new Set(drawnIds).size !== drawnIds.length) return null;
33778
+ const byId = new Map(participants.map((p) => [p.id, p]));
33779
+ const drawn = [];
33780
+ for (const id of drawnIds) {
33781
+ const p = byId.get(id);
33782
+ if (!p) return null;
33783
+ drawn.push(p);
33784
+ }
33785
+ const drawnSet = new Set(drawnIds);
33786
+ return {
33787
+ pool: participants.filter((p) => !drawnSet.has(p.id)),
33788
+ // Same cap as the live history (see the pop/promote paths).
33789
+ history: drawn.slice(0, 20)
33790
+ };
33791
+ }
33758
33792
  function RandomPlayerPicker({
33759
33793
  participants,
33760
33794
  mode = "wheel",
@@ -33769,6 +33803,7 @@ function RandomPlayerPicker({
33769
33803
  trigger = "button",
33770
33804
  label = "Spin",
33771
33805
  removeWinners = false,
33806
+ initialDrawnIds,
33772
33807
  spinDuration = 5e3,
33773
33808
  minRevolutions = 4,
33774
33809
  maxRevolutions = 7,
@@ -33795,12 +33830,21 @@ function RandomPlayerPicker({
33795
33830
  const reducedMotion = usePrefersReducedMotion();
33796
33831
  const intensityCfg = INTENSITY_CONFIG[intensity];
33797
33832
  const physicsDriven = intensity === "dramatic" && !reducedMotion;
33798
- const [pool, setPool] = useState70(participants);
33833
+ const [draftSeed] = useState70(
33834
+ () => computeDraftSeed(participants, initialDrawnIds, removeWinners)
33835
+ );
33836
+ const [pool, setPool] = useState70(
33837
+ draftSeed ? draftSeed.pool : participants
33838
+ );
33799
33839
  const [rotation, setRotation] = useState70(0);
33800
33840
  const [reelY, setReelY] = useState70(0);
33801
33841
  const [spinning, setSpinning] = useState70(false);
33802
- const [winner, setWinner] = useState70(null);
33803
- const [history, setHistory] = useState70([]);
33842
+ const [winner, setWinner] = useState70(
33843
+ draftSeed ? draftSeed.history[0] ?? null : null
33844
+ );
33845
+ const [history, setHistory] = useState70(
33846
+ draftSeed ? draftSeed.history : []
33847
+ );
33804
33848
  const [revealKey, setRevealKey] = useState70(0);
33805
33849
  const [particles, setParticles] = useState70([]);
33806
33850
  const winnerTimer = useRef43(null);
@@ -33812,7 +33856,9 @@ function RandomPlayerPicker({
33812
33856
  const tickAudioRef = useRef43(null);
33813
33857
  const winAudioRef = useRef43(null);
33814
33858
  const containerRef = useRef43(null);
33815
- const prevIdsRef = useRef43("");
33859
+ const prevIdsRef = useRef43(
33860
+ draftSeed ? participants.map((p) => p.id).join("|") : ""
33861
+ );
33816
33862
  const wheelSvgRef = useRef43(null);
33817
33863
  const reelTapeRef = useRef43(null);
33818
33864
  const rafRef = useRef43(null);
@@ -39418,8 +39464,8 @@ function RosterImport({
39418
39464
  maxNames,
39419
39465
  maxFileBytes,
39420
39466
  onClose: () => setOpen(false),
39421
- onImport: (names) => {
39422
- onImport(names);
39467
+ onImport: (names, info) => {
39468
+ onImport(names, info);
39423
39469
  setOpen(false);
39424
39470
  }
39425
39471
  }
@@ -39485,6 +39531,11 @@ function RosterImportModal({
39485
39531
  }),
39486
39532
  [table, column, skipHeader, maxNames]
39487
39533
  );
39534
+ const extractedTotal = useMemo34(
39535
+ () => extractRoster(table, { column, skipHeader }),
39536
+ [table, column, skipHeader]
39537
+ );
39538
+ const trimmedCount = Math.max(0, extractedTotal.length - extracted.length);
39488
39539
  const handleFile = useCallback49(
39489
39540
  async (e) => {
39490
39541
  setFileError(null);
@@ -39862,7 +39913,10 @@ function RosterImportModal({
39862
39913
  "button",
39863
39914
  {
39864
39915
  type: "button",
39865
- onClick: () => onImport(extracted),
39916
+ onClick: () => onImport(extracted, {
39917
+ trimmedCount,
39918
+ totalCount: extractedTotal.length
39919
+ }),
39866
39920
  disabled: !canImport,
39867
39921
  style: primaryBtn(!canImport),
39868
39922
  children: confirmLabel
@@ -40066,6 +40120,19 @@ function IconUsers({ size = 40 }) {
40066
40120
  }
40067
40121
  );
40068
40122
  }
40123
+ function mergeNames(prev, incoming, max) {
40124
+ const seen = new Set(prev.map((n) => n.toLowerCase()));
40125
+ const room = Math.max(0, max - prev.length);
40126
+ const deduped = [];
40127
+ for (const n of incoming) {
40128
+ const key = n.toLowerCase();
40129
+ if (seen.has(key)) continue;
40130
+ seen.add(key);
40131
+ deduped.push(n);
40132
+ if (deduped.length >= room) break;
40133
+ }
40134
+ return deduped.length > 0 ? [...prev, ...deduped] : prev;
40135
+ }
40069
40136
  function splitIntoTeams(names, count2) {
40070
40137
  const shuffled = shuffle(names);
40071
40138
  const teams = Array.from({ length: count2 }, () => []);
@@ -40139,29 +40206,14 @@ function TeamGenerator({
40139
40206
  const trimmed = input.trim();
40140
40207
  if (!trimmed) return;
40141
40208
  const newNames = trimmed.split(/[,\n]+/).map((n) => n.trim()).filter((n) => n.length > 0);
40142
- setNames((prev) => {
40143
- const room = Math.max(0, maxPlayers - prev.length);
40144
- return [...prev, ...newNames.slice(0, room)];
40145
- });
40209
+ setNames((prev) => mergeNames(prev, newNames, maxPlayers));
40146
40210
  setInput("");
40147
40211
  inputRef.current?.focus();
40148
40212
  }, [input, maxPlayers]);
40149
40213
  const importNames = useCallback50(
40150
40214
  (incoming) => {
40151
40215
  if (incoming.length === 0) return;
40152
- setNames((prev) => {
40153
- const seen = new Set(prev.map((n) => n.toLowerCase()));
40154
- const room = Math.max(0, maxPlayers - prev.length);
40155
- const deduped = [];
40156
- for (const n of incoming) {
40157
- const key = n.toLowerCase();
40158
- if (seen.has(key)) continue;
40159
- seen.add(key);
40160
- deduped.push(n);
40161
- if (deduped.length >= room) break;
40162
- }
40163
- return deduped.length > 0 ? [...prev, ...deduped] : prev;
40164
- });
40216
+ setNames((prev) => mergeNames(prev, incoming, maxPlayers));
40165
40217
  },
40166
40218
  [maxPlayers]
40167
40219
  );
@@ -40767,6 +40819,7 @@ function RoundRobinScheduler({
40767
40819
  resumeRounds,
40768
40820
  onTeamsChange,
40769
40821
  onGenerate,
40822
+ onBeforeScheduleDiscard,
40770
40823
  labels,
40771
40824
  ariaLabel = "Round robin scheduler",
40772
40825
  className,
@@ -40785,7 +40838,7 @@ function RoundRobinScheduler({
40785
40838
  useEffect62(() => {
40786
40839
  onTeamsChange?.(teams);
40787
40840
  }, [teams, onTeamsChange]);
40788
- const mergeNames = useCallback51(
40841
+ const mergeNames2 = useCallback51(
40789
40842
  (prev, incoming) => {
40790
40843
  const existing = new Set(prev.map((t) => t.toLowerCase()));
40791
40844
  const next = [...prev];
@@ -40802,23 +40855,29 @@ function RoundRobinScheduler({
40802
40855
  },
40803
40856
  [maxTeams, nameMaxLength]
40804
40857
  );
40858
+ const gateDiscard = useCallback51(
40859
+ (action) => rounds.length === 0 || !onBeforeScheduleDiscard || onBeforeScheduleDiscard(action),
40860
+ [rounds.length, onBeforeScheduleDiscard]
40861
+ );
40805
40862
  const addTeams = useCallback51(
40806
40863
  (raw) => {
40807
40864
  const names = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
40808
40865
  if (names.length === 0) return;
40809
- setTeams((prev) => mergeNames(prev, names));
40866
+ if (!gateDiscard("add")) return;
40867
+ setTeams((prev) => mergeNames2(prev, names));
40810
40868
  setInput("");
40811
40869
  setRounds([]);
40812
40870
  },
40813
- [mergeNames]
40871
+ [mergeNames2, gateDiscard]
40814
40872
  );
40815
40873
  const importTeams = useCallback51(
40816
40874
  (incoming) => {
40817
40875
  if (incoming.length === 0) return;
40818
- setTeams((prev) => mergeNames(prev, incoming));
40876
+ if (!gateDiscard("import")) return;
40877
+ setTeams((prev) => mergeNames2(prev, incoming));
40819
40878
  setRounds([]);
40820
40879
  },
40821
- [mergeNames]
40880
+ [mergeNames2, gateDiscard]
40822
40881
  );
40823
40882
  const handleKeyDown = useCallback51(
40824
40883
  (e) => {
@@ -40829,10 +40888,14 @@ function RoundRobinScheduler({
40829
40888
  },
40830
40889
  [addTeams, input]
40831
40890
  );
40832
- const removeTeam = useCallback51((idx) => {
40833
- setTeams((prev) => prev.filter((_, i) => i !== idx));
40834
- setRounds([]);
40835
- }, []);
40891
+ const removeTeam = useCallback51(
40892
+ (idx) => {
40893
+ if (!gateDiscard("remove")) return;
40894
+ setTeams((prev) => prev.filter((_, i) => i !== idx));
40895
+ setRounds([]);
40896
+ },
40897
+ [gateDiscard]
40898
+ );
40836
40899
  const handleGenerate = useCallback51(() => {
40837
40900
  if (teams.length < minTeams) return;
40838
40901
  const result = generateSchedule(teams, L.roundLabel);
@@ -40840,15 +40903,17 @@ function RoundRobinScheduler({
40840
40903
  onGenerate?.(result);
40841
40904
  }, [teams, minTeams, L.roundLabel, onGenerate]);
40842
40905
  const handleShuffle = useCallback51(() => {
40906
+ if (!gateDiscard("shuffle")) return;
40843
40907
  setTeams((prev) => shuffle(prev));
40844
40908
  setRounds([]);
40845
- }, []);
40909
+ }, [gateDiscard]);
40846
40910
  const handleClear = useCallback51(() => {
40911
+ if (!gateDiscard("clear")) return;
40847
40912
  setTeams([]);
40848
40913
  setRounds([]);
40849
40914
  setInput("");
40850
40915
  inputRef.current?.focus();
40851
- }, []);
40916
+ }, [gateDiscard]);
40852
40917
  const canGenerate = teams.length >= minTeams;
40853
40918
  const rootStyle = {
40854
40919
  borderRadius: 16,
@@ -41314,7 +41379,11 @@ function Scoreboard({
41314
41379
  onRoundAdvance,
41315
41380
  onNewGame,
41316
41381
  onReset,
41382
+ onBeforeReset,
41383
+ onBeforeNewGame,
41317
41384
  nameMaxLength,
41385
+ scoreMin = -9999,
41386
+ scoreMax = 9999,
41318
41387
  labels,
41319
41388
  ariaLabel = "Scoreboard",
41320
41389
  className
@@ -41358,11 +41427,19 @@ function Scoreboard({
41358
41427
  useEffect63(() => {
41359
41428
  onTeamsChange?.(teams);
41360
41429
  }, [teams, onTeamsChange]);
41361
- const updateScore = useCallback52((index, delta) => {
41362
- setTeams(
41363
- (prev) => prev.map((t, i) => i === index ? { ...t, score: t.score + delta } : t)
41364
- );
41365
- }, []);
41430
+ const updateScore = useCallback52(
41431
+ (index, delta) => {
41432
+ setTeams(
41433
+ (prev) => prev.map(
41434
+ (t, i) => i === index ? {
41435
+ ...t,
41436
+ score: Math.min(scoreMax, Math.max(scoreMin, t.score + delta))
41437
+ } : t
41438
+ )
41439
+ );
41440
+ },
41441
+ [scoreMin, scoreMax]
41442
+ );
41366
41443
  const updateName = useCallback52((index, name) => {
41367
41444
  setTeams((prev) => prev.map((t, i) => i === index ? { ...t, name } : t));
41368
41445
  }, []);
@@ -41385,19 +41462,21 @@ function Scoreboard({
41385
41462
  [minTeams]
41386
41463
  );
41387
41464
  const resetScores = useCallback52(() => {
41465
+ if (onBeforeReset && !onBeforeReset()) return;
41388
41466
  setTeams((prev) => prev.map((t) => ({ ...t, score: 0 })));
41389
41467
  setRound(1);
41390
41468
  setHistory([]);
41391
41469
  setHistoryOpen(false);
41392
41470
  onReset?.();
41393
- }, [onReset]);
41471
+ }, [onReset, onBeforeReset]);
41394
41472
  const newGame = useCallback52(() => {
41473
+ if (onBeforeNewGame && !onBeforeNewGame()) return;
41395
41474
  setTeams(createDefaultTeams(seedCount, L.teamNamePrefix));
41396
41475
  setRound(1);
41397
41476
  setHistory([]);
41398
41477
  setHistoryOpen(false);
41399
41478
  onNewGame?.();
41400
- }, [seedCount, L.teamNamePrefix, onNewGame]);
41479
+ }, [seedCount, L.teamNamePrefix, onNewGame, onBeforeNewGame]);
41401
41480
  const nextRound = useCallback52(() => {
41402
41481
  const snapshot = {
41403
41482
  round,