@johnmorrisdotca/kyogijo-stadiums 0.5.0 → 0.6.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.
Files changed (193) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/NOTICE.md +179 -3
  3. package/README.md +43 -11
  4. package/dist/arenas-entry.d.ts +4 -0
  5. package/dist/arenas-entry.js +3 -0
  6. package/dist/arenas.d.ts +125 -0
  7. package/dist/arenas.data.d.ts +2 -0
  8. package/dist/arenas.data.js +3763 -0
  9. package/dist/arenas.js +72 -0
  10. package/dist/atlas-entry.d.ts +3 -1
  11. package/dist/atlas-entry.js +3 -2
  12. package/dist/atlas.d.ts +37 -19
  13. package/dist/atlas.js +92 -7
  14. package/dist/background.js +2 -1
  15. package/dist/budo/describe.d.ts +13 -0
  16. package/dist/budo/describe.js +120 -0
  17. package/dist/budo/dojo.api.d.ts +64 -0
  18. package/dist/budo/dojo.api.js +46 -0
  19. package/dist/budo/dojo.d.ts +72 -0
  20. package/dist/budo/dojo.js +300 -0
  21. package/dist/budo/judo.d.ts +61 -0
  22. package/dist/budo/judo.js +109 -0
  23. package/dist/budo/karate.d.ts +29 -0
  24. package/dist/budo/karate.js +80 -0
  25. package/dist/budo/kendo.d.ts +18 -0
  26. package/dist/budo/kendo.js +44 -0
  27. package/dist/budo/kimarite.d.ts +19 -0
  28. package/dist/budo/kimarite.js +33 -0
  29. package/dist/budo/kit.d.ts +16 -0
  30. package/dist/budo/kit.js +24 -0
  31. package/dist/budo/kyudo.d.ts +28 -0
  32. package/dist/budo/kyudo.js +66 -0
  33. package/dist/budo/looks.d.ts +28 -0
  34. package/dist/budo/looks.js +58 -0
  35. package/dist/budo/parts.d.ts +32 -0
  36. package/dist/budo/parts.js +10 -0
  37. package/dist/budo/plan.d.ts +64 -0
  38. package/dist/budo/plan.js +139 -0
  39. package/dist/budo/room.d.ts +133 -0
  40. package/dist/budo/room.js +111 -0
  41. package/dist/budo/sumo.d.ts +31 -0
  42. package/dist/budo/sumo.js +71 -0
  43. package/dist/budo/tatami.d.ts +55 -0
  44. package/dist/budo/tatami.js +156 -0
  45. package/dist/budo/types.d.ts +192 -0
  46. package/dist/budo/types.js +2 -0
  47. package/dist/budo-entry.d.ts +22 -0
  48. package/dist/budo-entry.js +13 -0
  49. package/dist/budo.board.d.ts +28 -0
  50. package/dist/budo.board.js +81 -0
  51. package/dist/budo.score.d.ts +18 -0
  52. package/dist/budo.score.js +52 -0
  53. package/dist/budo.style.d.ts +6 -0
  54. package/dist/budo.style.js +67 -0
  55. package/dist/charts.baseball.js +1 -1
  56. package/dist/charts.js +4 -4
  57. package/dist/charts.soccer.js +3 -3
  58. package/dist/charts.types.d.ts +10 -0
  59. package/dist/colour.d.ts +4 -4
  60. package/dist/colour.js +4 -4
  61. package/dist/crowd.d.ts +11 -1
  62. package/dist/crowd.draw.js +15 -9
  63. package/dist/crowd.js +17 -2
  64. package/dist/draw.js +156 -31
  65. package/dist/element-entry.d.ts +2 -0
  66. package/dist/element-entry.js +1 -0
  67. package/dist/element.d.ts +2 -2
  68. package/dist/element.js +2 -1
  69. package/dist/emblems-source.d.ts +50 -0
  70. package/dist/emblems-source.js +56 -0
  71. package/dist/eras.d.ts +3 -1
  72. package/dist/eras.js +8 -3
  73. package/dist/features.js +73 -0
  74. package/dist/field.data.d.ts +16 -0
  75. package/dist/field.data.js +50 -0
  76. package/dist/fields.kit.d.ts +2 -2
  77. package/dist/fields.types.d.ts +9 -3
  78. package/dist/formations.js +18 -7
  79. package/dist/formations.sports.d.ts +8 -0
  80. package/dist/formations.sports.js +251 -0
  81. package/dist/frames.providers.js +9 -1
  82. package/dist/halls.d.ts +65 -0
  83. package/dist/halls.js +122 -0
  84. package/dist/heat.js +17 -10
  85. package/dist/heat.zones.d.ts +4 -2
  86. package/dist/heat.zones.js +55 -3
  87. package/dist/homeside.js +7 -0
  88. package/dist/index.d.ts +15 -3
  89. package/dist/index.js +8 -1
  90. package/dist/layers-entry.d.ts +2 -1
  91. package/dist/layers-entry.js +1 -0
  92. package/dist/layers.d.ts +2 -1
  93. package/dist/layers.env.d.ts +10 -1
  94. package/dist/layers.env.js +33 -3
  95. package/dist/layers.js +30 -11
  96. package/dist/layers.types.d.ts +9 -2
  97. package/dist/leagues/budo.d.ts +196 -0
  98. package/dist/leagues/budo.js +105 -0
  99. package/dist/leagues/hockey.js +41 -19
  100. package/dist/leagues/sources.js +21 -0
  101. package/dist/leagues/types.d.ts +1 -1
  102. package/dist/leagues.d.ts +4 -1
  103. package/dist/leagues.js +102 -2
  104. package/dist/lineup.codes.d.ts +23 -0
  105. package/dist/lineup.codes.js +122 -0
  106. package/dist/logo.d.ts +25 -0
  107. package/dist/logo.js +47 -0
  108. package/dist/outline.d.ts +1 -1
  109. package/dist/outline.js +17 -3
  110. package/dist/pageant.js +7 -3
  111. package/dist/pointer-entry.d.ts +2 -0
  112. package/dist/pointer-entry.js +2 -0
  113. package/dist/pointer.d.ts +113 -0
  114. package/dist/pointer.js +151 -0
  115. package/dist/pool.d.ts +7 -7
  116. package/dist/pool.figures.d.ts +57 -55
  117. package/dist/pool.figures.js +59 -55
  118. package/dist/pool.js +7 -7
  119. package/dist/pool.labels.js +2 -1
  120. package/dist/ranges.names.js +6 -0
  121. package/dist/road.draw.js +3 -2
  122. package/dist/road.js +2 -1
  123. package/dist/road.types.d.ts +3 -0
  124. package/dist/rules.d.ts +138 -0
  125. package/dist/rules.js +511 -0
  126. package/dist/rules.types.d.ts +4 -0
  127. package/dist/rules.types.js +64 -0
  128. package/dist/score.d.ts +3 -0
  129. package/dist/score.js +23 -1
  130. package/dist/situation.d.ts +135 -0
  131. package/dist/situation.draw.d.ts +34 -0
  132. package/dist/situation.draw.js +202 -0
  133. package/dist/situation.js +337 -0
  134. package/dist/situation.style.d.ts +2 -0
  135. package/dist/situation.style.js +45 -0
  136. package/dist/situation.types.d.ts +77 -0
  137. package/dist/situation.types.js +3 -0
  138. package/dist/situation.widgets.d.ts +39 -0
  139. package/dist/situation.widgets.js +129 -0
  140. package/dist/strings.d.ts +175 -1
  141. package/dist/strings.js +364 -20
  142. package/dist/style.js +9 -2
  143. package/dist/surfaces/artistic.d.ts +3 -3
  144. package/dist/surfaces/artistic.js +3 -3
  145. package/dist/surfaces/baseball.js +2 -0
  146. package/dist/surfaces/basketball.js +1 -1
  147. package/dist/surfaces/budo.d.ts +6 -0
  148. package/dist/surfaces/budo.js +104 -0
  149. package/dist/surfaces/dive.d.ts +3 -3
  150. package/dist/surfaces/dive.js +3 -3
  151. package/dist/surfaces/football.js +7 -0
  152. package/dist/surfaces/hockey.js +3 -2
  153. package/dist/surfaces/polo.d.ts +4 -4
  154. package/dist/surfaces/polo.js +5 -5
  155. package/dist/surfaces/pool.d.ts +1 -1
  156. package/dist/surfaces/pool.js +3 -3
  157. package/dist/surfaces/surface.d.ts +32 -0
  158. package/dist/surfaces/venue.d.ts +1 -1
  159. package/dist/surfaces/venue.js +1 -1
  160. package/dist/target-entry.d.ts +2 -0
  161. package/dist/target-entry.js +2 -0
  162. package/dist/target.d.ts +200 -0
  163. package/dist/target.js +525 -0
  164. package/dist/team.common.d.ts +1 -1
  165. package/dist/team.common.js +3 -3
  166. package/dist/team.formation.d.ts +3 -1
  167. package/dist/team.formation.js +3 -3
  168. package/dist/team.layers.d.ts +2 -2
  169. package/dist/team.layers.js +4 -1
  170. package/dist/team.lineup.d.ts +27 -0
  171. package/dist/team.lineup.js +207 -0
  172. package/dist/team.network.js +6 -5
  173. package/dist/team.shape.js +5 -5
  174. package/dist/team.types.d.ts +40 -1
  175. package/dist/tenants.data.js +848 -0
  176. package/dist/turn-entry.d.ts +2 -2
  177. package/dist/turn-entry.js +1 -1
  178. package/dist/turn.camera.d.ts +16 -3
  179. package/dist/turn.camera.js +53 -7
  180. package/dist/turn.dom.js +11 -9
  181. package/dist/turn.kit.d.ts +2 -2
  182. package/dist/turn.style.js +4 -3
  183. package/dist/types.d.ts +43 -7
  184. package/dist/units.js +7 -0
  185. package/dist/uprights.budo.d.ts +17 -0
  186. package/dist/uprights.budo.js +87 -0
  187. package/dist/uprights.js +10 -1
  188. package/dist/uprights.pools.js +4 -4
  189. package/dist/version.d.ts +1 -1
  190. package/dist/version.js +1 -1
  191. package/dist/zones-entry.d.ts +2 -0
  192. package/dist/zones-entry.js +2 -0
  193. package/package.json +39 -3
