Skip to content

JacksGo/score-bug

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Score Bug A shrugging GitHub Copilot logo with text: Built without a Copilot.

This is a (mostly) one-week side project I created in order to familiarize myself with Web Components. It's still rough around the edges in some places, but the foundations are there and everything largely works. In total, it's taken about ten days of on-and-off work to get things to where they are now. I created the first version in about 1,800 lines over the course of five days, but I've since gone back and refactored/rewritten large portions of the code, fixed bugs, and added features.

Where possible and deemed valuable, I've added thorough comments to describe functionality and explain my thinking. Some of these comments are too thorough. Some contain Unicode diagrams. Who doesn't love diagrams?

Warning

As of writing, this project is essentially non-functional in WebKit (even Technical Preview). Their handling of CSS trigonometric functions (and maybe variables writ large?) is badly broken, and returns wildly incorrect values for expressions that Gecko and Chromium get right (in particular Jane Ori's tan-atan2 trick1). There's a comment about this in style.css. Because I'm stubborn and uncompromising to a fault (hire me!), rather than remove the broken code, I'm releasing it as-is with a disclaimer. Surely they'll fix it at some point. Surely.

Features

I've packed a ton of features into this project. Frankly, I realized things were getting out of hand around the time I Googled how to say "2 outs" in Arabic, but hey, whaddaya gonna do?

Writing System-Agnostic

Left-to-right Right-to-left Vertical (Tategaki)
An image of the score bug with English text An image of the score bug with Arabic text An image of the score bug with Japanese text

With a little bit of finagling, this component will generally work with any writing system, even a vertical one. Functions to set stats on players come with optional arguments to change the separator text in proportions like "1 of 3", and to swap the order of the numbers for writing systems that use denominator-first fractions, like Japanese. Changing between writing modes simply requires setting the corresponding CSS properties on the document (or the score-bug component, specifically, if desired). For right-to-left writing systems, this is direction: rtl. For Tategaki, it's writing-mode: vertical-rl.

There are two caveats here.

  1. Because CSS doesn't currently support logical transform functions (e.g., translateBlock(-50%)), and also doesn't have a writing-mode selector or query function (e.g., :writing-mode(vertical)), animations do not work properly in vertical layouts.
  2. Full internationalization hasn't yet been implemented. This only really affects static UI strings, which, so far, means only the "Out" label. I plan to make the "Out" label customizable in the future, but in the meantime, it's been simulated in the screenshots above.

Font-Driven Sizing

Every measurement in the score-bug component and its children is anchored to its font-size. If you change the font size of the score-bug light-DOM component to xx-large with CSS, the entire component will grow to match that scale. You can also freely change the font, and unless you choose something deliberately wonky, the entire component should stretch or shrink to fit.

Animated Transitions

Team scores, the inning and half numbers, and all values inside stickies (the player banners above the score bug), transition smoothly between values, both in terms of content and the space needed to fit it.

Additionally, all sizing transitions either use hard-coded values or auto with interpolate-size. No getBoundingClientRect() here!

Important

As mentioned earlier, animations for stickies do not function correctly in vertical layouts. I'm largely at the mercy of CSSWG on this, so unless there's a compelling and exigent need that would warrant a JS-based hack, I'm inclined to leave it alone for now.

Accessible Components and Live Announcing

All displayed values in the score-bug component have accessible labels showing their value. Moreover, changes to relevant values will be automatically announced when using a screen reader. Labels, and especially game events, are currently designed for English only, but I may fix this at some point eventually.

In the future, I intend to add a greater degree of customization to the announcing functionality—in particular, giving screen reader users the option to choose which game events are announced.

To-Do List

I do not consider this to be a final product. I plan to continue working on it periodically, especially as new CSS features are added which might be helpful in improving the user experience. If you're interested in using this in a public-facing project of your own, you are welcome to do so, subject to the license restrictions. However, you should be aware that this is a "living work", and, as it's not currently packaged for distribution, incorporating updates and bug fixes from the repo is solely your responsibility. If you do intend to use this in a large-scale project, I'd encourage you to reach out ahread of time so I can help address any specific needs you may have.

Below is the list of things I plan to do, want to do, or can't do because of things outside of my control.

  • Standardize all components to use imperative interfaces (except for visual-only components, which should use attributes).
  • Improve the show/hide behavior. Add both an update and an updateInstant method to content-flipper to quash the transition when hidden.
  • Suspend sticky sizing animations on update until the sticky is shown. Collapse the sticky back down to default size once hidden. This would essentially just involve retaining the values in state and clearing them on hide.
  • Make content-flipper/numeric-display changes async/awaitable. For content-flipper, this means ditching the MutationObserver model, since there's no way to await a function triggered by an event.
  • Add back the gap between sticky items.
  • Add custom stickyable items, e.g., for injury timeouts, delays, etc. Support different stat values, including timers.
  • Support multiple custom stickyable items, with or without timeouts, using a monotonically increasing ID value as a handle to dismiss or update?
  • Externalize validation logic so that we don't need to call down to components to find out if they've changed. This is one of the reasons that readable-change events have to be called from their respective visual components and bubbled up.
  • Add screen reader options to allow for fine-grained control over which events/readable-changes get called out.
  • Create a sink which buckets incoming events and gives programmatic control over the readout order. E.g., "always read the score first".
  • Figure out why the 0.5px workaround is necessary in player-item.js.
  • Confirm full support for right-to-left writing systems.
  • Fully switch to logical properties to support vertical writing systems.
    • There are no logical transform functions, so sticky transitions don't function in vertical mode :(
  • Hope that CSSWG specs a selector/query for writing-mode2.
    • Conditionally remove the ordinal dot in vertical writing modes.
    • Restyle and reorder the game-state footer when vertical.
  • Hope that CSSWG adds logical transform functions (e.g., translateBlock())2.
    • content-flipper animations don't work correctly in vertical writing modes (because of translateY()).
    • Changing to or from a vertical writing mode after interacting with a sticky will cause that sticky to no longer function (because of fill: forwards).

Interface

The score-bug component has a veritable cornucopia of functions to play with. Collect them all!

Note

All of these functions can be called on the bug object on the demo page via the console.

bug.setTeam(side, data)

Set the team data of a given side.

Param Type Description
side 'home' | 'away' Which side of the score bug to modify.
data.abbr string The abbreviation that appears on the score bug.
data.name string The full name of the team. Read out by screen readers and shown on hover.
data.colors.background string The background color of the team on the score bug.
data.colors.text string The text color of the team on the score bug.

bug.setScore(side, score)

Set the score for a given side.

Param Type Description
side 'home' | 'away' Which side of the score bug to modify.
score number The number of runs to set to. Rolls over internally at 100.

bug.setBases(first, second, third)

Set each of the bases on the base diagram as occupied or empty. Any missing arguments are treated as false.

Param Type
first boolean
second boolean
third boolean

bug.setInning(half, inning)

Set the half arrow (top or bottom) and the inning number.

Param Type
half 'top' | 'bottom'
inning number

bug.setOuts(outs)

Set the number of outs.

Param Type
outs number

bug.setCount(balls, strikes)

Set the count of balls and strikes. Setting to 0-0 will hide the count.

Param Type
balls number
strikes number

bug.showBatter([data])

Show the batter's information above the score bug, optionally setting it first. If batter information hasn't previously been set, calling this without data will throw an error.

Param Type Description
data BattingPlayer
data.order number The batter's position in the batting order.
data.name string The batter's family name.
data.stat PlayerStat | undefined The initial statistic to display for the batter.

bug.hideBatter()

Hide the batter's information above the score bug.

bug.showBatterStat(data, [options])

Show a stat for the batter above the score bug. Optionally, include an object with mode: 'peek' to instead show the stat for a given duration, then switch back.

Param Type
data PlayerStat
options { mode: 'peek', duration: number } | undefined

bug.showPitcher([data])

Show the pitcher's information above the score bug, optionally setting it first. If pitcher information hasn't previously been set, calling this without data will throw an error.

Param Type Description
data PitchingPlayer
data.pitches number The initial number of pitches to display for the pitcher.
data.name string The pitcher's family name.
data.stat PlayerStat | undefined The initial statistic to display for the pitcher. If undefined, the pitch count is displayed automatically.

bug.hidePitcher()

Hide the pitcher's information above the score bug.

bug.showPitcherStat(data, [options])

Show a stat for the pitcher above the score bug. Optionally, include an object with mode: 'peek' to instead show the stat for a given duration, then switch back.

Param Type
data PlayerStat
options { mode: 'peek', duration: number } | undefined

bug.setPitches(count)

Set the number of pitches thrown by the current pitcher.

Param Type
count number

Types

PlayerStat

Any stat to be displayed next to a player. A discriminated union of the following:

  • PlayerProportionStat
  • PlayerIntegerStat
  • PlayerStringStat
  • PlayerSpeedStat

PlayerProportionStat

A stat containing two digits forming a fraction. Can be displayed in multiple formats:

  • 'of' - Displays as [count] OF [total].
    • The content of "OF" can be changed using the optional separator property.
    • If using a language which conveys fractions denominator-first (e.g., Japanese), add inverted: true.
  • 'decimal' - Displays count / total as a float with three decimal places. If the quotient is in the range (0, 1), the leading zero is omitted, batting average-style.
  • 'percentage' - Displays count / total as an integer percentage.

Properties

Name Type Description
type 'proportion'
format 'of' | 'decimal' | 'percentage'
count number The numerator of the proportion.
total number The denominator of the proportion.
separator string | undefined An optional replacement to the default "OF" text when using format: 'of'.
inverted boolean | undefined Whether the writing system displays written fractions denominator first.
label string | undefined

PlayerIntegerStat

A stat containing an arbitrary integer. Truncates fractional parts, if included.

Properties

Name Type
type 'integer'
value number
label string | undefined

PlayerStringStat

A stat containing an arbitrary string.

Properties

Name Type
type 'string'
value string
label string | undefined

PlayerSpeedStat

A stat with colored text. Typically used in mode: peek to show the last pitch's speed.

Properties

Name Type
type 'speed'
value string
label string | undefined

Closing Thoughts

  • Thanks for reading!
  • Please hire me

Footnotes

  1. https://dev.to/janeori/css-type-casting-to-numeric-tanatan2-scalars-582j

  2. I hoped, but nothing happened. 2

About

A TV-style baseball score bug implemented with Web Components

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published