useCardOpening
useCardOpening is a card opening without a look. Give it the cards, which of them are opened, and what to do when one is. It keeps the queue, tracks the card that is up, and turns presses into decisions: turn it now, send it home, stop the run.
phase: idle
- Unopened
- Unopened
- Unopened
- Unopened
- Unopened
- Unopened
The hook with a plain div and CSS. No ui package on this one.
Usage
import { useCardOpening, useOpenedCards } from "@react-cards-opening/core";
const { opened, open } = useOpenedCards("deal:ada:2026");
const hunt = useCardOpening({ cards, opened, onOpen: open });
hunt.start(); // open every unopened card, one after another
hunt.start(["ash-phoenix"]); // or only these
hunt.replay(); // go over all of them again, opening nothing
hunt.openAll(); // skip the showOptions
| Option | Type | What it is |
|---|---|---|
cards | readonly C[] | Every card in the deal, opened or not. A card is at least { id, rarity }. |
opened | readonly string[] | Ids already opened. Keep it in any state you like. |
onOpen | (id: string) => void | A card was turned face up for the first time. |
onRunChange? | (running: boolean) => void | A run started or came to rest. |
What it returns
| Field | Type | What it is |
|---|---|---|
opening | Opening | The rules themselves. Hand it to whatever draws the reveal. |
current | C | null | The card that is up, in a run or a peek. |
pending | readonly C[] | Cards nobody has opened yet. |
run | OpeningRun | null | The queue being opened: { id, queue, replay }. |
step | number | Which card of the queue is up. |
left | number | How many come after the one that is up. |
running | boolean | A run is on. |
allOpened | boolean | Nothing is left to open. |
peeking | OpeningPeek | null | An opened card being looked at again. |
start(queue?) | => boolean | Opens unopened cards. False when there is nothing to open. |
replay() | => boolean | Goes over every card again. onOpen stays quiet. |
openAll() | => void | Takes the show down and opens everything at once. |
peek(id, origin) | => void | Brings an opened card up out of what was pressed. |
close() | => void | Puts a peeked card back. |
pressCard() | => void | A press on the card. See the rules below. |
pressBackdrop() | => void | A press beside the card. |
The rules of a press
| When | A press on the card | A press beside it |
|---|---|---|
| Face down | Turns it over now. | Stops the run. The card goes back unopened. |
| Turning or face up | Sends it home now. | Stops the run. This card still lands, nothing else opens. |
| On its way home | Nothing. | Nothing. |
| Peeking | Puts it back. | Puts it back. |
A card counts as opened the moment it is face up, not when it lands. That way whatever your page does about it (a list growing by one, a request to a server) happens while the viewer is reading, and the card can leave the instant they press.
The phase
The phase changes several times a card, so it has its own hook. Only the component that shows it re-renders.
import { useOpeningPhase } from "@react-cards-opening/core";
const phase = useOpeningPhase(hunt.opening);
// "idle" | "lead" | "flight" | "hum" | "turn" | "hold" | "land" | "peek"Without React
The hook is a thin wrapper over the Opening class, which has no React in it. Use the class directly in a store, a test or a game loop.
import { Opening } from "@react-cards-opening/core";
const opening = new Opening();
opening.sync({ cards, opened, onOpen });
opening.subscribe(() => render(opening.getState()));
opening.start();