@@ -0,0 +1,23 @@
1
+ import type { Formation, FormationPosition, FormationSport, LineupPlayer } from "./team.types.ts";
2
+ /** The positions a short code may stand for, by sport, in the order a player takes them: a code that is also an id of the formation is that place first. */
3
+ export declare const LINEUP_CODES: Readonly<Record<FormationSport, Readonly<Record<string, readonly string[]>>>>;
4
+ /** A code as the table has it: upper case, with no spaces, dots or hyphens. */
5
+ export declare const codeOf: (pos: unknown) => string;
6
+ /** The ids a player's code may stand for in a formation of a sport, best first: the id itself, then the table's. A second line's ids end in its number. */
7
+ export declare function candidates(sport: FormationSport, player: LineupPlayer, ids: ReadonlySet<string>): string[];
8
+ /** The places of a formation that a lineup may fill: the side on the surface and, beside it, the places it only lists. */
9
+ export declare const spotsOf: (f: Formation) => FormationPosition[];
10
+ /** Where each player of a lineup stands in a formation, by the index of the player; the places taken; and the players with none. */
11
+ export interface Assignment {
12
+ /** The id of the position of each player, or undefined. */
13
+ of: (string | undefined)[];
14
+ placed: number;
15
+ /** How many took the place their own code names, which settles a tie between formations. */
16
+ exact: number;
17
+ }
18
+ /** Players to places. Those whose code is a place's id take it first, then the others take the first free place their code may stand for; a volleyball team turns by `rotation`. */
19
+ export declare function assign(f: Formation, players: readonly LineupPlayer[], rotation?: number): Assignment;
20
+ /** The formations that may hold a lineup on a surface, in table order. */
21
+ export declare const formationsFor: (sports: readonly FormationSport[]) => Formation[];
22
+ /** The formation that places the most of the players: the first of the table on a tie. */
23
+ export declare function bestFormation(candidatesList: readonly Formation[], players: readonly LineupPlayer[], rotation?: number): Formation | undefined;
@@ -0,0 +1,122 @@
1
+ // How a lineup finds a player's place: the short codes a team sheet uses (`CB`, `D`, `OF`, `PR`, the scorer's numbers 1 to 9) are matched to the ids of a formation's positions,
2
+ // and each player takes the first free place of the codes that fit. Written for this package; the codes are the conventional ones of each sport and are listed in docs/team-shape.md.
3
+ import { FORMATIONS } from "./formations.js";
4
+ /** The positions a short code may stand for, by sport, in the order a player takes them: a code that is also an id of the formation is that place first. */
5
+ export const LINEUP_CODES = Object.freeze({
6
+ soccer: {
7
+ GOALKEEPER: ["GK"], G: ["GK"],
8
+ CB: ["CB", "RCB", "LCB"], D: ["CB", "RCB", "LCB"], DF: ["CB", "RCB", "LCB"],
9
+ RB: ["RB", "RWB"], LB: ["LB", "LWB"], RWB: ["RWB", "RB"], LWB: ["LWB", "LB"],
10
+ DM: ["CDM", "RDM", "LDM"], CDM: ["CDM", "RDM", "LDM"], RDM: ["RDM", "CDM"], LDM: ["LDM", "CDM"],
11
+ CM: ["CM", "RCM", "LCM"], M: ["CM", "RCM", "LCM"], RCM: ["RCM", "CM"], LCM: ["LCM", "CM"],
12
+ AM: ["CAM", "RAM", "LAM"], CAM: ["CAM", "RAM", "LAM"], RAM: ["RAM", "CAM"], LAM: ["LAM", "CAM"],
13
+ RM: ["RM", "RW"], LM: ["LM", "LW"], RW: ["RW", "RM"], LW: ["LW", "LM"],
14
+ ST: ["ST", "RS", "LS"], CF: ["ST", "RS", "LS"], S: ["ST", "RS", "LS"], FW: ["ST", "RS", "LS", "RW", "LW"], RS: ["RS", "ST"], LS: ["LS", "ST"],
15
+ },
16
+ nhl: {
17
+ GOALIE: ["G"], GK: ["G"], D: ["LD", "RD"], W: ["LW", "RW"], F: ["C", "LW", "RW"],
18
+ G2: ["G"],
19
+ },
20
+ nba: { G: ["PG", "SG"], F: ["SF", "PF"], GF: ["SG", "SF"], FC: ["PF", "C"] },
21
+ nfl: {
22
+ WR: ["WR1", "WR2", "WR3"], RB: ["HB", "FB"], OG: ["LG", "RG"], OT: ["LT", "RT"], T: ["LT", "RT"], G: ["LG", "RG"],
23
+ DE: ["LDE", "RDE"], DT: ["LDT", "RDT", "NT"], NT: ["NT"], CB: ["LCB", "RCB"], S: ["FS", "SS"],
24
+ LB: ["MLB", "LLB", "RLB", "LOLB", "LILB", "RILB", "ROLB"], OLB: ["LOLB", "ROLB", "LLB", "RLB"], ILB: ["LILB", "RILB", "MLB"],
25
+ },
26
+ mlb: {
27
+ "1": ["P"], "2": ["C"], "3": ["1B"], "4": ["2B"], "5": ["3B"], "6": ["SS"], "7": ["LF"], "8": ["CF"], "9": ["RF"],
28
+ PITCHER: ["P"], CATCHER: ["C"], OF: ["LF", "CF", "RF"], IF: ["1B", "2B", "SS", "3B"], DESIGNATEDHITTER: ["DH"],
29
+ },
30
+ rugby: {
31
+ LHP: ["1"], PR: ["1", "3"], HK: ["2"], HO: ["2"], THP: ["3"], LK: ["4", "5"], LO: ["4", "5"], L: ["4", "5"], FL: ["6", "7"], BF: ["6"], OF: ["7"], N8: ["8"], NO8: ["8"],
32
+ SH: ["9"], FH: ["10"], SO: ["10"], IC: ["12"], OC: ["13"], CTB: ["12", "13"], C: ["12", "13"], W: ["11", "14"], WG: ["11", "14"], WTB: ["11", "14"], LW: ["11"], RW: ["14"], FB: ["15"],
33
+ },
34
+ afl: {
35
+ BP: ["BPL", "BPR"], HB: ["HBFL", "CHB", "HBFR"], HBF: ["HBFL", "HBFR"], W: ["WL", "WR"], HF: ["HFFL", "CHF", "HFFR"], HFF: ["HFFL", "HFFR"], FP: ["FPL", "FPR"],
36
+ R: ["RUC"], RK: ["RUC"], RUCK: ["RUC"], RUCKROVER: ["RR"], ROVER: ["ROV"], RO: ["ROV"],
37
+ },
38
+ cricket: {
39
+ BOWLER: ["BWL"], KEEPER: ["WK"], SL: ["SL1", "SL2", "SL3"], SLIP: ["SL1", "SL2", "SL3"], GULLY: ["GU"], POINT: ["PT"], COVER: ["CV"], MIDOFF: ["MO"], MIDON: ["MN"],
40
+ MIDWICKET: ["MW"], SQUARELEG: ["SQ"], FINELEG: ["FL"], THIRDMAN: ["TM"], LONGOFF: ["LO"], LONGON: ["LN"], DEEPCOVER: ["DC"], DEEPMIDWICKET: ["DM"],
41
+ },
42
+ volleyball: { "1": ["P1"], "2": ["P2"], "3": ["P3"], "4": ["P4"], "5": ["P5"], "6": ["P6"] },
43
+ handball: { G: ["GK"], P: ["PV"], PIVOT: ["PV"], LINE: ["PV"], W: ["LW", "RW"], B: ["LB", "CB", "RB"] },
44
+ waterpolo: { G: ["GK"], W: ["LW", "RW"], D: ["LD", "RD"], FLAT: ["LD", "RD"], HS: ["CF"], HOLE: ["CF"], H: ["CF"], PT: ["CB"], POINT: ["CB"] },
45
+ fieldhockey: {
46
+ G: ["GK"], FB: ["RB", "LB"], CB: ["CB", "RCB", "LCB"], D: ["CB", "RCB", "LCB"], M: ["CM", "RCM", "LCM"], CM: ["CM", "RCM", "LCM"], RM: ["RM", "RW"], LM: ["LM", "LW"],
47
+ F: ["CF", "RW", "LW"], ST: ["CF"], RW: ["RW", "RM"], LW: ["LW", "LM"],
48
+ },
49
+ netball: {},
50
+ });
51
+ /** A code as the table has it: upper case, with no spaces, dots or hyphens. */
52
+ export const codeOf = (pos) => String(typeof pos === "number" || typeof pos === "string" ? pos : "").trim().toUpperCase().replace(/[\s.\-_]+/g, "");
53
+ /** The ids a player's code may stand for in a formation of a sport, best first: the id itself, then the table's. A second line's ids end in its number. */
54
+ export function candidates(sport, player, ids) {
55
+ const own = typeof player.pos === "string" ? player.pos.trim() : String(player.pos);
56
+ const code = codeOf(player.pos);
57
+ const line = typeof player.line === "number" && Number.isInteger(player.line) && player.line > 1 ? String(player.line) : "";
58
+ const listed = Object.prototype.hasOwnProperty.call(LINEUP_CODES[sport], code) ? LINEUP_CODES[sport][code] : [];
59
+ const base = [own, code, ...listed];
60
+ const wanted = line === "" ? base : base.map((id) => `${id}${line}`);
61
+ return [...new Set(wanted)].filter((id) => ids.has(id));
62
+ }
63
+ /** The places of a formation that a lineup may fill: the side on the surface and, beside it, the places it only lists. */
64
+ export const spotsOf = (f) => [...f.positions, ...(f.extra ?? [])];
65
+ /** Players to places. Those whose code is a place's id take it first, then the others take the first free place their code may stand for; a volleyball team turns by `rotation`. */
66
+ export function assign(f, players, rotation = 1) {
67
+ const ids = new Set(spotsOf(f).map((p) => p.id));
68
+ const turned = f.sport === "volleyball" ? players.map((p) => zoneAfter(p, rotation)) : players;
69
+ const of = new Array(turned.length).fill(undefined);
70
+ const taken = new Set();
71
+ const lists = turned.map((p) => (typeof p === "object" && p !== null ? candidates(f.sport, p, ids) : []));
72
+ // The exact codes first, so that a CB on the sheet cannot take the place an RCB has asked for by name.
73
+ let exact = 0;
74
+ turned.forEach((p, i) => {
75
+ if (typeof p !== "object" || p === null)
76
+ return;
77
+ const line = typeof p.line === "number" && p.line > 1 ? String(p.line) : "";
78
+ const own = lists[i].find((id) => id === `${typeof p.pos === "string" ? p.pos.trim() : String(p.pos)}${line}` || id === `${codeOf(p.pos)}${line}`);
79
+ if (own !== undefined && !taken.has(own)) {
80
+ of[i] = own;
81
+ taken.add(own);
82
+ exact += 1;
83
+ }
84
+ });
85
+ turned.forEach((_, i) => {
86
+ if (of[i] !== undefined)
87
+ return;
88
+ const free = lists[i].find((id) => !taken.has(id));
89
+ if (free !== undefined) {
90
+ of[i] = free;
91
+ taken.add(free);
92
+ }
93
+ });
94
+ return { of, placed: of.filter((id) => id !== undefined).length, exact };
95
+ }
96
+ /** A volleyball player after the rotations: each moves one place clockwise, from 2 to 1, 1 to 6, 6 to 5, 5 to 4, 4 to 3 and 3 to 2. */
97
+ function zoneAfter(p, rotation) {
98
+ if (typeof p !== "object" || p === null)
99
+ return p;
100
+ const code = codeOf(p.pos).replace(/^P/, "");
101
+ if (!/^[1-6]$/.test(code))
102
+ return p;
103
+ const turns = Number.isInteger(rotation) && rotation >= 1 ? (rotation - 1) % 6 : 0;
104
+ const zone = ((((Number(code) - 1 - turns) % 6) + 6) % 6) + 1;
105
+ return { ...p, pos: `P${zone}` };
106
+ }
107
+ /** The formations that may hold a lineup on a surface, in table order. */
108
+ export const formationsFor = (sports) => Object.values(FORMATIONS).filter((f) => sports.includes(f.sport));
109
+ /** The formation that places the most of the players: the first of the table on a tie. */
110
+ export function bestFormation(candidatesList, players, rotation = 1) {
111
+ let best;
112
+ let score = -1;
113
+ for (const f of candidatesList) {
114
+ const a = assign(f, players, rotation);
115
+ const n = a.placed * 1000 + a.exact;
116
+ if (n > score) {
117
+ best = f;
118
+ score = n;
119
+ }
120
+ }
121
+ return best;
122
+ }
package/dist/logo.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ /** What a caller says about the team an emblem stands for: its colours and letters, so a crest id takes them; and the theme of the drawing it goes in. */
2
+ export interface EmblemContext {
3
+ color?: string;
4
+ accent?: string;
5
+ initials?: string;
6
+ theme?: "inherit" | "auto" | "light" | "dark";
7
+ }
8
+ /**
9
+ * The SVG document for a value that names an emblem (`"trophy:cup"`, `"crest:shield"`, `"sport:nhl/line"`), or `undefined` when it does not or when no renderer is plugged in. Drawn for an image: the theme is `auto` unless
10
+ * the context says otherwise. A crest takes the context's colours and letters.
11
+ */
12
+ export declare function emblemDocument(value: unknown, context?: EmblemContext): string | undefined;
13
+ /**
14
+ * A logo as it may be written into an image's `href`, or `undefined`. An `https:` or `http:` address, a relative path, an image `data:` address, an SVG document as text (written as an image, which cannot
15
+ * run scripts), or, with a renderer of emblems plugged in, the id of an emblem (`"trophy:cup"`, `"medal:gold"`, `"sport:nhl"`, `"crest:shield"`, a trophy or award by its short name such as `"cup"`, with `/line`
16
+ * for the outline style), drawn for an image with the outline in dark or pale ink by the device's setting. The second argument gives a crest the team's colours and letters. With a renderer, a name that is
17
+ * an emblem's id is the emblem, not a file of that name: write `./cup` for the file. Without one, an id is an address like any other, and `"trophy:cup"` is refused.
18
+ * @example
19
+ * ```ts
20
+ * import { safeLogo } from "@johnmorrisdotca/kyogijo-stadiums";
21
+ * console.log(safeLogo("https://example.com/gulls.svg"), safeLogo("javascript:alert(1)"));
22
+ * // https://example.com/gulls.svg undefined
23
+ * ```
24
+ */
25
+ export declare function safeLogo(value: unknown, context?: EmblemContext): string | undefined;
package/dist/logo.js ADDED
@@ -0,0 +1,47 @@
1
+ // The one path every logo, flag, emblem and mark takes into a drawing: an address, an image `data:` address, an SVG document as text, or, when a renderer of ready-made emblems is plugged in
2
+ // (`useEmblems`, or the `emblems` option), the id of one (`"trophy:cup"`, `"crest:shield"`, `"sport:hockey/line"`). The result is an address that may be written into an image's `href`, or `undefined`.
3
+ import { safePicture } from "./colour.js";
4
+ import { GROUPED_ID, emblemRenderer, renderedEmblem } from "./emblems-source.js";
5
+ let told = false;
6
+ /** Once, in development: an id with its group was given and nothing is plugged in to draw it. */
7
+ function hint(value) {
8
+ if (told || typeof value !== "string" || !GROUPED_ID.test(value.trim()))
9
+ return;
10
+ const process = globalThis.process;
11
+ if (process?.env?.NODE_ENV === "production")
12
+ return;
13
+ told = true;
14
+ console.warn(`[kyogijo-stadiums] "${value.trim().slice(0, 40)}" is the id of an emblem, but no emblem renderer is plugged in, so it was ignored. Plug in Dougu Sports once: import { renderEmblem } from "@johnmorrisdotca/dougu-sports/emblems"; useEmblems(renderEmblem); (or pass emblems: renderEmblem). See https://johnmorrisdotca.github.io/kyogijo-stadiums/docs/emblems.html`);
15
+ }
16
+ /**
17
+ * The SVG document for a value that names an emblem (`"trophy:cup"`, `"crest:shield"`, `"sport:nhl/line"`), or `undefined` when it does not or when no renderer is plugged in. Drawn for an image: the theme is `auto` unless
18
+ * the context says otherwise. A crest takes the context's colours and letters.
19
+ */
20
+ export function emblemDocument(value, context) {
21
+ const crest = typeof value === "string" && value.trim().startsWith("crest:");
22
+ return renderedEmblem(value, {
23
+ theme: context?.theme ?? "auto",
24
+ ...(crest ? { tint: context?.color, accent: context?.accent, initials: context?.initials } : {}),
25
+ });
26
+ }
27
+ /**
28
+ * A logo as it may be written into an image's `href`, or `undefined`. An `https:` or `http:` address, a relative path, an image `data:` address, an SVG document as text (written as an image, which cannot
29
+ * run scripts), or, with a renderer of emblems plugged in, the id of an emblem (`"trophy:cup"`, `"medal:gold"`, `"sport:nhl"`, `"crest:shield"`, a trophy or award by its short name such as `"cup"`, with `/line`
30
+ * for the outline style), drawn for an image with the outline in dark or pale ink by the device's setting. The second argument gives a crest the team's colours and letters. With a renderer, a name that is
31
+ * an emblem's id is the emblem, not a file of that name: write `./cup` for the file. Without one, an id is an address like any other, and `"trophy:cup"` is refused.
32
+ * @example
33
+ * ```ts
34
+ * import { safeLogo } from "@johnmorrisdotca/kyogijo-stadiums";
35
+ * console.log(safeLogo("https://example.com/gulls.svg"), safeLogo("javascript:alert(1)"));
36
+ * // https://example.com/gulls.svg undefined
37
+ * ```
38
+ */
39
+ export function safeLogo(value, context) {
40
+ const emblem = emblemDocument(value, context);
41
+ if (emblem !== undefined)
42
+ return safePicture(emblem);
43
+ const picture = safePicture(value);
44
+ if (picture === undefined && emblemRenderer() === undefined)
45
+ hint(value);
46
+ return picture;
47
+ }
package/dist/outline.d.ts CHANGED
@@ -22,4 +22,4 @@ export declare const frameSizeOf: (o: Pick<SurfaceOptions, "sport" | "league">,
22
22
  unit: "ft";
23
23
  } | undefined;
