courthive-components 6.1.0 → 6.2.1
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/dist/components/matchUpFormat/matchUpFormatLogic.d.ts +1 -1
- package/dist/components/scoring/approaches/dialPadLogic.d.ts +5 -0
- package/dist/components/scoring/keyboard.d.ts +24 -0
- package/dist/components/scoring/logic/dynamicSetsLogic.d.ts +25 -22
- package/dist/components/scoring/logic/scoreEntryModel.d.ts +175 -0
- package/dist/components/scoring/logic/scoreEntrySelectors.d.ts +88 -0
- package/dist/components/scoring/logic/scoreEntryState.d.ts +33 -0
- package/dist/components/scoring/logic/scoreEntryStore.d.ts +8 -0
- package/dist/components/scoring/logic/statusCodes.d.ts +34 -0
- package/dist/components/scoring/logic/tiebreakEntry.d.ts +14 -0
- package/dist/components/scoring/regions/dialPadRegion.d.ts +6 -1
- package/dist/components/scoring/regions/dynamicSetsRegion.d.ts +9 -0
- package/dist/components/scoring/regions/freeScoreRegion.d.ts +8 -2
- package/dist/components/scoring/regions/scoreLine.d.ts +2 -0
- package/dist/components/scoring/scoreEntryCard.d.ts +123 -15
- package/dist/components/scoring/scoreEntryDialog.d.ts +94 -0
- package/dist/courthive-components.css +1 -1
- package/dist/courthive-components.es.js +8426 -6530
- package/dist/courthive-components.umd.js +37 -37
- package/dist/index.d.ts +3 -0
- package/dist/types.d.ts +10 -0
- package/package.json +12 -12
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { ScoreEntryState, SideNumber } from './logic/scoreEntryState';
|
|
2
|
+
import { ScoreEntryModel } from './logic/scoreEntryModel';
|
|
3
|
+
import { ScoreEntryStore } from './logic/scoreEntryStore';
|
|
2
4
|
import { StatusCodeGroups } from './logic/statusCodes';
|
|
3
5
|
/**
|
|
4
6
|
* One column the region contributes to the participant rows.
|
|
@@ -19,26 +21,99 @@ export type ScoreColumn = {
|
|
|
19
21
|
/** A CSS grid track size. Defaults to the standard score-column width. */
|
|
20
22
|
width?: string;
|
|
21
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* What a score region IS, now that the card holds the model: render, and intent.
|
|
26
|
+
*
|
|
27
|
+
* S5 of the state-engine extraction. This contract used to carry fourteen members, six of which were
|
|
28
|
+
* ANSWERS — `scoreString`, `isComplete`, `winningSide`, `error`, `hasEntry`, `getSets` — computed by each
|
|
29
|
+
* region on the spot from its private copy of the score. Those are selectors over the model now, asked
|
|
30
|
+
* by the card, and a region that still answered them would be a second opinion the card ignores.
|
|
31
|
+
*
|
|
32
|
+
* What is left is what a region genuinely knows and the model cannot: how to DRAW the score (cells,
|
|
33
|
+
* a block, a control for the band), where entry begins, what is its own to reset after the model was
|
|
34
|
+
* emptied, and — for Free Score only — what the TEXT says that the model cannot represent: an ending
|
|
35
|
+
* parsed out of it, and a half-typed field.
|
|
36
|
+
*/
|
|
22
37
|
export type ScoreRegion = {
|
|
38
|
+
/**
|
|
39
|
+
* The store the region renders, when it made its own.
|
|
40
|
+
*
|
|
41
|
+
* A region built standalone — the behavioural tests, or a host placing a card inline — creates a store
|
|
42
|
+
* from its params, and the card adopts it so the two share one model. A host that opens the dialog
|
|
43
|
+
* gives the same store to the card and every region, and this is never consulted.
|
|
44
|
+
*/
|
|
45
|
+
store?: ScoreEntryStore;
|
|
23
46
|
/** The columns this region contributes to each participant row. */
|
|
24
47
|
columns?: () => ScoreColumn[];
|
|
25
48
|
/** The cells to place in one side's row. Must return one per column. */
|
|
26
49
|
rowCells?: (sideNumber: SideNumber) => HTMLElement[];
|
|
27
50
|
/** A block beneath the rows — a text field, or a keypad. */
|
|
28
51
|
block?: () => HTMLElement;
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
52
|
+
/**
|
|
53
|
+
* A compact control the region wants in the result band's right edge rather than in a row of its own.
|
|
54
|
+
*
|
|
55
|
+
* CA, 2026-09-27, on the smart-complements checkbox: *"I don't think '[] Smart Complements' should take
|
|
56
|
+
* up a whole row of the modal. I think it can be a little icon to the far right side of the row where you
|
|
57
|
+
* have 'No result entered yet'. Just (Smart) maybe, something compact that toggles."*
|
|
58
|
+
*
|
|
59
|
+
* It stays a REGION concern — it is a Dynamic Sets behaviour and nothing else reads it — so the region
|
|
60
|
+
* supplies the element and the card only decides where it sits.
|
|
61
|
+
*/
|
|
62
|
+
bandControl?: () => HTMLElement | undefined;
|
|
35
63
|
/**
|
|
36
64
|
* A matchUpStatus the region itself parsed out of what was typed — Free Score only.
|
|
37
65
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
66
|
+
* REPORTED, not selected: an inference about the text, which never enters the model's `ending`. Used
|
|
67
|
+
* ONLY when the operator has selected no ending. See `resolveReportedEnding` for why that precedence
|
|
68
|
+
* and not the other.
|
|
40
69
|
*/
|
|
41
70
|
matchUpStatus?: () => string | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* Reset what is the region's own after the card has emptied the model: a text field, a keypad mode,
|
|
73
|
+
* the set under edit. The score itself is already gone by the time this is called.
|
|
74
|
+
*/
|
|
75
|
+
clear?: () => void;
|
|
76
|
+
/**
|
|
77
|
+
* Put focus where entry begins, selecting what is there.
|
|
78
|
+
*
|
|
79
|
+
* Selecting matters as much as focusing: the old dialog's `focusAndSelect` is why typing into a cell
|
|
80
|
+
* that already holds a score REPLACES it. Used after `[Clear]`, and by a host opening the dialog.
|
|
81
|
+
*/
|
|
82
|
+
focusFirst?: () => void;
|
|
83
|
+
/**
|
|
84
|
+
* Entry the MODEL cannot see — Free Score's half-typed text. The card asks the model first and this
|
|
85
|
+
* second, so a `6-4 re` mid-word still counts as something to lose.
|
|
86
|
+
*/
|
|
87
|
+
hasEntry?: () => boolean;
|
|
88
|
+
};
|
|
89
|
+
/** One entry in the approach switcher's menu. */
|
|
90
|
+
export type ApproachOption = {
|
|
91
|
+
/** Reported to `onSelectApproach`. The host's own name for the approach. */
|
|
92
|
+
key: string;
|
|
93
|
+
label: string;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* What the card reports when Submit is pressed.
|
|
97
|
+
*
|
|
98
|
+
* `winningSide` is the ending's where one was recorded and the SCORE's otherwise. Both are real answers
|
|
99
|
+
* and a host needs whichever applies: measured 2026-09-27, this carried only the ending's, so an
|
|
100
|
+
* ordinary 6-4 6-3 submitted as `{ matchUpStatus: undefined, winningSide: undefined }` — the 95% case
|
|
101
|
+
* reporting nothing at all about who won.
|
|
102
|
+
*/
|
|
103
|
+
export type ScoreEntryOutcome = {
|
|
104
|
+
matchUpStatus?: string;
|
|
105
|
+
winningSide?: number;
|
|
106
|
+
reasonCode?: string;
|
|
107
|
+
/** The score as the region formats it, which is what a host stores alongside the outcome. */
|
|
108
|
+
score?: string;
|
|
109
|
+
/**
|
|
110
|
+
* The operator cleared a recorded outcome and submitted the empty result — REMOVE what is stored.
|
|
111
|
+
*
|
|
112
|
+
* Only ever `true`, never `false`, so a host that does not know about it is unaffected. It exists
|
|
113
|
+
* because an empty outcome is otherwise indistinguishable from a submission that says nothing, and
|
|
114
|
+
* the two must not be treated alike: one erases a stored score and the other must not.
|
|
115
|
+
*/
|
|
116
|
+
cleared?: boolean;
|
|
42
117
|
};
|
|
43
118
|
export type ScoreEntryCardParams = {
|
|
44
119
|
sides: [{
|
|
@@ -54,30 +129,63 @@ export type ScoreEntryCardParams = {
|
|
|
54
129
|
title?: string;
|
|
55
130
|
/** The score region for the active approach. */
|
|
56
131
|
region: ScoreRegion;
|
|
132
|
+
/**
|
|
133
|
+
* The model, when the host holds it — the dialog does, and shares it with every region it builds.
|
|
134
|
+
* Omitted, the card adopts the region's own store, or makes one.
|
|
135
|
+
*/
|
|
136
|
+
store?: ScoreEntryStore;
|
|
57
137
|
/** The policy's reason-code groups, if the tournament has a scoring policy attached. */
|
|
58
138
|
statusCodeGroups?: StatusCodeGroups;
|
|
59
139
|
/** Ending labels, so a locale can supply them. */
|
|
60
140
|
labels?: Record<string, string>;
|
|
61
141
|
/** The approach switcher's current label, e.g. `'Dynamic Sets'`. Omit to hide the switcher. */
|
|
62
142
|
approachLabel?: string;
|
|
143
|
+
/**
|
|
144
|
+
* Offered when the switcher should present a CHOICE. The label of the active approach stays
|
|
145
|
+
* `approachLabel`; these are what it can become.
|
|
146
|
+
*/
|
|
147
|
+
approaches?: ApproachOption[];
|
|
148
|
+
/** Called with the chosen approach's key. Wire to a host that swaps the region through `update`. */
|
|
149
|
+
onSelectApproach?: (key: string) => void;
|
|
150
|
+
/** Offered instead of `approaches` when the host drives switching from its own control. */
|
|
63
151
|
onSwitchApproach?: () => void;
|
|
152
|
+
/** Offered when the host can edit the scoring format. Omit and the format chip stays inert text. */
|
|
153
|
+
onEditFormat?: () => void;
|
|
64
154
|
onCancel?: () => void;
|
|
155
|
+
/** An ending already recorded, for reopening an outcome. Build it with `hydrateScoreEntryState`. */
|
|
156
|
+
initialState?: ScoreEntryState;
|
|
65
157
|
onClear?: () => void;
|
|
66
|
-
onSubmit?: (outcome:
|
|
67
|
-
matchUpStatus?: string;
|
|
68
|
-
winningSide?: number;
|
|
69
|
-
reasonCode?: string;
|
|
70
|
-
}) => void;
|
|
158
|
+
onSubmit?: (outcome: ScoreEntryOutcome) => void;
|
|
71
159
|
onClose?: () => void;
|
|
72
160
|
};
|
|
73
|
-
/** A card instance: its element, plus the handle the host needs to react to score-region changes. */
|
|
74
161
|
export type ScoreEntryCard = {
|
|
75
162
|
element: HTMLElement;
|
|
76
163
|
/** Re-render the band and the submit gate. Call when the score region's value changes. */
|
|
77
164
|
refresh: () => void;
|
|
78
165
|
/** Rebuild everything, including the region's cells. Call when the region's COLUMNS change. */
|
|
79
166
|
rerender: () => void;
|
|
80
|
-
/**
|
|
167
|
+
/**
|
|
168
|
+
* Change the region (an approach switch) or the scoring format, keeping the ENDING state.
|
|
169
|
+
*
|
|
170
|
+
* Both inputs change while the dialog is open and both invalidate the region's cells, so they share one
|
|
171
|
+
* mutator. What they must NOT invalidate is the ending: a walkover recorded against a row is a fact
|
|
172
|
+
* about the match, not about the approach used to type it or the format the score is read under, and
|
|
173
|
+
* an operator who corrects `SET3-S:6/TB7` to `SET3-S:6/TB7@5` has not retracted it.
|
|
174
|
+
*/
|
|
175
|
+
update: (next: {
|
|
176
|
+
region?: ScoreRegion;
|
|
177
|
+
matchUpFormat?: string;
|
|
178
|
+
approachLabel?: string;
|
|
179
|
+
}) => void;
|
|
180
|
+
/** The store the card renders, for a host that builds regions on it. */
|
|
181
|
+
store: ScoreEntryStore;
|
|
182
|
+
/** The model as it stands. */
|
|
183
|
+
getModel: () => ScoreEntryModel;
|
|
184
|
+
/** The current ending state — `getModel().ending` — for a host that needs to inspect it. */
|
|
81
185
|
getState: () => ScoreEntryState;
|
|
186
|
+
/** Whether anything at all has been entered: a score, an ending, or text the model cannot see. */
|
|
187
|
+
holdsEntry: () => boolean;
|
|
188
|
+
/** The DOM id of the card's heading, for a host's `aria-labelledby`. */
|
|
189
|
+
titleId: string;
|
|
82
190
|
};
|
|
83
191
|
export declare function renderScoreEntryCard(params: ScoreEntryCardParams): ScoreEntryCard;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { SetScore } from './types';
|
|
2
|
+
import { ScoreEntryCard, ScoreEntryCardParams, ScoreEntryOutcome } from './scoreEntryCard';
|
|
3
|
+
/** The three entry approaches. The key is what `onSelectApproach` reports and what a host persists. */
|
|
4
|
+
export type ScoreEntryApproach = 'dynamicSets' | 'freeScore' | 'dialPad';
|
|
5
|
+
export type ScoreEntryDialogParams = Omit<ScoreEntryCardParams, 'region' | 'approachLabel' | 'approaches' | 'onSelectApproach' | 'onSwitchApproach' | 'onEditFormat' | 'onClose' | 'onSubmit'> & {
|
|
6
|
+
/**
|
|
7
|
+
* Called with the outcome, then the dialog closes.
|
|
8
|
+
*
|
|
9
|
+
* `sets` is added to what the card reports, because the dialog knows its regions carry them and a
|
|
10
|
+
* host saving to the factory needs the structured sets rather than only the formatted string.
|
|
11
|
+
*/
|
|
12
|
+
onSubmit?: (outcome: ScoreEntryOutcome & {
|
|
13
|
+
sets: SetScore[];
|
|
14
|
+
}) => void;
|
|
15
|
+
/** Which approach opens. Defaults to Dynamic Sets. */
|
|
16
|
+
approach?: ScoreEntryApproach;
|
|
17
|
+
/** Which approaches the switcher offers. Defaults to all three; a single entry hides the menu. */
|
|
18
|
+
approaches?: ScoreEntryApproach[];
|
|
19
|
+
/** Sets already recorded, e.g. from a saved matchUp. Ignored when `matchUp` carries a score. */
|
|
20
|
+
sets?: SetScore[];
|
|
21
|
+
/**
|
|
22
|
+
* A matchUp whose outcome is being REOPENED.
|
|
23
|
+
*
|
|
24
|
+
* Everything a recorded outcome needs comes from here: the sets, the format, the ending, and the
|
|
25
|
+
* reason code — CA, 2026-09-28: *"we need to be able to open existing outcomes!"* Anything passed
|
|
26
|
+
* explicitly wins, so a host can override one part without unpacking the rest.
|
|
27
|
+
*
|
|
28
|
+
* `sides` stays separate rather than being read off `matchUp.sides`, because the card wants display
|
|
29
|
+
* names and a matchUp carries participants; that mapping belongs to the host, which already has it.
|
|
30
|
+
*/
|
|
31
|
+
matchUp?: {
|
|
32
|
+
matchUpFormat?: string;
|
|
33
|
+
matchUpStatus?: string;
|
|
34
|
+
winningSide?: number;
|
|
35
|
+
score?: {
|
|
36
|
+
sets?: SetScore[];
|
|
37
|
+
};
|
|
38
|
+
sideStatusCodes?: Record<number, string>;
|
|
39
|
+
matchUpStatusCode?: string;
|
|
40
|
+
matchUpStatusCodes?: unknown[];
|
|
41
|
+
};
|
|
42
|
+
/** Called with the chosen approach whenever it changes, so a host can remember the preference. */
|
|
43
|
+
onApproachChange?: (approach: ScoreEntryApproach) => void;
|
|
44
|
+
/** Called with a format chosen in the picker. Omit and the format chip stays inert text. */
|
|
45
|
+
onFormatChange?: (matchUpFormat: string) => void;
|
|
46
|
+
/**
|
|
47
|
+
* Called when a format change DISCARDS part of the score, with what was lost and why.
|
|
48
|
+
*
|
|
49
|
+
* Only when something was actually discarded — a change that costs the operator nothing says
|
|
50
|
+
* nothing. `discarded` carries the sets themselves rather than a count, because a host that means to
|
|
51
|
+
* tell the operator has to quote them in their own numbers, and `reason` is the factory validator's
|
|
52
|
+
* own words about the first casualty.
|
|
53
|
+
*/
|
|
54
|
+
onScoreDiscarded?: (discarded: {
|
|
55
|
+
sets: SetScore[];
|
|
56
|
+
discarded: SetScore[];
|
|
57
|
+
reason?: string;
|
|
58
|
+
matchUpFormat: string;
|
|
59
|
+
}) => void;
|
|
60
|
+
/** Called when the dialog closes, by `[X]`, by a footer button, or by `close()`. */
|
|
61
|
+
onClose?: () => void;
|
|
62
|
+
/**
|
|
63
|
+
* Whether to focus the first entry field on open. Defaults to true.
|
|
64
|
+
*
|
|
65
|
+
* `false` for a host that opens the dialog as a side effect of something else, where stealing focus
|
|
66
|
+
* would interrupt what the operator was actually doing.
|
|
67
|
+
*/
|
|
68
|
+
autoFocus?: boolean;
|
|
69
|
+
/** Overrides cModal's config. `maxWidth`, `clickAway` and `padding` have deliberate defaults. */
|
|
70
|
+
modalConfig?: Record<string, any>;
|
|
71
|
+
/**
|
|
72
|
+
* The format picker. Defaults to this package's `getMatchUpFormatModal`.
|
|
73
|
+
*
|
|
74
|
+
* Injectable because a unit test has no business opening the real picker's modal — it is a second
|
|
75
|
+
* dialog with its own state — and because a host may have its own.
|
|
76
|
+
*/
|
|
77
|
+
openFormatPicker?: (params: {
|
|
78
|
+
existingMatchUpFormat: string;
|
|
79
|
+
callback: (matchUpFormat: string) => void;
|
|
80
|
+
}) => void;
|
|
81
|
+
};
|
|
82
|
+
export type ScoreEntryDialog = {
|
|
83
|
+
/** The card, for a host that needs to read the state or drive it. */
|
|
84
|
+
card: ScoreEntryCard;
|
|
85
|
+
/** The approach currently shown. */
|
|
86
|
+
approach: () => ScoreEntryApproach;
|
|
87
|
+
/** Switch approach, carrying the typed score across. */
|
|
88
|
+
setApproach: (approach: ScoreEntryApproach) => void;
|
|
89
|
+
/** Change the scoring format, rebuilding the region under it. */
|
|
90
|
+
setMatchUpFormat: (matchUpFormat: string) => void;
|
|
91
|
+
/** Close the dialog. Runs `onClose` exactly once, however the close was triggered. */
|
|
92
|
+
close: () => void;
|
|
93
|
+
};
|
|
94
|
+
export declare function openScoreEntryDialog(params: ScoreEntryDialogParams): ScoreEntryDialog;
|