@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.
package/dist/index.mjs CHANGED
@@ -30656,6 +30656,7 @@ function DiceRoller({
30656
30656
  iconEmoji = "\u{1F3B2}",
30657
30657
  showHistory = false,
30658
30658
  historyMax = 8,
30659
+ initialHistory,
30659
30660
  rollRef,
30660
30661
  arenaHeight: arenaHeightProp,
30661
30662
  hideResults = false,
@@ -30667,7 +30668,9 @@ function DiceRoller({
30667
30668
  const dark = useDarkMode(darkMode);
30668
30669
  const [rolling, setRolling] = useState70(false);
30669
30670
  const [results, setResults] = useState70(null);
30670
- const [history, setHistory] = useState70([]);
30671
+ const [history, setHistory] = useState70(
30672
+ () => initialHistory && initialHistory.length > 0 ? initialHistory.slice(0, historyMax) : []
30673
+ );
30671
30674
  const dieWrapperRefs = useRef39([]);
30672
30675
  const cubeRefs = useRef39([]);
30673
30676
  const shadowRefs = useRef39([]);
@@ -30841,7 +30844,10 @@ function DiceRoller({
30841
30844
  defaultPositions,
30842
30845
  half
30843
30846
  ]);
30847
+ const prevSidesRef = useRef39(sides);
30844
30848
  useEffect54(() => {
30849
+ if (prevSidesRef.current === sides) return;
30850
+ prevSidesRef.current = sides;
30845
30851
  setHistory([]);
30846
30852
  }, [sides]);
30847
30853
  const roll = useCallback44(() => {
@@ -31904,32 +31910,26 @@ function BracketGenerator({
31904
31910
  const autoPlay = useCallback45(() => {
31905
31911
  if (simulating) {
31906
31912
  if (autoRef.current) clearTimeout(autoRef.current);
31913
+ autoRef.current = null;
31907
31914
  setSimulating(false);
31908
31915
  return;
31909
31916
  }
31910
31917
  setSimulating(true);
31911
- const tick = () => {
31912
- setBracketState((prev) => {
31913
- const next = prev.matches.find((m) => m.a && m.b && !m.winner);
31914
- if (!next) {
31915
- setSimulating(false);
31916
- return prev;
31917
- }
31918
- const completed = simulateMatch(next);
31919
- if (!completed) return prev;
31920
- let matches = prev.matches.map(
31921
- (m) => m.id === completed.id ? completed : m
31922
- );
31923
- matches = propagateWinner(matches, completed);
31924
- const finalMatch = matches.find((m) => m.round === prev.rounds - 1);
31925
- const champion = finalMatch?.winner || null;
31926
- if (!champion) autoRef.current = setTimeout(tick, simulateDelayMs);
31927
- else setSimulating(false);
31928
- return { ...prev, matches, champion };
31929
- });
31918
+ simulateNext();
31919
+ }, [simulating, simulateNext]);
31920
+ useEffect55(() => {
31921
+ if (!simulating) return;
31922
+ const hasNext = bracketState.matches.some((m) => m.a && m.b && !m.winner);
31923
+ if (bracketState.champion || !hasNext) {
31924
+ setSimulating(false);
31925
+ return;
31926
+ }
31927
+ autoRef.current = setTimeout(simulateNext, simulateDelayMs);
31928
+ return () => {
31929
+ if (autoRef.current) clearTimeout(autoRef.current);
31930
+ autoRef.current = null;
31930
31931
  };
31931
- tick();
31932
- }, [simulating, simulateDelayMs]);
31932
+ }, [simulating, bracketState, simulateDelayMs, simulateNext]);
31933
31933
  const advanceWinner = useCallback45(
31934
31934
  (matchId, winnerSlot) => {
31935
31935
  setBracketState((prev) => {
@@ -32923,6 +32923,8 @@ function CoinFlipper({
32923
32923
  tailsLabel = "Tails",
32924
32924
  showHistory = false,
32925
32925
  historyMax = 20,
32926
+ initialHistory,
32927
+ initialStats,
32926
32928
  flipRef,
32927
32929
  ariaLabel,
32928
32930
  className = ""
@@ -32931,8 +32933,23 @@ function CoinFlipper({
32931
32933
  const tc = typeof theme5 === "string" ? PRESETS3[theme5] || PRESETS3.gold : theme5;
32932
32934
  const [result, setResult] = useState72(null);
32933
32935
  const [flipping, setFlipping] = useState72(false);
32934
- const [history, setHistory] = useState72([]);
32935
- const [stats, setStats] = useState72({ heads: 0, tails: 0 });
32936
+ const [history, setHistory] = useState72(
32937
+ () => initialHistory && initialHistory.length > 0 ? initialHistory.slice(0, historyMax).map((result2, i) => ({ result: result2, id: -(i + 1) })) : []
32938
+ );
32939
+ const [stats, setStats] = useState72(() => {
32940
+ if (initialStats) {
32941
+ return {
32942
+ heads: Math.max(0, Math.floor(initialStats.heads)),
32943
+ tails: Math.max(0, Math.floor(initialStats.tails))
32944
+ };
32945
+ }
32946
+ if (initialHistory && initialHistory.length > 0) {
32947
+ const seeded = initialHistory.slice(0, historyMax);
32948
+ const heads = seeded.filter((r) => r === "heads").length;
32949
+ return { heads, tails: seeded.length - heads };
32950
+ }
32951
+ return { heads: 0, tails: 0 };
32952
+ });
32936
32953
  const flipIdRef = useRef41(0);
32937
32954
  const coinRef = useRef41(null);
32938
32955
  const shadowRef = useRef41(null);
@@ -33850,6 +33867,23 @@ function ReelStage({
33850
33867
  }
33851
33868
  );
33852
33869
  }
33870
+ function computeDraftSeed(participants, drawnIds, removeWinners) {
33871
+ if (!removeWinners || !drawnIds || drawnIds.length === 0) return null;
33872
+ if (new Set(drawnIds).size !== drawnIds.length) return null;
33873
+ const byId = new Map(participants.map((p) => [p.id, p]));
33874
+ const drawn = [];
33875
+ for (const id of drawnIds) {
33876
+ const p = byId.get(id);
33877
+ if (!p) return null;
33878
+ drawn.push(p);
33879
+ }
33880
+ const drawnSet = new Set(drawnIds);
33881
+ return {
33882
+ pool: participants.filter((p) => !drawnSet.has(p.id)),
33883
+ // Same cap as the live history (see the pop/promote paths).
33884
+ history: drawn.slice(0, 20)
33885
+ };
33886
+ }
33853
33887
  function RandomPlayerPicker({
33854
33888
  participants,
33855
33889
  mode = "wheel",
@@ -33864,6 +33898,7 @@ function RandomPlayerPicker({
33864
33898
  trigger = "button",
33865
33899
  label = "Spin",
33866
33900
  removeWinners = false,
33901
+ initialDrawnIds,
33867
33902
  spinDuration = 5e3,
33868
33903
  minRevolutions = 4,
33869
33904
  maxRevolutions = 7,
@@ -33890,12 +33925,21 @@ function RandomPlayerPicker({
33890
33925
  const reducedMotion = usePrefersReducedMotion();
33891
33926
  const intensityCfg = INTENSITY_CONFIG[intensity];
33892
33927
  const physicsDriven = intensity === "dramatic" && !reducedMotion;
33893
- const [pool, setPool] = useState73(participants);
33928
+ const [draftSeed] = useState73(
33929
+ () => computeDraftSeed(participants, initialDrawnIds, removeWinners)
33930
+ );
33931
+ const [pool, setPool] = useState73(
33932
+ draftSeed ? draftSeed.pool : participants
33933
+ );
33894
33934
  const [rotation, setRotation] = useState73(0);
33895
33935
  const [reelY, setReelY] = useState73(0);
33896
33936
  const [spinning, setSpinning] = useState73(false);
33897
- const [winner, setWinner] = useState73(null);
33898
- const [history, setHistory] = useState73([]);
33937
+ const [winner, setWinner] = useState73(
33938
+ draftSeed ? draftSeed.history[0] ?? null : null
33939
+ );
33940
+ const [history, setHistory] = useState73(
33941
+ draftSeed ? draftSeed.history : []
33942
+ );
33899
33943
  const [revealKey, setRevealKey] = useState73(0);
33900
33944
  const [particles, setParticles] = useState73([]);
33901
33945
  const winnerTimer = useRef42(null);
@@ -33907,7 +33951,9 @@ function RandomPlayerPicker({
33907
33951
  const tickAudioRef = useRef42(null);
33908
33952
  const winAudioRef = useRef42(null);
33909
33953
  const containerRef = useRef42(null);
33910
- const prevIdsRef = useRef42("");
33954
+ const prevIdsRef = useRef42(
33955
+ draftSeed ? participants.map((p) => p.id).join("|") : ""
33956
+ );
33911
33957
  const wheelSvgRef = useRef42(null);
33912
33958
  const reelTapeRef = useRef42(null);
33913
33959
  const rafRef = useRef42(null);
@@ -39718,8 +39764,8 @@ function RosterImport({
39718
39764
  maxNames,
39719
39765
  maxFileBytes,
39720
39766
  onClose: () => setOpen(false),
39721
- onImport: (names) => {
39722
- onImport(names);
39767
+ onImport: (names, info) => {
39768
+ onImport(names, info);
39723
39769
  setOpen(false);
39724
39770
  }
39725
39771
  }
@@ -39785,6 +39831,11 @@ function RosterImportModal({
39785
39831
  }),
39786
39832
  [table, column, skipHeader, maxNames]
39787
39833
  );
39834
+ const extractedTotal = useMemo36(
39835
+ () => extractRoster(table, { column, skipHeader }),
39836
+ [table, column, skipHeader]
39837
+ );
39838
+ const trimmedCount = Math.max(0, extractedTotal.length - extracted.length);
39788
39839
  const handleFile = useCallback52(
39789
39840
  async (e) => {
39790
39841
  setFileError(null);
@@ -40162,7 +40213,10 @@ function RosterImportModal({
40162
40213
  "button",
40163
40214
  {
40164
40215
  type: "button",
40165
- onClick: () => onImport(extracted),
40216
+ onClick: () => onImport(extracted, {
40217
+ trimmedCount,
40218
+ totalCount: extractedTotal.length
40219
+ }),
40166
40220
  disabled: !canImport,
40167
40221
  style: primaryBtn(!canImport),
40168
40222
  children: confirmLabel
@@ -40366,6 +40420,19 @@ function IconUsers({ size = 40 }) {
40366
40420
  }
40367
40421
  );
40368
40422
  }
40423
+ function mergeNames(prev, incoming, max) {
40424
+ const seen = new Set(prev.map((n) => n.toLowerCase()));
40425
+ const room = Math.max(0, max - prev.length);
40426
+ const deduped = [];
40427
+ for (const n of incoming) {
40428
+ const key = n.toLowerCase();
40429
+ if (seen.has(key)) continue;
40430
+ seen.add(key);
40431
+ deduped.push(n);
40432
+ if (deduped.length >= room) break;
40433
+ }
40434
+ return deduped.length > 0 ? [...prev, ...deduped] : prev;
40435
+ }
40369
40436
  function splitIntoTeams(names, count2) {
40370
40437
  const shuffled = shuffle(names);
40371
40438
  const teams = Array.from({ length: count2 }, () => []);
@@ -40439,29 +40506,14 @@ function TeamGenerator({
40439
40506
  const trimmed = input.trim();
40440
40507
  if (!trimmed) return;
40441
40508
  const newNames = trimmed.split(/[,\n]+/).map((n) => n.trim()).filter((n) => n.length > 0);
40442
- setNames((prev) => {
40443
- const room = Math.max(0, maxPlayers - prev.length);
40444
- return [...prev, ...newNames.slice(0, room)];
40445
- });
40509
+ setNames((prev) => mergeNames(prev, newNames, maxPlayers));
40446
40510
  setInput("");
40447
40511
  inputRef.current?.focus();
40448
40512
  }, [input, maxPlayers]);
40449
40513
  const importNames = useCallback53(
40450
40514
  (incoming) => {
40451
40515
  if (incoming.length === 0) return;
40452
- setNames((prev) => {
40453
- const seen = new Set(prev.map((n) => n.toLowerCase()));
40454
- const room = Math.max(0, maxPlayers - prev.length);
40455
- const deduped = [];
40456
- for (const n of incoming) {
40457
- const key = n.toLowerCase();
40458
- if (seen.has(key)) continue;
40459
- seen.add(key);
40460
- deduped.push(n);
40461
- if (deduped.length >= room) break;
40462
- }
40463
- return deduped.length > 0 ? [...prev, ...deduped] : prev;
40464
- });
40516
+ setNames((prev) => mergeNames(prev, incoming, maxPlayers));
40465
40517
  },
40466
40518
  [maxPlayers]
40467
40519
  );
@@ -41067,6 +41119,7 @@ function RoundRobinScheduler({
41067
41119
  resumeRounds,
41068
41120
  onTeamsChange,
41069
41121
  onGenerate,
41122
+ onBeforeScheduleDiscard,
41070
41123
  labels,
41071
41124
  ariaLabel = "Round robin scheduler",
41072
41125
  className,
@@ -41085,7 +41138,7 @@ function RoundRobinScheduler({
41085
41138
  useEffect64(() => {
41086
41139
  onTeamsChange?.(teams);
41087
41140
  }, [teams, onTeamsChange]);
41088
- const mergeNames = useCallback54(
41141
+ const mergeNames2 = useCallback54(
41089
41142
  (prev, incoming) => {
41090
41143
  const existing = new Set(prev.map((t) => t.toLowerCase()));
41091
41144
  const next = [...prev];
@@ -41102,23 +41155,29 @@ function RoundRobinScheduler({
41102
41155
  },
41103
41156
  [maxTeams, nameMaxLength]
41104
41157
  );
41158
+ const gateDiscard = useCallback54(
41159
+ (action) => rounds.length === 0 || !onBeforeScheduleDiscard || onBeforeScheduleDiscard(action),
41160
+ [rounds.length, onBeforeScheduleDiscard]
41161
+ );
41105
41162
  const addTeams = useCallback54(
41106
41163
  (raw) => {
41107
41164
  const names = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
41108
41165
  if (names.length === 0) return;
41109
- setTeams((prev) => mergeNames(prev, names));
41166
+ if (!gateDiscard("add")) return;
41167
+ setTeams((prev) => mergeNames2(prev, names));
41110
41168
  setInput("");
41111
41169
  setRounds([]);
41112
41170
  },
41113
- [mergeNames]
41171
+ [mergeNames2, gateDiscard]
41114
41172
  );
41115
41173
  const importTeams = useCallback54(
41116
41174
  (incoming) => {
41117
41175
  if (incoming.length === 0) return;
41118
- setTeams((prev) => mergeNames(prev, incoming));
41176
+ if (!gateDiscard("import")) return;
41177
+ setTeams((prev) => mergeNames2(prev, incoming));
41119
41178
  setRounds([]);
41120
41179
  },
41121
- [mergeNames]
41180
+ [mergeNames2, gateDiscard]
41122
41181
  );
41123
41182
  const handleKeyDown = useCallback54(
41124
41183
  (e) => {
@@ -41129,10 +41188,14 @@ function RoundRobinScheduler({
41129
41188
  },
41130
41189
  [addTeams, input]
41131
41190
  );
41132
- const removeTeam = useCallback54((idx) => {
41133
- setTeams((prev) => prev.filter((_, i) => i !== idx));
41134
- setRounds([]);
41135
- }, []);
41191
+ const removeTeam = useCallback54(
41192
+ (idx) => {
41193
+ if (!gateDiscard("remove")) return;
41194
+ setTeams((prev) => prev.filter((_, i) => i !== idx));
41195
+ setRounds([]);
41196
+ },
41197
+ [gateDiscard]
41198
+ );
41136
41199
  const handleGenerate = useCallback54(() => {
41137
41200
  if (teams.length < minTeams) return;
41138
41201
  const result = generateSchedule(teams, L.roundLabel);
@@ -41140,15 +41203,17 @@ function RoundRobinScheduler({
41140
41203
  onGenerate?.(result);
41141
41204
  }, [teams, minTeams, L.roundLabel, onGenerate]);
41142
41205
  const handleShuffle = useCallback54(() => {
41206
+ if (!gateDiscard("shuffle")) return;
41143
41207
  setTeams((prev) => shuffle(prev));
41144
41208
  setRounds([]);
41145
- }, []);
41209
+ }, [gateDiscard]);
41146
41210
  const handleClear = useCallback54(() => {
41211
+ if (!gateDiscard("clear")) return;
41147
41212
  setTeams([]);
41148
41213
  setRounds([]);
41149
41214
  setInput("");
41150
41215
  inputRef.current?.focus();
41151
- }, []);
41216
+ }, [gateDiscard]);
41152
41217
  const canGenerate = teams.length >= minTeams;
41153
41218
  const rootStyle = {
41154
41219
  borderRadius: 16,
@@ -41614,7 +41679,11 @@ function Scoreboard({
41614
41679
  onRoundAdvance,
41615
41680
  onNewGame,
41616
41681
  onReset,
41682
+ onBeforeReset,
41683
+ onBeforeNewGame,
41617
41684
  nameMaxLength,
41685
+ scoreMin = -9999,
41686
+ scoreMax = 9999,
41618
41687
  labels,
41619
41688
  ariaLabel = "Scoreboard",
41620
41689
  className
@@ -41658,11 +41727,19 @@ function Scoreboard({
41658
41727
  useEffect65(() => {
41659
41728
  onTeamsChange?.(teams);
41660
41729
  }, [teams, onTeamsChange]);
41661
- const updateScore = useCallback55((index, delta) => {
41662
- setTeams(
41663
- (prev) => prev.map((t, i) => i === index ? { ...t, score: t.score + delta } : t)
41664
- );
41665
- }, []);
41730
+ const updateScore = useCallback55(
41731
+ (index, delta) => {
41732
+ setTeams(
41733
+ (prev) => prev.map(
41734
+ (t, i) => i === index ? {
41735
+ ...t,
41736
+ score: Math.min(scoreMax, Math.max(scoreMin, t.score + delta))
41737
+ } : t
41738
+ )
41739
+ );
41740
+ },
41741
+ [scoreMin, scoreMax]
41742
+ );
41666
41743
  const updateName = useCallback55((index, name) => {
41667
41744
  setTeams((prev) => prev.map((t, i) => i === index ? { ...t, name } : t));
41668
41745
  }, []);
@@ -41685,19 +41762,21 @@ function Scoreboard({
41685
41762
  [minTeams]
41686
41763
  );
41687
41764
  const resetScores = useCallback55(() => {
41765
+ if (onBeforeReset && !onBeforeReset()) return;
41688
41766
  setTeams((prev) => prev.map((t) => ({ ...t, score: 0 })));
41689
41767
  setRound(1);
41690
41768
  setHistory([]);
41691
41769
  setHistoryOpen(false);
41692
41770
  onReset?.();
41693
- }, [onReset]);
41771
+ }, [onReset, onBeforeReset]);
41694
41772
  const newGame = useCallback55(() => {
41773
+ if (onBeforeNewGame && !onBeforeNewGame()) return;
41695
41774
  setTeams(createDefaultTeams(seedCount, L.teamNamePrefix));
41696
41775
  setRound(1);
41697
41776
  setHistory([]);
41698
41777
  setHistoryOpen(false);
41699
41778
  onNewGame?.();
41700
- }, [seedCount, L.teamNamePrefix, onNewGame]);
41779
+ }, [seedCount, L.teamNamePrefix, onNewGame, onBeforeNewGame]);
41701
41780
  const nextRound = useCallback55(() => {
41702
41781
  const snapshot = {
41703
41782
  round,