<goma-filters-settings>
A settings/filters modal that aggregates four controls behind a single trigger: a native time-window range slider, a read-only sport-scope indicator, a default-format market radio, and a fixed odds-range radio. With a Reset / Cancel header and an Apply CTA. The widget is self-contained (no embedded child widgets) and holds pending state until Apply commits it and emits a single consolidated goma:filters-apply event.
Element tag
html
<goma-filters-settings></goma-filters-settings>The element renders as a compact inline-block trigger by default, not a full-width block like most other widgets, so dropping it inline in a toolbar or filter bar gets the expected shrink-to-fit footprint alongside sibling controls. A host-supplied inline style="display:…" / style="width:…" still wins over this default.
Installation
bash
npm install @gomagaming/filters-settingsPlus the shared peer dependencies at your app level: see Installation for the one-time .npmrc token setup:
bash
npm install vue pinia vue-router vue-i18n @vueuse/core autobahn-browserautobahn-browser is only exercised when dataSource is 'rpc' or 'subscribe' (see Data sources): in the default 'prop' mode the widget never opens a WAMP connection.
Configuration
All keys can be set via the config object, as individual JS properties, or as kebab-cased HTML attributes.
| Key | Type | Default | Purpose |
|---|---|---|---|
locale | string | 'en' | i18n locale. |
theme | object | - | --goma-* overrides (see Theming). |
open | boolean | false | Open state of the modal. Drive from your own control. |
showTrigger | boolean | true | Render the built-in settings trigger button. Set false to open only via el.open = true. |
triggerIcon | string | '' | Raw inline-SVG string for the trigger (uses currentColor). Empty renders the built-in sliders glyph. |
mobileBreakpoint | string | 'sm' | Below this width the modal is a bottom-sheet. Tailwind token (sm..2xl) or px. |
iconBaseUrl | string | '' | CDN base for per-option glyphs on market/odds radio rows. Empty omits the glyph. |
showTimeFilter | boolean | true | Show the Time-window section. Set false when the window is owned elsewhere (a standalone <goma-time-filter> on the same page) or when it is moot, as on a live / in-play surface. Hidden, the section goes silent in both directions: Reset does not clear it, and goma:filters-apply carries time: null instead of asserting a window this modal does not own. |
Data sources
Four properties are edited in the modal and drive the emitted payload: selectedTimeValue, selectedSportId, selectedMarketId, oddsFilter. Only the sport list and the default-format market list have a pluggable source, gated by dataSource ('prop' | 'rpc' | 'subscribe', default 'prop'):
'prop'(default): fully host-fed, no WAMP. Feedel.sports(used only to resolve the displayed sport's name/icon on the read-only "Filters applied to" row) andel.marketOptions(orel.marketOptionsBySport, keyed by sport id, swapped locally with no host round-trip). A sport whose icon is not in the bundled set falls back to its family's glyph on that row (an e-sports category draws the e-sports icon), or to the neutralnoiconplaceholder when the sport list names no family: never a broken image. Inrpc/subscribemode the family is looked up across the whole sport list the feed sent, not only the sports in the current context slice. Your own artwork is asked for first either way (officonBaseUrlwhen it is set, origin-relative when it is not) and the fallback appears only once that request has failed.'rpc'/'subscribe': the widget resolves the sport list itself (useSportsSource, shared with<goma-sports-navigation-horizontal>) and derives the default-format market list from the active sport's matches-aggregator feed:bettingStore.mainMarketsintersected with the betting types present on the loaded events (falling back to the fullmainMarketslist if that intersection is empty). Both needsocketUrl/socketRealm(+apiBaseUrl/ucsOperatorId) inconfig.liveStatus('LIVE'default, or'NOT_LIVE'/'BOTH') selects which sports the feed returns.
The market list re-fetches every time the active sport changes (the pending sport while the modal is open, else the confirmed one): including right after a sibling sport picker changes it; see Empty / loading / error states for the loading skeleton and empty state this produces. The sport itself is never chosen inside this modal. It's set via selectedSportId, from the host or a sibling picker. The modal only displays it and re-scopes the market list to it.
If the previously-selected market isn't offered for the new sport (sport changed, or a stale confirmed id), pending falls back to the first available option: the stale-market guard runs both when the option list changes and when the modal re-seeds pending state on open.
js
const el = document.getElementById('fs')
el.config = { locale: 'en' }
el.sports = [
{ id: 1, name: 'Soccer' },
{ id: 2, name: 'Basketball' },
]
el.selectedSportId = 1
el.marketOptions = [
{ id: 1, label: 'Result', subtitle: 'Home Draw Away' },
{ id: 2, label: 'Winner' },
{ id: 3, label: 'Total Goals' },
]
el.selectedMarketId = 1Props / attributes
| Property | Attribute | Default | Description |
|---|---|---|---|
open | open | false | Modal open state. |
showTrigger | show-trigger | true | Render the built-in trigger button. |
triggerIcon | trigger-icon | '' | Inline-SVG string for the trigger. |
mobileBreakpoint | mobile-breakpoint | 'sm' | Bottom-sheet breakpoint. |
iconBaseUrl | icon-base-url | '' | CDN base for market/odds row glyphs. |
flagBaseUrl | flag-base-url | '' | Declared on this widget's public surface, but not currently wired to anything: the "Filters applied to" sport row renders only an sb-icon + name, with no flag glyph anywhere in the component tree. Setting this prop has no observable effect today. |
showTimeFilter | show-time-filter | true | Show the Time section. Hidden, the section is silent: Reset leaves the window alone and the Apply payload carries time: null. |
timeCardStyle | time-card-style | 'default' | Time card style. 'compact' opts into 12/20 padding, a 12px title and readout 2px apart, and the track 20px under them (98px tall instead of 144px). The other cards are unchanged. |
ticks | ticks (JSON) | The built-in 8-tick set (Now/1H/3H/6H/12H/24H/48H/Everything) | Time-slider ticks, Array<{ hours, label }>. Same contract as <goma-time-filter>. |
ticksJson | ticks-json | '' | Convenience JSON string; empty/whitespace leaves ticks untouched. |
selectedTimeValue | selected-time-value | 'all' | Confirmed time window: 'all' or '{startHours}-{endHours}'. |
sports | sports (JSON) | [] | Sport list for the read-only indicator (dataSource: 'prop'). |
selectedSportId | selected-sport-id | null | The sport the filters are scoped to: set by the host or a sibling picker. |
dataSource | data-source | 'prop' | 'prop' | 'rpc' | 'subscribe': see Data sources. |
liveStatus | live-status | 'LIVE' | 'LIVE' | 'NOT_LIVE' | 'BOTH', which sports the rpc/subscribe feed returns. |
marketOptions | market-options (JSON) | [] | Default-format markets, Array<{ id, label, icon?, subtitle? }>. |
marketOptionsBySport | market-options-by-sport (JSON) | {} | Optional map { [sportId]: marketOptions[] } for local, host-round-trip-free swaps in 'prop' mode. |
selectedMarketId | selected-market-id | null | Confirmed default-format market id. |
oddsFilter | odds-filter | 'all' | Confirmed odds-range filter: 'all' | 'between_2_3' | 'big_odds'. |
Events
Every event is composed: true and bubbles: true.
| Event | detail | When |
|---|---|---|
goma:dialog-open | {} | The modal opened. |
goma:dialog-close | {} | The modal closed (any cause, Cancel, backdrop click, ESC, native close). |
goma:filters-reset | FiltersPayload (same shape as goma:filters-apply) | Reset tapped: pending time/market/odds return to their defaults (sport scope untouched); not committed, dialog stays open. A section the host has hidden is left alone: with showTimeFilter: false the time window belongs to whatever else on the page owns it, so Reset does not touch it and the following Apply does not emit goma:time-select. |
goma:time-select | { timeValue, timeLabel, previousTimeValue } | Apply tapped and the time window changed: emitted immediately before goma:filters-apply. Identical payload to <goma-time-filter>'s, so a sibling <goma-events-horizontal followTimePicker> (on by default) re-scopes its feed with no host glue. Not emitted when only the market or odds range changed. |
goma:filters-apply | { time: { timeValue, timeLabel } | null, sportId, sportName, marketId, marketLabel, oddsFilter }: time is null when showTimeFilter is false, because a hidden section owns no window to report | Apply tapped: pending state committed to confirmed, filters persisted for the active sport, dialog closes. The consolidated summary of every section. |
ready | {} | Widget mounted. |
goma:error | { message, code, component? } | Two paths. Connection layer: the WAMP adapter (rpc/subscribe data sources, used for the sport name/icon and market list) exhausts its 5 retry attempts (code: 'API_INIT_ERROR'), alongside goma:connection { state: 'failed' }. Widget root scope: an error raised in the widget's own outermost component (its setup, its render, its lifecycle hooks or its watchers), where code is the error's name, e.g. 'TypeError' ('RUNTIME_ERROR' when it has none). Errors raised inside the widget's child components do not reach this event: the in-shadow error boundary catches those and shows the retry banner instead, emitting only goma:retry. |
goma:retry | {} | User clicked Retry on the render-error banner. |
Integrates with
Listens for nothing and follows no sibling widget: the sport it displays is set directly via selectedSportId, not through a follow* automation.
Nothing in the picker chain listens for goma:filters-apply, so the time section additionally re-emits goma:time-select on Apply when the window changed: that is the event the feeds follow. A <goma-events-horizontal> dropped alongside this modal therefore re-scopes on Apply exactly as it would for a <goma-time-filter> drag, with followTimePicker at its default.
<goma-favorites> listens for the goma:filters-apply this widget emits, two ways: followSportPicker (default false) reads detail.sportId to narrow the rendered favourites; followFilters (default false) reads detail.marketId, detail.oddsFilter and detail.time.timeValue to apply the default-format market, odds range and time window to the favourite event cards. See Cross-widget wiring for the full matrix.
Writes no shared-storage envelope in the cross-widget sense: the confirmed filter set persists to its own localStorage key, covered below under Per-sport persistence. See Shared state for why that key is a different mechanism from the true cross-widget envelopes (goma:betslip:v4, goma:favorites:v1, goma:session:v1).
Empty / loading / error states
- Mount / trigger: no widget-level loading state. The trigger button (or a host-driven
el.open = true) is interactive immediately. In the defaultdataSource: 'prop'mode the widget never opens a WAMP connection, so none of the connection states below are reachable: everything is host-fed and synchronous. - Markets loading.
'rpc'/'subscribe'only: whileuseSportMarketsSource'sgetFilteredEventsRPC is in flight (or, insubscribemode, until the feed's initial dump lands), the "Default format" card shows a 3-row skeleton (animate-pulseplaceholder rows) in place of the market radios. - No markets: once loading settles (or immediately, in
propmode) with an empty resolved market list, the card shows thefilters_settings_no_marketsmessage ("No markets available for this sport.") instead of the radiogroup. Inrpc/subscribemode this can only happen when the sport'smainMarketsfeed itself comes back empty:useSportMarketsSourcefalls back to the fullmainMarketslist whenever intersecting it against the loaded events' markets yields nothing, so a sparse event set alone doesn't trigger this state. - Connection failure.
'rpc'/'subscribe'only: after the WAMP adapter exhausts its 5 retry attempts,goma:errorfires (see Events for the full payload and the accompanyinggoma:connectionevent). Unreachable inpropmode, since no connection is ever opened. - Render error: an
onErrorCapturedboundary inFiltersSettingsAppcatches any render/runtime error in the tree, logs it to the console, and shows arole="alert"banner (ERROR: <message>) with a Retry button and a dismiss×. Retry clears the banner and emitsgoma:retry; the boundary returnsfalse, so an error raised inside the tree never reaches thegoma:erroremission path:goma:retryis the only event it produces. An error raised inFiltersSettingsAppitself is the exception: the boundary structurally cannot see its own component's errors, so those bypass the banner and emitgoma:errorinstead. The banner auto-clears after 8000 ms even with no interaction.
Interaction model
- The trigger (or
el.open = true) opens the modal; pending state is seeded from the confirmed selection. - Reset returns the pending time/market/odds values to their defaults (does not close, does not commit, sport scope is left as-is). A section the host has hidden is left alone: with
showTimeFilter: falsethe time window is owned by something else on the page, so Reset does not clear it and the following Apply reportstime: nullrather than a window this modal does not own. - Cancel (or backdrop click / ESC) closes the modal, discarding pending edits.
- Apply commits pending → confirmed, persists the sport's filter set, emits
goma:filters-apply, and closes.
Per-sport persistence
The confirmed filter set ({ timeValue, marketId, oddsFilter }) is remembered per sport in localStorage under a single key goma:filters-settings:v1 (a map { [sportId]: { … } }), read/written with plain localStorage.getItem/setItem: not the useSharedStorage() mechanism the true cross-widget envelopes use, so there's no BroadcastChannel/storage-event sync and no other widget reads this key. Apply writes the active sport's entry; when selectedSportId changes (host or sibling picker), that sport's saved entry (if any) is restored onto the confirmed selection (a stale marketId falls back via the stale-market guard on open). No saved entry → the host-provided defaults stand. See Shared state for the full writeup of why this key is a different mechanism from the envelope contracts.
Theming
All colours map to existing --goma-* tokens: no new tokens. Defaults below are the widget's own per-instance fallbacks (packages/filters-settings/src/styles/dialog.css), applied only when the host hasn't supplied a value.
| Token | Role | Default |
|---|---|---|
--goma-dialogBackground | Modal panel surface | #1b1d2b |
--goma-backgroundCards | Filter card background | #2a2f3e |
--goma-highlightPrimary | Accent (trigger, radios, in-range slider, Reset/Cancel text, CTA) | #29f2ff |
--goma-highlightPrimaryContrast | Text / ring on the accent | #062225 |
--goma-textPrimary | Primary text + slider thumbs | #ffffff |
--goma-textSecondary | Odds subtitle / muted text | #ade4ff |
--goma-separatorLine | Dividers + dim slider track + themed scrollbar | #3d425a |
i18n / message overrides
| Key | Default (en) | Used in |
|---|---|---|
settings | Settings | Modal title. |
reset | Reset | Header Reset button. |
cancel | Cancel | Header Cancel button. |
apply | Apply | Footer Apply CTA. |
time | Time | Time card title. |
time_filter_range_header | Starts of events: {left} – {right} | Time-slider inline header. |
time_filter_range_empty | Starts of events: - | Time-slider header when fewer than two ticks are usable. |
filters_applied_to | Filters applied to: | Sport-indicator label. |
sports_navigation_dialog_placeholder | Sport | Fallback text when no sport is resolved yet. |
filters_settings_default_format | Default format | Market-radio card title + radiogroup label. |
filters_settings_odds_heading | Choose the odds/results to display | Odds-radio card title. |
filters_settings_odds_above_3 | Odds above 3 | Label for the big_odds radio option. |
filters_settings_no_markets | No markets available for this sport. | No-markets empty state: see Empty / loading / error states. |
all_odds | All Odds | Label for the all odds option. |
between_2_3 | Between 2 and 3 | Label for the between_2_3 odds option. |
odds_filter | Odds Filter | aria-label on the odds radiogroup. |
retry | Retry | Render-error banner's retry button. |
Embedding examples
React
jsx
import '@gomagaming/filters-settings'
<goma-filters-settings
ref={(el) => {
if (!el) return
el.config = { locale: 'en' }
el.sports = sports
el.marketOptions = markets
el.selectedSportId = 1
el.addEventListener('goma:filters-apply', (e) => onApply(e.detail))
}}
/>Vue
vue
<script setup>
import '@gomagaming/filters-settings'
</script>
<template>
<goma-filters-settings
:sports.prop="sports"
:market-options.prop="markets"
:selected-sport-id.prop="1"
@goma:filters-apply="onApply"
/>
</template>Accessibility
| Element | Role | Attributes |
|---|---|---|
| Dialog | dialog | Native <dialog> + showModal(); aria-modal="true", aria-labelledby pointing at the title. |
| Trigger button | - | aria-haspopup="dialog", aria-expanded, aria-label (settings). |
| Collapsible card header | button (when collapsible) | aria-expanded. |
| Market / odds radio groups | radiogroup | aria-label. |
| Radio row | radio | aria-checked. |
| Time slider group | group | aria-labelledby pointing at the inline header. |
| Time slider thumbs | slider | tabindex="0", aria-valuemin, aria-valuemax, aria-valuenow, aria-valuetext; keyboard: ArrowLeft/Down (−1 tick), ArrowRight/Up (+1 tick), Home/End, PageDown/PageUp (±2 ticks). |
| Error banner | alert | role="alert". |
Focus starts on the panel (autofocus) when the dialog opens; the panel is marked inert while closed. prefers-reduced-motion: reduce is not special-cased beyond the shared slider component's own thumb-motion guard.
Integration Caveats
SSR/webview: importing this package outside a browser previously threw immediately; fixed with a guard on the shared element factory.
Sizing: the widget's own
:host{display:inline-block}CSS rule was dead (the shared element factory's inlinedisplay:blockalways won), so the trigger always rendered full-width instead of a compact inline button. Fixed with a local override in this widget's own element class.Initialization:
flagBaseUrl/flag-base-urlis declared and attribute-mapped but never actually rendered, so it is documented as currently inert.Misc hygiene:
.size-limit.jsonused to carry a ~1.2 MB bundle budget, about 100x the real bundle, left over from an early build that inlined the whole sport-icon registry. Fixed: the budget was re-measured and is now the measured size plus headroom (14.36 kB gzip for the main bundle), so a bundle-size regression can trip it. See the widget-by-widget notes.Initialization: this widget shares
@gomagaming/core's i18n singleton with every other widget on the page; mounting two widgets with differentlocalevalues on one page is not currently isolated per instance (a known constraint, not fixed in this pass).Configuring before the definition loads. Assigning
config(or any property) while the element is still an unknown element is safe: the value is replayed through the widget when it upgrades. That matters when loading from the CDN, whose loader imports widget modules asynchronously, so your script usually runs first. On release sets at 0.34.0 and earlier the value was silently discarded and the widget mounted with no configuration and a clean console: there, awaitwindow.__gomaCdn.readyfirst, or configure through attributes. See Setting a property before the widget's definition has loaded.
See Integration Caveats for the cross-widget picture.
Changelog
Current release: 0.37.11. Every GomaPulse package shares one version number and they are published together, so a release that changes nothing here still bumps this widget. Only the releases that changed it are listed below.
Every release that changed this widget, newest first. The latest is open; the rest expand.
0.37.1
- A sports picker set to
liveStatus: 'NOT_LIVE'(sports navigation horizontal and dialog, filters settings) no longer lists sports that only have live events. The full sports list the widget loads on connect could land after the widget's own pre-live list and mark every sport as pre-live; it now carries no such mark, so the pre-live list stays as the source sent it, inrpcandsubscribemodes. Pickers set to'LIVE'or'BOTH'are unchanged, and sports outside the pre-live list still get their names and icons.
0.36.1
Playground manifests (
@gomagaming/<widget>/playground) now offer a control for every setting each widget really has. Settings that cannot be a form field (raw provider data, read-only state mirrors, props a widget never reads) are recorded with a reason in the repo instead of being silently missing.Controls that never reached the widget are fixed:
- The betslips'
placeBetHeadersJson/placeBetBodyJson/quickAddAmountsJsonare nowplaceBetHeaders/placeBetBody/quickAddAmounts. - The time-filter and filters-settings ticks editors set
ticks. - live-chat's layout control writes
position, the config key the element reads. - The sports-navigation widgets drop the unread
sportsJson. - JSON-typed defaults are real objects and arrays instead of serialized strings. The top-competitions
highlightedImagesstring default was indexed character by character and requested bad image URLs on every load. The missions-hubemptyStateImagesillustrations now show.
No widget runtime behaviour changes.
- The betslips'
0.36.0
<goma-filters-settings>gains atimeCardStyleoption (time-card-style,'default'|'compact', default'default').'compact'opts into a tighter Time card: 12/20 padding, a 12px title and readout 2px apart, and the track 20px under them (98px tall instead of 144px). The default card and the other cards are unchanged.
0.35.0
<goma-filters-settings>: a hidden time section is now silent in both directions.showTimeFilter: falsemeans the time window is owned elsewhere (a standalone<goma-time-filter>on the same page) or is moot, as on a live surface. The modal used to assert a window anyway:Reset cleared the pending time even with the card hidden, so the next Apply emitted
goma:time-select { timeValue: 'all' }(the event every feed follows) and wiped a window the player had set on a control this modal cannot see.Apply shipped
time: { timeValue }in the payload regardless, seeded from the modal's own untouched default.<goma-favorites>readsdetail.time.timeValuedirectly underfollowFilters, so applying any other filter ungated the favourite cards to the full range while the real control still held the player's window.Apply also committed it: to the confirmed state, and into the shared per-sport store
goma:filters-settings:v1, which this widget and its hosts read back on every sport change. So a modal that owns no window still wrote its untouched default there, and the player's window disappeared the next time they switched sport and came back.
With the card hidden the section is silent in all three directions: Reset leaves the window alone, the payload carries
time: null, and Apply commits no window and emits none; what it persists for that sport is the window the host feeds, so a later sport switch restores the live one rather than a stale stored one. The stored entry keeps whatever the owning control put there.goma:time-selectis not emitted from a hidden section either, which also closes the case where a sport switch moved the confirmed value while the pending one still held the value from open. With the card showing, everything behaves exactly as before.Minor rather than patch: a host that hides the card and reads
detail.timegetsnullwhere it used to get an object, and a Reset that used to clear a persisted window no longer does. Both are observable, so the version should say so: the same reasoning@gomagaming/competitions-listrecorded when its own default changed.A sport with no bundled icon now falls back to a real glyph instead of the browser's broken-image icon.
SbIconresolves a bundled SVG by name and otherwise falls through to an<img>. For a sport id the feed ships before its artwork exists (e-Volleyball, in the sports carousel and in the "Sports available" list) that<img>404s, and the old error handler only faded it toopacity: 0.2, which leaves the broken-image glyph on screen at 20%. A failed<img>is now dropped from the render instead.What stands in is the sport's family glyph: every e-sports category falls back to the e-sports icon, which is a real icon rather than "no icon". The family is read from the sport list the widget was already fed (the parent whose
childrenIdsnames the category) so no id list has to be kept in step with the provider, and a flat feed that names no parent simply falls back to the neutralnoiconglyph as before. The newsportIconNameshelper in@gomagaming/coreis what the sport tiles, the picker's trigger pill and the settings modal's sport row all resolve through.A host serving its own sport artwork keeps priority, and keeps it whether or not it configured a base URL:
${iconBaseUrl}/icons/sports/dark_mode/{iconId}.svgwhen one is set, and the origin-relative/icons/sports/dark_mode/{iconId}.svgwhen it is not.iconBaseUrlhas no value that means "my own root" (''and'/'both read as unset), so a host serving icons from its root keeps working with no configuration at all. A stand-in is considered only once that request has failed.Every widget that draws a sport glyph through
SbIconinherits the change, which is why they are all listed here: the two sports pickers, the settings modal, both top-competitions widgets, the sport banners, the game-details header, the suggested-bet legs, the My Bets cards, the two betslips that embed them, the highlight card and the Pros' Choice card.The family is looked up across the whole sport list the feed sent rather than the slice on screen. In
rpc/subscribemodeuseSportsSourcemirrors the betting store filtered to the current context (LIVE vs popular), and a parent category carries no events of its own, so it is routinely missing from that slice: every e-sports child would have fallen back to the neutral glyph. The unfiltered list is mirrored alongside it asstate.sportsCatalog, andsportIconLookupList(new in@gomagaming/core) is what the tiles, the trigger pill and the settings row read.mapSportalso refresheschildrenIdson an existing entry: the list was written only when the entry was first created, so a family relation that arrived on a later dump, or a child added to one. Never reached the widget.<goma-time-filter>and the settings modal's time slider: the header no longer resizes the track while the player drags. The header is a live readout of the selected window, and abovesmit shares a flex row with theflex-1track, so it changed width on every tick the thumb passed ("Starts of events: Now – Everything" is far wider than "Starts of events: 3H – 48H"), the track resized with it, and the bar and both thumbs visibly jumped under the pointer. The header column now reserves the widest reading its tick set can produce, via hidden sizers stacked on the live text in one grid cell, so the track's width is constant for a given tick set.No API change: same
tickscontract, samegoma:time-selectpayload, same markup belowsm(where the header is on its own line and drives nothing).
0.32.0
Widgets no longer dispatch a bare
errorevent alongsidegoma:error.Every factory-built widget's app-level Vue error handler dispatched two events for the same failure:
goma:error, and an un-namespacederror. The bare one is gone;goma:erroris unchanged and still carries the same{ message, code, component? }detail.Like every GomaPulse event it was
composedand bubbling, so it did not stop at the widget. It crossed the shadow root, crossed the document, and arrived at whatever the host had onwindow.addEventListener('error', …): as aCustomEventwithmessage,errorandfilenameallundefined, since those areErrorEventfields aCustomEventdoes not have. A host with error reporting installed received a malformed error record it could neither read nor attribute.That had been written off as harmless on the grounds that the handler behind it could never fire. It can. Vue begins its
errorCapturedwalk at the throwing component's parent, so theonErrorCapturedboundary each widget installs in its own root component cannot see that component's own errors, and being the root, there is nothing above it to catch them either. Anything raised in a widget's root scope (its setup, its render, its lifecycle hooks, its watchers) skips the boundary and reaches the app-level handler every time. Only errors from child components are caught by the boundary, which is what it is for: those show the in-shadow banner and emitgoma:retry.Hosts listening for
goma:errorneed no change. A host listening for bareerroron a widget element should move togoma:error; it was documented as unreachable, so in practice it delivered nothing until now.
0.29.0
Integration caveats: fixes from a cross-widget review of every shipped widget from an integrator's point of view.
Cross-cutting fixes:
@gomagaming/core'screateWidgetElement()factory, andlive-chat's andmessenger's own element classes, now guard against importing a widget package in a non-browser context (SSR): theHTMLElementsubclass was previously defined at module top level in every widget package, so importing any of them under server-side rendering threw aReferenceErrorimmediately.
Per-package fixes:
favorites: addedsrc/themeTokens.jsand a:hoststylesheet (previously the package had neither, so it silently rendered core's base theme instead of its documented dark-navy/cyan/amber theme).suggested-bet. Fixed a leaked WAMP subscription: switching a mounted widget'sdataSourceaway fromrpc/subscribenever released the open topic.filters-settings: fixed a dead:host{display:inline-block}CSS rule (the shared element factory's inlinedisplay:blockalways won), so the widget always rendered full-width instead of the intended compact inline trigger.game-details: added amin-heightfallback for the inert/no-eventIdstate in an unsized container.betslip-sidebar,my-bets,competition-filter,suggested-bet,events-horizontal,top-competitions,top-competitions-pills,competitions-list,multi-banners,casino-games,sports-navigation-horizontal. CorrectedthemeTokens.jsregistries that had drifted from actual token usage, added missinggoma:retryEvents-table rows, and other small doc/registry corrections.region-filter: added a missingtypeof window !== 'undefined'guard on a window listener.live-chat: correctedcustom-elements.json'smembersarray (missingtheme/configaccessors, and an inaccurate description).sports-domain: corrected a stale code comment that seeded a documentation error in two Missions widgets.
Three fixes (
favorites,game-details,filters-settings) change real rendering and are flagged indocs/guide/integration-caveats.mdas needing human visual verification before release.
0.28.0
Event-contract audit: six fixes across shipped widgets. Two of them change behaviour for existing embeds: read the last two entries before upgrading.
The outright card's competition star now has a login gate: the star on
OutrightRegularOdds(shown for_type === 'TOURNAMENT'events, and so in<goma-events-horizontal>,<goma-favorites>and<goma-competitions-list>) was the only star in the family that skippedguardedFavoriteToggle. Tapped while signed out it wrote and emitted anyway, contradicting the promise in the cross-widget wiring guide that a signed-out star does not write. It now emitsgoma:login-requiredwhen signed out, and writes the sharedgoma:favorites:v1envelope (instead of reading a widget-local store slice) when signed in, so it agrees with every other competition star on the page. Itsgoma:favorite-togglepayload gainstypeandidand dropseventId, which held a competition id: a host resolving that value as an event id was looking up the wrong entity.type: 'competition'+idcarry the same information under honest names, and match what the other competition stars have always emitted.<goma-filters-settings>now moves the feed. Applying a time window emitted onlygoma:filters-apply, which no feed widget listens for, so the mobile filter funnel left every sibling feed exactly where it was, while the identical window picked in<goma-time-filter>moved it.applyFilters()now also emitsgoma:time-select({ timeValue, timeLabel, previousTimeValue }, the same payload<goma-time-filter>emits) when the window actually changed, immediately before the unchangedgoma:filters-applysummary event.<goma-game-details>gainseventNavigateSelector(event-navigate-selector, default'').goma:event-navigatehas about a dozen emitters andfollowEventNavigateis on by default, so two game-details panels on one page both jumped to every card tap anywhere. The new prop is the same CSS-selector scope guard<goma-events-horizontal>and the picker followers already carry, matched against the event's retargeted host. Empty (the default) keeps today's follow-everything behaviour.goma:outcome-deselectno longer puts an outcome id inbettingOfferId. Removing a row from the betslip panel emitted{ bettingOfferId: <outcomeId> }, while the card-side odds button sends a real offer id in that field: a host deduping ondetail.bettingOfferIdgot mismatched keys from the two surfaces. The panel now emits{ bettingOfferId: null, outcomeId }, the card emitsbettingOfferId: nullrather thanundefinedwhen there is no offer, and theGomaEventMaptype widens to{ bettingOfferId?: string | null; outcomeId: string }. Dedupe onoutcomeId. Both surfaces always carry it.Behaviour change:
<goma-hero-card>'s favourite star now writes shared state. The star was localrefstate emittinggoma:favorite-select, an event name no other widget uses, so a hero card and an events-horizontal card for the same match disagreed on the same page. It now goes throughguardedFavoriteToggleand the sharedgoma:favorites:v1envelope, and emits the canonicalgoma:favorite-toggle. If your page mounts<goma-favorites>or otherwise persists that envelope, a tap here is now persisted where it previously was not. TheisFavoriteprop becomes a seed (mirrored into the envelope once, while signed in) rather than a parallel source of truth.goma:favorite-selectis deprecated: still emitted alongsidegoma:favorite-togglefor one release so existing listeners keep working; migrate togoma:favorite-toggle, it will be removed next.Behaviour change:
<goma-top-competitions-pills>now follows the sport picker by default.followSportPickerflips fromfalsetotrue, matching<goma-region-filter>,<goma-competitions-list>and<goma-events-horizontal>, so swapping one of those for this widget no longer silently loses sport-following. An existing embed that mounts this widget alongside a sports picker and relied on the pill row staying put will now re-fetch on every sport change. Setfollow-sport-picker="false"(orel.followSportPicker = false) to keep the old behaviour.
0.26.0
Stop publishing source maps and package source.
dist/*.js.mapwas emitted by the shared Vite preset and swept into every tarball byfiles: ["dist/", "src/"], with the original source inlined insourcesContent.src/shipped the same source again, in the clear. Both are one URL away from a browser once a package is served from a CDN, so the preset now builds withsourcemap: falseandfilesno longer carriessrc/.@gomagaming/corekeeps the two paths its export map actually points at:src/styles/main.cssandsrc/types/index.d.ts. Its tarball drops from 1741 files to 576, which also brings it back under the 900-file limit a per-request unpacker can handle.Deep imports of
@gomagaming/<pkg>/src/*from an installed package no longer resolve. Nothing documented used them; imports inside the monorepo are unaffected.
0.15.0
UI fixes from the a separate change design review (Sports Pages, items 1.1–1.19):
- Suspended odds pill text uses
inputTextTitleDisabled(legible on lightened themes) and game-card team/player names render at 16px. - Brand font: Open Sans is bundled and registered via the
document.fontsAPI so widgets paint the intended typeface inside the shadow root. - Game-card market pill colours corrected (live
separatorLine, prelivehighlightPrimary); sports-navigation tile sizing + rounded container; multi-banners overlay gradient; casino-games flame colour + tile gradient border; filters-settings radio-row divider. - competition-filter: edge-trigger icon size, pill padding, and hover/pressed states.
- competitions-list: inset row divider (original colour) + corrected accordion caret orientation.
- game-details: header sport-icon/flag + rounded top corners; market-tab horizontal paging arrows with click-to-centre; long-market "See All" cap; corrected collapse-caret orientation.
- top-competitions: white sport icon + featured-card neon divider moved to the card bottom and thickened.
- suggested-bet: Bet / Total Odds / Winnings summary centre-aligned.
- Suspended odds pill text uses
0.12.0
<goma-filters-settings>is now self-contained: it no longer embeds the@gomagaming/time-filteror@gomagaming/sports-navigation-dialogwidgets (both dependencies removed). The Time card is a native in-widget range slider, and the "Filters applied to:" sport section is a read-only indicator (the sport is chosen elsewhere viaselectedSportIdand only displayed here). Removes thegoma:filters-sport-changeevent and the picker-onlyshowLiveCount/skeletonCountprops. Smaller bundle; thegoma:filters-applypayload and the time / market / odds selection behaviour are unchanged.Also adds a
showTimeFilterprop (defaulttrue): setfalseto hide the Time-window section entirely, e.g. on a live / in-play surface.
0.11.0
<goma-filters-settings>now composes the standalone widgets instead of reimplementing them: it embeds<goma-time-filter>in the Time card and<goma-sports-navigation-dialog>for the sport picker, both nested via hosted mode (they share this widget's Pinia/app/i18n/WAMP connection and shadow-root styles). The "Filters applied to:" row opens the embedded sport dialog and captures itsgoma:sport-select. Adds@gomagaming/time-filter+@gomagaming/sports-navigation-dialogas dependencies. Public API (props/events) is unchanged.Add
<goma-filters-settings>: a host-fed settings/filters modal aggregating a time-window range slider, a nested sport-scope picker, a default-format market radio, and an odds-range radio, with a Reset / Cancel header and a Place-bet (apply) CTA. Edit-then-confirm pending state; commits and emits a consolidatedgoma:filters-applyon apply. Addsfilters_settings_*i18n keys to@gomagaming/core.<goma-sports-navigation-dialog>: redesign the picker (60px-circle grid, "Filter by country…" search placeholder) and add composition props:showTrigger(hide the built-in pill),showBackButton(Back button →goma:dialog-back), andshowApplyButton(Apply footer: tapping a tile highlights, Apply commitsgoma:sport-select+ closes). It can now be opened externally via a composedgoma:sports-dialog-openevent. Panel height is now a definite90dvh/92dvhso it matches the<goma-filters-settings>settings modal exactly (they swap in place when embedded).