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.
- package/README.md +27 -39
- package/dist/components/scoring/logic/irregularEnding.d.ts +1 -1
- package/dist/components/scoring/logic/scoreEntrySelectors.d.ts +2 -0
- package/dist/components/scoring/logic/scoreEntryText.d.ts +93 -0
- package/dist/components/scoring/regions/dialPadRegion.d.ts +2 -0
- package/dist/components/scoring/regions/dynamicSetsRegion.d.ts +3 -0
- package/dist/components/scoring/regions/freeScoreRegion.d.ts +3 -1
- package/dist/components/scoring/regions/scoreReadout.d.ts +1 -1
- package/dist/components/scoring/scoreEntryCard.d.ts +4 -1
- package/dist/courthive-components.css +1 -1
- package/dist/courthive-components.es.js +8844 -10485
- package/dist/courthive-components.umd.js +73 -109
- package/dist/features.css +1 -1
- package/dist/index.d.ts +3 -3
- package/package.json +2 -2
- package/dist/components/scoring/approaches/dialPadApproach.d.ts +0 -2
- package/dist/components/scoring/approaches/dialPadLogic.d.ts +0 -17
- package/dist/components/scoring/approaches/dynamicSetsApproach.d.ts +0 -2
- package/dist/components/scoring/approaches/freeScoreApproach.d.ts +0 -2
- package/dist/components/scoring/approaches/inlineScoringApproach.d.ts +0 -2
- package/dist/components/scoring/config.d.ts +0 -28
- package/dist/components/scoring/index.d.ts +0 -6
- package/dist/components/scoring/logic/outcomeProjection.d.ts +0 -25
- package/dist/components/scoring/scoringModal.d.ts +0 -2
- package/dist/components/scoring/statusCodePicker.d.ts +0 -15
- package/dist/components/scoring/utils/scoreFormatters.d.ts +0 -12
- 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,
|
|
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
|
|
53
|
-
|
|
52
|
+
// Open the score entry dialog
|
|
53
|
+
openScoreEntryDialog({
|
|
54
54
|
matchUp: myMatchUpData,
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
#### **
|
|
171
|
+
#### **openScoreEntryDialog**
|
|
171
172
|
|
|
172
|
-
|
|
173
|
-
|
|
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 {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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,
|
|
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
|
|
406
|
-
|
|
400
|
+
// Score with the dialog; its outcome is already the shape setMatchUpStatus reads
|
|
401
|
+
openScoreEntryDialog({
|
|
407
402
|
matchUp,
|
|
408
|
-
|
|
409
|
-
|
|
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
|
-
/**
|
|
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;
|