react-raffle-picker 0.1.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.
@@ -0,0 +1,151 @@
1
+ import * as _$react from "react";
2
+ import { CSSProperties, ElementType, ReactNode, RefObject } from "react";
3
+ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
4
+
5
+ //#region src/types.d.ts
6
+ type AnimationType = 'roll' | 'fade' | 'blur' | 'reel';
7
+ type RafflePickValue$1 = number | string;
8
+ interface RafflePickRootProps {
9
+ items?: string[];
10
+ min?: number;
11
+ max?: number;
12
+ interval?: number;
13
+ random?: boolean;
14
+ inertia?: boolean;
15
+ autoStart?: boolean;
16
+ onSelect?: (value: RafflePickValue$1) => void;
17
+ as?: ElementType;
18
+ className?: string;
19
+ style?: CSSProperties;
20
+ children?: ReactNode;
21
+ }
22
+ interface RafflePickValueProps {
23
+ animation?: AnimationType;
24
+ className?: string;
25
+ style?: CSSProperties;
26
+ as?: ElementType;
27
+ }
28
+ interface RafflePickButtonProps {
29
+ className?: string;
30
+ style?: CSSProperties;
31
+ /** Fallback label across all states. */
32
+ children?: ReactNode;
33
+ /** Label for idle / frozen (click starts a round). */
34
+ startLabel?: ReactNode;
35
+ /** Label while running (click stops). */
36
+ stopLabel?: ReactNode;
37
+ /** Label while settling (button disabled). */
38
+ waitLabel?: ReactNode;
39
+ }
40
+ interface RafflePickSlotsProps {
41
+ /** Number of slots. */
42
+ length?: number;
43
+ /** Character pool each slot picks from. */
44
+ chars?: string;
45
+ /** Tick interval per slot, ms. Clamped ≥ 50. */
46
+ spinInterval?: number;
47
+ /** Delay between consecutive slot stops on settle, ms. */
48
+ staggerMs?: number;
49
+ className?: string;
50
+ slotClassName?: string;
51
+ style?: CSSProperties;
52
+ slotStyle?: CSSProperties;
53
+ /** Fires when last slot stops with joined result. */
54
+ onResult?: (result: string) => void;
55
+ }
56
+ interface RafflePickCountdownProps {
57
+ /** Seconds before auto-freeze. Required. */
58
+ seconds: number;
59
+ className?: string;
60
+ style?: CSSProperties;
61
+ /** Render-prop for fully custom output. Receives remaining seconds. */
62
+ children?: (remaining: number) => ReactNode;
63
+ }
64
+ //#endregion
65
+ //#region src/components/RafflePick/RafflePick.d.ts
66
+ declare function RafflePickRoot({
67
+ items,
68
+ min,
69
+ max,
70
+ interval,
71
+ random,
72
+ inertia,
73
+ autoStart,
74
+ onSelect,
75
+ as,
76
+ className,
77
+ style,
78
+ children
79
+ }: RafflePickRootProps): _$react.ReactElement<any, string | _$react.JSXElementConstructor<any>>;
80
+ //#endregion
81
+ //#region src/components/RafflePick/RafflePickValue.d.ts
82
+ declare function RafflePickValue({
83
+ animation,
84
+ className,
85
+ style,
86
+ as
87
+ }: RafflePickValueProps): _$react_jsx_runtime0.JSX.Element;
88
+ //#endregion
89
+ //#region src/components/RafflePick/RafflePickButton.d.ts
90
+ declare function RafflePickButton({
91
+ className,
92
+ style,
93
+ children,
94
+ startLabel,
95
+ stopLabel,
96
+ waitLabel
97
+ }: RafflePickButtonProps): _$react_jsx_runtime0.JSX.Element;
98
+ //#endregion
99
+ //#region src/components/RafflePick/RafflePickCountdown.d.ts
100
+ declare function RafflePickCountdown({
101
+ seconds,
102
+ className,
103
+ style,
104
+ children
105
+ }: RafflePickCountdownProps): _$react_jsx_runtime0.JSX.Element | null;
106
+ //#endregion
107
+ //#region src/components/RafflePick/RafflePickSlots.d.ts
108
+ declare function RafflePickSlots({
109
+ length,
110
+ chars,
111
+ spinInterval,
112
+ staggerMs,
113
+ className,
114
+ slotClassName,
115
+ style,
116
+ slotStyle,
117
+ onResult
118
+ }: RafflePickSlotsProps): _$react_jsx_runtime0.JSX.Element;
119
+ //#endregion
120
+ //#region src/utils/inertia.d.ts
121
+ type RafflePickPhase = 'idle' | 'starting' | 'running' | 'settling' | 'frozen';
122
+ //#endregion
123
+ //#region src/components/RafflePick/context.d.ts
124
+ interface RaffleContextValue {
125
+ phase: RafflePickPhase;
126
+ step: number;
127
+ displayed: RafflePickValue$1;
128
+ cycleInterval: number;
129
+ inertia: boolean;
130
+ hasItems: boolean;
131
+ initialIndex: number;
132
+ valueRef: RefObject<number>;
133
+ displayValue: (index: number) => RafflePickValue$1;
134
+ subscribe: (fn: (value: number) => void) => () => void;
135
+ start: () => void;
136
+ freeze: () => void;
137
+ reset: () => void;
138
+ }
139
+ declare const RaffleContext: _$react.Context<RaffleContextValue | null>;
140
+ declare const useRaffleContext: (componentName: string) => RaffleContextValue;
141
+ //#endregion
142
+ //#region src/components/RafflePick/index.d.ts
143
+ type RafflePickCompound = typeof RafflePickRoot & {
144
+ Value: typeof RafflePickValue;
145
+ Button: typeof RafflePickButton;
146
+ Countdown: typeof RafflePickCountdown;
147
+ Slots: typeof RafflePickSlots;
148
+ };
149
+ declare const RafflePick: RafflePickCompound;
150
+ //#endregion
151
+ export { type AnimationType, RaffleContext, RafflePick, RafflePickButton, type RafflePickButtonProps, RafflePickCountdown, type RafflePickCountdownProps, type RafflePickRootProps, RafflePickSlots, type RafflePickSlotsProps, RafflePickValue, type RafflePickValueProps, type RafflePickValue$1 as RafflePickValueType, useRaffleContext };
package/dist/index.mjs ADDED
@@ -0,0 +1,542 @@
1
+ import { createContext, createElement, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/utils/get-random.ts
4
+ const getRandom = (min, max) => {
5
+ min = Math.ceil(min);
6
+ max = Math.floor(max);
7
+ return Math.floor(Math.random() * (max - min + 1)) + min;
8
+ };
9
+ //#endregion
10
+ //#region src/hooks/useNumberCycle.ts
11
+ const useNumberCycle = ({ min, max, interval, random, running, valueRef, onTick }) => {
12
+ const onTickRef = useRef(onTick);
13
+ useEffect(() => {
14
+ onTickRef.current = onTick;
15
+ });
16
+ useEffect(() => {
17
+ if (!running) return;
18
+ const id = setInterval(() => {
19
+ const cur = valueRef.current;
20
+ const next = random ? getRandom(min, max) : cur >= max ? min : cur + 1;
21
+ valueRef.current = next;
22
+ onTickRef.current?.(next);
23
+ }, interval);
24
+ return () => clearInterval(id);
25
+ }, [
26
+ running,
27
+ interval,
28
+ min,
29
+ max,
30
+ random,
31
+ valueRef
32
+ ]);
33
+ return valueRef;
34
+ };
35
+ //#endregion
36
+ //#region src/utils/inertia.ts
37
+ const STOP_INERTIA_MS = 1080;
38
+ const START_INTERVAL_MULTIPLIERS = [
39
+ 2.4,
40
+ 1.55,
41
+ 1
42
+ ];
43
+ const STOP_INTERVAL_MULTIPLIERS = [
44
+ 1.15,
45
+ 1.65,
46
+ 2.35,
47
+ 3.25
48
+ ];
49
+ const START_INERTIA_STEPS = [{
50
+ delay: 140,
51
+ step: 1
52
+ }, {
53
+ delay: 300,
54
+ step: 2
55
+ }];
56
+ const STOP_INERTIA_STEPS = [
57
+ {
58
+ delay: 180,
59
+ step: 1
60
+ },
61
+ {
62
+ delay: 460,
63
+ step: 2
64
+ },
65
+ {
66
+ delay: 760,
67
+ step: 3
68
+ }
69
+ ];
70
+ const getInertiaMultiplier = (phase, step, inertia) => {
71
+ if (!inertia) return 1;
72
+ if (phase === "starting") return START_INTERVAL_MULTIPLIERS[step] ?? 1;
73
+ if (phase === "settling") return STOP_INTERVAL_MULTIPLIERS[step] ?? STOP_INTERVAL_MULTIPLIERS[STOP_INTERVAL_MULTIPLIERS.length - 1];
74
+ return 1;
75
+ };
76
+ //#endregion
77
+ //#region src/hooks/useRafflePhase.ts
78
+ const reducer = (s, a) => a.type === "phase" ? {
79
+ phase: a.phase,
80
+ step: 0
81
+ } : {
82
+ phase: s.phase,
83
+ step: a.step
84
+ };
85
+ function useRafflePhase(inertia, initialPhase, onSettle) {
86
+ const [state, dispatch] = useReducer(reducer, {
87
+ phase: initialPhase,
88
+ step: 0
89
+ });
90
+ const timersRef = useRef([]);
91
+ const onSettleRef = useRef(onSettle);
92
+ useEffect(() => {
93
+ onSettleRef.current = onSettle;
94
+ });
95
+ const clear = useCallback(() => {
96
+ const timers = timersRef.current;
97
+ for (let i = 0; i < timers.length; i++) clearTimeout(timers[i]);
98
+ timersRef.current = [];
99
+ }, []);
100
+ useEffect(() => {
101
+ if (state.phase === "starting") {
102
+ for (const { delay, step } of START_INERTIA_STEPS) timersRef.current.push(setTimeout(() => dispatch({
103
+ type: "step",
104
+ step
105
+ }), delay));
106
+ timersRef.current.push(setTimeout(() => dispatch({
107
+ type: "phase",
108
+ phase: "running"
109
+ }), 460));
110
+ } else if (state.phase === "settling") {
111
+ for (const { delay, step } of STOP_INERTIA_STEPS) timersRef.current.push(setTimeout(() => dispatch({
112
+ type: "step",
113
+ step
114
+ }), delay));
115
+ timersRef.current.push(setTimeout(() => {
116
+ onSettleRef.current();
117
+ dispatch({
118
+ type: "phase",
119
+ phase: "frozen"
120
+ });
121
+ }, STOP_INERTIA_MS));
122
+ }
123
+ return clear;
124
+ }, [state.phase, clear]);
125
+ const start = useCallback(() => {
126
+ clear();
127
+ dispatch({
128
+ type: "phase",
129
+ phase: inertia ? "starting" : "running"
130
+ });
131
+ }, [clear, inertia]);
132
+ const freeze = useCallback(() => {
133
+ clear();
134
+ if (inertia) dispatch({
135
+ type: "phase",
136
+ phase: "settling"
137
+ });
138
+ else {
139
+ onSettleRef.current();
140
+ dispatch({
141
+ type: "phase",
142
+ phase: "frozen"
143
+ });
144
+ }
145
+ }, [clear, inertia]);
146
+ const reset = useCallback(() => {
147
+ clear();
148
+ dispatch({
149
+ type: "phase",
150
+ phase: "idle"
151
+ });
152
+ }, [clear]);
153
+ return {
154
+ phase: state.phase,
155
+ step: state.step,
156
+ start,
157
+ freeze,
158
+ reset
159
+ };
160
+ }
161
+ //#endregion
162
+ //#region src/utils/class-names.ts
163
+ const joinClassNames = (...classNames) => classNames.filter(Boolean).join(" ");
164
+ //#endregion
165
+ //#region src/components/RafflePick/context.ts
166
+ const RaffleContext = createContext(null);
167
+ const useRaffleContext = (componentName) => {
168
+ const ctx = useContext(RaffleContext);
169
+ if (!ctx) throw new Error(`<${componentName}> must be rendered inside <RafflePick>.`);
170
+ return ctx;
171
+ };
172
+ //#endregion
173
+ //#region src/components/RafflePick/RafflePick.tsx
174
+ function RafflePickRoot({ items, min = 1, max = 100, interval = 100, random = true, inertia = false, autoStart = true, onSelect, as = "div", className, style, children }) {
175
+ const itemCount = items?.length ?? 0;
176
+ const hasItems = itemCount > 0;
177
+ const cycleMin = hasItems ? 0 : min;
178
+ const cycleMax = hasItems ? itemCount - 1 : max;
179
+ const initialPhase = autoStart ? inertia ? "starting" : "running" : "idle";
180
+ const itemsRef = useRef(items);
181
+ useEffect(() => {
182
+ itemsRef.current = items;
183
+ }, [items]);
184
+ const displayValue = useCallback((index) => {
185
+ const its = itemsRef.current;
186
+ return its && its.length > 0 ? its[index] : index;
187
+ }, []);
188
+ const [displayed, setDisplayed] = useState(() => items && items.length > 0 ? items[cycleMin] : cycleMin);
189
+ const subscribersRef = useRef(/* @__PURE__ */ new Set());
190
+ const subscribe = useCallback((fn) => {
191
+ subscribersRef.current.add(fn);
192
+ return () => {
193
+ subscribersRef.current.delete(fn);
194
+ };
195
+ }, []);
196
+ const onTick = useCallback((value) => {
197
+ subscribersRef.current.forEach((fn) => fn(value));
198
+ }, []);
199
+ const valueRef = useRef(cycleMin);
200
+ const { phase, step, start, freeze, reset } = useRafflePhase(inertia, initialPhase, useCallback(() => {
201
+ const v = displayValue(valueRef.current);
202
+ setDisplayed(v);
203
+ onSelect?.(v);
204
+ }, [displayValue, onSelect]));
205
+ const multiplier = getInertiaMultiplier(phase, step, inertia);
206
+ const cycleInterval = Math.round(Math.max(50, interval) * multiplier);
207
+ useNumberCycle({
208
+ min: cycleMin,
209
+ max: cycleMax,
210
+ interval: cycleInterval,
211
+ random,
212
+ running: phase === "starting" || phase === "running" || phase === "settling",
213
+ valueRef,
214
+ onTick
215
+ });
216
+ const ctxValue = useMemo(() => ({
217
+ phase,
218
+ step,
219
+ displayed,
220
+ cycleInterval,
221
+ inertia,
222
+ hasItems,
223
+ initialIndex: cycleMin,
224
+ valueRef,
225
+ displayValue,
226
+ subscribe,
227
+ start,
228
+ freeze,
229
+ reset
230
+ }), [
231
+ phase,
232
+ step,
233
+ displayed,
234
+ cycleInterval,
235
+ inertia,
236
+ hasItems,
237
+ cycleMin,
238
+ displayValue,
239
+ subscribe,
240
+ start,
241
+ freeze,
242
+ reset
243
+ ]);
244
+ const selectionState = phase === "idle" ? "idle" : phase === "frozen" ? "frozen" : "running";
245
+ return createElement(as, {
246
+ className: joinClassNames("rrp", className),
247
+ "data-state": selectionState,
248
+ "data-phase": phase,
249
+ "data-inertia-step": step,
250
+ "data-inertia": inertia ? "" : void 0,
251
+ style
252
+ }, /* @__PURE__ */ jsx(RaffleContext.Provider, {
253
+ value: ctxValue,
254
+ children
255
+ }));
256
+ }
257
+ //#endregion
258
+ //#region src/components/RafflePick/RafflePickValue.tsx
259
+ function RafflePickValue({ animation = "roll", className, style, as = "span" }) {
260
+ const { phase, step, displayed, cycleInterval, valueRef, displayValue, subscribe } = useRaffleContext("RafflePick.Value");
261
+ const nodeRef = useRef(null);
262
+ const writeNode = useCallback((value) => {
263
+ const node = nodeRef.current;
264
+ if (!node) return;
265
+ const txt = String(displayValue(value));
266
+ node.textContent = txt;
267
+ node.setAttribute("data-value", txt);
268
+ if (typeof node.getAnimations === "function") {
269
+ const anims = node.getAnimations({ subtree: true });
270
+ for (let i = 0; i < anims.length; i++) anims[i].currentTime = 0;
271
+ }
272
+ }, [displayValue]);
273
+ useEffect(() => subscribe(writeNode), [subscribe, writeNode]);
274
+ const running = phase === "starting" || phase === "running" || phase === "settling";
275
+ useLayoutEffect(() => {
276
+ if (running) writeNode(valueRef.current);
277
+ });
278
+ return /* @__PURE__ */ jsx(as, {
279
+ ref: nodeRef,
280
+ className: useMemo(() => joinClassNames("rrp-value", className), [className]),
281
+ "data-animation": animation,
282
+ "data-value": displayed,
283
+ "data-phase": phase,
284
+ "data-inertia-step": step,
285
+ style: useMemo(() => ({
286
+ ...style,
287
+ ["--rrp-tick"]: `${cycleInterval}ms`
288
+ }), [style, cycleInterval]),
289
+ children: displayed
290
+ });
291
+ }
292
+ //#endregion
293
+ //#region src/components/RafflePick/RafflePickButton.tsx
294
+ function RafflePickButton({ className, style, children, startLabel, stopLabel, waitLabel }) {
295
+ const { phase, start, freeze, reset } = useRaffleContext("RafflePick.Button");
296
+ const running = phase === "starting" || phase === "running" || phase === "settling";
297
+ const handleClick = useCallback(() => {
298
+ if (phase === "settling") return;
299
+ if (running) {
300
+ freeze();
301
+ return;
302
+ }
303
+ reset();
304
+ start();
305
+ }, [
306
+ phase,
307
+ running,
308
+ freeze,
309
+ reset,
310
+ start
311
+ ]);
312
+ const cls = useMemo(() => joinClassNames("rrp-button", className), [className]);
313
+ let label;
314
+ if (phase === "settling") label = waitLabel ?? stopLabel ?? children;
315
+ else if (phase === "starting" || phase === "running") label = stopLabel ?? children;
316
+ else label = startLabel ?? children;
317
+ return /* @__PURE__ */ jsx("button", {
318
+ className: cls,
319
+ style,
320
+ disabled: phase === "settling",
321
+ onClick: handleClick,
322
+ "data-phase": phase,
323
+ children: label
324
+ });
325
+ }
326
+ //#endregion
327
+ //#region src/components/RafflePick/RafflePickCountdown.tsx
328
+ function RafflePickCountdown({ seconds, className, style, children }) {
329
+ const { phase } = useRaffleContext("RafflePick.Countdown");
330
+ if (phase !== "running") return null;
331
+ return /* @__PURE__ */ jsx(CountdownRunning, {
332
+ seconds,
333
+ className,
334
+ style,
335
+ children
336
+ }, `countdown-${seconds}`);
337
+ }
338
+ function CountdownRunning({ seconds, className, style, children }) {
339
+ const { freeze } = useRaffleContext("RafflePick.Countdown");
340
+ const [remaining, setRemaining] = useState(seconds);
341
+ useEffect(() => {
342
+ if (!seconds || seconds <= 0) return;
343
+ const tickId = setInterval(() => {
344
+ setRemaining((r) => r > 1 ? r - 1 : 0);
345
+ }, 1e3);
346
+ const stopId = setTimeout(() => freeze(), seconds * 1e3);
347
+ return () => {
348
+ clearInterval(tickId);
349
+ clearTimeout(stopId);
350
+ };
351
+ }, [seconds, freeze]);
352
+ const cls = useMemo(() => joinClassNames("rrp-countdown", className), [className]);
353
+ const mergedStyle = useMemo(() => ({
354
+ ...style,
355
+ ["--rrp-countdown"]: `${seconds}s`
356
+ }), [style, seconds]);
357
+ if (children) return /* @__PURE__ */ jsx("span", {
358
+ className: cls,
359
+ style: mergedStyle,
360
+ "aria-hidden": "true",
361
+ children: children(remaining)
362
+ });
363
+ return /* @__PURE__ */ jsxs("span", {
364
+ className: cls,
365
+ style: mergedStyle,
366
+ "aria-hidden": "true",
367
+ children: [/* @__PURE__ */ jsxs("svg", {
368
+ className: "rrp-countdown__svg",
369
+ viewBox: "0 0 36 36",
370
+ children: [/* @__PURE__ */ jsx("circle", {
371
+ className: "rrp-countdown__track",
372
+ cx: "18",
373
+ cy: "18",
374
+ r: "16"
375
+ }), /* @__PURE__ */ jsx("circle", {
376
+ className: "rrp-countdown__bar",
377
+ cx: "18",
378
+ cy: "18",
379
+ r: "16"
380
+ })]
381
+ }), /* @__PURE__ */ jsx("span", {
382
+ className: "rrp-countdown__label",
383
+ children: remaining
384
+ })]
385
+ });
386
+ }
387
+ //#endregion
388
+ //#region src/components/RafflePick/RafflePickSlots.tsx
389
+ const pickRandom = (pool) => pool[Math.floor(Math.random() * pool.length)] ?? "";
390
+ const makeRefs = () => ({
391
+ root: null,
392
+ col: null,
393
+ prev: null,
394
+ curr: null,
395
+ next: null,
396
+ currChar: "",
397
+ stopped: false
398
+ });
399
+ function RafflePickSlots({ length = 3, chars = "0123456789", spinInterval = 80, staggerMs = 220, className, slotClassName, style, slotStyle, onResult }) {
400
+ const { phase } = useRaffleContext("RafflePick.Slots");
401
+ const slotsRef = useRef([]);
402
+ const stopTimersRef = useRef([]);
403
+ const finalRef = useRef([]);
404
+ const onResultRef = useRef(onResult);
405
+ useEffect(() => {
406
+ onResultRef.current = onResult;
407
+ });
408
+ if (slotsRef.current.length !== length) slotsRef.current = Array.from({ length }, (_, i) => slotsRef.current[i] ?? makeRefs());
409
+ const pool = useMemo(() => Array.from(chars), [chars]);
410
+ const safeInterval = Math.max(50, spinInterval);
411
+ const running = phase === "starting" || phase === "running";
412
+ const settling = phase === "settling" || phase === "frozen";
413
+ const writeSlot = (s, prev, curr, next) => {
414
+ if (s.prev) s.prev.textContent = prev;
415
+ if (s.curr) s.curr.textContent = curr;
416
+ if (s.next) s.next.textContent = next;
417
+ s.currChar = curr;
418
+ if (s.root) s.root.setAttribute("data-value", curr);
419
+ if (s.col && typeof s.col.getAnimations === "function") {
420
+ const anims = s.col.getAnimations();
421
+ for (let a = 0; a < anims.length; a++) anims[a].currentTime = 0;
422
+ }
423
+ };
424
+ useEffect(() => {
425
+ if (!running) return;
426
+ for (let i = 0; i < length; i++) {
427
+ const s = slotsRef.current[i];
428
+ s.stopped = false;
429
+ if (s.root) s.root.removeAttribute("data-stopped");
430
+ writeSlot(s, pickRandom(pool), pickRandom(pool), pickRandom(pool));
431
+ }
432
+ finalRef.current = new Array(length).fill("");
433
+ const id = setInterval(() => {
434
+ for (let i = 0; i < length; i++) {
435
+ const s = slotsRef.current[i];
436
+ if (s.stopped) continue;
437
+ const newPrev = s.currChar;
438
+ writeSlot(s, newPrev, s.next?.textContent ?? pickRandom(pool), pickRandom(pool));
439
+ }
440
+ }, safeInterval);
441
+ return () => clearInterval(id);
442
+ }, [
443
+ running,
444
+ length,
445
+ pool,
446
+ safeInterval
447
+ ]);
448
+ useEffect(() => {
449
+ if (!settling) return;
450
+ const clear = () => {
451
+ stopTimersRef.current.forEach(clearTimeout);
452
+ stopTimersRef.current = [];
453
+ };
454
+ clear();
455
+ for (let i = 0; i < length; i++) {
456
+ const id = setTimeout(() => {
457
+ const s = slotsRef.current[i];
458
+ s.stopped = true;
459
+ if (s.root) s.root.setAttribute("data-stopped", "");
460
+ finalRef.current[i] = s.currChar;
461
+ if (i === length - 1) onResultRef.current?.(finalRef.current.join(""));
462
+ }, i * staggerMs);
463
+ stopTimersRef.current.push(id);
464
+ }
465
+ return clear;
466
+ }, [
467
+ settling,
468
+ length,
469
+ staggerMs
470
+ ]);
471
+ useEffect(() => {
472
+ if (phase !== "idle") return;
473
+ const c = pool[0] ?? "";
474
+ for (let i = 0; i < length; i++) {
475
+ const s = slotsRef.current[i];
476
+ s.stopped = true;
477
+ writeSlot(s, c, c, c);
478
+ if (s.root) s.root.removeAttribute("data-stopped");
479
+ }
480
+ }, [
481
+ phase,
482
+ length,
483
+ pool
484
+ ]);
485
+ const cls = useMemo(() => joinClassNames("rrp-slots", className), [className]);
486
+ const slotCls = useMemo(() => joinClassNames("rrp-slot", slotClassName), [slotClassName]);
487
+ const mergedSlotStyle = useMemo(() => ({
488
+ ...slotStyle,
489
+ ["--rrp-tick"]: `${safeInterval}ms`
490
+ }), [slotStyle, safeInterval]);
491
+ return /* @__PURE__ */ jsx("div", {
492
+ className: cls,
493
+ style,
494
+ "data-phase": phase,
495
+ children: Array.from({ length }, (_, i) => /* @__PURE__ */ jsx("span", {
496
+ ref: (n) => {
497
+ slotsRef.current[i].root = n;
498
+ },
499
+ className: slotCls,
500
+ style: mergedSlotStyle,
501
+ "data-slot-index": i,
502
+ children: /* @__PURE__ */ jsxs("span", {
503
+ className: "rrp-slot__col",
504
+ ref: (n) => {
505
+ slotsRef.current[i].col = n;
506
+ },
507
+ children: [
508
+ /* @__PURE__ */ jsx("span", {
509
+ className: "rrp-slot__cell",
510
+ ref: (n) => {
511
+ slotsRef.current[i].prev = n;
512
+ },
513
+ children: pool[0] ?? ""
514
+ }),
515
+ /* @__PURE__ */ jsx("span", {
516
+ className: "rrp-slot__cell",
517
+ ref: (n) => {
518
+ slotsRef.current[i].curr = n;
519
+ },
520
+ children: pool[0] ?? ""
521
+ }),
522
+ /* @__PURE__ */ jsx("span", {
523
+ className: "rrp-slot__cell",
524
+ ref: (n) => {
525
+ slotsRef.current[i].next = n;
526
+ },
527
+ children: pool[0] ?? ""
528
+ })
529
+ ]
530
+ })
531
+ }, i))
532
+ });
533
+ }
534
+ //#endregion
535
+ //#region src/components/RafflePick/index.ts
536
+ const RafflePick = RafflePickRoot;
537
+ RafflePick.Value = RafflePickValue;
538
+ RafflePick.Button = RafflePickButton;
539
+ RafflePick.Countdown = RafflePickCountdown;
540
+ RafflePick.Slots = RafflePickSlots;
541
+ //#endregion
542
+ export { RaffleContext, RafflePick, RafflePickButton, RafflePickCountdown, RafflePickSlots, RafflePickValue, useRaffleContext };