@johnmorrisdotca/kyogijo-stadiums 0.0.0-stage → 0.4.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.
Files changed (321) hide show
  1. package/CHANGELOG.md +129 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE.md +293 -0
  4. package/README.md +600 -2
  5. package/dist/annotate.d.ts +47 -0
  6. package/dist/annotate.js +349 -0
  7. package/dist/atlas-entry.d.ts +4 -0
  8. package/dist/atlas-entry.js +3 -0
  9. package/dist/atlas.d.ts +131 -0
  10. package/dist/atlas.js +149 -0
  11. package/dist/background.d.ts +56 -0
  12. package/dist/background.js +127 -0
  13. package/dist/ballpark.d.ts +48 -0
  14. package/dist/ballpark.js +129 -0
  15. package/dist/canvas-entry.d.ts +2 -0
  16. package/dist/canvas-entry.js +2 -0
  17. package/dist/canvas.d.ts +85 -0
  18. package/dist/canvas.js +279 -0
  19. package/dist/charts-entry.d.ts +10 -0
  20. package/dist/charts-entry.js +8 -0
  21. package/dist/charts.baseball.d.ts +30 -0
  22. package/dist/charts.baseball.js +152 -0
  23. package/dist/charts.curling.d.ts +52 -0
  24. package/dist/charts.curling.js +196 -0
  25. package/dist/charts.d.ts +172 -0
  26. package/dist/charts.js +508 -0
  27. package/dist/charts.network.d.ts +66 -0
  28. package/dist/charts.network.js +64 -0
  29. package/dist/charts.soccer.d.ts +43 -0
  30. package/dist/charts.soccer.js +191 -0
  31. package/dist/charts.types.d.ts +396 -0
  32. package/dist/charts.types.js +1 -0
  33. package/dist/colour.d.ts +29 -0
  34. package/dist/colour.js +97 -0
  35. package/dist/common.d.ts +14 -0
  36. package/dist/common.js +32 -0
  37. package/dist/court.d.ts +89 -0
  38. package/dist/court.js +248 -0
  39. package/dist/courts.style.d.ts +20 -0
  40. package/dist/courts.style.js +54 -0
  41. package/dist/cricket.board.d.ts +18 -0
  42. package/dist/cricket.board.js +67 -0
  43. package/dist/curling.board.d.ts +24 -0
  44. package/dist/curling.board.js +147 -0
  45. package/dist/d3-entry.d.ts +3 -0
  46. package/dist/d3-entry.js +2 -0
  47. package/dist/d3.d.ts +45 -0
  48. package/dist/d3.js +56 -0
  49. package/dist/draw.d.ts +52 -0
  50. package/dist/draw.js +705 -0
  51. package/dist/element-define.d.ts +2 -0
  52. package/dist/element-define.js +4 -0
  53. package/dist/element-entry.d.ts +1 -0
  54. package/dist/element-entry.js +2 -0
  55. package/dist/element.d.ts +39 -0
  56. package/dist/element.js +321 -0
  57. package/dist/eras-entry.d.ts +2 -0
  58. package/dist/eras-entry.js +2 -0
  59. package/dist/eras.d.ts +204 -0
  60. package/dist/eras.js +396 -0
  61. package/dist/events-entry.d.ts +3 -0
  62. package/dist/events-entry.js +3 -0
  63. package/dist/events.d.ts +54 -0
  64. package/dist/events.data.d.ts +59 -0
  65. package/dist/events.data.js +297 -0
  66. package/dist/events.js +127 -0
  67. package/dist/features.d.ts +31 -0
  68. package/dist/features.js +284 -0
  69. package/dist/field.score.d.ts +5 -0
  70. package/dist/field.score.js +33 -0
  71. package/dist/fields/afl.d.ts +11 -0
  72. package/dist/fields/afl.js +103 -0
  73. package/dist/fields/cricket.d.ts +9 -0
  74. package/dist/fields/cricket.js +81 -0
  75. package/dist/fields/fieldhockey.d.ts +6 -0
  76. package/dist/fields/fieldhockey.js +81 -0
  77. package/dist/fields/gaelic.d.ts +10 -0
  78. package/dist/fields/gaelic.js +86 -0
  79. package/dist/fields/handball.d.ts +6 -0
  80. package/dist/fields/handball.js +85 -0
  81. package/dist/fields/lacrosse.d.ts +17 -0
  82. package/dist/fields/lacrosse.js +169 -0
  83. package/dist/fields/rugby.d.ts +10 -0
  84. package/dist/fields/rugby.js +130 -0
  85. package/dist/fields.d.ts +12 -0
  86. package/dist/fields.js +44 -0
  87. package/dist/fields.kit.d.ts +63 -0
  88. package/dist/fields.kit.js +108 -0
  89. package/dist/fields.style.d.ts +4 -0
  90. package/dist/fields.style.js +19 -0
  91. package/dist/fields.types.d.ts +155 -0
  92. package/dist/fields.types.js +1 -0
  93. package/dist/format.d.ts +10 -0
  94. package/dist/format.js +28 -0
  95. package/dist/formations.d.ts +18 -0
  96. package/dist/formations.js +211 -0
  97. package/dist/frames-entry.d.ts +8 -0
  98. package/dist/frames-entry.js +7 -0
  99. package/dist/frames.d.ts +199 -0
  100. package/dist/frames.js +271 -0
  101. package/dist/frames.providers.d.ts +16 -0
  102. package/dist/frames.providers.js +302 -0
  103. package/dist/geojson-entry.d.ts +2 -0
  104. package/dist/geojson-entry.js +2 -0
  105. package/dist/geojson.d.ts +79 -0
  106. package/dist/geojson.js +147 -0
  107. package/dist/geometry-entry.d.ts +7 -0
  108. package/dist/geometry-entry.js +5 -0
  109. package/dist/geometry.d.ts +295 -0
  110. package/dist/geometry.js +361 -0
  111. package/dist/grounds-entry.d.ts +4 -0
  112. package/dist/grounds-entry.js +3 -0
  113. package/dist/grounds.d.ts +134 -0
  114. package/dist/grounds.data.d.ts +2 -0
  115. package/dist/grounds.data.js +1849 -0
  116. package/dist/grounds.js +74 -0
  117. package/dist/heat.colour.d.ts +37 -0
  118. package/dist/heat.colour.js +54 -0
  119. package/dist/heat.d.ts +43 -0
  120. package/dist/heat.js +614 -0
  121. package/dist/heat.stats.d.ts +123 -0
  122. package/dist/heat.stats.js +413 -0
  123. package/dist/heat.types.d.ts +145 -0
  124. package/dist/heat.types.js +1 -0
  125. package/dist/heat.zones.d.ts +54 -0
  126. package/dist/heat.zones.js +424 -0
  127. package/dist/homeside.d.ts +16 -0
  128. package/dist/homeside.js +43 -0
  129. package/dist/index.d.ts +47 -0
  130. package/dist/index.js +29 -0
  131. package/dist/layers-entry.d.ts +19 -0
  132. package/dist/layers-entry.js +11 -0
  133. package/dist/layers.d.ts +41 -0
  134. package/dist/layers.env.d.ts +72 -0
  135. package/dist/layers.env.js +79 -0
  136. package/dist/layers.js +533 -0
  137. package/dist/layers.marks.d.ts +45 -0
  138. package/dist/layers.marks.js +168 -0
  139. package/dist/layers.types.d.ts +226 -0
  140. package/dist/layers.types.js +1 -0
  141. package/dist/layout.d.ts +97 -0
  142. package/dist/layout.js +381 -0
  143. package/dist/layout.style.d.ts +6 -0
  144. package/dist/layout.style.js +17 -0
  145. package/dist/layouts-entry.d.ts +5 -0
  146. package/dist/layouts-entry.js +4 -0
  147. package/dist/leagues/aussie.d.ts +57 -0
  148. package/dist/leagues/aussie.js +36 -0
  149. package/dist/leagues/baseball.d.ts +58 -0
  150. package/dist/leagues/baseball.js +171 -0
  151. package/dist/leagues/basketball.d.ts +67 -0
  152. package/dist/leagues/basketball.js +128 -0
  153. package/dist/leagues/courts.d.ts +212 -0
  154. package/dist/leagues/courts.js +114 -0
  155. package/dist/leagues/cricket.d.ts +45 -0
  156. package/dist/leagues/cricket.js +34 -0
  157. package/dist/leagues/curling.d.ts +64 -0
  158. package/dist/leagues/curling.js +53 -0
  159. package/dist/leagues/fieldhockey.d.ts +48 -0
  160. package/dist/leagues/fieldhockey.js +29 -0
  161. package/dist/leagues/fields.d.ts +10 -0
  162. package/dist/leagues/fields.js +6 -0
  163. package/dist/leagues/football.d.ts +8 -0
  164. package/dist/leagues/football.js +82 -0
  165. package/dist/leagues/gaelic.d.ts +50 -0
  166. package/dist/leagues/gaelic.js +29 -0
  167. package/dist/leagues/handball.d.ts +45 -0
  168. package/dist/leagues/handball.js +32 -0
  169. package/dist/leagues/hockey.d.ts +63 -0
  170. package/dist/leagues/hockey.js +166 -0
  171. package/dist/leagues/lacrosse.d.ts +103 -0
  172. package/dist/leagues/lacrosse.js +175 -0
  173. package/dist/leagues/rugby.d.ts +59 -0
  174. package/dist/leagues/rugby.js +72 -0
  175. package/dist/leagues/sources.d.ts +18 -0
  176. package/dist/leagues/sources.js +95 -0
  177. package/dist/leagues/types.d.ts +43 -0
  178. package/dist/leagues/types.js +5 -0
  179. package/dist/leagues-entry.d.ts +23 -0
  180. package/dist/leagues-entry.js +15 -0
  181. package/dist/leagues.d.ts +172 -0
  182. package/dist/leagues.js +462 -0
  183. package/dist/look.d.ts +50 -0
  184. package/dist/look.js +186 -0
  185. package/dist/mark.d.ts +35 -0
  186. package/dist/mark.js +59 -0
  187. package/dist/mount-entry.d.ts +2 -0
  188. package/dist/mount-entry.js +2 -0
  189. package/dist/mount.d.ts +63 -0
  190. package/dist/mount.js +167 -0
  191. package/dist/mow.d.ts +36 -0
  192. package/dist/mow.js +111 -0
  193. package/dist/names.d.ts +77 -0
  194. package/dist/names.js +97 -0
  195. package/dist/outline.d.ts +25 -0
  196. package/dist/outline.js +112 -0
  197. package/dist/parks-entry.d.ts +4 -0
  198. package/dist/parks-entry.js +3 -0
  199. package/dist/parks.d.ts +200 -0
  200. package/dist/parks.data.d.ts +2 -0
  201. package/dist/parks.data.js +4489 -0
  202. package/dist/parks.js +92 -0
  203. package/dist/plot-entry.d.ts +2 -0
  204. package/dist/plot-entry.js +2 -0
  205. package/dist/plot.d.ts +74 -0
  206. package/dist/plot.js +68 -0
  207. package/dist/png-entry.d.ts +2 -0
  208. package/dist/png-entry.js +2 -0
  209. package/dist/png.d.ts +26 -0
  210. package/dist/png.js +46 -0
  211. package/dist/ranges.d.ts +33 -0
  212. package/dist/ranges.js +178 -0
  213. package/dist/ranges.names.d.ts +31 -0
  214. package/dist/ranges.names.js +65 -0
  215. package/dist/react-entry.d.ts +2 -0
  216. package/dist/react-entry.js +2 -0
  217. package/dist/react.d.ts +53 -0
  218. package/dist/react.js +58 -0
  219. package/dist/replay-entry.d.ts +5 -0
  220. package/dist/replay-entry.js +4 -0
  221. package/dist/replay.control.d.ts +47 -0
  222. package/dist/replay.control.js +184 -0
  223. package/dist/replay.d.ts +170 -0
  224. package/dist/replay.dom.d.ts +21 -0
  225. package/dist/replay.dom.js +388 -0
  226. package/dist/replay.js +579 -0
  227. package/dist/replay.types.d.ts +152 -0
  228. package/dist/replay.types.js +1 -0
  229. package/dist/restyle.d.ts +21 -0
  230. package/dist/restyle.js +70 -0
  231. package/dist/road.d.ts +26 -0
  232. package/dist/road.draw.d.ts +6 -0
  233. package/dist/road.draw.js +269 -0
  234. package/dist/road.js +59 -0
  235. package/dist/road.model.d.ts +63 -0
  236. package/dist/road.model.js +215 -0
  237. package/dist/road.style.d.ts +7 -0
  238. package/dist/road.style.js +26 -0
  239. package/dist/road.types.d.ts +122 -0
  240. package/dist/road.types.js +1 -0
  241. package/dist/scene-entry.d.ts +7 -0
  242. package/dist/scene-entry.js +5 -0
  243. package/dist/scene.css.d.ts +44 -0
  244. package/dist/scene.css.js +296 -0
  245. package/dist/scene.d.ts +27 -0
  246. package/dist/scene.fit.d.ts +12 -0
  247. package/dist/scene.fit.js +18 -0
  248. package/dist/scene.flat.d.ts +38 -0
  249. package/dist/scene.flat.js +167 -0
  250. package/dist/scene.js +420 -0
  251. package/dist/scene.path.d.ts +40 -0
  252. package/dist/scene.path.js +357 -0
  253. package/dist/scene.types.d.ts +172 -0
  254. package/dist/scene.types.js +1 -0
  255. package/dist/scene.xml.d.ts +31 -0
  256. package/dist/scene.xml.js +81 -0
  257. package/dist/score.d.ts +70 -0
  258. package/dist/score.js +212 -0
  259. package/dist/sets.board.d.ts +22 -0
  260. package/dist/sets.board.js +89 -0
  261. package/dist/sheet.d.ts +18 -0
  262. package/dist/sheet.js +38 -0
  263. package/dist/special.d.ts +44 -0
  264. package/dist/special.js +204 -0
  265. package/dist/special.style.d.ts +8 -0
  266. package/dist/special.style.js +36 -0
  267. package/dist/strings.d.ts +581 -0
  268. package/dist/strings.js +411 -0
  269. package/dist/style.d.ts +35 -0
  270. package/dist/style.js +177 -0
  271. package/dist/surfaces/baseball.d.ts +33 -0
  272. package/dist/surfaces/baseball.js +208 -0
  273. package/dist/surfaces/baseball.walls.d.ts +16 -0
  274. package/dist/surfaces/baseball.walls.js +150 -0
  275. package/dist/surfaces/basketball.d.ts +8 -0
  276. package/dist/surfaces/basketball.js +199 -0
  277. package/dist/surfaces/curling.d.ts +23 -0
  278. package/dist/surfaces/curling.js +128 -0
  279. package/dist/surfaces/field.d.ts +12 -0
  280. package/dist/surfaces/field.js +163 -0
  281. package/dist/surfaces/football.d.ts +12 -0
  282. package/dist/surfaces/football.js +208 -0
  283. package/dist/surfaces/hockey.d.ts +10 -0
  284. package/dist/surfaces/hockey.js +166 -0
  285. package/dist/surfaces/net.d.ts +7 -0
  286. package/dist/surfaces/net.js +151 -0
  287. package/dist/surfaces/soccer-providers.d.ts +124 -0
  288. package/dist/surfaces/soccer-providers.js +334 -0
  289. package/dist/surfaces/soccer-spec.d.ts +138 -0
  290. package/dist/surfaces/soccer-spec.js +175 -0
  291. package/dist/surfaces/soccer.d.ts +25 -0
  292. package/dist/surfaces/soccer.js +270 -0
  293. package/dist/surfaces/surface.d.ts +180 -0
  294. package/dist/surfaces/surface.js +49 -0
  295. package/dist/team.common.d.ts +75 -0
  296. package/dist/team.common.js +97 -0
  297. package/dist/team.formation.d.ts +24 -0
  298. package/dist/team.formation.js +121 -0
  299. package/dist/team.geometry.d.ts +66 -0
  300. package/dist/team.geometry.js +304 -0
  301. package/dist/team.layers.d.ts +16 -0
  302. package/dist/team.layers.js +32 -0
  303. package/dist/team.motion.d.ts +9 -0
  304. package/dist/team.motion.js +325 -0
  305. package/dist/team.network.d.ts +43 -0
  306. package/dist/team.network.js +215 -0
  307. package/dist/team.shape.d.ts +12 -0
  308. package/dist/team.shape.js +217 -0
  309. package/dist/team.types.d.ts +189 -0
  310. package/dist/team.types.js +1 -0
  311. package/dist/text.d.ts +31 -0
  312. package/dist/text.js +88 -0
  313. package/dist/themes.d.ts +14 -0
  314. package/dist/themes.js +24 -0
  315. package/dist/types.d.ts +565 -0
  316. package/dist/types.js +1 -0
  317. package/dist/units.d.ts +45 -0
  318. package/dist/units.js +90 -0
  319. package/dist/version.d.ts +2 -0
  320. package/dist/version.js +2 -0
  321. package/package.json +234 -4
