courthive-components 6.14.0 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +27 -39
  2. package/dist/components/composition-editor/sections/colorsSection.d.ts +1 -1
  3. package/dist/components/composition-editor/sections/layoutSection.d.ts +1 -1
  4. package/dist/components/renderSchematicStructure/index.d.ts +1 -1
  5. package/dist/components/renderSchematicStructure/renderSchematicMatchUp.d.ts +29 -3
  6. package/dist/components/renderSchematicStructure/renderSchematicRound.d.ts +3 -2
  7. package/dist/components/renderSchematicStructure/renderSchematicStructure.d.ts +4 -2
  8. package/dist/components/renderStructure/connectors.d.ts +25 -0
  9. package/dist/components/renderStructure/matchUpState.d.ts +32 -0
  10. package/dist/components/renderStructure/renderMatchUp.d.ts +6 -3
  11. package/dist/components/renderStructure/renderMatchUpActions.d.ts +14 -0
  12. package/dist/components/renderStructure/renderMatchUpInPlace.d.ts +19 -0
  13. package/dist/components/renderStructure/renderOnlineResources.d.ts +20 -0
  14. package/dist/components/renderStructure/renderParticipantDetail.d.ts +1 -1
  15. package/dist/components/renderStructure/renderRound.d.ts +5 -1
  16. package/dist/components/renderStructure/renderRoundHeader.d.ts +3 -2
  17. package/dist/components/renderStructure/renderRoundPager.d.ts +14 -0
  18. package/dist/components/renderStructure/renderSchedule.d.ts +10 -3
  19. package/dist/components/renderStructure/renderSlot.d.ts +7 -0
  20. package/dist/components/renderStructure/renderStructure.d.ts +20 -1
  21. package/dist/components/renderStructure/renderStructureMinimap.d.ts +5 -1
  22. package/dist/components/renderStructure/scoreboardState.d.ts +17 -0
  23. package/dist/components/renderStructure/structurePaths.d.ts +21 -0
  24. package/dist/components/scoring/logic/irregularEnding.d.ts +1 -1
  25. package/dist/components/scoring/logic/scoreEntrySelectors.d.ts +2 -0
  26. package/dist/components/scoring/logic/scoreEntryText.d.ts +93 -0
  27. package/dist/components/scoring/regions/dialPadRegion.d.ts +2 -0
  28. package/dist/components/scoring/regions/dynamicSetsRegion.d.ts +3 -0
  29. package/dist/components/scoring/regions/freeScoreRegion.d.ts +3 -1
  30. package/dist/components/scoring/regions/scoreReadout.d.ts +1 -1
  31. package/dist/components/scoring/scoreEntryCard.d.ts +4 -1
  32. package/dist/courthive-components.css +1 -1
  33. package/dist/courthive-components.es.js +10497 -12368
  34. package/dist/courthive-components.umd.js +77 -113
  35. package/dist/courts.mjs +2 -4
  36. package/dist/{dist-DPnSc4eG.mjs → dist-BuZ2aY07.mjs} +13 -46
  37. package/dist/features.css +1 -1
  38. package/dist/index.d.ts +7 -5
  39. package/dist/{range-C-KhEV-d.mjs → range-5-_fCM2L.mjs} +1 -2
  40. package/dist/segmented-bar.mjs +1 -2
  41. package/dist/styles/themes/createTheme.d.ts +6 -0
  42. package/dist/types.d.ts +297 -4
  43. package/package.json +9 -9
  44. package/dist/components/scoring/approaches/dialPadApproach.d.ts +0 -2
  45. package/dist/components/scoring/approaches/dialPadLogic.d.ts +0 -17
  46. package/dist/components/scoring/approaches/dynamicSetsApproach.d.ts +0 -2
  47. package/dist/components/scoring/approaches/freeScoreApproach.d.ts +0 -2
  48. package/dist/components/scoring/approaches/inlineScoringApproach.d.ts +0 -2
  49. package/dist/components/scoring/config.d.ts +0 -28
  50. package/dist/components/scoring/index.d.ts +0 -6
  51. package/dist/components/scoring/logic/outcomeProjection.d.ts +0 -25
  52. package/dist/components/scoring/scoringModal.d.ts +0 -2
  53. package/dist/components/scoring/statusCodePicker.d.ts +0 -15
  54. package/dist/components/scoring/utils/scoreFormatters.d.ts +0 -12
  55. package/dist/components/scoring/utils/scoreValidator.d.ts +0 -16
  56. package/dist/styles/getLinkStyle.d.ts +0 -25
package/README.md CHANGED
@@ -40,7 +40,7 @@ Update a budget deliberately with `pnpm verify:consumer-bundle --update-baseline
40
40
  ## Quick Start
41
41
 
42
42
  ```javascript
43
- import { renderMatchUp, scoringModal, compositions } from 'courthive-components';
43
+ import { renderMatchUp, openScoreEntryDialog, compositions } from 'courthive-components';
44
44
 
45
45
  // Render a match display
