courthive-components 4.3.0 → 4.5.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/dist/components/inline-scoring/inlineScoringTypes.d.ts +2 -0
- package/dist/components/schedule-page/index.d.ts +1 -1
- package/dist/components/schedule-page/ui/activeStrip.d.ts +33 -0
- package/dist/components/scoring/logic/irregularEnding.d.ts +141 -0
- package/dist/components/scoring/logic/statusCodes.d.ts +62 -0
- package/dist/components/scoring/statusCodePicker.d.ts +13 -0
- package/dist/components/scoring/types.d.ts +16 -3
- package/dist/courthive-components.css +1 -1
- package/dist/courthive-components.es.js +7391 -7152
- package/dist/courthive-components.umd.js +45 -45
- package/dist/index.d.ts +2 -1
- package/package.json +1 -1
|
@@ -24,6 +24,8 @@ export interface InlineScoringCallbacks {
|
|
|
24
24
|
onEndMatch?: (params: {
|
|
25
25
|
matchUpId: string;
|
|
26
26
|
matchUpStatus: string;
|
|
27
|
+
/** Resolved from the exiting side; absent for a double exit and the non-directing statuses. */
|
|
28
|
+
winningSide?: number;
|
|
27
29
|
sideNumber?: number;
|
|
28
30
|
engine: any;
|
|
29
31
|
}) => void;
|
|
@@ -23,7 +23,7 @@ export type { ScheduleInspectorPanelOptions } from './ui/inspectorPanel';
|
|
|
23
23
|
export { buildCourtGridSlot } from './ui/courtGridSlot';
|
|
24
24
|
export { buildSchedulePageLayout } from './ui/schedulePageLayout';
|
|
25
25
|
export { buildActiveStripPanel } from './ui/activeStrip';
|
|
26
|
-
export type { ActiveStripPanel, ActiveStripPanelCallbacks, ActiveStripPanelData, ActiveStripPanelOptions, ActiveStripCourtMeta, ActiveStripCourtBlock } from './ui/activeStrip';
|
|
26
|
+
export type { ActiveStripPanel, ActiveStripPanelCallbacks, ActiveStripPanelData, ActiveStripPanelOptions, ActiveStripCourtMeta, ActiveStripCourtBlock, ActiveStripUpcomingBlock } from './ui/activeStrip';
|
|
27
27
|
export { buildScheduleGridCell, mapMatchUpToCellData, DEFAULT_SCHEDULE_CELL_CONFIG } from './ui/scheduleGridCell';
|
|
28
28
|
export { activateScheduleCellTypeAhead } from './ui/scheduleCellTypeAhead';
|
|
29
29
|
export type { ScheduleCellTypeAheadOptions } from './ui/scheduleCellTypeAhead';
|
|
@@ -22,6 +22,22 @@ export interface ActiveStripCourtBlock {
|
|
|
22
22
|
/** Optional short note (block.reason, registrant name from a future practice-registration system, etc.). */
|
|
23
23
|
detail?: string;
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* A block that has not started yet on a court that is free right now.
|
|
27
|
+
*
|
|
28
|
+
* Distinct from `ActiveStripCourtBlock`, which is a block in force. This one is
|
|
29
|
+
* about the runway left before it: the question a director asks of an idle court
|
|
30
|
+
* is "can I still put something on it", and the answer is a duration, not a
|
|
31
|
+
* banner.
|
|
32
|
+
*/
|
|
33
|
+
export interface ActiveStripUpcomingBlock {
|
|
34
|
+
/** Block type, for the colour — same vocabulary as `ActiveStripCourtBlock.type`. */
|
|
35
|
+
type: string;
|
|
36
|
+
/** Pre-formatted by the consumer, e.g. "40m → MAINTENANCE". */
|
|
37
|
+
label: string;
|
|
38
|
+
/** Hover text — the consumer owns the wording. */
|
|
39
|
+
title?: string;
|
|
40
|
+
}
|
|
25
41
|
export interface ActiveStripPanelData {
|
|
26
42
|
/** Court columns, in display order. The strip computes one cell per column. */
|
|
27
43
|
grid: ActiveStripGrid;
|
|
@@ -31,6 +47,23 @@ export interface ActiveStripPanelData {
|
|
|
31
47
|
* moves with the clock.
|
|
32
48
|
*/
|
|
33
49
|
dueMatchUpIds?: readonly string[];
|
|
50
|
+
/**
|
|
51
|
+
* courtId → the block bearing down on a court that is currently FREE. The
|
|
52
|
+
* consumer decides when one is worth showing; the strip only paints it.
|
|
53
|
+
*
|
|
54
|
+
* Ignored for a court that has a block in force (`courtBlocks` wins — a banner
|
|
55
|
+
* about now beats a countdown to later) and for one with a matchUp on it,
|
|
56
|
+
* where `courtBlockEdges` carries the same fact in the form that suits an
|
|
57
|
+
* occupied court.
|
|
58
|
+
*/
|
|
59
|
+
courtUpcomingBlocks?: Record<string, ActiveStripUpcomingBlock>;
|
|
60
|
+
/**
|
|
61
|
+
* courtId → a block that will come into force while the matchUp currently on
|
|
62
|
+
* that court is still expected to be playing. Painted as a top-edge stripe:
|
|
63
|
+
* a separate axis from the left-border state language, so a LIVE match on a
|
|
64
|
+
* court about to close shows both rather than one hiding the other.
|
|
65
|
+
*/
|
|
66
|
+
courtBlockEdges?: Record<string, ActiveStripUpcomingBlock>;
|
|
34
67
|
/** Optional display labels per courtId. Falls back to courtId. */
|
|
35
68
|
courts?: ActiveStripCourtMeta[];
|
|
36
69
|
/**
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure logic for resolving an irregular ending into a matchUpStatus.
|
|
3
|
+
*
|
|
4
|
+
* Every score-entry approach needs the same rule, and until now each one carried its own copy:
|
|
5
|
+
* "walkover with a winner is a WALKOVER, walkover without one is a DOUBLE_WALKOVER". Three copies
|
|
6
|
+
* of a rule is three chances to disagree about it, and they already disagreed about when Submit
|
|
7
|
+
* should be live.
|
|
8
|
+
*
|
|
9
|
+
* The rule this module encodes differs from those copies in one deliberate way. A missing winner
|
|
10
|
+
* used to mean "double exit" — so merely selecting Walkover produced a valid, submittable
|
|
11
|
+
* DOUBLE_WALKOVER before the operator had chosen anything. That is a fail-open default (Mentat
|
|
12
|
+
* architectural standard A3): the absence of an answer was read as a particular answer, and the
|
|
13
|
+
* particular answer it was read as is one of the most consequential outcomes in the draw, because a
|
|
14
|
+
* double exit advances nobody and propagates a walkover downstream.
|
|
15
|
+
*
|
|
16
|
+
* Here, absence is absence. `undefined` is "the operator has not answered yet" and is never valid;
|
|
17
|
+
* a double exit requires explicitly choosing NEITHER_SIDE. No DOM dependencies, no side effects.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* The winner-selection value meaning "no side won this matchUp" — the explicit choice that produces
|
|
21
|
+
* a double exit. A sentinel rather than `null` so the three states (unanswered / a side / neither)
|
|
22
|
+
* stay distinguishable in a radio group's string values.
|
|
23
|
+
*/
|
|
24
|
+
export declare const NEITHER_SIDE = "NEITHER";
|
|
25
|
+
/** Shown when an irregular ending has been chosen but the winner question is unanswered. */
|
|
26
|
+
export declare const WINNER_REQUIRED_ERROR = "Select a winner";
|
|
27
|
+
/** The statuses whose selection opens the winner question at all. */
|
|
28
|
+
export declare const WINNER_REQUIRING_STATUSES: Set<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Endings that resolve nobody: the match did not produce a result, so there is no winner to name
|
|
31
|
+
* and no winner question to answer. The factory classifies these as non-directing
|
|
32
|
+
* (`nonDirectingMatchUpStatuses`) — nothing advances out of them.
|
|
33
|
+
*
|
|
34
|
+
* These are NOT a smaller version of the winner-requiring endings. Asking "who won an abandoned
|
|
35
|
+
* match" is not a question with a missing answer; it is not a question. So unlike a walkover with
|
|
36
|
+
* no winner selection, one of these is valid the moment it is chosen.
|
|
37
|
+
*/
|
|
38
|
+
export declare const NON_DIRECTING_ENDINGS: Set<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Every irregular ending a set-entry approach offers, in display order.
|
|
41
|
+
*
|
|
42
|
+
* These six are exactly the keys the factory's scoring policy can refine with `matchUpStatusCodes`
|
|
43
|
+
* (ABANDONED, CANCELLED, DEFAULTED, INCOMPLETE, RETIRED, WALKOVER), which is why the list is this
|
|
44
|
+
* list: a status with no code group cannot carry a reason, and a code group with no status cannot
|
|
45
|
+
* be reached. Ordered so the three an operator reaches for most sit first.
|
|
46
|
+
*/
|
|
47
|
+
export declare const SELECTABLE_ENDINGS: string[];
|
|
48
|
+
/** Whether choosing this ending obliges the operator to answer the winner question. */
|
|
49
|
+
export declare function requiresWinner(selectedOutcome: string | undefined): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Statuses that carry NO score at all, so there is nothing to validate and nothing to submit.
|
|
52
|
+
*
|
|
53
|
+
* This mirrors the factory, which is the authority: `modifyMatchUpScore.ts:236` blanks the score
|
|
54
|
+
* outright for exactly this set — `Object.assign(matchUp, { ...toBePlayed })`, commented "a walkover
|
|
55
|
+
* has none". Note what is NOT here: `DEFAULTED` and `DOUBLE_DEFAULT` are absent in the factory too,
|
|
56
|
+
* because a player can default part-way through a match that was genuinely played, and that partial
|
|
57
|
+
* score is real. The same goes for `RETIRED` — a retirement keeps whatever was played, and needs no
|
|
58
|
+
* completed set to be a valid result.
|
|
59
|
+
*
|
|
60
|
+
* `CANCELLED` and `DEAD_RUBBER` are deliberately NOT here even though this repo's `validateScore`
|
|
61
|
+
* strips scores for them. They are unreachable from the set-entry approaches today (only freeScore
|
|
62
|
+
* parses them) and the factory does not blank scores for them, so adding them here would be a
|
|
63
|
+
* guess. Revisit when the status list widens — see Mentat SCORING_MODAL_STATUS_CODES.md M2.
|
|
64
|
+
*/
|
|
65
|
+
export declare const NO_SCORE_STATUSES: Set<string>;
|
|
66
|
+
/**
|
|
67
|
+
* Whether this ending means "no score exists", so score validation must be skipped rather than run
|
|
68
|
+
* and then overridden.
|
|
69
|
+
*/
|
|
70
|
+
export declare function carriesNoScore(selectedOutcome: string | undefined): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Unanswered (`undefined`), a side (`1` | `2`), or explicitly neither.
|
|
73
|
+
*/
|
|
74
|
+
export type WinnerSelection = 1 | 2 | typeof NEITHER_SIDE | undefined;
|
|
75
|
+
/** The double-exit statuses themselves, as opposed to the endings that produce them. */
|
|
76
|
+
export declare const DOUBLE_EXIT_STATUSES: Set<string>;
|
|
77
|
+
/**
|
|
78
|
+
* Whether this status IS a double exit — no side advances.
|
|
79
|
+
*
|
|
80
|
+
* Distinct from `supportsNeitherSide`, which asks whether an *ending* can become one. Use this
|
|
81
|
+
* where a status arrives already resolved, as it does from the inline popover.
|
|
82
|
+
*/
|
|
83
|
+
export declare function isDoubleExitStatus(matchUpStatus: string | undefined): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* The winner implied by a per-side exit control, where clicking a side's pill means THAT side is the
|
|
86
|
+
* one exiting and the other therefore wins.
|
|
87
|
+
*
|
|
88
|
+
* Returns `undefined` for a double exit (nobody advances) and for the non-directing statuses —
|
|
89
|
+
* SUSPENDED, CANCELLED, ABANDONED — which resolve nothing. The inline popover previously listed the
|
|
90
|
+
* double exits among the statuses that "produce a winner", so had one ever been selectable it would
|
|
91
|
+
* have stamped a winningSide on an outcome that must carry none.
|
|
92
|
+
*/
|
|
93
|
+
export declare function winnerFromExitingSide(matchUpStatus: string | undefined, exitingSideNumber?: number): number | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* Whether "Neither side" is a legitimate answer for this irregular ending.
|
|
96
|
+
*/
|
|
97
|
+
export declare function supportsNeitherSide(selectedOutcome: string | undefined): boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Message shown beside a double-exit selection. A double exit is not a scoring detail — it advances
|
|
100
|
+
* nobody and propagates a walkover into the next round — so the modal says what it will do before it
|
|
101
|
+
* is submitted rather than after.
|
|
102
|
+
*/
|
|
103
|
+
export declare function doubleExitWarning(selectedOutcome: string | undefined): string;
|
|
104
|
+
export type IrregularEndingResolution = {
|
|
105
|
+
/** The status to submit. `undefined` while the selection is incomplete. */
|
|
106
|
+
matchUpStatus?: string;
|
|
107
|
+
/** Present only when a side was named. A double exit deliberately carries none. */
|
|
108
|
+
winningSide?: number;
|
|
109
|
+
/** Whether this resolution may be submitted. */
|
|
110
|
+
isValid: boolean;
|
|
111
|
+
/** True when the operator still owes a winner answer — the fail-closed state. */
|
|
112
|
+
awaitingWinner: boolean;
|
|
113
|
+
/** True when the resolution is a double exit, so callers can warn before it is submitted. */
|
|
114
|
+
isDoubleExit: boolean;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Resolve an irregular ending plus a winner selection into the status to submit.
|
|
118
|
+
*
|
|
119
|
+
* `COMPLETED` is not an irregular ending — callers keep their own completion handling and this
|
|
120
|
+
* returns an inert resolution for it.
|
|
121
|
+
*/
|
|
122
|
+
export declare function resolveIrregularEnding(params: {
|
|
123
|
+
selectedOutcome: string | undefined;
|
|
124
|
+
winnerSelection: WinnerSelection;
|
|
125
|
+
}): IrregularEndingResolution;
|
|
126
|
+
/**
|
|
127
|
+
* Apply a resolution onto a validation object in place, the shape the approaches already pass to
|
|
128
|
+
* `onScoreChange`.
|
|
129
|
+
*
|
|
130
|
+
* It also clears any `error` the score validator left behind, and the two cases that reach here are
|
|
131
|
+
* worth separating because only one of them is legitimate:
|
|
132
|
+
*
|
|
133
|
+
* - **RETIRED / DEFAULTED with a partial score.** The validator correctly reports "this is not a
|
|
134
|
+
* completed match"; the ending is what makes it a valid result anyway. Clearing the error is the
|
|
135
|
+
* right call — the validator answered a question that is no longer the one being asked.
|
|
136
|
+
* - **A walkover.** There is no score to validate, so the validator should never have run. Callers
|
|
137
|
+
* must check `carriesNoScore()` and skip validation entirely; the clear here is a backstop, not
|
|
138
|
+
* the fix. A walkover that reported `isValid: true` beside
|
|
139
|
+
* `error: 'At least one set is required'` was the symptom of exactly that missing check.
|
|
140
|
+
*/
|
|
141
|
+
export declare function applyIrregularEndingToValidation(validation: any, selectedOutcome: string | undefined, winnerSelection: WinnerSelection): IrregularEndingResolution;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a scoring policy's `matchUpStatusCodes` vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* The policy refines a matchUpStatus with caller-defined codes — a retirement becomes "Ret [inj]",
|
|
5
|
+
* a default becomes "Def [dq]". The modal renders them; it never invents them. If a tournament's
|
|
6
|
+
* policy carries no codes for the chosen status, there is nothing to show and no control is drawn.
|
|
7
|
+
* That is the designed state, not a degraded one: CA, 2026-09-20 — TMX ships no built-in default,
|
|
8
|
+
* so the reason field exists exactly where a governing body's vocabulary applies and nowhere else.
|
|
9
|
+
*
|
|
10
|
+
* No DOM, no side effects.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* One entry of a policy's code group, as the factory's fixtures ship it.
|
|
14
|
+
*
|
|
15
|
+
* CA settled the canonical shape on 2026-09-19: the OBJECT form is authoritative, because a bare
|
|
16
|
+
* string cannot carry the display text the picker exists to render.
|
|
17
|
+
*/
|
|
18
|
+
export type StatusCodeEntry = {
|
|
19
|
+
matchUpStatusCode: string;
|
|
20
|
+
matchUpStatusCodeDisplay?: string;
|
|
21
|
+
label?: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
};
|
|
24
|
+
/** A policy's groups, keyed by the matchUpStatus each refines. */
|
|
25
|
+
export type StatusCodeGroups = Record<string, StatusCodeEntry[]>;
|
|
26
|
+
/**
|
|
27
|
+
* The group key a status reads its codes from.
|
|
28
|
+
*
|
|
29
|
+
* A double exit has no group of its own — the policy files "Wo/Wo" inside WALKOVER and "Def/Def"
|
|
30
|
+
* inside DEFAULTED, because they are walkovers and defaults. Mapping them here is what keeps the
|
|
31
|
+
* double-exit choice and the code list from becoming two controls that can disagree.
|
|
32
|
+
*/
|
|
33
|
+
export declare function groupKeyForStatus(matchUpStatus: string | undefined): string | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Normalise one element of a code group, or of a matchUp's `matchUpStatusCodes`, to its code string.
|
|
36
|
+
*
|
|
37
|
+
* Three shapes are legitimate and all three occur. `matchUpStatusCodes` on a matchUp is an `any[]`
|
|
38
|
+
* shared by three tenants (policy codes, propagation provenance, and `{ code }` wrappers), and
|
|
39
|
+
* `updateMatchUpStatusCodes` in the factory rewraps every string element as `{ code }` — so a code
|
|
40
|
+
* written as `'RJ'` reads back as `{ code: 'RJ' }` once propagation has touched the matchUp. A
|
|
41
|
+
* reader that assumes a string is wrong, and it is wrong only sometimes, which is worse.
|
|
42
|
+
*
|
|
43
|
+
* Returns `undefined` for a provenance element, which carries no code at all.
|
|
44
|
+
*/
|
|
45
|
+
export declare function normalizeStatusCode(entry: unknown): string | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* The codes a policy offers for this status, or an empty list when it offers none.
|
|
48
|
+
*
|
|
49
|
+
* Entries without a `matchUpStatusCode` are dropped rather than rendered as a blank option — an
|
|
50
|
+
* unpickable row in a picker is worse than a shorter picker.
|
|
51
|
+
*/
|
|
52
|
+
export declare function codesForStatus(groups: StatusCodeGroups | undefined, matchUpStatus: string | undefined): StatusCodeEntry[];
|
|
53
|
+
/**
|
|
54
|
+
* What to show for a code: the policy's display form, falling back to its label, then to the code.
|
|
55
|
+
*
|
|
56
|
+
* The three fields are authored per code and mean different things — `matchUpStatusCodeDisplay` is
|
|
57
|
+
* the terse form an operator recognises ("Ret [inj]"), `label` the prose one ("Injury"). Never
|
|
58
|
+
* synthesise a display from the code: a policy that authored none is saying the code IS the label.
|
|
59
|
+
*/
|
|
60
|
+
export declare function statusCodeDisplay(entry: StatusCodeEntry): string;
|
|
61
|
+
/** The secondary line beside the display form, when the policy authored one that adds something. */
|
|
62
|
+
export declare function statusCodeSubtext(entry: StatusCodeEntry): string | undefined;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { StatusCodeGroups } from './logic/statusCodes';
|
|
2
|
+
import { ScoringModalLabels } from './types';
|
|
3
|
+
export type StatusCodePickerHandle = {
|
|
4
|
+
element: HTMLElement;
|
|
5
|
+
/** Re-render for a new status. Returns the code still selected, if any. */
|
|
6
|
+
update: (matchUpStatus: string | undefined) => string | undefined;
|
|
7
|
+
/** The chosen code, or undefined when none is chosen or none is offered. */
|
|
8
|
+
getSelectedCode: () => string | undefined;
|
|
9
|
+
};
|
|
10
|
+
export declare function buildStatusCodePicker(params: {
|
|
11
|
+
groups?: StatusCodeGroups;
|
|
12
|
+
labels?: ScoringModalLabels;
|
|
13
|
+
}): StatusCodePickerHandle;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Type definitions for scoring modal V2
|
|
3
|
-
*/
|
|
1
|
+
import { StatusCodeGroups } from './logic/statusCodes';
|
|
4
2
|
export type SetScore = {
|
|
5
3
|
setNumber?: number;
|
|
6
4
|
side1Score?: number;
|
|
@@ -17,6 +15,8 @@ export type ScoreOutcome = {
|
|
|
17
15
|
scoreObject?: any;
|
|
18
16
|
winningSide?: number;
|
|
19
17
|
matchUpStatus?: string;
|
|
18
|
+
/** Policy reason codes chosen for this outcome, as code strings — e.g. `['RJ']`. */
|
|
19
|
+
matchUpStatusCodes?: string[];
|
|
20
20
|
error?: string;
|
|
21
21
|
matchUpFormat?: string;
|
|
22
22
|
score?: string;
|
|
@@ -30,13 +30,19 @@ export type ScoringModalLabels = {
|
|
|
30
30
|
formatEditTitle?: string;
|
|
31
31
|
irregularEnding?: string;
|
|
32
32
|
winner?: string;
|
|
33
|
+
neitherSide?: string;
|
|
33
34
|
retired?: string;
|
|
35
|
+
abandoned?: string;
|
|
36
|
+
cancelled?: string;
|
|
37
|
+
incomplete?: string;
|
|
34
38
|
walkover?: string;
|
|
35
39
|
defaulted?: string;
|
|
36
40
|
validScore?: string;
|
|
37
41
|
scoreIncomplete?: string;
|
|
38
42
|
invalidScore?: string;
|
|
39
43
|
scoreTips?: string;
|
|
44
|
+
reasonCode?: string;
|
|
45
|
+
noReasonCode?: string;
|
|
40
46
|
setScores?: string;
|
|
41
47
|
tiebreaks?: string;
|
|
42
48
|
matchTiebreaks?: string;
|
|
@@ -50,6 +56,13 @@ export type ScoringModalParams = {
|
|
|
50
56
|
callback: (outcome: any) => void;
|
|
51
57
|
onClose?: () => void;
|
|
52
58
|
labels?: ScoringModalLabels;
|
|
59
|
+
/**
|
|
60
|
+
* The scoring policy's `matchUpStatusCodes` groups, resolved by the CALLER — components never
|
|
61
|
+
* reaches into factory fixtures or the engine. Omit it (or pass groups with no entries for the
|
|
62
|
+
* chosen status) and no reason control is drawn, which is the designed state for a tournament
|
|
63
|
+
* with no governing-body policy attached.
|
|
64
|
+
*/
|
|
65
|
+
matchUpStatusCodes?: StatusCodeGroups;
|
|
53
66
|
};
|
|
54
67
|
export type ScoreChangeHandler = (outcome: ScoreOutcome) => void;
|
|
55
68
|
export type RenderScoreEntryParams = {
|