courthive-components 6.14.0 → 7.0.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 (27) hide show
  1. package/README.md +27 -39
  2. package/dist/components/scoring/logic/irregularEnding.d.ts +1 -1
  3. package/dist/components/scoring/logic/scoreEntrySelectors.d.ts +2 -0
  4. package/dist/components/scoring/logic/scoreEntryText.d.ts +93 -0
  5. package/dist/components/scoring/regions/dialPadRegion.d.ts +2 -0
  6. package/dist/components/scoring/regions/dynamicSetsRegion.d.ts +3 -0
  7. package/dist/components/scoring/regions/freeScoreRegion.d.ts +3 -1
  8. package/dist/components/scoring/regions/scoreReadout.d.ts +1 -1
  9. package/dist/components/scoring/scoreEntryCard.d.ts +4 -1
  10. package/dist/courthive-components.css +1 -1
  11. package/dist/courthive-components.es.js +8844 -10485
  12. package/dist/courthive-components.umd.js +73 -109
  13. package/dist/features.css +1 -1
  14. package/dist/index.d.ts +3 -3
  15. package/package.json +2 -2
  16. package/dist/components/scoring/approaches/dialPadApproach.d.ts +0 -2
  17. package/dist/components/scoring/approaches/dialPadLogic.d.ts +0 -17
  18. package/dist/components/scoring/approaches/dynamicSetsApproach.d.ts +0 -2
  19. package/dist/components/scoring/approaches/freeScoreApproach.d.ts +0 -2
  20. package/dist/components/scoring/approaches/inlineScoringApproach.d.ts +0 -2
  21. package/dist/components/scoring/config.d.ts +0 -28
  22. package/dist/components/scoring/index.d.ts +0 -6
  23. package/dist/components/scoring/logic/outcomeProjection.d.ts +0 -25
  24. package/dist/components/scoring/scoringModal.d.ts +0 -2
  25. package/dist/components/scoring/statusCodePicker.d.ts +0 -15
  26. package/dist/components/scoring/utils/scoreFormatters.d.ts +0 -12
  27. package/dist/components/scoring/utils/scoreValidator.d.ts +0 -16
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
  ```
@@ -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;
@@ -7,6 +7,8 @@ export type DialPadRegionParams = {
7
7
  matchUpFormat?: string;
8
8
  /** Sets already recorded, e.g. from a saved matchUp. Read only when no `store` is given. */
9
9
  sets?: SetScore[];
10
+ /** The host's translations (see `SCORE_ENTRY_TEXT`); anything left out reads English. */
11
+ labels?: Record<string, string | undefined>;
10
12
  onChange?: () => void;
11
13
  };
12
14
  export type DialPadRegion = ScoreRegion & {
@@ -11,7 +11,10 @@ export type DynamicSetsRegionParams = {
11
11
  sets?: SetScore[];
12
12
  /** Whether smart complements starts enabled. Defaults to on. */
13
13
  smartComplements?: boolean;
14
+ /** The toggle's word. Wins over `labels.smartComplements`. */
14
15
  smartComplementsLabel?: string;
16
+ /** The host's translations (see `SCORE_ENTRY_TEXT`); anything left out reads English. */
17
+ labels?: Record<string, string | undefined>;
15
18
  /** Called after a change affecting only the score. Wire to the card's `refresh`. */
16
19
  onChange?: () => void;
17
20
  /** Called when the COLUMNS change. Wire to the card's `rerender`. */
@@ -9,8 +9,10 @@ export type FreeScoreRegionParams = {
9
9
  initialText?: string;
10
10
  disabledMatchUpStatuses?: string[];
11
11
  statusScopeLabels?: Record<string, string>;
12
- /** Label above the field. */
12
+ /** Label above the field. Wins over `labels.typeTheScore`. */
13
13
  label?: string;
14
+ /** The host's translations (see `SCORE_ENTRY_TEXT`); anything left out reads English. */
15
+ labels?: Record<string, string | undefined>;
14
16
  /** Called after any change. Wire to the card's `refresh`. */
15
17
  onChange?: () => void;
16
18
  };
@@ -8,4 +8,4 @@ export type ScoreReadouts = {
8
8
  /** Rewrite every registered readout from the current sets. */
9
9
  update: (sets: SetScore[]) => void;
10
10
  };
11
- export declare function createScoreReadouts(): ScoreReadouts;
11
+ export declare function createScoreReadouts(labels?: Record<string, string | undefined>): ScoreReadouts;
@@ -136,7 +136,10 @@ export type ScoreEntryCardParams = {
136
136
  store?: ScoreEntryStore;
137
137
  /** The policy's reason-code groups, if the tournament has a scoring policy attached. */
138
138
  statusCodeGroups?: StatusCodeGroups;
139
- /** Ending labels, so a locale can supply them. */
139
+ /**
140
+ * Translations: the status names (`RETIRED`, …) and every key of `SCORE_ENTRY_TEXT`. Anything left out
141
+ * reads the English default.
142
+ */
140
143
  labels?: Record<string, string>;
141
144
  /** The approach switcher's current label, e.g. `'Dynamic Sets'`. Omit to hide the switcher. */
142
145
  approachLabel?: string;