46
46
  const matchUpElement = renderMatchUp({
@@ -49,11 +49,12 @@ const matchUpElement = renderMatchUp({
49
49
  });
50
50
  document.getElementById('container').appendChild(matchUpElement);
51
51
 
52
- // Open scoring modal
53
- scoringModal({
52
+ // Open the score entry dialog
53
+ openScoreEntryDialog({
54
54
  matchUp: myMatchUpData,
55
- callback: (outcome) => {
56
- console.log('Score submitted:', outcome);
55
+ sides: [{ participantName: 'Ann' }, { participantName: 'Bea' }],
56
+ onSubmit: ({ outcome }) => {
57
+ console.log('Engine-ready outcome:', outcome);
57
58
  }
58
59
  });
59
60
  ```
@@ -167,30 +168,24 @@ cModal.open({
167
168
  });
168
169
  ```
169
170
 
170
- #### **scoringModal**
171
+ #### **openScoreEntryDialog**
171
172
 
172
- Two scoring APIs remain supported. New integrations should use `openScoreEntryDialog`
173
- (the shared model/store-based card dialog); see `src/components/scoring/TMX_INTEGRATION.md`
174
- for its engine-outcome handoff. TMX chooses between it and `scoringModal` through a feature flag.
175
- `scoringModal` below is the existing configurable modal API. Their callback contracts differ.
176
-
177
- Interactive score entry with multiple input approaches.
173
+ Score entry in a modal: three interchangeable approaches over one card, a live format picker,
174
+ irregular endings with their policy reason codes, and reopening a recorded outcome.
178
175
 
179
176
  ```javascript
180
- import { scoringModal, setScoringConfig } from 'courthive-components';
181
-
182
- // Configure scoring behavior
183
- setScoringConfig({
184
- scoringApproach: 'dynamicSets', // 'dynamicSets' | 'freeScore' | 'dialPad'
185
- smartComplements: true,
186
- composition: 'Australian'
187
- });
188
-
189
- // Open scoring modal
190
- scoringModal({
191
- matchUp: matchUpData,
192
- callback: (outcome) => {
193
- // outcome: { isValid, sets, winningSide, matchUpStatus }
177
+ import { openScoreEntryDialog } from 'courthive-components';
178
+
179
+ openScoreEntryDialog({
180
+ matchUp, // prefills a recorded outcome: matchUp.score.sets, matchUpStatus, winningSide
181
+ sides: [{ participantName: 'Ann', seed: '1' }, { participantName: 'Bea' }],
182
+ approach: 'dynamicSets', // 'dynamicSets' | 'freeScore' | 'dialPad'; the operator can switch in the dialog
183
+ statusCodeGroups, // the scoring policy's matchUpStatusCodes; omit and no reason chips are drawn
184
+ clearable: true, // false when a later match depends on the result, so an empty submit is refused
185
+ disabledMatchUpStatuses: [], // statuses the engine's SCORE action refuses for this matchUp
186
+ onApproachChange: (approach) => savePreference(approach),
187
+ onSubmit: ({ outcome, sets }) => {
188
+ // outcome is engine-ready: hand it to setMatchUpStatus as is
194
189
  }
195
190
  });
196
191
  ```
@@ -394,7 +389,7 @@ Components are designed to work with TODS (Tennis Open Data Standards) data stru
394
389
 
395
390
  ```javascript
396
391
  import { tournamentEngine } from 'tods-competition-factory';
397
- import { renderMatchUp, scoringModal } from 'courthive-components';
392
+ import { renderMatchUp, openScoreEntryDialog } from 'courthive-components';
398
393
 
399
394
  // Get match data from factory
400
395
  const { matchUp } = tournamentEngine.findMatchUp({ matchUpId });
@@ -402,19 +397,12 @@ const { matchUp } = tournamentEngine.findMatchUp({ matchUpId });
402
397
  // Render with components
403
398
  const display = renderMatchUp({ matchUp });
404
399
 
405
- // Score with modal
406
- scoringModal({
400
+ // Score with the dialog; its outcome is already the shape setMatchUpStatus reads
401
+ openScoreEntryDialog({
407
402
  matchUp,
408
- callback: (outcome) => {
409
- // Update tournament using factory
410
- tournamentEngine.setMatchUpStatus({
411
- matchUpId,
412
- outcome: {
413
- score: { sets: outcome.sets },
414
- winningSide: outcome.winningSide,
415
- matchUpStatus: outcome.matchUpStatus
416
- }
417
- });
403
+ sides: matchUp.sides.map((side) => ({ participantName: side.participant?.participantName ?? '' })),
404
+ onSubmit: ({ outcome }) => {
405
+ tournamentEngine.setMatchUpStatus({ matchUpId, drawId: matchUp.drawId, outcome });
418
406
  }
419
407
  });
420
408
  ```
@@ -1,3 +1,3 @@
1
- import { CompositionEditorStore } from '../compositionEditorStore';
2
1
  import { EditorPanel } from '../compositionEditorTypes';
2
+ import { CompositionEditorStore } from '../compositionEditorStore';
3
3
  export declare function buildColorsSection(store: CompositionEditorStore): EditorPanel;
@@ -1,3 +1,3 @@
1
- import { CompositionEditorStore } from '../compositionEditorStore';
2
1
  import { EditorPanel } from '../compositionEditorTypes';
2
+ import { CompositionEditorStore } from '../compositionEditorStore';
3
3
  export declare function buildLayoutSection(store: CompositionEditorStore): EditorPanel;
@@ -1,4 +1,4 @@
1
1
  export { renderSchematicStructure } from './renderSchematicStructure';
2
2
  export { renderSchematicRound } from './renderSchematicRound';
3
3
  export { renderSchematicMatchUp } from './renderSchematicMatchUp';
4
- export type { SchematicMatchUp } from './renderSchematicMatchUp';
4
+ export type { SchematicMatchUp, SchematicOptions } from './renderSchematicMatchUp';
@@ -1,3 +1,26 @@
1
+ /**
2
+ * The schematic's geometry and colours. Every value has a default equal to the original fixed rendering, so
3
+ * leaving it out changes nothing. Set on the structure root as CSS variables, which the stylesheet reads.
4
+ */
5
+ export interface SchematicOptions {
6
+ /** height in px of one participant slot; a matchUp is two slots and a 1px divider. Default 5 */
7
+ slotHeight?: number;
8
+ /** width in px of the connector between rounds. Default 8 */
9
+ connectorWidth?: number;
10
+ /** width in px of a matchUp. Default 40 */
11
+ matchUpWidth?: number;
12
+ /** corner radius of a slot, any CSS length. Default 1px */
13
+ radius?: string;
14
+ /** connector colour, any CSS colour. Default the structure's connector colour */
15
+ connectorColor?: string;
16
+ /** slot fill, any CSS colour. Default `--chc-border-primary` */
17
+ slotColor?: string;
18
+ }
19
+ export declare const SCHEMATIC_DEFAULTS: {
20
+ slotHeight: number;
21
+ connectorWidth: number;
22
+ matchUpWidth: number;
23
+ };
1
24
  export interface SchematicMatchUp {
2
25
  matchUpId: string;
3
26
  roundNumber: number;
@@ -12,10 +35,10 @@ export interface SchematicMatchUp {
12
35
  collectionId?: string;
13
36
  }
14
37
  /**
15
- * Renders a compact matchup: two 5px-tall slots separated by a 1px divider.
16
- * Total height 11px vs ~60px for the full renderMatchUp.
38
+ * Renders a compact matchup: two slots (5px tall by default, `slotHeight`) separated by a 1px divider.
39
+ * Total height 11px by default, vs ~60px for the full renderMatchUp.
17
40
  */
18
- export declare function renderSchematicMatchUp({ matchUp, moiety, isFirstRound, isFinalRound, isRoundRobin, roundFactor: roundFactorParam, isLucky }: {
41
+ export declare function renderSchematicMatchUp({ matchUp, moiety, isFirstRound, isFinalRound, isRoundRobin, roundFactor: roundFactorParam, connectorWidth, slotHeight, isLucky }: {
19
42
  matchUp: SchematicMatchUp;
20
43
  moiety?: boolean;
21
44
  isFirstRound?: boolean;
@@ -28,4 +51,7 @@ export declare function renderSchematicMatchUp({ matchUp, moiety, isFirstRound,
28
51
  * Falls back to the matchUp's own factor for direct callers.
29
52
  */
30
53
  roundFactor?: number;
54
+ /** `SchematicOptions.slotHeight`, which the connector heights scale with */
55
+ slotHeight?: number;
56
+ connectorWidth?: number;
31
57
  }): HTMLElement;
@@ -1,9 +1,9 @@
1
- import { SchematicMatchUp } from './renderSchematicMatchUp';
1
+ import { SchematicMatchUp, SchematicOptions } from './renderSchematicMatchUp';
2
2
  /**
3
3
  * Renders a round column with optional simplified header (round number only)
4
4
  * and compact schematic matchups.
5
5
  */
6
- export declare function renderSchematicRound({ initialRoundFactor, initialRoundNumber, isFinalRound, isRoundRobin, roundNumber, matchUps, showHeader, isLucky }: {
6
+ export declare function renderSchematicRound({ initialRoundFactor, initialRoundNumber, isFinalRound, isRoundRobin, roundNumber, matchUps, showHeader, options, isLucky }: {
7
7
  initialRoundFactor?: number;
8
8
  initialRoundNumber?: number;
9
9
  isFinalRound?: boolean;
@@ -11,5 +11,6 @@ export declare function renderSchematicRound({ initialRoundFactor, initialRoundN
11
11
  roundNumber: number;
12
12
  matchUps: SchematicMatchUp[];
13
13
  showHeader?: boolean;
14
+ options?: SchematicOptions;
14
15
  isLucky?: boolean;
15
16
  }): HTMLElement;
@@ -1,12 +1,14 @@
1
- import { SchematicMatchUp } from './renderSchematicMatchUp';
1
+ import { SchematicMatchUp, SchematicOptions } from './renderSchematicMatchUp';
2
2
  /**
3
3
  * Compact bracket renderer showing structure shape without participant details.
4
4
  * Uses the same getRoundMatchUps() engine call as renderStructure but produces
5
5
  * a much smaller visual (~14px per matchup vs ~60px).
6
6
  */
7
- export declare function renderSchematicStructure({ initialRoundNumber, showHeaders, structureId, matchUps }: {
7
+ export declare function renderSchematicStructure({ initialRoundNumber, showHeaders, structureId, matchUps, options }: {
8
8
  initialRoundNumber?: number;
9
9
  showHeaders?: boolean;
10
10
  structureId?: string;
11
11
  matchUps: SchematicMatchUp[];
12
+ /** geometry and colours; defaults reproduce the original rendering */
13
+ options?: SchematicOptions;
12
14
  }): HTMLElement;
@@ -0,0 +1,25 @@
1
+ import { MatchUp } from '../../types';
2
+ /**
3
+ * How a matchUp connects to the next round: `m1` / `m2` the upper / lower matchUp of a pair (the line turns
4
+ * down / up to their junction), `m0` straight across (a feed or qualifying round), `noProgression` the final,
5
+ * `mr` no connector at all (round robin, ad hoc, lucky, a search result, a tie's collection).
6
+ */
7
+ export type ConnectorVariant = 'm1' | 'm2' | 'm0' | 'noProgression' | 'mr';
8
+ export declare function connectorVariant({ searchActive, isFinalRound, matchUp, isAdHoc, isLucky, moiety }: {
9
+ searchActive?: boolean;
10
+ isFinalRound?: boolean;
11
+ matchUp: MatchUp;
12
+ isAdHoc?: boolean;
13
+ isLucky?: boolean;
14
+ moiety?: boolean;
15
+ }): ConnectorVariant;
16
+ /**
17
+ * The cell a connected matchUp sits in. A round of cells is a grid of equal rows, so a cell in the next round
18
+ * spans exactly the two it is fed by, and the connector is drawn from the cell as percentages of its height:
19
+ * no JS heights, and nothing depends on how tall a card is.
20
+ */
21
+ export declare function renderConnectorCell({ connectorWidth, isFirstRound, variant }: {
22
+ connectorWidth?: number;
23
+ isFirstRound?: boolean;
24
+ variant: ConnectorVariant;
25
+ }): HTMLElement;
@@ -0,0 +1,32 @@
1
+ import { MatchUp, Side } from '../../types';
2
+ /**
3
+ * Entry statuses shown beside the seed, with their abbreviation and full name. LUCKY_LOSER is absent on
4
+ * purpose: the name already carries an `LL` badge. Registration states (CONFIRMED, REGISTERED, ...) are
5
+ * not draw entries and are absent too.
6
+ */
7
+ export declare const ENTRY_STATUS_LABELS: Record<string, {
8
+ abbr: string;
9
+ name: string;
10
+ }>;
11
+ /**
12
+ * The entry-status label to show for a side, or undefined. `setting` is `configuration.entryStatus`:
13
+ * `true` shows every status but direct acceptance, the ordinary case; `'all'` shows that too.
14
+ */
15
+ export declare function entryStatusLabel(side: Side | undefined, setting: boolean | 'all' | undefined): {
16
+ abbr: string;
17
+ name: string;
18
+ } | undefined;
19
+ /**
20
+ * A seed beaten in a fully scored result by an unseeded side or a lower seed: `{ winnerSeed, loserSeed, winningSide }`,
21
+ * else undefined. The verdict is the factory's `getMatchUpUpset` (CA, 2026-10-26: upset belongs in the factory, played
22
+ * results only and only fully scored), on its default SEEDING basis.
23
+ */
24
+ export declare function upsetOf(matchUp?: MatchUp): {
25
+ winnerSeed?: number;
26
+ loserSeed: number;
27
+ winningSide: number;
28
+ } | undefined;
29
+ export declare function upsetTitle(upset: {
30
+ winnerSeed?: number;
31
+ loserSeed: number;
32
+ }): string;
@@ -3,6 +3,10 @@ export declare function renderMatchUp(params: {
3
3
  composition?: Composition;
4
4
  initialRoundNumber?: number;
5
5
  matchUp: MatchUp;
6
+ /**
7
+ * @deprecated The connector is drawn by the cell `renderRound` puts the matchUp in, so a matchUp no longer
8
+ * needs to know whether it is the upper or lower of a pair. Ignored.
9
+ */
6
10
  moiety?: boolean;
7
11
  selectedMatchUpId?: string;
8
12
  searchActive?: boolean;
@@ -12,9 +16,8 @@ export declare function renderMatchUp(params: {
12
16
  eventHandlers?: EventHandlers;
13
17
  className?: string;
14
18
  /**
15
- * Connector-height scale for THIS render. `renderRound` computes it for the
16
- * round the view currently starts at and passes it down; direct callers can
17
- * leave it out and the matchUp's own factor is used.
19
+ * @deprecated Connector heights are no longer computed: a round is a grid of equal rows and the connector is
20
+ * drawn as percentages of its cell. Ignored.
18
21
  */
19
22
  roundFactor?: number;
20
23
  }): HTMLElement;
@@ -0,0 +1,14 @@
1
+ import { Configuration, EventHandlers, MatchUp } from '../../types';
2
+ /**
3
+ * `configuration.matchUpActions`: Watch and Share under the card, or undefined when neither can do anything.
4
+ *
5
+ * Watch needs a STREAM or VIDEO resource and opens it as the header icons do: `eventHandlers.onlineResourceClick`
6
+ * when the host gives one, else the resource's http(s) link in a new tab. Share needs `eventHandlers.matchUpShare`;
7
+ * a share button that did nothing would be worse than none. Clicks are properties, not listeners (TMX's morphdom
8
+ * carries properties across a refresh), and stop propagating so they do not also select the matchUp.
9
+ */
10
+ export declare function renderMatchUpActions({ configuration, eventHandlers, matchUp }: {
11
+ configuration?: Configuration;
12
+ eventHandlers?: EventHandlers;
13
+ matchUp: MatchUp;
14
+ }): HTMLElement | undefined;
@@ -0,0 +1,19 @@
1
+ import { Composition, EventHandlers, MatchUp } from '../../types';
2
+ /**
3
+ * Re-render one matchUp of a rendered structure and swap it in, instead of rebuilding the structure and
4
+ * diffing the whole tree, e.g. on a live score update. The matchUp's place in the structure (the view's first
5
+ * round, final / lucky / ad hoc / search) is read from what `renderMatchUp` recorded on its `.tmx-m`, so the
6
+ * swapped node is what a full render would have produced. Its connector belongs to the cell around it, which
7
+ * the swap keeps.
8
+ *
9
+ * Returns the new node, or undefined when the matchUp is not in `root`. The path index behind
10
+ * `configuration.pathHighlight` is built per structure render, so a path reflects the last full render.
11
+ */
12
+ export declare function renderMatchUpInPlace({ selectedMatchUpId, eventHandlers, composition, favourites, matchUp, root }: {
13
+ selectedMatchUpId?: string;
14
+ eventHandlers?: EventHandlers;
15
+ composition?: Composition;
16
+ favourites?: string[] | Set<string>;
17
+ matchUp: MatchUp;
18
+ root: Element | Document;
19
+ }): HTMLElement | undefined;
@@ -0,0 +1,20 @@
1
+ import { Configuration, EventHandlers, MatchUp, OnlineResource, OnlineResourceSubType } from '../../types';
2
+ /** True when the matchUp carries a URL resource of subtype VIDEO or STREAM. Read for `data-has-video`. */
3
+ export declare function hasVideo(matchUp?: MatchUp): boolean;
4
+ /** The first URL resource of each wanted subtype, in display order. */
5
+ export declare function getMatchUpOnlineResources(matchUp?: MatchUp, subTypes?: OnlineResourceSubType[]): OnlineResource[];
6
+ /** Only http(s): an identifier is data from the record, and `javascript:` must never become a link. */
7
+ export declare function isWebUrl(identifier?: string): boolean;
8
+ /**
9
+ * The matchUp's online resources as icons, or `undefined` when the option is off or nothing renders.
10
+ *
11
+ * Clicks are bound as PROPERTIES (`onclick`), never with `addEventListener`: TMX refreshes a draw with
12
+ * morphdom, which carries handler properties across to a reused node and cannot carry listeners. Each
13
+ * click stops propagating, so it opens the resource without also selecting the matchUp or the schedule.
14
+ */
15
+ export declare function renderOnlineResources({ configuration, eventHandlers, placement, matchUp }: {
16
+ configuration?: Configuration;
17
+ eventHandlers?: EventHandlers;
18
+ placement: 'schedule' | 'corner';
19
+ matchUp?: MatchUp;
20
+ }): HTMLElement | undefined;
@@ -4,4 +4,4 @@ export declare function renderParticipantDetail(params: {
4
4
  individualParticipant?: IndividualParticipant;
5
5
  className?: string;
6
6
  [key: string]: any;
7
- }): HTMLElement;
7
+ }): HTMLElement | undefined;
@@ -1,5 +1,5 @@
1
1
  import { Composition, EventHandlers, MatchUp } from '../../types';
2
- export declare function renderRound({ initialRoundNumber, selectedMatchUpId, eventHandlers, isFinalRound, isRoundRobin, searchActive, composition, roundFactor, roundNumber, roundProfile, minWidth, matchUps, context, isLucky }: {
2
+ export declare function renderRound({ initialRoundNumber, selectedMatchUpId, eventHandlers, isFinalRound, isRoundRobin, searchActive, composition, roundNumber, roundProfile, minWidth, matchUps, context, isLucky }: {
3
3
  initialRoundNumber?: number;
4
4
  selectedMatchUpId?: string;
5
5
  eventHandlers?: EventHandlers;
@@ -7,6 +7,10 @@ export declare function renderRound({ initialRoundNumber, selectedMatchUpId, eve
7
7
  isRoundRobin?: boolean;
8
8
  searchActive?: boolean;
9
9
  composition?: Composition;
10
+ /**
11
+ * @deprecated Connector heights are no longer computed (see `connectors.ts`), so there is no scale to
12
+ * override. Ignored.
13
+ */
10
14
  roundFactor?: number;
11
15
  roundNumber: number;
12
16
  roundProfile?: any;
@@ -1,8 +1,9 @@
1
- import { EventHandlers, MatchUp } from '../../types';
2
- export declare function renderRoundHeader({ eventHandlers, roundMatchUps, roundProfile, roundNumber, context }: {
1
+ import { EventHandlers, MatchUp, StructureRenderers } from '../../types';
2
+ export declare function renderRoundHeader({ eventHandlers, roundMatchUps, roundProfile, roundNumber, renderers, context }: {
3
3
  eventHandlers?: EventHandlers;
4
4
  roundMatchUps?: MatchUp[];
5
5
  roundProfile?: any;
6
6
  roundNumber: number;
7
+ renderers?: StructureRenderers;
7
8
  context?: any;
8
9
  }): HTMLElement;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * A pager for a structure rendered with `carousel`: one button per round that scrolls the carousel to it,
3
+ * with `aria-current` on the round in view. Mount it next to the structure; call `destroy` when the structure
4
+ * is removed or re-rendered, which disconnects the observer that tracks the round in view.
5
+ *
6
+ * Labels come from each round's header when it has one (`configuration.roundHeader`), else `R1`, `R2`, ...
7
+ */
8
+ export declare function renderRoundPager({ behavior, structure }: {
9
+ behavior?: 'smooth' | 'instant' | 'auto';
10
+ structure: HTMLElement;
11
+ }): {
12
+ element: HTMLElement;
13
+ destroy: () => void;
14
+ };
@@ -1,5 +1,12 @@
1
- import { EventHandlers, MatchUp } from '../../types';
2
- export declare function renderSchedule({ matchUp, eventHandlers }: {
3
- matchUp?: MatchUp;
1
+ import { Configuration, EventHandlers, MatchUp } from '../../types';
2
+ export declare function renderSchedule({ configuration, eventHandlers, matchUp }: {
3
+ configuration?: Configuration;
4
4
  eventHandlers?: EventHandlers;
5
+ matchUp?: MatchUp;
5
6
  }): HTMLElement;
7
+ /**
8
+ * The schedule header's date and time, compact enough not to wrap beside a court name: `Mon 26 Oct., 8:00AM`.
9
+ * The month is abbreviated with a period when it is shortened (`May` stays as it is), and the hour drops the
10
+ * leading zero and the space before AM/PM (CA, 2026-10-26: `07:30 AM` wrapped the header).
11
+ */
12
+ export declare function formatScheduleDate(date: string, time?: string): string;
@@ -0,0 +1,7 @@
1
+ import { StructureRenderers } from '../../types';
2
+ /**
3
+ * Call a host's slot renderer and wrap what it returns, or return undefined when there is no renderer or it
4
+ * renders nothing. The host's element is wrapped, never modified: `chc-slot chc-slot--<name>` goes on the
5
+ * wrapper. A renderer that throws is a host defect and is left to surface.
6
+ */
7
+ export declare function renderSlot<K extends keyof StructureRenderers>(renderers: StructureRenderers | undefined, name: K, ctx: Parameters<NonNullable<StructureRenderers[K]>>[0], tagName?: 'div' | 'span'): HTMLElement | undefined;
@@ -1,5 +1,5 @@
1
1
  import { Composition, EventHandlers, MatchUp } from '../../types';
2
- export declare function renderStructure({ initialRoundNumber, selectedMatchUpId, eventHandlers, searchActive, composition, structureId, finalColumn, matchUps, minWidth, context }: {
2
+ export declare function renderStructure({ initialRoundNumber, selectedMatchUpId, eventHandlers, searchActive, composition, structureId, finalColumn, favourites, carousel, matchUps, minWidth, context }: {
3
3
  initialRoundNumber?: number;
4
4
  selectedMatchUpId?: string;
5
5
  eventHandlers?: EventHandlers;
@@ -7,7 +7,26 @@ export declare function renderStructure({ initialRoundNumber, selectedMatchUpId,
7
7
  composition?: Composition;
8
8
  structureId?: string;
9
9
  finalColumn?: boolean | HTMLElement;
10
+ /**
11
+ * participantIds the host pins (a pair's or an individual's): their sides take the favourite style and
12
+ * their matchUps carry `data-favourite`. Host state, so a parameter rather than a composition option.
13
+ */
14
+ favourites?: string[] | Set<string>;
15
+ /**
16
+ * Lay the rounds out as a horizontal carousel for narrow screens: one round per scroll-snap stop, with
17
+ * `peek` (default 0.2) of the next round showing, so connectors lead somewhere. Round headers move with
18
+ * their column. Pair it with `renderRoundPager` for a pager. The host decides when, e.g. by viewport.
19
+ */
20
+ carousel?: boolean | {
21
+ peek?: number;
22
+ };
10
23
  matchUps: MatchUp[];
11
24
  minWidth?: string;
12
25
  context?: any;
13
26
  }): HTMLElement;
27
+ /**
28
+ * Mark the sides of pinned participants. A side matches on its own participantId (a pair, a team) or on any
29
+ * of its individuals', so pinning one player of a pair pins the pair's side. Exported for hosts that render
30
+ * matchUps one at a time.
31
+ */
32
+ export declare function applyFavourites(root: HTMLElement, favourites?: string[] | Set<string>): void;
@@ -3,5 +3,9 @@ export interface StructureMinimapOptions {
3
3
  matchUps: MatchUp[];
4
4
  width?: number;
5
5
  quarterCount?: number;
6
+ /** connector line colour, any CSS colour. Default the structure's connector colour */
7
+ connectorColor?: string;
8
+ /** connector stroke width in px. Default 1 */
9
+ strokeWidth?: number;
6
10
  }
7
- export declare function buildStructureMinimap({ matchUps, width, quarterCount }: StructureMinimapOptions): HTMLDivElement | null;
11
+ export declare function buildStructureMinimap({ matchUps, width, quarterCount, connectorColor, strokeWidth }: StructureMinimapOptions): HTMLDivElement | null;
@@ -0,0 +1,17 @@
1
+ import { Configuration, MatchUp, ScheduleInfoOptions } from '../../types';
2
+ /** `configuration.scheduleInfo` in its object form; `true` (or nothing) is the plain header. */
3
+ export declare function scheduleInfoOptions(configuration?: Configuration): ScheduleInfoOptions;
4
+ /** The side's colour from `configuration.sideColors`, or undefined. */
5
+ export declare function sideColorOf(configuration: Configuration | undefined, matchUp: MatchUp, sideNumber: number): string;
6
+ export declare function isFinished(matchUp?: MatchUp): boolean;
7
+ /** The side due to serve, while the matchUp is unfinished; undefined when nobody is tracking service. */
8
+ export declare function servingSideOf(matchUp?: MatchUp): 1 | 2 | undefined;
9
+ /**
10
+ * Playing time in milliseconds, or undefined before the matchUp starts. The factory's `schedule.milliseconds`
11
+ * first (it excludes suspensions); otherwise `startTime` to `endTime`, or to `now` while the matchUp is unfinished.
12
+ */
13
+ export declare function elapsedOf(matchUp?: MatchUp, now?: number): number | undefined;
14
+ /** `h:mm`: `0:16`, `1:42`, `12:05`. */
15
+ export declare function formatElapsed(milliseconds: number): string;
16
+ /** `Men's Singles - R32`, from what the matchUp carries; empty when it carries neither. */
17
+ export declare function eventRoundLabel(matchUp?: MatchUp): string;
@@ -0,0 +1,21 @@
1
+ import { MatchUp } from '../../types';
2
+ export type ParticipantPath = {
3
+ /** every matchUp the participant appears in */
4
+ played: string[];
5
+ /** the matchUps the participant won: their outgoing connector is part of the path */
6
+ won: string[];
7
+ /** the matchUps ahead while the participant is still alive, by draw position */
8
+ projected: string[];
9
+ };
10
+ /**
11
+ * Index a structure's matchUps by participant, for path highlighting. `projectable` is false for draws whose
12
+ * later rounds are not reached by position (round robin, ad hoc, lucky); their paths are then the matchUps
13
+ * played, with nothing projected.
14
+ */
15
+ export declare function buildPathIndex({ roundProfile, projectable, matchUps }: {
16
+ roundProfile?: Record<string, {
17
+ matchUpsCount?: number;
18
+ }>;
19
+ projectable: boolean;
20
+ matchUps: MatchUp[];
21
+ }): (participantId: string) => ParticipantPath | undefined;
@@ -177,7 +177,7 @@ export declare function supportsNeitherSide(selectedOutcome: string | undefined)
177
177
  * nobody and propagates a walkover into the next round — so the modal says what it will do before it
178
178
  * is submitted rather than after.
179
179
  */
180
- export declare function doubleExitWarning(selectedOutcome: string | undefined): string;
180
+ export declare function doubleExitWarning(selectedOutcome: string | undefined, labels?: Record<string, string | undefined>): string;
181
181
  export type IrregularEndingResolution = {
182
182
  /** The status to submit. `undefined` while the selection is incomplete. */
183
183
  matchUpStatus?: string;
@@ -53,6 +53,8 @@ export declare function matchUpStatus(model: ScoreEntryModel): string | undefine
53
53
  export type ErrorOptions = {
54
54
  /** Names for the two sides, so a message can say who rather than "side 1". */
55
55
  sideNames?: [string, string];
56
+ /** The host's translations (see `SCORE_ENTRY_TEXT`). */
57
+ labels?: Record<string, string | undefined>;
56
58
  };
57
59
  export declare function error(model: ScoreEntryModel, options?: ErrorOptions): string | undefined;
58
60
  /**
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Every word the score entry dialog shows, in English, keyed so a host can translate it.
3
+ *
4
+ * A host passes translations through the dialog's `labels`, the same map that already carries the
5
+ * status names (`RETIRED`, `WALKOVER`, …) and `statusUnavailable`. A key the host leaves out reads the
6
+ * English below, so a partial translation degrades to English for the missing words rather than to a
7
+ * key name or a blank.
8
+ *
9
+ * Placeholders are `{{name}}`, the i18next form, so a host using i18next can hand its catalog's strings
10
+ * over untouched (`t(key, { skipInterpolation: true })`) and the dialog fills them in. Status names fill
11
+ * `{{status}}` already translated, because they come from the same map.
12
+ *
13
+ * Two placeholders exist only for translators: set messages carry both `{{ordinal}}` (`1st`, English
14
+ * only) and `{{number}}` (`1`). English uses the ordinal; a language that has no `1st` uses the number.
15
+ *
16
+ * Score strings (`6-4 6-3`), format codes and participant names are data and are never translated here.
17
+ * Messages the factory itself returns (a set the engine refuses, a parse error) arrive in the factory's
18
+ * words.
19
+ */
20
+ export declare const SCORE_ENTRY_TEXT: {
21
+ readonly title: "Score Entry";
22
+ readonly submit: "Submit";
23
+ readonly clear: "Clear";
24
+ readonly cancel: "Cancel";
25
+ readonly player: "PLAYER";
26
+ readonly formatEditTitle: "Edit the scoring format";
27
+ readonly formatEditAria: "Scoring format {{format}} — edit";
28
+ readonly approachDynamicSets: "Dynamic Sets";
29
+ readonly approachFreeScore: "Free Score";
30
+ readonly approachDialPad: "Dial Pad";
31
+ readonly other: "Other…";
32
+ readonly otherBase: "Other";
33
+ readonly endedEarlyTitle: "How did this match end for them?";
34
+ readonly endedEarlyAria: "{{name}} — ended early";
35
+ readonly sidePanelHead: "What happened to {{name}}?";
36
+ readonly hintWalkover: "Did not play at all — no score";
37
+ readonly hintRetired: "Started, could not finish — score kept";
38
+ readonly hintDefaulted: "Removed by the referee";
39
+ readonly reason: "REASON";
40
+ readonly bothSidesOut: "{{name}} did not appear either — no one advances";
41
+ readonly endingNeedsScore: "No score yet — {{status}} keeps the score that was played";
42
+ readonly endingAfterCompleteScore: "The score is complete — {{status}} cannot follow it";
43
+ readonly doubleWalkover: "Double walkover";
44
+ readonly doubleDefault: "Double default";
45
+ readonly doubleExitWarning: "{{ending}} — neither side advances, and a walkover propagates to the next round.";
46
+ readonly defeated: "{{winner}} def. {{loser}} {{score}}";
47
+ readonly advancesTo: "advances to {{round}}";
48
+ readonly notFinished: "{{score}} — not a finished result";
49
+ readonly noResultYet: "No result entered yet";
50
+ readonly neitherAdvances: "{{status}} — neither side advances";
51
+ readonly sideAdvances: "{{status}} — {{name}} advances";
52
+ readonly noScoreRecorded: "no score recorded";
53
+ readonly partScoreCleared: "{{status}} — the part-score of {{score}} has been cleared";
54
+ readonly nobodyAdvances: "nobody advances";
55
+ readonly scoreRecorded: "{{status}} — {{score}} recorded";
56
+ readonly noWinnerYet: "{{status}} — no winner yet";
57
+ readonly notReadyToSubmit: "not ready to submit";
58
+ readonly clearedHeadline: "The recorded result will be removed — Submit to clear it.";
59
+ readonly clearRefused: "Enter a replacement score: a later match depends on this result.";
60
+ readonly setPrefix: "{{ordinal}} set:";
61
+ readonly sideFallback: "side {{side}}";
62
+ readonly tiebreakContradiction: "{{set}} {{name}} won it, so they must win the tiebreak";
63
+ readonly setHasNoScore: "{{set}} has no score";
64
+ readonly setNotFinished: "{{set}} is not finished";
65
+ readonly setHeading: "{{ordinal}}";
66
+ readonly tiebreakHeading: "TB";
67
+ readonly editSet: "Click to edit this set";
68
+ readonly gamesAria: "{{ordinal}} set, side {{side}} games";
69
+ readonly tiebreakAria: "{{ordinal}} set tiebreak, side {{side}} points";
70
+ readonly enteredSetAria: "{{ordinal}} set, side {{side}}: {{entered}} — edit";
71
+ readonly gamesEntered: "{{games}} games";
72
+ readonly gamesAndTiebreakEntered: "{{games}} games, tiebreak {{points}}";
73
+ readonly tiebreakSpoken: "tiebreak {{points}}";
74
+ readonly smartComplements: "Smart";
75
+ readonly smartComplementsExplain: "Smart complements — fill the opposing score from the one you type";
76
+ readonly typeTheScore: "TYPE THE SCORE";
77
+ readonly notAScore: "Not a score this format allows";
78
+ readonly completeMatch: "A complete match under {{format}}";
79
+ readonly tiebreak: "Tiebreak";
80
+ readonly backspace: "Backspace";
81
+ readonly readoutAria: "Side {{side}}: {{score}}";
82
+ readonly readoutNoScore: "Side {{side}}: no score";
83
+ };
84
+ export type ScoreEntryTextKey = keyof typeof SCORE_ENTRY_TEXT;
85
+ /** A host's translations: any of the keys above, plus the status names, each optional. */
86
+ export type ScoreEntryLabels = Partial<Record<ScoreEntryTextKey, string>> & Record<string, string | undefined>;
87
+ /**
88
+ * The host's string for `key`, or the English, with `{{placeholders}}` filled.
89
+ *
90
+ * A placeholder with no value is left as written, so a missing variable shows up as `{{name}}` in
91
+ * review rather than vanishing from the sentence.
92
+ */
93
+ export declare function scoreEntryText(labels: Record<string, string | undefined> | undefined, key: ScoreEntryTextKey, values?: Record<string, string | number | undefined>): string;