@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.js CHANGED
@@ -30710,6 +30710,7 @@ function DiceRoller({
30710
30710
  iconEmoji = "\u{1F3B2}",
30711
30711
  showHistory = false,
30712
30712
  historyMax = 8,
30713
+ initialHistory,
30713
30714
  rollRef,
30714
30715
  arenaHeight: arenaHeightProp,
30715
30716
  hideResults = false,
@@ -30721,7 +30722,9 @@ function DiceRoller({
30721
30722
  const dark = useDarkMode(darkMode);
30722
30723
  const [rolling, setRolling] = (0, import_react68.useState)(false);
30723
30724
  const [results, setResults] = (0, import_react68.useState)(null);
30724
- const [history, setHistory] = (0, import_react68.useState)([]);
30725
+ const [history, setHistory] = (0, import_react68.useState)(
30726
+ () => initialHistory && initialHistory.length > 0 ? initialHistory.slice(0, historyMax) : []
30727
+ );
30725
30728
  const dieWrapperRefs = (0, import_react68.useRef)([]);
30726
30729
  const cubeRefs = (0, import_react68.useRef)([]);
30727
30730
  const shadowRefs = (0, import_react68.useRef)([]);
@@ -30895,7 +30898,10 @@ function DiceRoller({
30895
30898
  defaultPositions,
30896
30899
  half
30897
30900
  ]);
30901
+ const prevSidesRef = (0, import_react68.useRef)(sides);
30898
30902
  (0, import_react68.useEffect)(() => {
30903
+ if (prevSidesRef.current === sides) return;
30904
+ prevSidesRef.current = sides;
30899
30905
  setHistory([]);
30900
30906
  }, [sides]);
30901
30907
  const roll = (0, import_react68.useCallback)(() => {
@@ -31951,32 +31957,26 @@ function BracketGenerator({
31951
31957
  const autoPlay = (0, import_react69.useCallback)(() => {
31952
31958
  if (simulating) {
31953
31959
  if (autoRef.current) clearTimeout(autoRef.current);
31960
+ autoRef.current = null;
31954
31961
  setSimulating(false);
31955
31962
  return;
31956
31963
  }
31957
31964
  setSimulating(true);
31958
- const tick = () => {
31959
- setBracketState((prev) => {
31960
- const next = prev.matches.find((m) => m.a && m.b && !m.winner);
31961
- if (!next) {
31962
- setSimulating(false);
31963
- return prev;
31964
- }
31965
- const completed = simulateMatch(next);
31966
- if (!completed) return prev;
31967
- let matches = prev.matches.map(
31968
- (m) => m.id === completed.id ? completed : m
31969
- );
31970
- matches = propagateWinner(matches, completed);
31971
- const finalMatch = matches.find((m) => m.round === prev.rounds - 1);
31972
- const champion = finalMatch?.winner || null;
31973
- if (!champion) autoRef.current = setTimeout(tick, simulateDelayMs);
31974
- else setSimulating(false);
31975
- return { ...prev, matches, champion };
31976
- });
31965
+ simulateNext();
31966
+ }, [simulating, simulateNext]);
31967
+ (0, import_react69.useEffect)(() => {
31968
+ if (!simulating) return;
31969
+ const hasNext = bracketState.matches.some((m) => m.a && m.b && !m.winner);
31970
+ if (bracketState.champion || !hasNext) {
31971
+ setSimulating(false);
31972
+ return;
31973
+ }
31974
+ autoRef.current = setTimeout(simulateNext, simulateDelayMs);
31975
+ return () => {
31976
+ if (autoRef.current) clearTimeout(autoRef.current);
31977
+ autoRef.current = null;
31977
31978
  };
31978
- tick();
31979
- }, [simulating, simulateDelayMs]);
31979
+ }, [simulating, bracketState, simulateDelayMs, simulateNext]);
31980
31980
  const advanceWinner = (0, import_react69.useCallback)(
31981
31981
  (matchId, winnerSlot) => {
31982
31982
  setBracketState((prev) => {
@@ -32964,6 +32964,8 @@ function CoinFlipper({
32964
32964
  tailsLabel = "Tails",
32965
32965
  showHistory = false,
32966
32966
  historyMax = 20,
32967
+ initialHistory,
32968
+ initialStats,
32967
32969
  flipRef,
32968
32970
  ariaLabel,
32969
32971
  className = ""
@@ -32972,8 +32974,23 @@ function CoinFlipper({
32972
32974
  const tc = typeof theme5 === "string" ? PRESETS3[theme5] || PRESETS3.gold : theme5;
32973
32975
  const [result, setResult] = (0, import_react70.useState)(null);
32974
32976
  const [flipping, setFlipping] = (0, import_react70.useState)(false);
32975
- const [history, setHistory] = (0, import_react70.useState)([]);
32976
- const [stats, setStats] = (0, import_react70.useState)({ heads: 0, tails: 0 });
32977
+ const [history, setHistory] = (0, import_react70.useState)(
32978
+ () => initialHistory && initialHistory.length > 0 ? initialHistory.slice(0, historyMax).map((result2, i) => ({ result: result2, id: -(i + 1) })) : []
32979
+ );
32980
+ const [stats, setStats] = (0, import_react70.useState)(() => {
32981
+ if (initialStats) {
32982
+ return {
32983
+ heads: Math.max(0, Math.floor(initialStats.heads)),
32984
+ tails: Math.max(0, Math.floor(initialStats.tails))
32985
+ };
32986
+ }
32987
+ if (initialHistory && initialHistory.length > 0) {
32988
+ const seeded = initialHistory.slice(0, historyMax);
32989
+ const heads = seeded.filter((r) => r === "heads").length;
32990
+ return { heads, tails: seeded.length - heads };
32991
+ }
32992
+ return { heads: 0, tails: 0 };
32993
+ });
32977
32994
  const flipIdRef = (0, import_react70.useRef)(0);
32978
32995
  const coinRef = (0, import_react70.useRef)(null);
32979
32996
  const shadowRef = (0, import_react70.useRef)(null);
@@ -33885,6 +33902,23 @@ function ReelStage({
33885
33902
  }
33886
33903
  );
33887
33904
  }
33905
+ function computeDraftSeed(participants, drawnIds, removeWinners) {
33906
+ if (!removeWinners || !drawnIds || drawnIds.length === 0) return null;
33907
+ if (new Set(drawnIds).size !== drawnIds.length) return null;
33908
+ const byId = new Map(participants.map((p) => [p.id, p]));
33909
+ const drawn = [];
33910
+ for (const id of drawnIds) {
33911
+ const p = byId.get(id);
33912
+ if (!p) return null;
33913
+ drawn.push(p);
33914
+ }
33915
+ const drawnSet = new Set(drawnIds);
33916
+ return {
33917
+ pool: participants.filter((p) => !drawnSet.has(p.id)),
33918
+ // Same cap as the live history (see the pop/promote paths).
33919
+ history: drawn.slice(0, 20)
33920
+ };
33921
+ }
33888
33922
  function RandomPlayerPicker({
33889
33923
  participants,
33890
33924
  mode = "wheel",
@@ -33899,6 +33933,7 @@ function RandomPlayerPicker({
33899
33933
  trigger = "button",
33900
33934
  label = "Spin",
33901
33935
  removeWinners = false,
33936
+ initialDrawnIds,
33902
33937
  spinDuration = 5e3,
33903
33938
  minRevolutions = 4,
33904
33939
  maxRevolutions = 7,
@@ -33925,12 +33960,21 @@ function RandomPlayerPicker({
33925
33960
  const reducedMotion = usePrefersReducedMotion();
33926
33961
  const intensityCfg = INTENSITY_CONFIG[intensity];
33927
33962
  const physicsDriven = intensity === "dramatic" && !reducedMotion;
33928
- const [pool, setPool] = (0, import_react71.useState)(participants);
33963
+ const [draftSeed] = (0, import_react71.useState)(
33964
+ () => computeDraftSeed(participants, initialDrawnIds, removeWinners)
33965
+ );
33966
+ const [pool, setPool] = (0, import_react71.useState)(
33967
+ draftSeed ? draftSeed.pool : participants
33968
+ );
33929
33969
  const [rotation, setRotation] = (0, import_react71.useState)(0);
33930
33970
  const [reelY, setReelY] = (0, import_react71.useState)(0);
33931
33971
  const [spinning, setSpinning] = (0, import_react71.useState)(false);
33932
- const [winner, setWinner] = (0, import_react71.useState)(null);
33933
- const [history, setHistory] = (0, import_react71.useState)([]);
33972
+ const [winner, setWinner] = (0, import_react71.useState)(
33973
+ draftSeed ? draftSeed.history[0] ?? null : null
33974
+ );
33975
+ const [history, setHistory] = (0, import_react71.useState)(
33976
+ draftSeed ? draftSeed.history : []
33977
+ );
33934
33978
  const [revealKey, setRevealKey] = (0, import_react71.useState)(0);
33935
33979
  const [particles, setParticles] = (0, import_react71.useState)([]);
33936
33980
  const winnerTimer = (0, import_react71.useRef)(null);
@@ -33942,7 +33986,9 @@ function RandomPlayerPicker({
33942
33986
  const tickAudioRef = (0, import_react71.useRef)(null);
33943
33987
  const winAudioRef = (0, import_react71.useRef)(null);
33944
33988
  const containerRef = (0, import_react71.useRef)(null);
33945
- const prevIdsRef = (0, import_react71.useRef)("");
33989
+ const prevIdsRef = (0, import_react71.useRef)(
33990
+ draftSeed ? participants.map((p) => p.id).join("|") : ""
33991
+ );
33946
33992
  const wheelSvgRef = (0, import_react71.useRef)(null);
33947
33993
  const reelTapeRef = (0, import_react71.useRef)(null);
33948
33994
  const rafRef = (0, import_react71.useRef)(null);
@@ -39721,8 +39767,8 @@ function RosterImport({
39721
39767
  maxNames,
39722
39768
  maxFileBytes,
39723
39769
  onClose: () => setOpen(false),
39724
- onImport: (names) => {
39725
- onImport(names);
39770
+ onImport: (names, info) => {
39771
+ onImport(names, info);
39726
39772
  setOpen(false);
39727
39773
  }
39728
39774
  }
@@ -39788,6 +39834,11 @@ function RosterImportModal({
39788
39834
  }),
39789
39835
  [table, column, skipHeader, maxNames]
39790
39836
  );
39837
+ const extractedTotal = (0, import_react75.useMemo)(
39838
+ () => extractRoster(table, { column, skipHeader }),
39839
+ [table, column, skipHeader]
39840
+ );
39841
+ const trimmedCount = Math.max(0, extractedTotal.length - extracted.length);
39791
39842
  const handleFile = (0, import_react75.useCallback)(
39792
39843
  async (e) => {
39793
39844
  setFileError(null);
@@ -40165,7 +40216,10 @@ function RosterImportModal({
40165
40216
  "button",
40166
40217
  {
40167
40218
  type: "button",
40168
- onClick: () => onImport(extracted),
40219
+ onClick: () => onImport(extracted, {
40220
+ trimmedCount,
40221
+ totalCount: extractedTotal.length
40222
+ }),
40169
40223
  disabled: !canImport,
40170
40224
  style: primaryBtn(!canImport),
40171
40225
  children: confirmLabel
@@ -40369,6 +40423,19 @@ function IconUsers({ size = 40 }) {
40369
40423
  }
40370
40424
  );
40371
40425
  }
40426
+ function mergeNames(prev, incoming, max) {
40427
+ const seen = new Set(prev.map((n) => n.toLowerCase()));
40428
+ const room = Math.max(0, max - prev.length);
40429
+ const deduped = [];
40430
+ for (const n of incoming) {
40431
+ const key = n.toLowerCase();
40432
+ if (seen.has(key)) continue;
40433
+ seen.add(key);
40434
+ deduped.push(n);
40435
+ if (deduped.length >= room) break;
40436
+ }
40437
+ return deduped.length > 0 ? [...prev, ...deduped] : prev;
40438
+ }
40372
40439
  function splitIntoTeams(names, count2) {
40373
40440
  const shuffled = shuffle(names);
40374
40441
  const teams = Array.from({ length: count2 }, () => []);
@@ -40442,29 +40509,14 @@ function TeamGenerator({
40442
40509
  const trimmed = input.trim();
40443
40510
  if (!trimmed) return;
40444
40511
  const newNames = trimmed.split(/[,\n]+/).map((n) => n.trim()).filter((n) => n.length > 0);
40445
- setNames((prev) => {
40446
- const room = Math.max(0, maxPlayers - prev.length);
40447
- return [...prev, ...newNames.slice(0, room)];
40448
- });
40512
+ setNames((prev) => mergeNames(prev, newNames, maxPlayers));
40449
40513
  setInput("");
40450
40514
  inputRef.current?.focus();
40451
40515
  }, [input, maxPlayers]);
40452
40516
  const importNames = (0, import_react76.useCallback)(
40453
40517
  (incoming) => {
40454
40518
  if (incoming.length === 0) return;
40455
- setNames((prev) => {
40456
- const seen = new Set(prev.map((n) => n.toLowerCase()));
40457
- const room = Math.max(0, maxPlayers - prev.length);
40458
- const deduped = [];
40459
- for (const n of incoming) {
40460
- const key = n.toLowerCase();
40461
- if (seen.has(key)) continue;
40462
- seen.add(key);
40463
- deduped.push(n);
40464
- if (deduped.length >= room) break;
40465
- }
40466
- return deduped.length > 0 ? [...prev, ...deduped] : prev;
40467
- });
40519
+ setNames((prev) => mergeNames(prev, incoming, maxPlayers));
40468
40520
  },
40469
40521
  [maxPlayers]
40470
40522
  );
@@ -41065,6 +41117,7 @@ function RoundRobinScheduler({
41065
41117
  resumeRounds,
41066
41118
  onTeamsChange,
41067
41119
  onGenerate,
41120
+ onBeforeScheduleDiscard,
41068
41121
  labels,
41069
41122
  ariaLabel = "Round robin scheduler",
41070
41123
  className,
@@ -41083,7 +41136,7 @@ function RoundRobinScheduler({
41083
41136
  (0, import_react77.useEffect)(() => {
41084
41137
  onTeamsChange?.(teams);
41085
41138
  }, [teams, onTeamsChange]);
41086
- const mergeNames = (0, import_react77.useCallback)(
41139
+ const mergeNames2 = (0, import_react77.useCallback)(
41087
41140
  (prev, incoming) => {
41088
41141
  const existing = new Set(prev.map((t) => t.toLowerCase()));
41089
41142
  const next = [...prev];
@@ -41100,23 +41153,29 @@ function RoundRobinScheduler({
41100
41153
  },
41101
41154
  [maxTeams, nameMaxLength]
41102
41155
  );
41156
+ const gateDiscard = (0, import_react77.useCallback)(
41157
+ (action) => rounds.length === 0 || !onBeforeScheduleDiscard || onBeforeScheduleDiscard(action),
41158
+ [rounds.length, onBeforeScheduleDiscard]
41159
+ );
41103
41160
  const addTeams = (0, import_react77.useCallback)(
41104
41161
  (raw) => {
41105
41162
  const names = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
41106
41163
  if (names.length === 0) return;
41107
- setTeams((prev) => mergeNames(prev, names));
41164
+ if (!gateDiscard("add")) return;
41165
+ setTeams((prev) => mergeNames2(prev, names));
41108
41166
  setInput("");
41109
41167
  setRounds([]);
41110
41168
  },
41111
- [mergeNames]
41169
+ [mergeNames2, gateDiscard]
41112
41170
  );
41113
41171
  const importTeams = (0, import_react77.useCallback)(
41114
41172
  (incoming) => {
41115
41173
  if (incoming.length === 0) return;
41116
- setTeams((prev) => mergeNames(prev, incoming));
41174
+ if (!gateDiscard("import")) return;
41175
+ setTeams((prev) => mergeNames2(prev, incoming));
41117
41176
  setRounds([]);
41118
41177
  },
41119
- [mergeNames]
41178
+ [mergeNames2, gateDiscard]
41120
41179
  );
41121
41180
  const handleKeyDown = (0, import_react77.useCallback)(
41122
41181
  (e) => {
@@ -41127,10 +41186,14 @@ function RoundRobinScheduler({
41127
41186
  },
41128
41187
  [addTeams, input]
41129
41188
  );
41130
- const removeTeam = (0, import_react77.useCallback)((idx) => {
41131
- setTeams((prev) => prev.filter((_, i) => i !== idx));
41132
- setRounds([]);
41133
- }, []);
41189
+ const removeTeam = (0, import_react77.useCallback)(
41190
+ (idx) => {
41191
+ if (!gateDiscard("remove")) return;
41192
+ setTeams((prev) => prev.filter((_, i) => i !== idx));
41193
+ setRounds([]);
41194
+ },
41195
+ [gateDiscard]
41196
+ );
41134
41197
  const handleGenerate = (0, import_react77.useCallback)(() => {
41135
41198
  if (teams.length < minTeams) return;
41136
41199
  const result = generateSchedule(teams, L.roundLabel);
@@ -41138,15 +41201,17 @@ function RoundRobinScheduler({
41138
41201
  onGenerate?.(result);
41139
41202
  }, [teams, minTeams, L.roundLabel, onGenerate]);
41140
41203
  const handleShuffle = (0, import_react77.useCallback)(() => {
41204
+ if (!gateDiscard("shuffle")) return;
41141
41205
  setTeams((prev) => shuffle(prev));
41142
41206
  setRounds([]);
41143
- }, []);
41207
+ }, [gateDiscard]);
41144
41208
  const handleClear = (0, import_react77.useCallback)(() => {
41209
+ if (!gateDiscard("clear")) return;
41145
41210
  setTeams([]);
41146
41211
  setRounds([]);
41147
41212
  setInput("");
41148
41213
  inputRef.current?.focus();
41149
- }, []);
41214
+ }, [gateDiscard]);
41150
41215
  const canGenerate = teams.length >= minTeams;
41151
41216
  const rootStyle = {
41152
41217
  borderRadius: 16,
@@ -41607,7 +41672,11 @@ function Scoreboard({
41607
41672
  onRoundAdvance,
41608
41673
  onNewGame,
41609
41674
  onReset,
41675
+ onBeforeReset,
41676
+ onBeforeNewGame,
41610
41677
  nameMaxLength,
41678
+ scoreMin = -9999,
41679
+ scoreMax = 9999,
41611
41680
  labels,
41612
41681
  ariaLabel = "Scoreboard",
41613
41682
  className
@@ -41651,11 +41720,19 @@ function Scoreboard({
41651
41720
  (0, import_react78.useEffect)(() => {
41652
41721
  onTeamsChange?.(teams);
41653
41722
  }, [teams, onTeamsChange]);
41654
- const updateScore = (0, import_react78.useCallback)((index, delta) => {
41655
- setTeams(
41656
- (prev) => prev.map((t, i) => i === index ? { ...t, score: t.score + delta } : t)
41657
- );
41658
- }, []);
41723
+ const updateScore = (0, import_react78.useCallback)(
41724
+ (index, delta) => {
41725
+ setTeams(
41726
+ (prev) => prev.map(
41727
+ (t, i) => i === index ? {
41728
+ ...t,
41729
+ score: Math.min(scoreMax, Math.max(scoreMin, t.score + delta))
41730
+ } : t
41731
+ )
41732
+ );
41733
+ },
41734
+ [scoreMin, scoreMax]
41735
+ );
41659
41736
  const updateName = (0, import_react78.useCallback)((index, name) => {
41660
41737
  setTeams((prev) => prev.map((t, i) => i === index ? { ...t, name } : t));
41661
41738
  }, []);
@@ -41678,19 +41755,21 @@ function Scoreboard({
41678
41755
  [minTeams]
41679
41756
  );
41680
41757
  const resetScores = (0, import_react78.useCallback)(() => {
41758
+ if (onBeforeReset && !onBeforeReset()) return;
41681
41759
  setTeams((prev) => prev.map((t) => ({ ...t, score: 0 })));
41682
41760
  setRound(1);
41683
41761
  setHistory([]);
41684
41762
  setHistoryOpen(false);
41685
41763
  onReset?.();
41686
- }, [onReset]);
41764
+ }, [onReset, onBeforeReset]);
41687
41765
  const newGame = (0, import_react78.useCallback)(() => {
41766
+ if (onBeforeNewGame && !onBeforeNewGame()) return;
41688
41767
  setTeams(createDefaultTeams(seedCount, L.teamNamePrefix));
41689
41768
  setRound(1);
41690
41769
  setHistory([]);
41691
41770
  setHistoryOpen(false);
41692
41771
  onNewGame?.();
41693
- }, [seedCount, L.teamNamePrefix, onNewGame]);
41772
+ }, [seedCount, L.teamNamePrefix, onNewGame, onBeforeNewGame]);
41694
41773
  const nextRound = (0, import_react78.useCallback)(() => {
41695
41774
  const snapshot = {
41696
41775
  round,