goma-top-competitions
Sidebar "Top Competitions" list — a section title and a vertical list of competition rows (sport icon + region flag + name). One-shot fetch per sport, single-selection state, emits goma:top-competition-select on tap.
Live demo
Open in the playground → — toggle Top Competitions in the widget rail.
Install
@gomagaming/top-competitions is a scoped private package. See Installation for the one-time .npmrc setup.
bash
npm install @gomagaming/top-competitionsYou also need the shared peer dependencies installed at your app level:
bash
npm install vue pinia vue-router vue-i18n @vueuse/core autobahn-browser swiper vue-toastificationElement
html
<goma-top-competitions id="tc"></goma-top-competitions>Configuration
js
const el = document.getElementById('tc')
el.config = {
socketUrl: 'wss://sportsapi.example.com/v2',
socketRealm: 'www.example.com',
apiBaseUrl: 'https://api.example.com',
bettingApiBaseUrl: 'https://sports-api.example.com',
ucsOperatorId: '2838',
locale: 'en',
}| Key | Type | Required | Description |
|---|---|---|---|
socketUrl | string | When dataSource !== 'prop' | WAMP socket URL. The widget hits popularTournaments over this transport. |
socketRealm | string | When dataSource !== 'prop' | WAMP realm. |
apiBaseUrl | string | When dataSource !== 'prop' | REST base URL. |
bettingApiBaseUrl | string | No | REST base URL for betting offers. |
ucsOperatorId | string | When dataSource !== 'prop' | Operator ID — used to build per-tenant URLs. |
locale | string | No | i18n locale. Defaults to 'en'. |
theme | object | No | CSS variable overrides — see Theming. |
messages | object | No | i18n overrides per locale. |
debug | boolean | No | Verbose console logging. |
Data sources
'prop' — host-fed
The widget renders whatever the host writes to el.competitions. Use this when the competition list comes from a REST endpoint or a CMS layer you already own.
js
el.dataSource = 'prop'
el.competitions = [
{ id: 106, name: 'UEFA Champions League', sportId: 1, venueId: 9 },
{ id: 102, name: 'La Liga', sportId: 1, venueId: 11 },
{ id: 110, name: 'Primeira Liga', sportId: 1, venueId: 16 },
]'rpc' — one-shot WAMP call (default)
The widget calls apiSession.$wcall(popularTournaments, { sportId?, numberOfCompetitions, lang, liveStatus: 'BOTH', sortByPopularity: true }) once the WAMP connection is up. sportId is optional on the backend:
- Cross-sport (default,
sportId: null) — omitsportIdfrom the kwargs. Backend returns top tournaments aggregated across every sport. - Sport-scoped (
sportId: 1etc.) — passsportId. Backend returns the top N for that sport.
Re-runs whenever sportId or numberOfCompetitions changes.
js
// Cross-sport (default — no sportId)
el.dataSource = 'rpc'
el.config = { /* socket + REST creds */ }
// Sport-scoped
el.dataSource = 'rpc'
el.sportId = 1
el.numberOfCompetitions = 5
el.config = { /* socket + REST creds */ }'subscribe' — live WAMP subscription (placeholder)
Note: the backend topic for live top-competition updates is not yet available. The widget accepts
dataSource: 'subscribe'but internally falls back to the one-shot RPC. Once the topic ships, the fallback flips automatically.
Props (HTML attributes + JS properties)
| Property | Attribute | Default | Description |
|---|---|---|---|
dataSource | data-source | 'rpc' | 'prop' / 'rpc' / 'subscribe'. |
sportId | sport-id | null | Sport id. null / 0 / missing (default) → cross-sport REST. A real number → sport-scoped WAMP RPC. |
numberOfCompetitions | number-of-competitions | 5 | Max number of competitions to request (sport-scoped path only). |
competitions | — | [] | Array of GomaCompetition objects. Populated by the WAMP composable when dataSource !== 'prop'. |
selectedCompetitionId | selected-competition-id | null | Id of the currently-selected row. Two-way: clicks update this value. |
showTitle | show-title | true | Render the "Top Competitions" header above the list. |
skeletonCount | skeleton-count | 5 | Number of placeholder rows shown until the first non-empty competitions assignment. |
flagBaseUrl | flag-base-url | 'https://static.glastcoper.com/omfe-widgets/s/assets/1.10.2/om1/icons/flag/' | Base URL for country-flag PNG assets. Flags resolve to ${flagBaseUrl}{venueId}.png. |
iconBaseUrl | icon-base-url | '' | Base URL for sport-icon SVG assets. Sport icons resolve to ${iconBaseUrl}/icons/sports/dark_mode/{sportId}.svg. |
highlightedImages | highlighted-images (JSON) | {} | Map of competition-id → banner image URL. The first competition in the list whose id appears here is rendered as the featured card (banner + gradient divider + row). |
GomaCompetition shape
ts
interface GomaCompetition {
id: number
name: string
translatedName?: string
shortTranslatedName?: string
sportId: number
sportName?: string
venueId?: number // country/region id — used to resolve the flag PNG
venueName?: string
}Events
| Status | Event | Detail | Emitted when |
|---|---|---|---|
| Canonical | ready | {} | Widget mounted. |
| Canonical | goma:connection | { state, … } | Connection state transition. |
| Canonical | goma:error | { message, code, component? } | Render or data error. |
| Canonical | goma:top-competition-select | { competitionId, competitionName, competitionSportId, competitionVenueId, isFeatured, previousCompetitionId } | User taps a competition row. isFeatured: true when the tapped row was the featured card. |
Embedding examples
Vanilla HTML
html
<goma-top-competitions id="tc" sport-id="1" number-of-competitions="5"></goma-top-competitions>
<script type="module">
import '@gomagaming/top-competitions'
document.getElementById('tc').config = {
socketUrl: 'wss://sportsapi.example.com/v2',
socketRealm: 'www.example.com',
apiBaseUrl: 'https://api.example.com',
ucsOperatorId: '2838',
locale: 'en',
}
document.getElementById('tc').addEventListener('goma:top-competition-select', (e) => {
console.log('selected', e.detail.competitionId, e.detail.competitionName)
})
</script>Host-fed
html
<goma-top-competitions id="tc" data-source="prop"></goma-top-competitions>
<script type="module">
import '@gomagaming/top-competitions'
const el = document.getElementById('tc')
el.competitions = await fetch('/api/top-competitions').then((r) => r.json())
</script>React
jsx
import '@gomagaming/top-competitions'
import { useEffect, useRef } from 'react'
function TopCompetitions({ config, sportId = 1 }) {
const ref = useRef(null)
useEffect(() => {
if (!ref.current) return
ref.current.sportId = sportId
ref.current.config = config
}, [config, sportId])
return (
<goma-top-competitions
ref={ref}
onGoma:top-competition-select={(e) => console.log(e.detail)}
/>
)
}Vue 3
vue
<script setup>
import '@gomagaming/top-competitions'
import { ref, onMounted } from 'vue'
const el = ref(null)
const props = defineProps(['config'])
onMounted(() => {
el.value.sportId = 1
el.value.config = props.config
})
</script>
<template>
<goma-top-competitions ref="el" @goma:top-competition-select="onCompetitionSelect" />
</template>Theming
Override via el.theme = { … }.
The outer panel paints with backgroundSecondary; the competition rows, the featured card, and the loading skeleton paint with backgroundPrimary, so the rows read as recessed against the panel.
| Token | Element |
|---|---|
--goma-backgroundSecondary | Outer panel background |
--goma-backgroundPrimary | Row / featured-card / skeleton background |
--goma-textPrimary | Row label + title |
--goma-textSecondary | Flag fallback glyph |
--goma-iconSecondary | Sport icon colour |
--goma-highlightPrimary | Selected row ring |
--goma-highlightGradientStart | Featured-card divider gradient start (cyan default) |
--goma-highlightGradientEnd | Featured-card divider gradient end (purple default) |
Accessibility
role="list"outer container,role="listitem"+tabindex="0"+aria-selectedon each row.- Keyboard parity: Enter / Space activate selection.
- Row aria-label is the competition name.
Source layout
| Layer | File |
|---|---|
| Element class | packages/top-competitions/src/TopCompetitionsElement.js |
| App wrapper | packages/top-competitions/src/TopCompetitionsApp.vue |
| Component tree | packages/top-competitions/src/components/TopCompetitions/{TopCompetitionsList,TopCompetitionItem,TopCompetitionFeaturedItem}.vue |
| Source composable | packages/sports-domain/src/composables/ui/useTopCompetitionsSource.js |
| Top-competitions RPC | packages/sports-domain/src/api/everymatrix/modules/betting.js (getTopCompetitions — sportId optional) |
| Manifest | packages/top-competitions/custom-elements.json |
Featured cards via highlightedImages
Pass an object map of competition-id → banner image URL to mark specific competitions as featured. Every competition in the list whose id appears in the map renders with the banner image on top + a cyan→purple gradient divider + the same info row underneath; the rest render as plain rows. The featured cards keep the natural feed order — the map only flips the row variant, it does not reorder. An empty map (the default) renders the entire list as plain rows.
js
el.highlightedImages = {
'106': 'https://photobucket.com/comp/ucl.png',
'999': 'https://photobucket.com/comp/europa.png',
}html
<goma-top-competitions
highlighted-images='{"106":"https://photobucket.com/comp/ucl.png"}'
></goma-top-competitions>The selection event detail includes isFeatured: boolean so hosts can fork their handler on whether the tapped row was the banner variant.
What's deferred
- Real subscribe path. Backend topic for live top-competition updates not yet known;
dataSource: 'subscribe'falls back to one-shot RPC.