24
24
  /** The outline of the surface the options draw. */
25
- export declare function outlineOf(o: Pick<SurfaceOptions, "sport" | "park" | "fence" | "units" | "pitch" | "league" | "year" | "sheet" | "net"> & Partial<Pick<SurfaceOptions, "field" | "detail" | "pool" | "athletics" | "skating">>): Outline;
25
+ export declare function outlineOf(o: Pick<SurfaceOptions, "sport" | "park" | "fence" | "units" | "pitch" | "league" | "year" | "sheet" | "net"> & Partial<Pick<SurfaceOptions, "field" | "detail" | "pool" | "athletics" | "skating" | "budo" | "dojo" | "arena">>): Outline;
package/dist/outline.js CHANGED
@@ -2,6 +2,7 @@
2
2
  // a ballpark's x is across the field and its y minus the distance out from home plate): where a data layer is cut, and the size
3
3
  // its marks follow.
4
4
  import { FEET_PER_METRE, FOOTBALL, NBA, NHL, footballLength, surfaceSize } from "./geometry.js";
5
+ import { arenaById, arenaCourtIn, arenaRinkIn } from "./arenas.js";
5
6
  import { chooseLeague, courtOf, diamondOf, fieldOf, rinkOf } from "./leagues.js";
6
7
  import { diamondFence } from "./leagues/baseball.js";
