Events
All HTMLGameKit events bubble and are composed (they cross shadow DOM boundaries). This means you can dispatch them from any depth in the component tree and <game-shell> will catch them.
Round Lifecycle
Game mechanics communicate with the shell by dispatching custom DOM events. The shell catches them, updates signals, and drives scene transitions:
1. Scene enters "playing"
→ Timer starts (if present)
→ Game mechanic sets up the round
2. Player interacts
→ GameRoundPassEvent(score, feedback) success
OR GameRoundFailEvent(reason, retry) failure
OR GameTimerExpiredEvent time ran out
3. Shell catches the event, updates signals
→ scene → "between"
→ Director computes next difficulty
→ After betweenDelay, round increments, scene → "playing"
4. After all rounds (or GameCompleteEvent)
→ scene → "result"
Event Overview
| Event | Fired by | Caught by |
|---|---|---|
game-round-pass |
Your game | Shell |
game-round-fail |
Your game | Shell |
game-timer-tick |
<game-timer> |
Your game (optional) |
game-timer-expired |
<game-timer> |
Shell |
game-timer-countdown |
<game-timer> |
<game-audio> (optional) |
game-stat-update |
Your game | Shell |
game-lifecycle |
Shell | Your entry script |
game-start-request |
Intro button | Shell |
game-restart-request |
Result button | Shell |
game-complete |
Your game | Shell |
game-pause-request |
Any child | Shell |
game-resume-request |
Any child | Shell |
game-next-round |
Any child | Shell |
game-practice-start |
Any child | Shell |
game-trophy-unlock |
<game-trophy> |
Shell |
game-tile-input |
<game-tile-input> |
<game-audio> |
game-tile-submit |
<game-tile-input> |
Your game |
pending-task |
Any component | Shell |
game-preference-change |
<game-preferences> |
Your entry script |
Import
import {
GameRoundPassEvent,
GameRoundFailEvent,
GameTimerTickEvent,
GameTimerExpiredEvent,
GameTimerCountdownEvent,
GameStatUpdateEvent,
GameLifecycleEvent,
GameStartRequestEvent,
GameRestartRequestEvent,
GameCompleteEvent,
GamePauseRequestEvent,
GameResumeRequestEvent,
GameNextRoundEvent,
GamePracticeStartEvent,
GameTileInputEvent,
GameTileSubmitEvent,
PendingTaskEvent,
} from "htmlgamekit";
// Trophy and preference events are exported alongside their components:
import { GameTrophyUnlockEvent } from "htmlgamekit"; // dispatched by <game-trophy>
import { GamePreferenceChangeEvent } from "htmlgamekit"; // dispatched by <game-preferences>
GameRoundPassEvent
Signals that the player completed a round successfully.
- new GameRoundPassEvent(score, feedback?)
-
Event name:
"game-round-pass"Parameters:
score—number— The numeric score for this round.feedback—string(optional) — A short feedback string to show the player (e.g."Nice!","342ms").
- .score
number— The round score.- .feedback
string | undefined— Optional feedback text.
// Player answered correctly in 342ms
this.dispatchEvent(new GameRoundPassEvent(342, "342ms"));
GameRoundFailEvent
Signals that the player failed a round.
- new GameRoundFailEvent(reason?, retry?)
-
Event name:
"game-round-fail"Parameters:
reason—string(optional) — A message explaining the failure (e.g."Too slow","Wrong answer").retry—boolean(optional, defaultfalse) — Iftrue, the round does not count against the player's total. The round number stays the same and the player tries again. Iffalse, the round is consumed (counts as a failed attempt).
- .reason
string | undefined— Failure reason.- .retry
boolean— Whether this is a non-counting retry.
// Counting failure — round is consumed
this.dispatchEvent(new GameRoundFailEvent("Too slow"));
// Non-counting retry — player tries the same round again
this.dispatchEvent(new GameRoundFailEvent("False start!", true));
GameTimerTickEvent
Fired by <game-timer> on each tick to report remaining time.
- new GameTimerTickEvent(remaining, fraction)
-
Event name:
"game-timer-tick"Parameters:
remaining—number— Seconds remaining.fraction—number— Value between0and1representing the proportion of time elapsed (0 = start, 1 = expired).
- .remaining
number— Seconds left on the timer.- .fraction
number— Fraction of total time elapsed (0–1).
shell.addEventListener("game-timer-tick", (e) => {
if (e.remaining < 3) {
urgencyIndicator.classList.add("warning");
}
});
GameTimerExpiredEvent
Fired when the round timer reaches zero.
- new GameTimerExpiredEvent()
-
Event name:
"game-timer-expired"Takes no parameters. The shell catches this event and treats it as a round failure.
// Typically fired internally by <game-timer>, but you can dispatch it manually:
this.dispatchEvent(new GameTimerExpiredEvent());
GameStatUpdateEvent
Updates an arbitrary stat in the game state's stats map.
- new GameStatUpdateEvent(key, value)
-
Event name:
"game-stat-update"Parameters:
key—string— The stat key to update.value—any— The new value.
- .key
string— Stat key.- .value
any— Stat value.
// Track how many hints the player used
this.dispatchEvent(new GameStatUpdateEvent("hintsUsed", 3));
The shell merges this into the stats signal, so any component observing it will see the updated value.
GameLifecycleEvent
Fired by <game-shell> on every scene transition. This is the primary event for reacting to game scene changes.
- new GameLifecycleEvent(action, state)
-
Event name:
"game-lifecycle"Parameters:
action—string— The new scene name (e.g."playing","between","result").state—object— A snapshot of the full game state after the transition.
- .action
string— The triggering action.- .state
object— Full state snapshot.- .scene
string— Convenience alias fore.state.scene.
document.querySelector("game-shell").addEventListener("game-lifecycle", (e) => {
if (e.action === "result") {
console.log("Game over! Final score:", e.state.score);
}
if (e.state.scene === "playing") {
console.log(`Round ${e.state.round} of ${e.state.rounds}`);
}
});
GameStartRequestEvent
Signals that the player wants to start a game. Dispatched by the intro overlay (<div when-some-scene="intro" data-overlay>) when the user clicks a start button. The shell catches this and calls .start().
- new GameStartRequestEvent()
-
Event name:
"game-start-request"Takes no parameters.
// Typically dispatched by the intro overlay, but you can trigger it manually:
this.dispatchEvent(new GameStartRequestEvent());
GameRestartRequestEvent
Signals that the player wants to restart the game. Dispatched by the result overlay (<div when-some-scene="result" data-overlay>) when the user clicks a restart button. The shell catches this, resets state, and starts a new game.
- new GameRestartRequestEvent()
-
Event name:
"game-restart-request"Takes no parameters.
// Typically dispatched by the result overlay, but you can trigger it manually:
this.dispatchEvent(new GameRestartRequestEvent());
GameCompleteEvent
Signals that the game should end immediately, skipping any remaining rounds. The shell catches this, optionally sets the final score, and transitions to the result scene.
- new GameCompleteEvent(score?)
-
Event name:
"game-complete"Parameters:
score—number(optional) — If provided, overrides the shell's accumulated score before entering the result scene.
- .score
number | undefined— Optional final score override.
// End the game early with a specific score
this.dispatchEvent(new GameCompleteEvent(42));
// End the game early, keeping the accumulated score
this.dispatchEvent(new GameCompleteEvent());
GamePauseRequestEvent
Requests that the shell pause the game. The shell catches this and transitions from playing to paused.
- new GamePauseRequestEvent()
-
Event name:
"game-pause-request"Takes no parameters.
this.dispatchEvent(new GamePauseRequestEvent());
GameResumeRequestEvent
Requests that the shell resume a paused game. The shell catches this and transitions from paused back to playing.
- new GameResumeRequestEvent()
-
Event name:
"game-resume-request"Takes no parameters.
this.dispatchEvent(new GameResumeRequestEvent());
GameNextRoundEvent
Requests that the shell skip the between-round delay and advance to the next round immediately. Only takes effect when the current scene is between.
- new GameNextRoundEvent()
-
Event name:
"game-next-round"Takes no parameters.
this.dispatchEvent(new GameNextRoundEvent());
GamePracticeStartEvent
Signals that the player wants to enter practice mode. The shell catches this and sets scene to "practice".
- new GamePracticeStartEvent()
-
Event name:
"game-practice-start"Takes no parameters.
// Dispatch from a "Practice" button handler:
this.dispatchEvent(new GamePracticeStartEvent());
GameTileInputEvent
Dispatched by <game-tile-input> on every character change (type or delete). Used by <game-audio> with trigger="input" to play keystroke sounds.
- new GameTileInputEvent(value, position)
-
Event name:
"game-tile-input"Parameters:
value—string— The current input string.position—number— Index of the last changed character.
- .value
string— The current input string.- .position
number— Index of the last changed character.
tileInput.addEventListener("game-tile-input", (e) => {
console.log(`Input: "${e.value}", position: ${e.position}`);
});
GameTimerCountdownEvent
Fired by <game-timer> on each whole-second tick of the countdown. Used by <game-audio> with trigger="countdown" to play a ticking sound once per second.
- new GameTimerCountdownEvent(seconds)
-
Event name:
"game-timer-countdown"Parameters:
seconds—number— The whole number of seconds remaining.
- .seconds
number— Whole seconds remaining.
shell.addEventListener("game-timer-countdown", (e) => {
if (e.seconds <= 3) urgencyIndicator.classList.add("warning");
});
GameTileSubmitEvent
Dispatched by <game-tile-input> when the player presses Enter with a complete word.
- new GameTileSubmitEvent(value)
-
Event name:
"game-tile-submit"Parameters:
value—string— The submitted word.
- .value
string— The submitted word.
tileInput.addEventListener("game-tile-submit", (e) => {
const guess = e.value.toLowerCase();
// Validate and process the guess
});
GameTrophyUnlockEvent
Dispatched by <game-trophy> when a trophy is unlocked (either via .unlock() or auto-unlock conditions). Exported from "htmlgamekit" alongside GameTrophy.
- new GameTrophyUnlockEvent(id, name)
-
Event name:
"game-trophy-unlock"Parameters:
id--string-- The trophy'sidattribute.name--string-- The trophy'snameattribute.
- .trophyId
string-- The trophy's id.- .trophyName
string-- The trophy's display name.
shell.addEventListener("game-trophy-unlock", (e) => {
console.log(`Unlocked: ${e.trophyName} (${e.trophyId})`);
});
GamePreferenceChangeEvent
Dispatched by <game-preferences> when a preference value changes.
- new GamePreferenceChangeEvent(key, value)
-
Event name:
"game-preference-change"Parameters:
key--string-- The preference key.value--any-- The new value (boolean for toggles, number for ranges).
- .key
string-- The preference key.- .value
any-- The new value.
shell.addEventListener("game-preference-change", (e) => {
if (e.key === "dark-mode") {
document.body.classList.toggle("dark", e.value);
}
});
PendingTaskEvent
Signals that an asynchronous task is in progress. Used by components such as <game-score-form> and <game-leaderboard> to expose in-flight promises to any external loading coordinator (e.g. a loading indicator). The shell itself does not queue or await these tasks.
- new PendingTaskEvent(complete)
-
Event name:
"pending-task"Parameters:
complete—Promise— A promise that resolves when the task is done.
- .complete
Promise— The pending promise.
// Delay the next round until a sound effect finishes
const audioPromise = playSound("correct.mp3");
this.dispatchEvent(new PendingTaskEvent(audioPromise));
this.dispatchEvent(new GameRoundPassEvent(100, "Correct!"));