@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/web/index.js CHANGED
@@ -30539,6 +30539,7 @@ function DiceRoller({
30539
30539
  iconEmoji = "\u{1F3B2}",
30540
30540
  showHistory = false,
30541
30541
  historyMax = 8,
30542
+ initialHistory,
30542
30543
  rollRef,
30543
30544
  arenaHeight: arenaHeightProp,
30544
30545
  hideResults = false,
@@ -30550,7 +30551,9 @@ function DiceRoller({
30550
30551
  const dark = useDarkMode(darkMode);
30551
30552
  const [rolling, setRolling] = (0, import_react69.useState)(false);
30552
30553
  const [results, setResults] = (0, import_react69.useState)(null);
30553
- const [history, setHistory] = (0, import_react69.useState)([]);
30554
+ const [history, setHistory] = (0, import_react69.useState)(
30555
+ () => initialHistory && initialHistory.length > 0 ? initialHistory.slice(0, historyMax) : []
30556
+ );
30554
30557
  const dieWrapperRefs = (0, import_react69.useRef)([]);
30555
30558
  const cubeRefs = (0, import_react69.useRef)([]);
30556
30559
  const shadowRefs = (0, import_react69.useRef)([]);
@@ -30724,7 +30727,10 @@ function DiceRoller({
30724
30727
  defaultPositions,
30725
30728
  half
30726
30729
  ]);
30730
+ const prevSidesRef = (0, import_react69.useRef)(sides);
30727
30731
  (0, import_react69.useEffect)(() => {
30732
+ if (prevSidesRef.current === sides) return;
30733
+ prevSidesRef.current = sides;
30728
30734
  setHistory([]);
30729
30735
  }, [sides]);
30730
30736
  const roll = (0, import_react69.useCallback)(() => {
@@ -31780,32 +31786,26 @@ function BracketGenerator({
31780
31786
  const autoPlay = (0, import_react70.useCallback)(() => {
31781
31787
  if (simulating) {
31782
31788
  if (autoRef.current) clearTimeout(autoRef.current);
31789
+ autoRef.current = null;
31783
31790
  setSimulating(false);
31784
31791
  return;
31785
31792
  }
31786
31793
  setSimulating(true);
31787
- const tick = () => {
31788
- setBracketState((prev) => {
31789
- const next = prev.matches.find((m) => m.a && m.b && !m.winner);
31790
- if (!next) {
31791
- setSimulating(false);
31792
- return prev;
31793
- }
31794
- const completed = simulateMatch(next);
31795
- if (!completed) return prev;
31796
- let matches = prev.matches.map(
31797
- (m) => m.id === completed.id ? completed : m
31798
- );
31799
- matches = propagateWinner(matches, completed);
31800
- const finalMatch = matches.find((m) => m.round === prev.rounds - 1);
31801
- const champion = finalMatch?.winner || null;
31802
- if (!champion) autoRef.current = setTimeout(tick, simulateDelayMs);
31803
- else setSimulating(false);
31804
- return { ...prev, matches, champion };
31805
- });
31794
+ simulateNext();
31795
+ }, [simulating, simulateNext]);
31796
+ (0, import_react70.useEffect)(() => {
31797
+ if (!simulating) return;
31798
+ const hasNext = bracketState.matches.some((m) => m.a && m.b && !m.winner);
31799
+ if (bracketState.champion || !hasNext) {
31800
+ setSimulating(false);
31801
+ return;
31802
+ }
31803
+ autoRef.current = setTimeout(simulateNext, simulateDelayMs);
31804
+ return () => {
31805
+ if (autoRef.current) clearTimeout(autoRef.current);
31806
+ autoRef.current = null;
31806
31807
  };
31807
- tick();
31808
- }, [simulating, simulateDelayMs]);
31808
+ }, [simulating, bracketState, simulateDelayMs, simulateNext]);
31809
31809
  const advanceWinner = (0, import_react70.useCallback)(
31810
31810
  (matchId, winnerSlot) => {
31811
31811
  setBracketState((prev) => {
@@ -32793,6 +32793,8 @@ function CoinFlipper({
32793
32793
  tailsLabel = "Tails",
32794
32794
  showHistory = false,
32795
32795
  historyMax = 20,
32796
+ initialHistory,
32797
+ initialStats,
32796
32798
  flipRef,
32797
32799
  ariaLabel,
32798
32800
  className = ""
@@ -32801,8 +32803,23 @@ function CoinFlipper({
32801
32803
  const tc = typeof theme5 === "string" ? PRESETS3[theme5] || PRESETS3.gold : theme5;
32802
32804
  const [result, setResult] = (0, import_react71.useState)(null);
32803
32805
  const [flipping, setFlipping] = (0, import_react71.useState)(false);
32804
- const [history, setHistory] = (0, import_react71.useState)([]);
32805
- const [stats, setStats] = (0, import_react71.useState)({ heads: 0, tails: 0 });
32806
+ const [history, setHistory] = (0, import_react71.useState)(
32807
+ () => initialHistory && initialHistory.length > 0 ? initialHistory.slice(0, historyMax).map((result2, i) => ({ result: result2, id: -(i + 1) })) : []
32808
+ );
32809
+ const [stats, setStats] = (0, import_react71.useState)(() => {
32810
+ if (initialStats) {
32811
+ return {
32812
+ heads: Math.max(0, Math.floor(initialStats.heads)),
32813
+ tails: Math.max(0, Math.floor(initialStats.tails))
32814
+ };
32815
+ }
32816
+ if (initialHistory && initialHistory.length > 0) {
32817
+ const seeded = initialHistory.slice(0, historyMax);
32818
+ const heads = seeded.filter((r) => r === "heads").length;
32819
+ return { heads, tails: seeded.length - heads };
32820
+ }
32821
+ return { heads: 0, tails: 0 };
32822
+ });
32806
32823
  const flipIdRef = (0, import_react71.useRef)(0);
32807
32824
  const coinRef = (0, import_react71.useRef)(null);
32808
32825
  const shadowRef = (0, import_react71.useRef)(null);
@@ -33731,6 +33748,23 @@ function ReelStage({
33731
33748
  }
33732
33749
  );
33733
33750
  }
33751
+ function computeDraftSeed(participants, drawnIds, removeWinners) {
33752
+ if (!removeWinners || !drawnIds || drawnIds.length === 0) return null;
33753
+ if (new Set(drawnIds).size !== drawnIds.length) return null;
33754
+ const byId = new Map(participants.map((p) => [p.id, p]));
33755
+ const drawn = [];
33756
+ for (const id of drawnIds) {
33757
+ const p = byId.get(id);
33758
+ if (!p) return null;
33759
+ drawn.push(p);
33760
+ }
33761
+ const drawnSet = new Set(drawnIds);
33762
+ return {
33763
+ pool: participants.filter((p) => !drawnSet.has(p.id)),
33764
+ // Same cap as the live history (see the pop/promote paths).
33765
+ history: drawn.slice(0, 20)
33766
+ };
33767
+ }
33734
33768
  function RandomPlayerPicker({
33735
33769
  participants,
33736
33770
  mode = "wheel",
@@ -33745,6 +33779,7 @@ function RandomPlayerPicker({
33745
33779
  trigger = "button",
33746
33780
  label = "Spin",
33747
33781
  removeWinners = false,
33782
+ initialDrawnIds,
33748
33783
  spinDuration = 5e3,
33749
33784
  minRevolutions = 4,
33750
33785
  maxRevolutions = 7,
@@ -33771,12 +33806,21 @@ function RandomPlayerPicker({
33771
33806
  const reducedMotion = usePrefersReducedMotion();
33772
33807
  const intensityCfg = INTENSITY_CONFIG[intensity];
33773
33808
  const physicsDriven = intensity === "dramatic" && !reducedMotion;
33774
- const [pool, setPool] = (0, import_react73.useState)(participants);
33809
+ const [draftSeed] = (0, import_react73.useState)(
33810
+ () => computeDraftSeed(participants, initialDrawnIds, removeWinners)
33811
+ );
33812
+ const [pool, setPool] = (0, import_react73.useState)(
33813
+ draftSeed ? draftSeed.pool : participants
33814
+ );
33775
33815
  const [rotation, setRotation] = (0, import_react73.useState)(0);
33776
33816
  const [reelY, setReelY] = (0, import_react73.useState)(0);
33777
33817
  const [spinning, setSpinning] = (0, import_react73.useState)(false);
33778
- const [winner, setWinner] = (0, import_react73.useState)(null);
33779
- const [history, setHistory] = (0, import_react73.useState)([]);
33818
+ const [winner, setWinner] = (0, import_react73.useState)(
33819
+ draftSeed ? draftSeed.history[0] ?? null : null
33820
+ );
33821
+ const [history, setHistory] = (0, import_react73.useState)(
33822
+ draftSeed ? draftSeed.history : []
33823
+ );
33780
33824
  const [revealKey, setRevealKey] = (0, import_react73.useState)(0);
33781
33825
  const [particles, setParticles] = (0, import_react73.useState)([]);
33782
33826
  const winnerTimer = (0, import_react73.useRef)(null);
@@ -33788,7 +33832,9 @@ function RandomPlayerPicker({
33788
33832
  const tickAudioRef = (0, import_react73.useRef)(null);
33789
33833
  const winAudioRef = (0, import_react73.useRef)(null);
33790
33834
  const containerRef = (0, import_react73.useRef)(null);
33791
- const prevIdsRef = (0, import_react73.useRef)("");
33835
+ const prevIdsRef = (0, import_react73.useRef)(
33836
+ draftSeed ? participants.map((p) => p.id).join("|") : ""
33837
+ );
33792
33838
  const wheelSvgRef = (0, import_react73.useRef)(null);
33793
33839
  const reelTapeRef = (0, import_react73.useRef)(null);
33794
33840
  const rafRef = (0, import_react73.useRef)(null);
@@ -39362,8 +39408,8 @@ function RosterImport({
39362
39408
  maxNames,
39363
39409
  maxFileBytes,
39364
39410
  onClose: () => setOpen(false),
39365
- onImport: (names) => {
39366
- onImport(names);
39411
+ onImport: (names, info) => {
39412
+ onImport(names, info);
39367
39413
  setOpen(false);
39368
39414
  }
39369
39415
  }
@@ -39429,6 +39475,11 @@ function RosterImportModal({
39429
39475
  }),
39430
39476
  [table, column, skipHeader, maxNames]
39431
39477
  );
39478
+ const extractedTotal = (0, import_react77.useMemo)(
39479
+ () => extractRoster(table, { column, skipHeader }),
39480
+ [table, column, skipHeader]
39481
+ );
39482
+ const trimmedCount = Math.max(0, extractedTotal.length - extracted.length);
39432
39483
  const handleFile = (0, import_react77.useCallback)(
39433
39484
  async (e) => {
39434
39485
  setFileError(null);
@@ -39806,7 +39857,10 @@ function RosterImportModal({
39806
39857
  "button",
39807
39858
  {
39808
39859
  type: "button",
39809
- onClick: () => onImport(extracted),
39860
+ onClick: () => onImport(extracted, {
39861
+ trimmedCount,
39862
+ totalCount: extractedTotal.length
39863
+ }),
39810
39864
  disabled: !canImport,
39811
39865
  style: primaryBtn(!canImport),
39812
39866
  children: confirmLabel
@@ -40010,6 +40064,19 @@ function IconUsers({ size = 40 }) {
40010
40064
  }
40011
40065
  );
40012
40066
  }
40067
+ function mergeNames(prev, incoming, max) {
40068
+ const seen = new Set(prev.map((n) => n.toLowerCase()));
40069
+ const room = Math.max(0, max - prev.length);
40070
+ const deduped = [];
40071
+ for (const n of incoming) {
40072
+ const key = n.toLowerCase();
40073
+ if (seen.has(key)) continue;
40074
+ seen.add(key);
40075
+ deduped.push(n);
40076
+ if (deduped.length >= room) break;
40077
+ }
40078
+ return deduped.length > 0 ? [...prev, ...deduped] : prev;
40079
+ }
40013
40080
  function splitIntoTeams(names, count2) {
40014
40081
  const shuffled = shuffle(names);
40015
40082
  const teams = Array.from({ length: count2 }, () => []);
@@ -40083,29 +40150,14 @@ function TeamGenerator({
40083
40150
  const trimmed = input.trim();
40084
40151
  if (!trimmed) return;
40085
40152
  const newNames = trimmed.split(/[,\n]+/).map((n) => n.trim()).filter((n) => n.length > 0);
40086
- setNames((prev) => {
40087
- const room = Math.max(0, maxPlayers - prev.length);
40088
- return [...prev, ...newNames.slice(0, room)];
40089
- });
40153
+ setNames((prev) => mergeNames(prev, newNames, maxPlayers));
40090
40154
  setInput("");
40091
40155
  inputRef.current?.focus();
40092
40156
  }, [input, maxPlayers]);
40093
40157
  const importNames = (0, import_react78.useCallback)(
40094
40158
  (incoming) => {
40095
40159
  if (incoming.length === 0) return;
40096
- setNames((prev) => {
40097
- const seen = new Set(prev.map((n) => n.toLowerCase()));
40098
- const room = Math.max(0, maxPlayers - prev.length);
40099
- const deduped = [];
40100
- for (const n of incoming) {
40101
- const key = n.toLowerCase();
40102
- if (seen.has(key)) continue;
40103
- seen.add(key);
40104
- deduped.push(n);
40105
- if (deduped.length >= room) break;
40106
- }
40107
- return deduped.length > 0 ? [...prev, ...deduped] : prev;
40108
- });
40160
+ setNames((prev) => mergeNames(prev, incoming, maxPlayers));
40109
40161
  },
40110
40162
  [maxPlayers]
40111
40163
  );
@@ -40706,6 +40758,7 @@ function RoundRobinScheduler({
40706
40758
  resumeRounds,
40707
40759
  onTeamsChange,
40708
40760
  onGenerate,
40761
+ onBeforeScheduleDiscard,
40709
40762
  labels,
40710
40763
  ariaLabel = "Round robin scheduler",
40711
40764
  className,
@@ -40724,7 +40777,7 @@ function RoundRobinScheduler({
40724
40777
  (0, import_react79.useEffect)(() => {
40725
40778
  onTeamsChange?.(teams);
40726
40779
  }, [teams, onTeamsChange]);
40727
- const mergeNames = (0, import_react79.useCallback)(
40780
+ const mergeNames2 = (0, import_react79.useCallback)(
40728
40781
  (prev, incoming) => {
40729
40782
  const existing = new Set(prev.map((t) => t.toLowerCase()));
40730
40783
  const next = [...prev];
@@ -40741,23 +40794,29 @@ function RoundRobinScheduler({
40741
40794
  },
40742
40795
  [maxTeams, nameMaxLength]
40743
40796
  );
40797
+ const gateDiscard = (0, import_react79.useCallback)(
40798
+ (action) => rounds.length === 0 || !onBeforeScheduleDiscard || onBeforeScheduleDiscard(action),
40799
+ [rounds.length, onBeforeScheduleDiscard]
40800
+ );
40744
40801
  const addTeams = (0, import_react79.useCallback)(
40745
40802
  (raw) => {
40746
40803
  const names = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
40747
40804
  if (names.length === 0) return;
40748
- setTeams((prev) => mergeNames(prev, names));
40805
+ if (!gateDiscard("add")) return;
40806
+ setTeams((prev) => mergeNames2(prev, names));
40749
40807
  setInput("");
40750
40808
  setRounds([]);
40751
40809
  },
40752
- [mergeNames]
40810
+ [mergeNames2, gateDiscard]
40753
40811
  );
40754
40812
  const importTeams = (0, import_react79.useCallback)(
40755
40813
  (incoming) => {
40756
40814
  if (incoming.length === 0) return;
40757
- setTeams((prev) => mergeNames(prev, incoming));
40815
+ if (!gateDiscard("import")) return;
40816
+ setTeams((prev) => mergeNames2(prev, incoming));
40758
40817
  setRounds([]);
40759
40818
  },
40760
- [mergeNames]
40819
+ [mergeNames2, gateDiscard]
40761
40820
  );
40762
40821
  const handleKeyDown = (0, import_react79.useCallback)(
40763
40822
  (e) => {
@@ -40768,10 +40827,14 @@ function RoundRobinScheduler({
40768
40827
  },
40769
40828
  [addTeams, input]
40770
40829
  );
40771
- const removeTeam = (0, import_react79.useCallback)((idx) => {
40772
- setTeams((prev) => prev.filter((_, i) => i !== idx));
40773
- setRounds([]);
40774
- }, []);
40830
+ const removeTeam = (0, import_react79.useCallback)(
40831
+ (idx) => {
40832
+ if (!gateDiscard("remove")) return;
40833
+ setTeams((prev) => prev.filter((_, i) => i !== idx));
40834
+ setRounds([]);
40835
+ },
40836
+ [gateDiscard]
40837
+ );
40775
40838
  const handleGenerate = (0, import_react79.useCallback)(() => {
40776
40839
  if (teams.length < minTeams) return;
40777
40840
  const result = generateSchedule(teams, L.roundLabel);
@@ -40779,15 +40842,17 @@ function RoundRobinScheduler({
40779
40842
  onGenerate?.(result);
40780
40843
  }, [teams, minTeams, L.roundLabel, onGenerate]);
40781
40844
  const handleShuffle = (0, import_react79.useCallback)(() => {
40845
+ if (!gateDiscard("shuffle")) return;
40782
40846
  setTeams((prev) => shuffle(prev));
40783
40847
  setRounds([]);
40784
- }, []);
40848
+ }, [gateDiscard]);
40785
40849
  const handleClear = (0, import_react79.useCallback)(() => {
40850
+ if (!gateDiscard("clear")) return;
40786
40851
  setTeams([]);
40787
40852
  setRounds([]);
40788
40853
  setInput("");
40789
40854
  inputRef.current?.focus();
40790
- }, []);
40855
+ }, [gateDiscard]);
40791
40856
  const canGenerate = teams.length >= minTeams;
40792
40857
  const rootStyle = {
40793
40858
  borderRadius: 16,
@@ -41248,7 +41313,11 @@ function Scoreboard({
41248
41313
  onRoundAdvance,
41249
41314
  onNewGame,
41250
41315
  onReset,
41316
+ onBeforeReset,
41317
+ onBeforeNewGame,
41251
41318
  nameMaxLength,
41319
+ scoreMin = -9999,
41320
+ scoreMax = 9999,
41252
41321
  labels,
41253
41322
  ariaLabel = "Scoreboard",
41254
41323
  className
@@ -41292,11 +41361,19 @@ function Scoreboard({
41292
41361
  (0, import_react80.useEffect)(() => {
41293
41362
  onTeamsChange?.(teams);
41294
41363
  }, [teams, onTeamsChange]);
41295
- const updateScore = (0, import_react80.useCallback)((index, delta) => {
41296
- setTeams(
41297
- (prev) => prev.map((t, i) => i === index ? { ...t, score: t.score + delta } : t)
41298
- );
41299
- }, []);
41364
+ const updateScore = (0, import_react80.useCallback)(
41365
+ (index, delta) => {
41366
+ setTeams(
41367
+ (prev) => prev.map(
41368
+ (t, i) => i === index ? {
41369
+ ...t,
41370
+ score: Math.min(scoreMax, Math.max(scoreMin, t.score + delta))
41371
+ } : t
41372
+ )
41373
+ );
41374
+ },
41375
+ [scoreMin, scoreMax]
41376
+ );
41300
41377
  const updateName = (0, import_react80.useCallback)((index, name) => {
41301
41378
  setTeams((prev) => prev.map((t, i) => i === index ? { ...t, name } : t));
41302
41379
  }, []);
@@ -41319,19 +41396,21 @@ function Scoreboard({
41319
41396
  [minTeams]
41320
41397
  );
41321
41398
  const resetScores = (0, import_react80.useCallback)(() => {
41399
+ if (onBeforeReset && !onBeforeReset()) return;
41322
41400
  setTeams((prev) => prev.map((t) => ({ ...t, score: 0 })));
41323
41401
  setRound(1);
41324
41402
  setHistory([]);
41325
41403
  setHistoryOpen(false);
41326
41404
  onReset?.();
41327
- }, [onReset]);
41405
+ }, [onReset, onBeforeReset]);
41328
41406
  const newGame = (0, import_react80.useCallback)(() => {
41407
+ if (onBeforeNewGame && !onBeforeNewGame()) return;
41329
41408
  setTeams(createDefaultTeams(seedCount, L.teamNamePrefix));
41330
41409
  setRound(1);
41331
41410
  setHistory([]);
41332
41411
  setHistoryOpen(false);
41333
41412
  onNewGame?.();
41334
- }, [seedCount, L.teamNamePrefix, onNewGame]);
41413
+ }, [seedCount, L.teamNamePrefix, onNewGame, onBeforeNewGame]);
41335
41414
  const nextRound = (0, import_react80.useCallback)(() => {
41336
41415
  const snapshot = {
41337
41416
  round,