7
8
  import { fenceFor, parkById } from "./parks.js";
@@ -17,6 +18,7 @@ import { sheetFor } from "./sheet.js";
17
18
  import { isPoolSport, poolBox } from "./pool.js";
18
19
  import { athleticsPlan } from "./athletics/plan.js";
19
20
  import { skatingPlan } from "./skating/plan.js";
21
+ import { budoPlan, isBudoSport } from "./budo/plan.js";
20
22
  const inBox = (box, x, y) => x >= box.x && x <= box.x + box.w && y >= box.y && y <= box.y + box.h;
21
23
  const rect = (w, h) => {
22
24
  const box = { x: 0, y: 0, w, h };
@@ -34,7 +36,7 @@ function inPolygon(points, x, y) {
34
36
  return inside;
35
37
  }
36
38
  /** The size a provider's frame is fitted to: the drawn surface's own `box`, for a soccer pitch, a curling sheet, a net sport's court and a league's surface; none for a sport's own fixed one. */
37
- export const frameSizeOf = (o, box) => o.sport === "soccer" || o.sport === "curling" || isNetSport(o.sport) || isFieldSport(o.sport) || isPoolSport(o.sport) || o.sport === "athletics" || o.sport === "skating" || chooseLeague(o).league !== undefined ? { length: box.w, width: box.h, unit: "ft" } : undefined;
39
+ export const frameSizeOf = (o, box) => o.sport === "soccer" || o.sport === "curling" || isNetSport(o.sport) || isFieldSport(o.sport) || isPoolSport(o.sport) || o.sport === "athletics" || o.sport === "skating" || isBudoSport(o.sport) || chooseLeague(o).league !== undefined ? { length: box.w, width: box.h, unit: "ft" } : undefined;
38
40
  /** The outline of the surface the options draw. */
39
41
  export function outlineOf(o) {
40
42
  const { sport, league } = chooseLeague(o);
@@ -50,7 +52,8 @@ export function outlineOf(o) {
50
52
  }
51
53
  case "nhl": {
52
54
  const rink = league?.family === "hockey" ? rinkOf(league, o.year) : undefined;
53
- const [length, width, r] = rink === undefined ? [NHL.length, NHL.width, NHL.cornerRadius] : [rink.length, rink.width, rink.cornerRadius];
55
+ const sized = league === undefined ? arenaRinkIn(arenaById(o.arena), o.year) : undefined;
56
+ const [length, width, r] = rink !== undefined ? [rink.length, rink.width, rink.cornerRadius] : sized !== undefined ? [sized.length, sized.width, NHL.cornerRadius] : [NHL.length, NHL.width, NHL.cornerRadius];
54
57
  const box = { x: 0, y: 0, w: length, h: width };
55
58
  // A rectangle with quarter-circle corners: outside only when a point is in a corner's square but beyond its arc.
56
59
  const contains = (x, y) => {
@@ -67,7 +70,8 @@ export function outlineOf(o) {
67
70
  const court = courtOf(league, o.year);
68
71
  return rect(court.length, court.width);
69
72
  }
70
- return rect(NBA.length, NBA.width);
73
+ const sized = league === undefined ? arenaCourtIn(arenaById(o.arena), o.year) : undefined;
74
+ return sized === undefined ? rect(NBA.length, NBA.width) : rect(sized.length, sized.width);
71
75
  }
72
76
  case "mlb": {
73
77
  const generic = league?.family === "baseball" ? diamondFence(diamondOf(league, o.year)) : undefined;
@@ -126,6 +130,16 @@ export function outlineOf(o) {
126
130
  const k = 1 / 0.3048;
127
131
  return rect((plan.box.x1 - plan.box.x0) * k, (plan.box.y1 - plan.box.y0) * k);
128
132
  }
133
+ case "judo":
134
+ case "karate":
135
+ case "kendo":
136
+ case "sumo":
137
+ case "kyudo":
138
+ case "dojo": {
139
+ const plan = budoPlan({ ...o, sport, detail: o.detail });
140
+ const k = 1 / 0.3048;
141
+ return rect((plan.box.x1 - plan.box.x0) * k, (plan.box.y1 - plan.box.y0) * k);
142
+ }
129
143
  case "athletics": {
130
144
  const plan = athleticsPlan(o);
131
145
  const k = 1 / 0.3048;
package/dist/pageant.js CHANGED
@@ -2,7 +2,9 @@
2
2
  // (a `carpet`: a rectangle at the flag's own ratio with a border; a `stage`: a crest on a large round disc with a ring), and an emblem or a trophy in the centre circle. This file
3
3
  // decides what is asked (`pageantOf`) and where each picture goes (`pageantLayout`); `pageant.draw.ts` draws them. Written for this package; the idea is a broadcast's, and no
4
4
  // flag, crest or emblem is drawn or supplied here.
5
- import { safeColour, safeLogo } from "./colour.js";
5
+ import { safeColour } from "./colour.js";
6
+ import { renderedEmblem } from "./emblems-source.js";
7
+ import { safeLogo } from "./logo.js";
6
8
  import { isNetSport } from "./court.js";
7
9
  /** The border band of a carpet, as a share of its shorter side: the least, the most and the usual. */
8
10
  export const PAGEANT_BORDER = { min: 0.02, max: 0.15, usual: 0.06 };
@@ -38,7 +40,7 @@ export function pageantOf(o) {
38
40
  return undefined;
39
41
  const p = given === true ? {} : typeof given === "string" ? { style: given } : typeof given === "object" ? given : {};
40
42
  const style = oneOf(p.style, ["carpet", "stage"]) ?? "carpet";
41
- const href = safeLogo(p.emblem);
43
+ const href = safeLogo(p.emblem, { theme: o.theme === "light" || o.theme === "dark" ? o.theme : undefined });
42
44
  const asked = oneOf(p.score, ["auto", "centre", "strip"]) ?? "auto";
43
45
  // The emblem takes the middle, and a board is as wide as the middle: both send the score to the strip, unless the options say otherwise.
44
46
  const score = asked === "auto" && (href !== undefined || BOARDS(o.sport)) ? "strip" : asked;
@@ -75,7 +77,9 @@ export function ratioOf(team) {
75
77
  return clamp(team.logoRatio);
76
78
  const logo = typeof team.logo === "string" ? team.logo.trim() : "";
77
79
  let found;
78
- if (/^(?:<\?xml|<svg)\b/i.test(logo))
80
+ if (renderedEmblem(logo) !== undefined)
81
+ found = 1;
82
+ else if (/^(?:<\?xml|<svg)\b/i.test(logo))
79
83
  found = svgRatio(logo);
80
84
  else if (/^data:image\/svg\+xml/i.test(logo)) {
81
85
  const comma = logo.indexOf(",");
@@ -0,0 +1,2 @@
1
+ export { clientPointToField, clientPointToFrames, fieldPointToClient } from "./pointer.ts";
2
+ export type { ElementLike, FieldPoint, MatrixLike, PointerOptions, SvgLike } from "./pointer.ts";
@@ -0,0 +1,2 @@
1
+ // A click on a drawing as a place on the field: the pointer in a provider's numbers with the zone, and the way back.
2
+ export { clientPointToField, clientPointToFrames, fieldPointToClient } from "./pointer.js";
@@ -0,0 +1,113 @@
1
+ import { type Attacking, type Frame } from "./frames.ts";
2
+ import { type PlaceLike, type ZoneOptions } from "./rules.ts";
3
+ import { type Unit } from "./units.ts";
4
+ import type { Language } from "./types.ts";
5
+ /** The six numbers of a matrix, as an SVG's `DOMMatrix` has them. */
6
+ export interface MatrixLike {
7
+ a: number;
8
+ b: number;
9
+ c: number;
10
+ d: number;
11
+ e: number;
12
+ f: number;
13
+ }
14
+ /** What the functions need of an element of the drawing: its attributes, and for the surface group its matrix to the page. */
15
+ export interface ElementLike {
16
+ getAttribute(name: string): string | null;
17
+ getScreenCTM?(): MatrixLike | null;
18
+ }
19
+ /** What the functions need of the drawing's `<svg>`: to find the surface group in it. A real `SVGSVGElement` is one. */
20
+ export interface SvgLike extends ElementLike {
21
+ querySelector(selectors: string): unknown;
22
+ closest?(selectors: string): unknown;
23
+ }
24
+ /** What the pointer functions take besides the position. */
25
+ export interface PointerOptions {
26
+ /** The frame the answer is written in: the name of a frame of the drawing's sport (`nhl-api`, `opta`, `statsbomb`, `mlbam`), a frame, or `"surface"` for the drawing's own numbers (feet, or `units`). `"surface"` when missing. */
27
+ provider?: Frame | string;
28
+ /** The unit of the `"surface"` numbers: feet when missing. */
29
+ units?: Unit;
30
+ /** The end the provider's team attacks, as in `toSurface`: `left` says the provider's numbers are of a team that attacks the left end of the surface as it is drawn wide. */
31
+ attacking?: Attacking;
32
+ /** The language of the zone names. */
33
+ language?: Language;
34
+ /** The size of the surface when the frame is a variable one, in place of the drawing's own. */
35
+ size?: ZoneOptions["size"];
36
+ }
37
+ /** Where the pointer is on the field. */
38
+ export interface FieldPoint {
39
+ /** The place in the provider's frame (feet in the drawing's wide frame for `"surface"`). */
40
+ x: number;
41
+ y: number;
42
+ /** The frame's name. */
43
+ provider: string;
44
+ /** The place on the surface: feet in the drawing's wide frame (x along the surface from the left, y across it from the top). */
45
+ surface: {
46
+ x: number;
47
+ y: number;
48
+ };
49
+ /** True when the pointer is on the surface, or on its edge: not on the apron, the stands or the page round it. */
50
+ inBounds: boolean;
51
+ /** The zone and the finer area, by id (`zoneAt` in the zones entry): `undefined` off the surface. */
52
+ zone: string | undefined;
53
+ subzone: string | undefined;
54
+ /** Every id that holds, with its name. */
55
+ zones: string[];
56
+ names: Record<string, string>;
57
+ }
58
+ /**
59
+ * Where a page position is on the field of a drawing, in a provider's numbers, with the zone the rules put it in. `svg` is the drawing's `<svg>` element, and
60
+ * the position is a pointer event's `clientX` and `clientY`. It holds whatever the drawing is: wide or tall, any size or padding, a range, a cropped viewBox. It answers `null` for
61
+ * a drawing with no surface to ask (a badge, a special game, or the 3D view, whose floor is laid in CSS 3D). `provider` names a frame of the drawing's sport (`nhl-api`,
62
+ * `opta`, `statsbomb`, `mlbam`...) and a frame of another sport is an error; without one, the numbers are the surface's own in feet.
63
+ * @example
64
+ * ```ts no-run
65
+ * import { clientPointToField } from "@johnmorrisdotca/kyogijo-stadiums/pointer";
66
+ * const svg = document.querySelector<SVGSVGElement>("svg.kyogijo")!;
67
+ * svg.addEventListener("click", (event) => {
68
+ * const at = clientPointToField(svg, event.clientX, event.clientY, { provider: "nhl-api" });
69
+ * if (at !== null) console.log(at.x, at.y, at.zone, at.inBounds);
70
+ * });
71
+ * ```
72
+ */
73
+ export declare function clientPointToField(svg: SvgLike | Element, clientX: number, clientY: number, options?: PointerOptions): FieldPoint | null;
74
+ /**
75
+ * The page position of a place on the field: the undoing of `clientPointToField`. The place is in a provider's numbers (or the surface's own, in feet or `units`); the answer is
76
+ * `clientX` and `clientY`, to put a marker over the drawing with. `null` for a drawing with no surface to ask.
77
+ * @example
78
+ * ```ts no-run
79
+ * import { fieldPointToClient } from "@johnmorrisdotca/kyogijo-stadiums/pointer";
80
+ * const svg = document.querySelector<SVGSVGElement>("svg.kyogijo")!;
81
+ * const at = fieldPointToClient(svg, [69, 22], { provider: "nhl-api" });
82
+ * declare const marker: HTMLElement;
83
+ * if (at !== null) marker.style.transform = `translate(${at.x}px, ${at.y}px)`;
84
+ * ```
85
+ */
86
+ export declare function fieldPointToClient(svg: SvgLike | Element, point: PlaceLike, options?: PointerOptions): {
87
+ x: number;
88
+ y: number;
89
+ } | null;
90
+ /**
91
+ * The same page position in each of several frames at once, with the zone: what a readout that shows a place in every provider asks (the demo's). `providers` defaults to every
92
+ * frame of the drawing's sport.
93
+ * @example
94
+ * ```ts no-run
95
+ * import { clientPointToFrames } from "@johnmorrisdotca/kyogijo-stadiums/pointer";
96
+ * const svg = document.querySelector<SVGSVGElement>("svg.kyogijo")!;
97
+ * svg.addEventListener("pointermove", (event) => {
98
+ * const all = clientPointToFrames(svg, event.clientX, event.clientY);
99
+ * console.log(all?.points.map((p) => `${p.provider}: ${p.x}, ${p.y}`), all?.zone);
100
+ * });
101
+ * ```
102
+ */
103
+ export declare function clientPointToFrames(svg: SvgLike | Element, clientX: number, clientY: number, options?: Omit<PointerOptions, "provider"> & {
104
+ providers?: readonly (Frame | string)[];
105
+ }): (Omit<FieldPoint, "x" | "y" | "provider"> & {
106
+ points: {
107
+ provider: string;
108
+ title: string;
109
+ unit: string;
110
+ x: number;
111
+ y: number;
112
+ }[];
113
+ }) | null;
@@ -0,0 +1,151 @@
1
+ // A click on a drawing, as a place on the field. `clientPointToField(svg, clientX, clientY, { provider })` asks the drawing where the pointer is: the browser's own
2
+ // matrix for the surface group (`getScreenCTM`) carries a page coordinate back through the CSS size, the viewBox and its padding, the preserved aspect ratio, a
3
+ // tall drawing's quarter turn and a range's crop, to the surface's wide frame in feet; the frame of the provider (frames.ts) turns that into the provider's numbers,
4
+ // and the rules engine (rules.ts) names the zone. `fieldPointToClient` is the way back. A drawing writes the box of its surface and the options that decide its rules
5
+ // on its surface group (field.data.ts), so the answer needs nothing but the SVG.
6
+ import { FRAMES, frameByName, fromSurface, toSurface } from "./frames.js";
7
+ import { frameSizeOf } from "./outline.js";
8
+ import { readField } from "./field.data.js";
9
+ import { zoneAtSurface } from "./rules.js";
10
+ import { FEET_PER, isUnit } from "./units.js";
11
+ const invert = (m) => {
12
+ const det = m.a * m.d - m.b * m.c;
13
+ if (!Number.isFinite(det) || Math.abs(det) < 1e-12)
14
+ return undefined;
15
+ return { a: m.d / det, b: -m.b / det, c: -m.c / det, d: m.a / det, e: (m.c * m.f - m.d * m.e) / det, f: (m.b * m.e - m.a * m.f) / det };
16
+ };
17
+ const apply = (m, x, y) => [m.a * x + m.c * y + m.e, m.b * x + m.d * y + m.f];
18
+ /** The surface group of a drawing and what it says of its surface, or `undefined` for one the question does not apply to: a badge, a special game, a 3D view. */
19
+ function find(svg) {
20
+ const root = svg;
21
+ // The 3D view lays the floor in CSS 3D, where the browser's matrix for an SVG element is the flat drawing's: it would answer for a place that is not under the pointer.
22
+ if (typeof root.closest === "function" && root.closest(".ky-turn") !== null)
23
+ return undefined;
24
+ const group = root.querySelector(".ky-surface-group");
25
+ if (group === null || typeof group.getAttribute !== "function")
26
+ return undefined;
27
+ const field = readField((name) => group.getAttribute(name));
28
+ const sport = root.getAttribute("data-sport");
29
+ if (field === undefined || sport === null)
30
+ return undefined;
31
+ return { sport: sport, league: root.getAttribute("data-league") ?? undefined, box: field.box, rules: field.rules, group };
32
+ }
33
+ const unitOf = (units) => (units !== undefined && isUnit(units) ? units : "ft");
34
+ function frameOf(found, provider) {
35
+ if (provider === undefined || provider === "surface")
36
+ return undefined;
37
+ const frame = typeof provider === "string" ? frameByName(provider) : provider;
38
+ if (frame.sport !== found.sport)
39
+ throw new RangeError(`The frame "${frame.name}" is for ${frame.sport}, and this drawing is of ${found.sport}: use one of ${Object.values(FRAMES).filter((f) => f.sport === found.sport).map((f) => f.name).join(", ") || "surface"}`);
40
+ return frame;
41
+ }
42
+ const sizeOf = (found, options) => {
43
+ if (options.size !== undefined)
44
+ return options.size;
45
+ return frameSizeOf({ sport: found.sport, league: found.league }, { w: found.box.w, h: found.box.h });
46
+ };
47
+ /** The place on the surface, in feet, under a page position; `undefined` where the drawing has no matrix to ask (not in the page, or hidden). */
48
+ function toLocal(found, clientX, clientY) {
49
+ const matrix = found.group.getScreenCTM?.();
50
+ if (matrix === null || matrix === undefined)
51
+ return undefined;
52
+ const back = invert(matrix);
53
+ return back === undefined ? undefined : apply(back, clientX, clientY);
54
+ }
55
+ /**
56
+ * Where a page position is on the field of a drawing, in a provider's numbers, with the zone the rules put it in. `svg` is the drawing's `<svg>` element, and
57
+ * the position is a pointer event's `clientX` and `clientY`. It holds whatever the drawing is: wide or tall, any size or padding, a range, a cropped viewBox. It answers `null` for
58
+ * a drawing with no surface to ask (a badge, a special game, or the 3D view, whose floor is laid in CSS 3D). `provider` names a frame of the drawing's sport (`nhl-api`,
59
+ * `opta`, `statsbomb`, `mlbam`...) and a frame of another sport is an error; without one, the numbers are the surface's own in feet.
60
+ * @example
61
+ * ```ts no-run
62
+ * import { clientPointToField } from "@johnmorrisdotca/kyogijo-stadiums/pointer";
63
+ * const svg = document.querySelector<SVGSVGElement>("svg.kyogijo")!;
64
+ * svg.addEventListener("click", (event) => {
65
+ * const at = clientPointToField(svg, event.clientX, event.clientY, { provider: "nhl-api" });
66
+ * if (at !== null) console.log(at.x, at.y, at.zone, at.inBounds);
67
+ * });
68
+ * ```
69
+ */
70
+ export function clientPointToField(svg, clientX, clientY, options = {}) {
71
+ if (!Number.isFinite(clientX) || !Number.isFinite(clientY))
72
+ throw new TypeError("clientX and clientY are numbers");
73
+ const found = find(svg);
74
+ if (found === undefined)
75
+ return null;
76
+ const local = toLocal(found, clientX, clientY);
77
+ if (local === undefined)
78
+ return null;
79
+ const frame = frameOf(found, options.provider);
80
+ const unit = unitOf(options.units);
81
+ let x;
82
+ let y;
83
+ if (frame === undefined)
84
+ [x, y] = [local[0] / FEET_PER[unit], local[1] / FEET_PER[unit]];
85
+ else
86
+ [x, y] = fromSurface([local], { to: frame, size: sizeOf(found, options), attacking: options.attacking })[0];
87
+ const rules = { ...found.rules, league: found.league ?? found.rules.league, language: options.language };
88
+ const zone = zoneAtSurface(found.sport, local, rules);
89
+ return { x, y, provider: frame?.name ?? "surface", surface: { x: local[0], y: local[1] }, inBounds: zone.inBounds, zone: zone.zone, subzone: zone.subzone, zones: zone.zones, names: zone.names };
90
+ }
91
+ /**
92
+ * The page position of a place on the field: the undoing of `clientPointToField`. The place is in a provider's numbers (or the surface's own, in feet or `units`); the answer is
93
+ * `clientX` and `clientY`, to put a marker over the drawing with. `null` for a drawing with no surface to ask.
94
+ * @example
95
+ * ```ts no-run
96
+ * import { fieldPointToClient } from "@johnmorrisdotca/kyogijo-stadiums/pointer";
97
+ * const svg = document.querySelector<SVGSVGElement>("svg.kyogijo")!;
98
+ * const at = fieldPointToClient(svg, [69, 22], { provider: "nhl-api" });
99
+ * declare const marker: HTMLElement;
100
+ * if (at !== null) marker.style.transform = `translate(${at.x}px, ${at.y}px)`;
101
+ * ```
102
+ */
103
+ export function fieldPointToClient(svg, point, options = {}) {
104
+ const found = find(svg);
105
+ if (found === undefined)
106
+ return null;
107
+ const matrix = found.group.getScreenCTM?.();
108
+ if (matrix === null || matrix === undefined)
109
+ return null;
110
+ const xy = Array.isArray(point) ? point : [point.x, point.y];
111
+ const [px, py] = xy;
112
+ if (typeof px !== "number" || typeof py !== "number" || !Number.isFinite(px) || !Number.isFinite(py))
113
+ throw new TypeError("a point is [x, y] or { x, y }, two finite numbers");
114
+ const frame = frameOf(found, options.provider);
115
+ const unit = unitOf(options.units);
116
+ const local = frame === undefined ? [px * FEET_PER[unit], py * FEET_PER[unit]] : toSurface([[px, py]], { from: frame, size: sizeOf(found, options), attacking: options.attacking })[0];
117
+ const [x, y] = apply(matrix, local[0], local[1]);
118
+ return { x, y };
119
+ }
120
+ /**
121
+ * The same page position in each of several frames at once, with the zone: what a readout that shows a place in every provider asks (the demo's). `providers` defaults to every
122
+ * frame of the drawing's sport.
123
+ * @example
124
+ * ```ts no-run
125
+ * import { clientPointToFrames } from "@johnmorrisdotca/kyogijo-stadiums/pointer";
126
+ * const svg = document.querySelector<SVGSVGElement>("svg.kyogijo")!;
127
+ * svg.addEventListener("pointermove", (event) => {
128
+ * const all = clientPointToFrames(svg, event.clientX, event.clientY);
129
+ * console.log(all?.points.map((p) => `${p.provider}: ${p.x}, ${p.y}`), all?.zone);
130
+ * });
131
+ * ```
132
+ */
133
+ export function clientPointToFrames(svg, clientX, clientY, options = {}) {
134
+ const found = find(svg);
135
+ if (found === undefined)
136
+ return null;
137
+ const first = clientPointToField(svg, clientX, clientY, { ...options, provider: "surface" });
138
+ if (first === null)
139
+ return null;
140
+ const list = options.providers ?? Object.values(FRAMES).filter((frame) => frame.sport === found.sport);
141
+ const points = list.map((entry) => {
142
+ const frame = typeof entry === "string" ? frameByName(entry) : entry;
143
+ const [x, y] = fromSurface([[first.surface.x, first.surface.y]], { to: frame, size: sizeOf(found, options), attacking: options.attacking })[0];
144
+ return { provider: frame.name, title: frame.title, unit: frame.unit, x, y };
145
+ });
146
+ const { x: _x, y: _y, provider: _provider, ...rest } = first;
147
+ void _x;
148
+ void _y;
149
+ void _provider;
150
+ return { ...rest, points };
151
+ }