@johnmorrisdotca/taisen-brackets 0.1.1 → 0.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/CHANGELOG.md +30 -1
- package/NOTICE.md +7 -1
- package/README.md +113 -10
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/layout-entry.d.ts +3 -1
- package/dist/layout-entry.js +2 -1
- package/dist/layout.js +11 -1
- package/dist/locales.d.ts +42 -0
- package/dist/locales.js +179 -0
- package/dist/matchup.d.ts +9 -6
- package/dist/matchup.js +54 -23
- package/dist/presets.d.ts +2 -2
- package/dist/presets.js +4 -1
- package/dist/render.d.ts +6 -6
- package/dist/render.js +82 -67
- package/dist/rounds.d.ts +2 -2
- package/dist/rounds.js +4 -4
- package/dist/table.d.ts +4 -5
- package/dist/table.js +17 -13
- package/dist/theme.js +1 -1
- package/dist/types.d.ts +62 -14
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -3
- package/dist/words.d.ts +0 -7
- package/dist/words.js +0 -33
package/dist/table.js
CHANGED
|
@@ -1,34 +1,38 @@
|
|
|
1
1
|
import { layoutBracket } from "./layout.js";
|
|
2
|
-
import {
|
|
2
|
+
import { noteLong } from "./matchup.js";
|
|
3
|
+
import { fill, voiceOf } from "./locales.js";
|
|
4
|
+
import { localizedLayout } from "./render.js";
|
|
3
5
|
import { roundLabel } from "./rounds.js";
|
|
4
6
|
import { esc } from "./text.js";
|
|
5
|
-
import { fill, wordsWith } from "./words.js";
|
|
6
7
|
/**
|
|
7
8
|
* The bracket as an HTML table, for a reader the drawing does not serve: one row for every tie, round by round, with the
|
|
8
|
-
* two sides, the score, the winner in bold
|
|
9
|
+
* two sides, the score, the winner in bold, the date where the tie has one (written as the language writes dates) and a note.
|
|
10
|
+
* It is a plain, valid `<table>` with a caption and header cells, in the language of `locale`.
|
|
9
11
|
*/
|
|
10
12
|
export function bracketTable(input, options = {}) {
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
13
|
+
const v = voiceOf(options);
|
|
14
|
+
const layout = localizedLayout("ties" in input && "rounds" in input ? input : layoutBracket(input), v);
|
|
15
|
+
const w = v.words;
|
|
16
|
+
const caption = options.caption ?? fill(layout.rounds === 1 ? w.summaryOne : w.summary, { n: v.number(layout.teams.length), r: v.number(layout.rounds) });
|
|
14
17
|
const order = layout.ties
|
|
15
18
|
.filter((t) => t.round > 0 || layout.robin.includes(t.index))
|
|
16
19
|
.sort((a, b) => Number(a.third) - Number(b.third) || a.round - b.round || (a.date < b.date ? -1 : a.date > b.date ? 1 : a.index - b.index));
|
|
20
|
+
const dated = order.some((t) => v.date(t.date) !== null);
|
|
17
21
|
const name = (t, k) => {
|
|
18
22
|
const team = t.sides[k];
|
|
19
|
-
const text = team ? esc(team.name) : esc(
|
|
23
|
+
const text = team ? esc(team.name) : esc(w.tbd);
|
|
20
24
|
return t.winner === k ? `<strong>${text}</strong>` : text;
|
|
21
25
|
};
|
|
22
|
-
const score = (t) => (t.result.score ?
|
|
26
|
+
const score = (t) => (t.result.score ? fill(w.scoreText, { a: v.number(t.result.score[0]), b: v.number(t.result.score[1]) }) : "");
|
|
23
27
|
const rows = order.map((t) => {
|
|
24
|
-
const roundText = t.third ?
|
|
25
|
-
const
|
|
26
|
-
return `<tr><th scope="row">${esc(roundText)}</th
|
|
28
|
+
const roundText = t.third ? w.thirdPlace : t.round === 0 ? "" : roundLabel(layout, t.round, options.roundNames, v);
|
|
29
|
+
const date = dated ? `<td>${esc(v.date(t.date) ?? "")}</td>` : "";
|
|
30
|
+
return `<tr><th scope="row">${esc(roundText)}</th>${date}<td>${name(t, 0)}</td><td>${esc(score(t))}</td><td>${name(t, 1)}</td><td>${esc(noteLong(t, v).join(w.noteJoin))}</td></tr>`;
|
|
27
31
|
});
|
|
28
32
|
return [
|
|
29
|
-
`<table class="${esc(options.className ?? "tb-table")}">`,
|
|
33
|
+
`<table class="${esc(options.className ?? "tb-table")}" lang="${esc(v.locale.code)}">`,
|
|
30
34
|
`<caption>${esc(caption)}</caption>`,
|
|
31
|
-
`<thead><tr><th scope="col">${esc(
|
|
35
|
+
`<thead><tr><th scope="col">${esc(w.columnRound)}</th>${dated ? `<th scope="col">${esc(w.columnDate)}</th>` : ""}<th scope="col" colspan="3">${esc(w.columnTie)}</th><th scope="col">${esc(w.columnNote)}</th></tr></thead>`,
|
|
32
36
|
`<tbody>${rows.join("")}</tbody>`,
|
|
33
37
|
"</table>",
|
|
34
38
|
].join("\n");
|
package/dist/theme.js
CHANGED
|
@@ -4,7 +4,7 @@ export const THEMES = Object.freeze({
|
|
|
4
4
|
dark: { bg: "#141614", ink: "#ece8dc", muted: "#9aa095", line: "#6c7168", box: "#1e211d", edge: "#3b403a", win: "#e0705a", gold: "#e0b43b", plain: "#3b403a" },
|
|
5
5
|
poster: { bg: "#0a1130", ink: "#f6f1e3", muted: "#a9b2d3", line: "#d9b24c", box: "#111b47", edge: "#8e7632", win: "#f0c64a", gold: "#f0c64a", plain: "#26336b" },
|
|
6
6
|
});
|
|
7
|
-
const FONT = 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
|
|
7
|
+
const FONT = 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Hiragino Sans", "Yu Gothic UI", "Noto Sans CJK JP", Meiryo, sans-serif';
|
|
8
8
|
/** The rules for one palette, each colour read from its custom property with the palette's value as the fallback. */
|
|
9
9
|
function rules(p, poster, withFont) {
|
|
10
10
|
const v = (name) => `var(--tb-${name}, ${p[name]})`;
|
package/dist/types.d.ts
CHANGED
|
@@ -21,6 +21,10 @@ export interface Team {
|
|
|
21
21
|
group?: string;
|
|
22
22
|
/** A colour for the team's initials when it has no mark, any CSS colour. */
|
|
23
23
|
color?: string;
|
|
24
|
+
/** The name in other languages, by locale code: `{ fr: "Allemagne", ja: "ドイツ" }`. The language of a drawing picks its own, else `name`. */
|
|
25
|
+
names?: Readonly<Record<string, string>>;
|
|
26
|
+
/** An ISO 3166 region code (`DE`), for `countryName` and a `teamName` that uses it. */
|
|
27
|
+
region?: string;
|
|
24
28
|
}
|
|
25
29
|
/** One game of a tie: a match of a knockout, a game of a series, a leg of an aggregate, a replay. */
|
|
26
30
|
export interface Game {
|
|
@@ -86,6 +90,8 @@ export interface Matchup {
|
|
|
86
90
|
pens?: readonly [number, number];
|
|
87
91
|
/** The game with no `games` list went to extra time. */
|
|
88
92
|
extraTime?: boolean;
|
|
93
|
+
/** The tie was settled without a game (a side withdrew): the note says so. */
|
|
94
|
+
walkover?: boolean;
|
|
89
95
|
/** This is the third-place match: it leads nowhere and is drawn apart. */
|
|
90
96
|
third?: boolean;
|
|
91
97
|
note?: string;
|
|
@@ -121,6 +127,7 @@ export interface Tie {
|
|
|
121
127
|
date: string;
|
|
122
128
|
no: number | null;
|
|
123
129
|
third: boolean;
|
|
130
|
+
walkover: boolean;
|
|
124
131
|
note: string | null;
|
|
125
132
|
}
|
|
126
133
|
/** Where the layout put a tie. */
|
|
@@ -140,6 +147,8 @@ export interface LayoutBox {
|
|
|
140
147
|
order: readonly [0 | 1, 0 | 1];
|
|
141
148
|
/** The side that had a bye (it came from no tie, though the other did), or `null`. */
|
|
142
149
|
bye: 0 | 1 | null;
|
|
150
|
+
/** Every side that skipped the earlier rounds: the `bye` side, and both sides of a tie of a later round that no tie feeds (MLS's round one, where seven teams of nine skip the wild card round). */
|
|
151
|
+
byes: readonly (0 | 1)[];
|
|
143
152
|
}
|
|
144
153
|
export interface LayoutSide {
|
|
145
154
|
/** `l` the left half (the top half on a phone), `r` the right. */
|
|
@@ -193,38 +202,73 @@ export interface LayoutOptions {
|
|
|
193
202
|
/** The order of the halves' groups, first on the left: `["West", "East"]`. Left out, a name with "west" goes left and one with "east" right. */
|
|
194
203
|
groupOrder?: readonly string[];
|
|
195
204
|
}
|
|
196
|
-
/** The words a drawing and a table use. Every one can be replaced (`words`). */
|
|
205
|
+
/** The words a drawing and a table use, in one language. Every one can be replaced (`words`), and a language can be added (`defineLocale`). Phrases take `{name}`-style holes. */
|
|
197
206
|
export interface Words {
|
|
207
|
+
/** A side not known yet. */
|
|
198
208
|
tbd: string;
|
|
209
|
+
/** The word beside the score of a side that had a bye. */
|
|
199
210
|
bye: string;
|
|
211
|
+
/** A tie decided without a game (`Matchup.walkover`). */
|
|
212
|
+
walkover: string;
|
|
213
|
+
/** The label under the champion. */
|
|
200
214
|
champion: string;
|
|
201
215
|
thirdPlace: string;
|
|
202
216
|
/** The heading over ties that lead nowhere near the final. */
|
|
203
217
|
other: string;
|
|
204
|
-
|
|
218
|
+
/** Short notes in a box: after extra time, penalties, aggregate, replay. */
|
|
205
219
|
aet: string;
|
|
206
220
|
pens: string;
|
|
207
221
|
aggregate: string;
|
|
208
222
|
replay: string;
|
|
209
|
-
/**
|
|
223
|
+
/** The same in a sentence (tooltip, table, description). */
|
|
224
|
+
aetLong: string;
|
|
225
|
+
/** `{a}`, `{b}`: the shoot-out's score. */
|
|
226
|
+
pensScore: string;
|
|
227
|
+
aggregateLong: string;
|
|
228
|
+
/** `{n}`: "in 7" (games). */
|
|
210
229
|
inGames: string;
|
|
211
|
-
/** `{n}
|
|
230
|
+
/** `{n}` games, `{w}` wins that settle it: "best of 7". */
|
|
212
231
|
bestOf: string;
|
|
213
|
-
/** `{n}`:
|
|
232
|
+
/** `{n}`: a round with no name. */
|
|
214
233
|
round: string;
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
234
|
+
/** `{n}`: "Round of 64", for a round the language has no word of its own for. */
|
|
235
|
+
roundOf: string;
|
|
236
|
+
/** `{a}`, `{b}`: an undecided tie. */
|
|
237
|
+
tieVs: string;
|
|
238
|
+
/** `{a}`, `{b}` the names, `{sa}`, `{sb}` the scores. */
|
|
239
|
+
tieScore: string;
|
|
240
|
+
/** `{a}`, `{b}`: a score as written ("3–1"). */
|
|
241
|
+
scoreText: string;
|
|
242
|
+
/** `{name}`. */
|
|
243
|
+
winnerIs: string;
|
|
244
|
+
championIs: string;
|
|
245
|
+
/** `{round}`, `{tie}`. */
|
|
246
|
+
finalLine: string;
|
|
222
247
|
/** `{n}` teams, `{r}` rounds. */
|
|
223
248
|
summary: string;
|
|
224
|
-
/** The same for a bracket of one round. */
|
|
225
249
|
summaryOne: string;
|
|
250
|
+
/** Joins the sentences of a description, and ends it. */
|
|
251
|
+
sep: string;
|
|
252
|
+
end: string;
|
|
253
|
+
/** `{notes}`: the notes after a tie in a sentence; and what joins several notes. */
|
|
254
|
+
note: string;
|
|
255
|
+
noteJoin: string;
|
|
256
|
+
columnRound: string;
|
|
257
|
+
columnTie: string;
|
|
258
|
+
columnNote: string;
|
|
259
|
+
columnDate: string;
|
|
260
|
+
/** The `<title>` of a drawing with no `title` option. */
|
|
226
261
|
title: string;
|
|
227
262
|
}
|
|
263
|
+
/** A language: its words, the names of the rounds (by their English names), and the tag `Intl` formats numbers and dates for. */
|
|
264
|
+
export interface Locale {
|
|
265
|
+
/** The code (`fr`) and the tag (`fr-CA`, `ja-JP`) used for numbers, dates and capitals. */
|
|
266
|
+
code: string;
|
|
267
|
+
tag: string;
|
|
268
|
+
words: Words;
|
|
269
|
+
/** Round names by their English name: `{ "Round of 16": "Huitièmes de finale" }`. A round with no entry keeps its name (or `roundOf`). */
|
|
270
|
+
rounds: Readonly<Record<string, string>>;
|
|
271
|
+
}
|
|
228
272
|
export type ThemeName = "auto" | "light" | "dark" | "poster" | "none";
|
|
229
273
|
export type SizeName = "wide" | "square" | "phone";
|
|
230
274
|
export interface Size {
|
|
@@ -272,8 +316,12 @@ export interface DrawOptions extends LayoutOptions {
|
|
|
272
316
|
thirdPlace?: "below" | "hide";
|
|
273
317
|
/** Highlight the side that went through. Default true. */
|
|
274
318
|
winnerHighlight?: boolean;
|
|
275
|
-
/** The
|
|
319
|
+
/** The language: `en` (the default), `fr`, `ja`, `es`, `de`, or a `Locale` you made with `defineLocale`. Sets the words, the round names, the number and date formats and the `lang` of the drawing. */
|
|
320
|
+
locale?: string | Locale;
|
|
321
|
+
/** Replace any of the language's words. */
|
|
276
322
|
words?: Partial<Words>;
|
|
323
|
+
/** The name to draw for a team in this language: `undefined` keeps `Team.names[locale]` or `Team.name`. `countryName(team.region, locale)` makes a country's name from Intl, with no list of your own. */
|
|
324
|
+
teamName?: (team: Team, locale: Locale) => string | undefined;
|
|
277
325
|
/** Prefix for the drawing's ids. Default: a hash of the bracket and the options. */
|
|
278
326
|
id?: string;
|
|
279
327
|
/** Extra class names for the root `<svg>`. */
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.2.1";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.
|
|
1
|
+
export const VERSION = "0.2.1";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johnmorrisdotca/taisen-brackets",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Taisen Brackets
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Taisen Brackets 対戦, knockout brackets as SVG: from a list of ties (games, best-of series, two legs, replays, byes, a third-place match) it works out the rounds and halves and draws the bracket with the halves meeting at the final, or left to right, wide, square or for a phone, in light, dark or a gold-on-navy poster look, with each team's logo or round flag, the champion in the centre, and an accessible table. NHL, NBA, MLB, NFL, CFL, MLS and World Cup formats by era, in English, French, Japanese, Spanish and German. Zero dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bracket",
|
|
7
7
|
"tournament",
|
|
@@ -17,8 +17,11 @@
|
|
|
17
17
|
"mlb",
|
|
18
18
|
"nfl",
|
|
19
19
|
"cfl",
|
|
20
|
+
"mls",
|
|
20
21
|
"soccer",
|
|
21
22
|
"sports",
|
|
23
|
+
"multilingual",
|
|
24
|
+
"i18n",
|
|
22
25
|
"visualization",
|
|
23
26
|
"accessible",
|
|
24
27
|
"dark-mode",
|
|
@@ -77,9 +80,10 @@
|
|
|
77
80
|
"typecheck": "tsc -p tsconfig.check.json",
|
|
78
81
|
"test": "node --test test/*.test.ts",
|
|
79
82
|
"test:package": "pnpm build && node scripts/check-package.mjs",
|
|
80
|
-
"test:readme": "pnpm build && node scripts/make-formats-doc.mjs --check && node scripts/check-readme-examples.mjs",
|
|
83
|
+
"test:readme": "pnpm build && node scripts/make-formats-doc.mjs --check && node scripts/make-languages-doc.mjs --check && node scripts/check-readme-examples.mjs",
|
|
81
84
|
"check": "pnpm lint && pnpm typecheck && pnpm test && pnpm build && node scripts/check-package.mjs",
|
|
82
85
|
"prepublishOnly": "pnpm check",
|
|
86
|
+
"family:readme": "node scripts/family-readme.mjs",
|
|
83
87
|
"dev": "pnpm build && node scripts/dev.mjs",
|
|
84
88
|
"flags": "node scripts/make-flags.mjs",
|
|
85
89
|
"site": "pnpm build && node scripts/site.mjs",
|
package/dist/words.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { Words } from "./types.ts";
|
|
2
|
-
/** The English words of a drawing and a table. Pass `words` to replace any of them. */
|
|
3
|
-
export declare const WORDS: Readonly<Words>;
|
|
4
|
-
/** The words with any replacements applied. */
|
|
5
|
-
export declare function wordsWith(over?: Partial<Words>): Words;
|
|
6
|
-
/** Fills `{n}`-style holes in a phrase. */
|
|
7
|
-
export declare function fill(phrase: string, values: Record<string, string | number>): string;
|
package/dist/words.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/** The English words of a drawing and a table. Pass `words` to replace any of them. */
|
|
2
|
-
export const WORDS = Object.freeze({
|
|
3
|
-
tbd: "TBD",
|
|
4
|
-
bye: "bye",
|
|
5
|
-
champion: "Champion",
|
|
6
|
-
thirdPlace: "Third place",
|
|
7
|
-
other: "Other ties",
|
|
8
|
-
final: "Final",
|
|
9
|
-
aet: "a.e.t.",
|
|
10
|
-
pens: "pens",
|
|
11
|
-
aggregate: "agg.",
|
|
12
|
-
replay: "replay",
|
|
13
|
-
inGames: "in {n}",
|
|
14
|
-
bestOf: "best of {n}",
|
|
15
|
-
round: "Round {n}",
|
|
16
|
-
winner: "Winner",
|
|
17
|
-
versus: "v",
|
|
18
|
-
columnRound: "Round",
|
|
19
|
-
columnTie: "Tie",
|
|
20
|
-
columnResult: "Result",
|
|
21
|
-
columnNote: "Note",
|
|
22
|
-
summary: "{n} teams, {r} rounds",
|
|
23
|
-
summaryOne: "{n} teams, 1 round",
|
|
24
|
-
title: "Knockout bracket",
|
|
25
|
-
});
|
|
26
|
-
/** The words with any replacements applied. */
|
|
27
|
-
export function wordsWith(over) {
|
|
28
|
-
return { ...WORDS, ...over };
|
|
29
|
-
}
|
|
30
|
-
/** Fills `{n}`-style holes in a phrase. */
|
|
31
|
-
export function fill(phrase, values) {
|
|
32
|
-
return phrase.replace(/\{(\w+)\}/g, (hole, key) => (key in values ? String(values[key]) : hole));
|
|
33
|
-
}
|