@@ -0,0 +1,388 @@
1
+ // The page's side of a replay: the controls under the drawing, the keyboard, the live region, and the painting of each frame by moving the
2
+ // marks that move and nothing else. The clock (replay.control.ts) and the frame (replay.ts) have no DOM; this file is the one that does. It is
3
+ // used by `mountKyogijo` (the `replay` method of a mount) and so by `<kyogijo-field>`.
4
+ import { num } from "./format.js";
5
+ import { renderLayers } from "./layers.js";
6
+ import { REPLAY_SPEEDS, createReplay } from "./replay.control.js";
7
+ import { describeEvent, drawFrame, replayClock, replayGroups, replaySpoken, replayInfo, replayPlacer, replaySnapshot } from "./replay.js";
8
+ import { KYOGIJO_STRINGS, fill } from "./strings.js";
9
+ const SVG_NS = "http://www.w3.org/2000/svg";
10
+ let counter = 0;
11
+ /** The controls' stylesheet: written once beside the controls, scoped to them, in the page's own colours. */
12
+ export const REPLAY_STYLE = `
13
+ .ky-replay{position:relative;display:block;color:inherit}
14
+ .ky-replay .ky-stage:focus-visible,.ky-replay .ky-controls button:focus-visible,.ky-replay .ky-controls select:focus-visible,.ky-replay .ky-controls input:focus-visible{outline:2px solid currentColor;outline-offset:2px}
15
+ .ky-replay .ky-controls{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;margin-top:.5rem;font:inherit}
16
+ .ky-replay .ky-controls[hidden]{display:none}
17
+ .ky-replay .ky-controls button,.ky-replay .ky-controls select{box-sizing:border-box;min-width:44px;min-height:44px;margin:0;padding:0 .6rem;border:1px solid currentColor;border-radius:8px;background:transparent;color:inherit;font:inherit;cursor:pointer;display:inline-flex;align-items:center;justify-content:center}
18
+ .ky-replay .ky-controls button[hidden],.ky-replay .ky-controls select[hidden]{display:none}
19
+ .ky-replay .ky-controls button[aria-pressed=true]{background:color-mix(in srgb,currentColor 22%,transparent)}
20
+ .ky-replay .ky-controls svg{width:22px;height:22px;fill:currentColor;pointer-events:none}
21
+ .ky-replay .ky-controls .ky-loop svg{fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
22
+ .ky-replay .ky-controls input[type=range]{flex:1 1 140px;min-width:120px;min-height:44px;margin:0;accent-color:currentColor}
23
+ .ky-replay .ky-controls output{min-width:6.5ch;font-variant-numeric:tabular-nums;text-align:end}
24
+ .ky-replay .ky-live,.ky-replay .ky-hint{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
25
+ `.trim();
26
+ const ICONS = {
27
+ play: "M8 5v14l11-7z",
28
+ pause: "M6 5h4v14H6zM14 5h4v14h-4z",
29
+ back: "M6 5h2v14H6zM9.5 12L19 5v14z",
30
+ forward: "M16 5h2v14h-2zM14.5 12L5 5v14z",
31
+ loop: "M17 2l4 4-4 4M3 11V9a3 3 0 0 1 3-3h15M7 22l-4-4 4-4M21 13v2a3 3 0 0 1-3 3H3",
32
+ };
33
+ const icon = (d) => `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="${d}"/></svg>`;
34
+ const pathOf = (points) => points.map(([x, y], i) => `${i === 0 ? "M" : "L"}${num(x)} ${num(y)}`).join("");
35
+ /**
36
+ * Attach a replay to a container: the stage with the drawing, the controls under it, the keyboard and the live region. The drawing is written once
37
+ * and then only the marks that move are moved, on `requestAnimationFrame`; a mark that comes or goes (an object that enters, an event that
38
+ * begins) redraws the replay's own layers, never the surface. With `prefers-reduced-motion` nothing plays by itself and play steps instead.
39
+ */
40
+ export function createReplayView(host, timeline, settings, initial) {
41
+ const doc = host.ownerDocument;
42
+ const info = replayInfo(timeline);
43
+ const media = typeof matchMedia === "function" ? matchMedia("(prefers-reduced-motion: reduce)") : undefined;
44
+ const motionNow = () => (settings.motion === "smooth" ? "smooth" : settings.motion === "step" ? "step" : media?.matches === true ? "step" : "smooth");
45
+ const controller = createReplay(timeline, { ...settings, motion: motionNow() });
46
+ const id = initial.id ?? `ky-r${++counter}`;
47
+ let options = { ...initial, id };
48
+ let language = options.language === "ja" ? "ja" : "en";
49
+ const words = () => KYOGIJO_STRINGS[language].replay;
50
+ const root = doc.createElement("div");
51
+ root.className = "ky-replay";
52
+ const style = doc.createElement("style");
53
+ style.textContent = REPLAY_STYLE;
54
+ const stage = doc.createElement("div");
55
+ stage.className = "ky-stage";
56
+ stage.tabIndex = 0;
57
+ stage.setAttribute("role", "group");
58
+ const hint = doc.createElement("p");
59
+ hint.className = "ky-hint";
60
+ hint.id = `${id}-keys`;
61
+ const bar = doc.createElement("div");
62
+ bar.className = "ky-controls";
63
+ bar.setAttribute("role", "group");
64
+ const live = doc.createElement("div");
65
+ live.className = "ky-live";
66
+ live.setAttribute("role", "status");
67
+ live.setAttribute("aria-live", "polite");
68
+ root.append(style, stage, hint, bar, live);
69
+ host.replaceChildren(root);
70
+ // ---- The painter. ----
71
+ let svg = null;
72
+ let placer = replayPlacer(options, timeline);
73
+ /** For each group of the replay in the drawing, the marks it holds (their keys): a group whose keys changed is drawn again, the others only move. */
74
+ let shown = new Map();
75
+ let bound = new Map();
76
+ /** The groups whose marks cannot be matched to elements one for one, so that they are drawn again each time. */
77
+ const loose = new Set();
78
+ let painted = Number.NaN;
79
+ const look = settings;
80
+ const groupsAt = (t) => {
81
+ const snap = replaySnapshot(timeline, t, look);
82
+ const groups = replayGroups(timeline, snap, look, language);
83
+ const places = placer(snap.objects.map((o) => [o.x, o.y]));
84
+ return { groups, placed: new Map(snap.objects.map((o, i) => [o.id, places[i]])) };
85
+ };
86
+ const positions = (g, placed) => (g.kind === "lines" ? g.coords.map((path) => placer(path)) : g.keys.map((k) => placed.get(k) ?? [0, 0]));
87
+ const holderOf = (g) => svg?.querySelector(`[id="${id}-${typeof g === "string" ? g : g.id}"]`) ?? null;
88
+ /** Match the elements of a group that was just drawn to its marks, remembering where they are. */
89
+ const bind = (g, placed) => {
90
+ bound.delete(g.id);
91
+ loose.delete(g.id);
92
+ if (!g.moving)
93
+ return;
94
+ const holder = holderOf(g);
95
+ const elements = holder === null ? [] : [...holder.children].filter((c) => c.localName !== "title" && c.localName !== "desc" && c.localName !== "defs");
96
+ if (elements.length !== g.keys.length) {
97
+ loose.add(g.id);
98
+ return;
99
+ }
100
+ const at = positions(g, placed);
101
+ const base = g.kind === "lines" ? at.map((path) => path[0]) : at;
102
+ const own = elements.map((el) => el.getAttribute("transform"));
103
+ bound.set(g.id, { elements, base, last: elements.map((_, i) => (g.kind === "lines" ? "" : `translate(0 0)${own[i] === null ? "" : ` ${own[i]}`}`)), own });
104
+ };
105
+ /** Draw the groups that changed again, in their place among the others, and take away the ones that are gone. The surface and the groups that only move are left. */
106
+ const patch = (groups, changed, gone, placed) => {
107
+ const container = svg?.querySelector("g.ky-layers") ?? null;
108
+ if (svg === null || container === null)
109
+ return false;
110
+ for (const gid of gone) {
111
+ holderOf(gid)?.remove();
112
+ shown.delete(gid);
113
+ bound.delete(gid);
114
+ loose.delete(gid);
115
+ }
116
+ for (const g of changed) {
117
+ const scratch = doc.createElementNS(SVG_NS, "g");
118
+ scratch.innerHTML = renderLayers(options, [g.layer]).svg;
119
+ const made = scratch.querySelector("g.ky-layers > g.ky-layer");
120
+ if (made === null)
121
+ return false;
122
+ const old = holderOf(g);
123
+ if (old !== null)
124
+ old.replaceWith(made);
125
+ else {
126
+ // Among the others in the order they are drawn: before the first later group that is there.
127
+ let before = null;
128
+ for (let j = groups.indexOf(g) + 1; j < groups.length && before === null; j += 1)
129
+ before = holderOf(groups[j]);
130
+ if (before === null)
131
+ container.append(made);
132
+ else
133
+ before.before(made);
134
+ }
135
+ shown.set(g.id, g.keys.join(","));
136
+ bind(g, placed);
137
+ }
138
+ container.setAttribute("data-layers", String(container.querySelectorAll(":scope > g.ky-layer").length));
139
+ return true;
140
+ };
141
+ const move = (groups, placed) => {
142
+ for (const g of groups) {
143
+ const b = bound.get(g.id);
144
+ if (b === undefined)
145
+ continue;
146
+ const at = positions(g, placed);
147
+ if (g.kind === "lines") {
148
+ at.forEach((path, i) => {
149
+ const d = pathOf(path);
150
+ if (b.last[i] !== d) {
151
+ b.elements[i].setAttribute("d", d);
152
+ b.last[i] = d;
153
+ }
154
+ });
155
+ }
156
+ else {
157
+ at.forEach(([x, y], i) => {
158
+ const value = `translate(${num(x - b.base[i][0])} ${num(y - b.base[i][1])})${b.own[i] === null ? "" : ` ${b.own[i]}`}`;
159
+ if (b.last[i] !== value) {
160
+ b.elements[i].setAttribute("transform", value);
161
+ b.last[i] = value;
162
+ }
163
+ });
164
+ }
165
+ }
166
+ };
167
+ const paint = (t) => {
168
+ if (svg === null || t === painted)
169
+ return;
170
+ painted = t;
171
+ const { groups, placed } = groupsAt(t);
172
+ const changed = groups.filter((g) => loose.has(g.id) || shown.get(g.id) !== g.keys.join(","));
173
+ const gone = [...shown.keys()].filter((gid) => !groups.some((g) => g.id === gid));
174
+ if (changed.length > 0 || gone.length > 0) {
175
+ if (!patch(groups, changed, gone, placed)) {
176
+ drawStage(t);
177
+ return;
178
+ }
179
+ }
180
+ move(groups, placed);
181
+ };
182
+ function drawStage(t) {
183
+ stage.innerHTML = drawFrame(options, timeline, t, look);
184
+ svg = stage.querySelector("svg.kyogijo");
185
+ placer = replayPlacer(options, timeline);
186
+ const { groups, placed } = groupsAt(t);
187
+ shown = new Map(groups.map((g) => [g.id, g.keys.join(",")]));
188
+ bound = new Map();
189
+ loose.clear();
190
+ painted = t;
191
+ for (const g of groups)
192
+ bind(g, placed);
193
+ }
194
+ // ---- The controls. ----
195
+ let sync = () => { };
196
+ const buildBar = () => {
197
+ const w = words();
198
+ const state = controller.state();
199
+ const stepMode = state.motion === "step";
200
+ const button = (cls, label, d, onClick) => {
201
+ const b = doc.createElement("button");
202
+ b.type = "button";
203
+ b.className = cls;
204
+ b.setAttribute("aria-label", label);
205
+ b.title = label;
206
+ b.innerHTML = icon(d);
207
+ b.addEventListener("click", onClick);
208
+ return b;
209
+ };
210
+ bar.setAttribute("aria-label", w.group);
211
+ bar.replaceChildren();
212
+ const back = button("ky-back", stepMode ? w.stepBack : w.back, ICONS.back, () => controller.step(-1));
213
+ const play = button("ky-play", w.play, ICONS.play, () => controller.toggle());
214
+ const forward = button("ky-forward", stepMode ? w.stepForward : w.forward, ICONS.forward, () => controller.step(1));
215
+ const speed = doc.createElement("select");
216
+ speed.className = "ky-speed";
217
+ speed.setAttribute("aria-label", w.speed);
218
+ speed.title = w.speed;
219
+ for (const value of REPLAY_SPEEDS) {
220
+ const option = doc.createElement("option");
221
+ option.value = String(value);
222
+ option.textContent = `${value}×`;
223
+ speed.append(option);
224
+ }
225
+ speed.addEventListener("change", () => controller.setSpeed(Number(speed.value)));
226
+ const loop = button("ky-loop", w.loop, ICONS.loop, () => controller.setLoop(!controller.state().loop));
227
+ const range = doc.createElement("input");
228
+ range.type = "range";
229
+ range.className = "ky-position";
230
+ range.min = "0";
231
+ range.max = String(info.duration);
232
+ range.step = String(stepMode ? (settings.stepSeconds ?? 1) : info.step > 0 ? Math.round(info.step * 1e6) / 1e6 : "any");
233
+ range.disabled = info.duration === 0;
234
+ range.setAttribute("aria-label", w.position);
235
+ range.addEventListener("input", () => controller.seek(info.start + Number(range.value)));
236
+ const clock = doc.createElement("output");
237
+ clock.className = "ky-clock";
238
+ clock.setAttribute("aria-hidden", "true");
239
+ // Nothing plays by itself in step mode: its controls are the steps and the position.
240
+ play.hidden = stepMode;
241
+ speed.hidden = stepMode;
242
+ loop.hidden = stepMode;
243
+ bar.append(back, play, forward, speed, loop, range, clock);
244
+ hint.textContent = stepMode ? w.keysStep : w.keys;
245
+ stage.setAttribute("aria-label", w.region);
246
+ stage.setAttribute("aria-describedby", hint.id);
247
+ stage.setAttribute("aria-keyshortcuts", "Space ArrowLeft ArrowRight Shift+ArrowLeft Shift+ArrowRight Home End");
248
+ let shown = "";
249
+ sync = () => {
250
+ const s = controller.state();
251
+ const total = replayClock(info.duration);
252
+ const text = `${replayClock(s.t - info.start)} / ${total}`;
253
+ if (text !== shown) {
254
+ shown = text;
255
+ clock.textContent = text;
256
+ range.setAttribute("aria-valuetext", fill(w.positionValue, { t: replaySpoken(s.t - info.start, w), total: replaySpoken(info.duration, w) }));
257
+ }
258
+ const at = String(s.t - info.start);
259
+ if (range.value !== at)
260
+ range.value = at;
261
+ const label = s.playing ? w.pause : w.play;
262
+ if (play.getAttribute("aria-label") !== label) {
263
+ play.setAttribute("aria-label", label);
264
+ play.title = label;
265
+ play.innerHTML = icon(s.playing ? ICONS.pause : ICONS.play);
266
+ }
267
+ if (speed.value !== String(s.speed))
268
+ speed.value = String(s.speed);
269
+ loop.setAttribute("aria-pressed", String(s.loop));
270
+ root.dataset.state = s.ended ? "ended" : s.playing ? "playing" : "paused";
271
+ root.dataset.motion = s.motion;
272
+ };
273
+ sync(undefined);
274
+ };
275
+ if (settings.controls === false)
276
+ bar.hidden = true;
277
+ // ---- The clock on the page: animation frames while playing, and the events said aloud. ----
278
+ let raf = 0;
279
+ let last = 0;
280
+ const frame = (now) => {
281
+ raf = 0;
282
+ if (!controller.state().playing)
283
+ return;
284
+ const dt = Math.min(0.25, Math.max(0, (now - last) / 1000));
285
+ last = now;
286
+ controller.advance(dt);
287
+ if (controller.state().playing && raf === 0)
288
+ raf = requestAnimationFrame(frame);
289
+ };
290
+ const stop = controller.subscribe((change) => {
291
+ sync(change);
292
+ paint(change.state.t);
293
+ if (settings.announce !== false && change.crossed.length > 0 && (change.reason === "tick" || change.reason === "step")) {
294
+ live.textContent = change.crossed.map((e) => describeEvent(e, language, info.start)).join(". ");
295
+ }
296
+ if (change.reason === "play" && raf === 0) {
297
+ last = performance.now();
298
+ raf = requestAnimationFrame(frame);
299
+ }
300
+ if (!change.state.playing && raf !== 0) {
301
+ cancelAnimationFrame(raf);
302
+ raf = 0;
303
+ }
304
+ });
305
+ const keys = (event) => {
306
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey)
307
+ return;
308
+ const tag = event.target?.tagName ?? "";
309
+ if (tag === "INPUT" || tag === "SELECT" || tag === "TEXTAREA")
310
+ return;
311
+ switch (event.key) {
312
+ case " ":
313
+ case "Spacebar":
314
+ // A button takes the space itself.
315
+ if (tag === "BUTTON")
316
+ return;
317
+ controller.toggle();
318
+ break;
319
+ case "ArrowRight":
320
+ if (event.shiftKey)
321
+ controller.jump(5);
322
+ else
323
+ controller.step(1);
324
+ break;
325
+ case "ArrowLeft":
326
+ if (event.shiftKey)
327
+ controller.jump(-5);
328
+ else
329
+ controller.step(-1);
330
+ break;
331
+ case "Home":
332
+ controller.seek(info.start);
333
+ break;
334
+ case "End":
335
+ controller.seek(info.end);
336
+ break;
337
+ default:
338
+ return;
339
+ }
340
+ event.preventDefault();
341
+ };
342
+ root.addEventListener("keydown", keys);
343
+ // A page that is hidden does no work: the clock stops, and the reader starts it again.
344
+ const hidden = () => {
345
+ if (doc.visibilityState === "hidden")
346
+ controller.pause();
347
+ };
348
+ doc.addEventListener("visibilitychange", hidden);
349
+ const motion = () => {
350
+ controller.setMotion(motionNow());
351
+ buildBar();
352
+ };
353
+ if (settings.motion === undefined || settings.motion === "auto") {
354
+ if (typeof media?.addEventListener === "function")
355
+ media.addEventListener("change", motion);
356
+ else if (typeof media?.addListener === "function")
357
+ media.addListener(motion);
358
+ }
359
+ buildBar();
360
+ drawStage(controller.state().t);
361
+ if (settings.autoplay === true && controller.state().motion === "smooth" && doc.visibilityState !== "hidden")
362
+ controller.play();
363
+ return {
364
+ controller,
365
+ stage,
366
+ draw(next) {
367
+ const before = language;
368
+ options = { ...next, id };
369
+ language = options.language === "ja" ? "ja" : "en";
370
+ // The controls are made again only when the words change: a reader's focus on one of them stays where it is when a score moves.
371
+ if (language !== before)
372
+ buildBar();
373
+ drawStage(controller.state().t);
374
+ },
375
+ destroy() {
376
+ stop();
377
+ if (raf !== 0)
378
+ cancelAnimationFrame(raf);
379
+ doc.removeEventListener("visibilitychange", hidden);
380
+ if (typeof media?.removeEventListener === "function")
381
+ media.removeEventListener("change", motion);
382
+ else if (typeof media?.removeListener === "function")
383
+ media.removeListener(motion);
384
+ controller.pause();
385
+ host.replaceChildren();
386
+ },
387
+ };
388
+ }