@johnmorrisdotca/dougu-sports 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,32 @@ All notable changes to this project are written here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.8.0] - 2026-10-14
10
+
11
+ ### Added
12
+
13
+ - **`bindTurn` takes an emblem object and is light.** `bindTurn(element, TROPHY_HOCKEY_CUP, options)` draws and turns the emblem it is given with its own `frame` and imports neither a drawing nor the registry, so a bundle of `bindTurn` and one cup is about 9.4 kB gzipped (the turning is about 2.5 kB of it), where it was about 81 kB. `test/size.test.ts` holds it to 10 kB, and the other emblems stay tree-shakeable.
14
+ - **`ready`** on the controller `bindTurn` returns: a promise that resolves when the emblem is drawn and bound (already, for an emblem object) and rejects for an id that names nothing.
15
+
16
+ ### Changed
17
+
18
+ - **`bindTurn` of `./turn` loads the registry only for an id.** An id (`"trophy:tennis-cup"`) is read from the registry, which `bindTurn` now imports on demand (a dynamic import, a chunk of its own in a bundler) instead of at the top. The first id bound is drawn when `ready` resolves, not before; later ones at once. `bindTurn` of the main entry (`@johnmorrisdotca/dougu-sports`) has the registry already and draws an id at once, as before. New modules: `src/turn-registry.ts` (what is loaded) and `src/turn-full.ts` (the main entry's `bindTurn`).
19
+
20
+ ## [0.7.3] - 2026-10-14
21
+
22
+ The repository's own tooling is TypeScript. No change to the package: the same files are published, and the pictures are byte for byte the same.
23
+
24
+ ### Changed
25
+
26
+ - **The scripts, the browser tests, the test helpers and the demo's source are `.ts`.** `scripts/*.ts` (build, gallery, package check, README examples, release notes, contact sheets, the pictures script, the demo's server and page maker), `e2e/*.demo.ts`, `playwright.config.ts` and `test/pieces-gallery.test.ts` were `.mjs` or `.js`. They run with Node's own type stripping, with no `tsx` and no build step, so `package.json`'s scripts call the `.ts` files directly. The shared family files stay as they are, byte for byte: `scripts/family-template.mjs` and `scripts/family-readme.mjs` (plain JavaScript, with the types of the template written by hand in `scripts/family-template.d.mts`) and `scripts/family-backgrounds.ts`.
27
+ - **`pnpm typecheck` checks all of it.** `tsconfig.check.json` covers `src` and `test`, the new `tsconfig.tools.json` covers `scripts`, `e2e` and the Playwright config (with the browser's types, since the tests and the pictures script run code in the page), and the new `tsconfig.demo.json` covers `demo`. The syntax is the erasable kind only (`erasableSyntaxOnly` was already on), with `import type` and `.ts` extensions.
28
+ - **The demo's scripts are `demo/*.ts`, built with esbuild.** A browser cannot run TypeScript, so `scripts/page.ts` builds each one into `demo.js`, `compose.js` and `gallery.js` for the dev server (on each request) and for `pnpm site`. A script imports the package from `../src/index.ts`, so that it is type-checked against the real source; the build changes that one import to `./dist/index.js`, the built package, as before. The page is the same.
29
+ - **Node.** Working on the repository needs Node 22.18 or later (where type stripping is on by default); CI already runs 22 and 24. `engines` stays `>=22`, because the published package is plain JavaScript and needs no more. `CONTRIBUTING.md` says so.
30
+
31
+ ### Fixed
32
+
33
+ - **The belts note counted the two-colour pairs wrong.** `docs/japanese-sports.md` and `docs/japanese-sports.ja.md` said the IJF names "these four pairs" under a table of five. The IJF's page names four (white-yellow, yellow-orange, orange-green, green-blue); blue-brown is drawn for completeness and no source found names it. Both pages now say that.
34
+
9
35
  ## [0.7.2] - 2026-10-14
10
36
 
11
37
  The docs are one language to a page. No change to the pictures; the only change to the package is one English description.
package/NOTICE.md CHANGED
@@ -72,4 +72,4 @@ Facts only: a dimension or a mass is a fact, not a work. The NHL's puck rule was
72
72
  | What | Made from | Terms |
73
73
  | --- | --- | --- |
74
74
  | The demo's page | Written for this package, with the family stylesheet `demo/family.css` | MIT |
75
- | The README's pictures (`docs/images`) | Taken by `scripts/screenshots.mjs` from the demo, which draws the package's own emblems | MIT |
75
+ | The README's pictures (`docs/images`) | Taken by `scripts/screenshots.ts` from the demo, which draws the package's own emblems | MIT |
package/README.md CHANGED
@@ -137,7 +137,7 @@ The alias is a name in your `package.json` only: the same package, the same code
137
137
  | --- | --- |
138
138
  | `@johnmorrisdotca/dougu-sports` | The full registry: every emblem by id, the functions, and each emblem as a constant |
139
139
  | `@johnmorrisdotca/dougu-sports/emblems` | `renderEmblem`, the ids and the types, with no single drawing imported by name |
140
- | `@johnmorrisdotca/dougu-sports/turn` | `emblemFrame`, `ballFrame` and `bindTurn` |
140
+ | `@johnmorrisdotca/dougu-sports/turn` | `emblemFrame`, `ballFrame` and `bindTurn`; `bindTurn` with an emblem object takes nothing else with it |
141
141
  | `@johnmorrisdotca/dougu-sports/<group>/<name>` | One emblem, alone: `trophy/hockey-cup`, `medal/gold`, `crest/shield`, `sport/nhl`, `award/crown`, `ball/soccer`, `pennant/championship`, `gear/<name>` |
142
142
 
143
143
  ### One emblem and nothing else
@@ -379,12 +379,13 @@ Both are one SVG string in the page's text colour, with a title and a descriptio
379
379
 
380
380
  ```js no-run
381
381
  import { bindTurn } from "@johnmorrisdotca/dougu-sports/turn";
382
+ import { TROPHY_TENNIS_CUP } from "@johnmorrisdotca/dougu-sports/trophy/tennis-cup";
382
383
 
383
- const turner = bindTurn(document.getElementById("cup"), "trophy:tennis-cup", { size: 160, speed: 36 });
384
+ const turner = bindTurn(document.getElementById("cup"), TROPHY_TENNIS_CUP, { size: 160, speed: 36 });
384
385
  document.getElementById("spin").onclick = () => turner.togglePlay();
385
386
  ```
386
387
 
387
- A drag turns it, the arrow keys turn it, Space starts and stops an auto-turn, and nothing moves until you ask; see [docs/turning.md](./docs/turning.md).
388
+ A drag turns it, the arrow keys turn it, Space starts and stops an auto-turn, and nothing moves until you ask. Pass the emblem itself and the page carries the turning and that one emblem and nothing else (a cup and the turning are about 9 kB gzipped); pass an id, `"trophy:tennis-cup"`, and the registry is loaded when it is needed. See [docs/turning.md](./docs/turning.md).
388
389
 
389
390
  ### In React, Vue and the rest
390
391
 
@@ -434,7 +435,7 @@ The thirty-eight sports the ball, gear and sport emblems are made for are `SPORT
434
435
  | `sportIcon(sport, options?)` | The pictogram of a sport, by the names of `SPORT_NAMES`; an unknown sport is an error |
435
436
  | `lineup(ids, options?)` | Several balls side by side, at their true relative sizes (or all as large as the largest) |
436
437
  | `emblemFrame(ref, options?)` | An emblem turned by `yaw` and `pitch` degrees (and `roll` for a ball) |
437
- | `bindTurn(element, ref, options?)` | Draws an emblem into an element and lets a drag, the arrow keys, Space or a clock turn it; returns a controller with `angles`, `playing`, `turnable`, `setAngles`, `update`, `play`, `pause`, `togglePlay` and `destroy` |
438
+ | `bindTurn(element, ref, options?)` | Draws an emblem (the emblem itself, or its id) into an element and lets a drag, the arrow keys, Space or a clock turn it; returns a controller with `angles`, `playing`, `turnable`, `ready`, `setAngles`, `update`, `play`, `pause`, `togglePlay` and `destroy` |
438
439
  | `emblemDataUri(ref, options?)` | An emblem as a `data:image/svg+xml;utf8,…` address for an `<img>`, a CSS background or a canvas, with the ink made a colour (`color`, or the theme's) |
439
440
  | `emblemSprite(ids, options?)` | A hidden sheet of `<symbol>`s with unique ids, and `use(id, { size, class, title })` for the markup of each use |
440
441
  | `sportEquipment(sport)` | The ball, gear, trophy and medal of a sport, as ids of the registry |
@@ -604,7 +605,7 @@ Bug reports and ideas go in the [issues](https://github.com/johnmorrisdotca/doug
604
605
 
605
606
  ## Changes
606
607
 
607
- See [CHANGELOG.md](./CHANGELOG.md). 0.7.2 makes every docs page single-language (a page in Japanese beside each page that had Japanese words in it, and a test that holds them apart), with no change to the pictures; 0.7.1 puts real countries (Canada, Japan) in the crest and puck examples in place of made-up teams, with no change to the code; 0.7.0 gives the national volleyballs layouts that tell them apart (Japan a crimson band, Poland white above and red below, the USA a navy field), strengthens the 2010-style grooved soccer ball, and adds six pictures of how the judo belt is tied, `gear:obi-step-1` to `gear:obi-step-6` (269 emblems: [docs/obi-steps.md](./docs/obi-steps.md)); 0.6.0 gives the judo suit and the karate suit their sleeves (a judogi has no short ones: they reach the wrist) and adds the judogi seen from behind with the IJF-style back number, a family name and a country code on a patch, with a `backnumber` option (269 emblems: [docs/judogi-back.md](./docs/judogi-back.md)); 0.5.1 redraws the judo suit, the belt and the football pictograms and adds the blue and folded suits and the belts of every grade (261 emblems: [docs/japanese-sports.md](./docs/japanese-sports.md)); 0.5.0 adds fifty balls: vintage leather soccer balls, era-style and shop-wall soccer balls, baseballs of other threads and volleyballs in national colours, with a `colors` option (244 emblems: [docs/vintage-balls.md](./docs/vintage-balls.md)); 0.4.0 adds eight printed pucks and a `face` option (194 emblems: [docs/pucks.md](./docs/pucks.md)); 0.3.0 adds nineteen soccer balls (26, 18, 14, 12 and 6 panels, winter orange and high-visibility yellow, futsal and beach soccer: 186 emblems); 0.2.1 draws every ball and implement to the rulebook's proportions; 0.2.0 is 167 emblems, every one drawn here, with the Japanese sports, engraving, a turn for every emblem and the compose helpers; 0.1.0 brought the first full set of 148; 0.0.1 was the first preview.
608
+ See [CHANGELOG.md](./CHANGELOG.md). 0.8.0 makes `bindTurn` light: it takes an emblem object and then needs no registry, so a turnable cup is about 9.4 kB gzipped instead of about 81 kB, and an id loads the registry on demand; 0.7.3 turns the build, docs and picture scripts, the browser tests and the demo's source from JavaScript into TypeScript, with no change to the package; 0.7.2 makes every docs page single-language (a page in Japanese beside each page that had Japanese words in it, and a test that holds them apart), with no change to the pictures; 0.7.1 puts real countries (Canada, Japan) in the crest and puck examples in place of made-up teams, with no change to the code; 0.7.0 gives the national volleyballs layouts that tell them apart (Japan a crimson band, Poland white above and red below, the USA a navy field), strengthens the 2010-style grooved soccer ball, and adds six pictures of how the judo belt is tied, `gear:obi-step-1` to `gear:obi-step-6` (269 emblems: [docs/obi-steps.md](./docs/obi-steps.md)); 0.6.0 gives the judo suit and the karate suit their sleeves (a judogi has no short ones: they reach the wrist) and adds the judogi seen from behind with the IJF-style back number, a family name and a country code on a patch, with a `backnumber` option (269 emblems: [docs/judogi-back.md](./docs/judogi-back.md)); 0.5.1 redraws the judo suit, the belt and the football pictograms and adds the blue and folded suits and the belts of every grade (261 emblems: [docs/japanese-sports.md](./docs/japanese-sports.md)); 0.5.0 adds fifty balls: vintage leather soccer balls, era-style and shop-wall soccer balls, baseballs of other threads and volleyballs in national colours, with a `colors` option (244 emblems: [docs/vintage-balls.md](./docs/vintage-balls.md)); 0.4.0 adds eight printed pucks and a `face` option (194 emblems: [docs/pucks.md](./docs/pucks.md)); 0.3.0 adds nineteen soccer balls (26, 18, 14, 12 and 6 panels, winter orange and high-visibility yellow, futsal and beach soccer: 186 emblems); 0.2.1 draws every ball and implement to the rulebook's proportions; 0.2.0 is 167 emblems, every one drawn here, with the Japanese sports, engraving, a turn for every emblem and the compose helpers; 0.1.0 brought the first full set of 148; 0.0.1 was the first preview.
608
609
 
609
610
  ## Credits
610
611
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from "./emblems-entry.ts";
2
2
  export { ballFrame, emblemFrame } from "./emblems/emblem.api.ts";
3
- export { bindTurn } from "./turn-control.ts";
3
+ export { bindTurn } from "./turn-full.ts";
4
4
  export type { TurnAngles, TurnContainer, TurnControlOptions, TurnController, TurnEvent } from "./turn-control.ts";
5
5
  export { EMBLEM_TABLE } from "./emblems/emblem.registry.ts";
6
6
  export { AWARD_MEDAL } from "./emblems/award/medal.ts";
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // its own module (`@johnmorrisdotca/dougu-sports/trophy/hockey-cup`), and for the functions alone `@johnmorrisdotca/dougu-sports/emblems`.
3
3
  export * from "./emblems-entry.js";
4
4
  export { ballFrame, emblemFrame } from "./emblems/emblem.api.js";
5
- export { bindTurn } from "./turn-control.js";
5
+ export { bindTurn } from "./turn-full.js";
6
6
  export { EMBLEM_TABLE } from "./emblems/emblem.registry.js";
7
7
  export { AWARD_MEDAL } from "./emblems/award/medal.js";
8
8
  export { AWARD_BADGE } from "./emblems/award/badge.js";
@@ -1,5 +1,13 @@
1
- import { type EmblemRef } from "./emblems/emblem.api.ts";
2
- import type { FrameOptions } from "./emblems/emblem.core.ts";
1
+ import type { EmblemRef } from "./emblems/emblem.api.ts";
2
+ import type { Emblem, FrameOptions } from "./emblems/emblem.core.ts";
3
+ /** What reading an id needs of the registry: `emblemOf`, `renderEmblem` and `emblemFrame` of `emblem.api`. The main entry hands them to `bindTurn` itself; `./turn` loads them when an id is bound. */
4
+ export interface TurnRegistry {
5
+ emblemOf(ref: EmblemRef): {
6
+ emblem: Emblem;
7
+ };
8
+ renderEmblem(ref: EmblemRef, options?: FrameOptions): string;
9
+ emblemFrame(ref: EmblemRef, options?: FrameOptions): string;
10
+ }
3
11
  /** The three angles of a turn, in degrees. */
4
12
  export interface TurnAngles {
5
13
  /** About the vertical axis, from -180 (excluded) to 180. */
@@ -37,8 +45,13 @@ export interface TurnController {
37
45
  readonly angles: TurnAngles;
38
46
  /** Whether the auto-turn is running. */
39
47
  readonly playing: boolean;
40
- /** Whether the emblem now drawn can be turned at all (read from the registry, so an emblem that gains a turn is turnable here without a change). */
48
+ /** Whether the emblem now drawn can be turned at all (read from the emblem's own frames, so an emblem that gains a turn is turnable here without a change). `false` until an id has loaded; see `ready`. */
41
49
  readonly turnable: boolean;
50
+ /**
51
+ * Resolves when the emblem is drawn and bound. At once (already resolved) for an emblem object, and for an id from the main entry or once the registry has loaded; otherwise after the registry's chunk has loaded.
52
+ * Rejects for an id that names no emblem.
53
+ */
54
+ readonly ready: Promise<void>;
42
55
  /** Sets some of the angles (wrapped or clamped as a drag would) and draws. Pauses the auto-turn when `pauseOnInteract` is on. */
43
56
  setAngles(angles: Partial<TurnAngles>): void;
44
57
  /** Draws another emblem, or the same one with other options, and keeps the angles (unless the options carry some). */
@@ -86,16 +99,22 @@ export interface TurnContainer {
86
99
  /** An angle wrapped into -180 (excluded) to 180, as the demo's slider is. */
87
100
  export declare function wrapAngle(value: number): number;
88
101
  /**
89
- * Binds an emblem to an element so that it can be turned: it draws `emblemFrame` into `container`, and a drag turns it (left and right are yaw, up and down are pitch, and Shift on a ball is roll), the arrow keys turn
102
+ * Binds an emblem to an element so that it can be turned: it draws the emblem's `frame` into `container`, and a drag turns it (left and right are yaw, up and down are pitch, and Shift on a ball is roll), the arrow keys turn
90
103
  * it, Home or 0 puts it back, and Space starts and stops an auto-turn. An emblem that cannot turn is drawn as it is, and nothing is listened to. Nothing moves by itself: `autoPlay` is off by default and, under
91
104
  * `prefers-reduced-motion`, only starts when it is `"force"`. The element gets `touch-action: none`, `user-select: none`, a grab cursor, a tab stop, a role description and a hint; `destroy()` takes them away.
105
+ *
106
+ * Pass the emblem itself (`TROPHY_HOCKEY_CUP`, from its own module) and nothing but the turning and that one emblem is in the bundle. Pass an id (`"trophy:hockey-cup"`) and the registry is loaded on
107
+ * demand: the element is drawn when `ready` resolves, not before. The main entry's `bindTurn` has the registry already and draws an id at once.
92
108
  * @example
93
109
  * ```ts
94
110
  * import { bindTurn } from "@johnmorrisdotca/dougu-sports/turn";
95
- * const turner = bindTurn(document.getElementById("cup")!, "trophy:tennis-cup", { size: 160 });
111
+ * import { TROPHY_TENNIS_CUP } from "@johnmorrisdotca/dougu-sports/trophy/tennis-cup";
112
+ * const turner = bindTurn(document.getElementById("cup")!, TROPHY_TENNIS_CUP, { size: 160 });
96
113
  * turner.setAngles({ yaw: 40 });
97
114
  * console.log(turner.angles.yaw);
98
115
  * // 40
99
116
  * ```
100
117
  */
101
118
  export declare function bindTurn(container: TurnContainer, ref: EmblemRef, options?: TurnControlOptions): TurnController;
119
+ /** `bindTurn` with the registry in hand (or none, to load it when an id is bound): the main entry gives it, so that an id is drawn at once. An emblem object needs neither. */
120
+ export declare function bindTurnWith(registry: TurnRegistry | undefined, container: TurnContainer, ref: EmblemRef, options?: TurnControlOptions): TurnController;
@@ -1,6 +1,3 @@
1
- // `bindTurn`: an emblem that a pointer, the arrow keys, the Space bar or a clock can turn. It draws `emblemFrame` into an element and listens to that element, and it is a module of its own, so importing an
2
- // emblem never pulls it in. Nothing here moves by itself: an auto-turn starts only when the caller asks for it, and not at all under `prefers-reduced-motion` unless the caller forces it.
3
- import { emblemFrame, emblemOf, renderEmblem } from "./emblems/emblem.api.js";
4
1
  const WORDS = {
5
2
  en: { role: "turnable emblem", hint: "Drag, or use the arrow keys, to turn it. Space starts and stops the turning." },
6
3
  ja: { role: "回せるエンブレム", hint: "ドラッグするか矢印キーで回せます。スペースキーで自動回転を始めたり止めたりできます。" },
@@ -13,7 +10,7 @@ export function wrapAngle(value) {
13
10
  const finite = (value, fallback) => (typeof value === "number" && Number.isFinite(value) ? value : fallback);
14
11
  const clamp = (value, low, high) => Math.max(low, Math.min(high, value));
15
12
  const globals = globalThis;
16
- /** Whether an emblem turns at all, read from the registry's own frames: a turned view that differs from the resting one. An emblem may say so itself with a `turnable` flag. */
13
+ /** Whether an emblem turns at all, read from the emblem's own frames: a turned view that differs from the resting one. An emblem may say so itself with a `turnable` flag. */
17
14
  const turns = new WeakMap();
18
15
  function canTurn(emblem) {
19
16
  const flag = emblem.turnable;
@@ -26,22 +23,41 @@ function canTurn(emblem) {
26
23
  }
27
24
  return known;
28
25
  }
26
+ const fromEmblem = (emblem) => ({ emblem, still: (o) => emblem.svg(o), frame: (o) => emblem.frame(o) });
27
+ const fromId = (registry, ref) => ({ emblem: registry.emblemOf(ref).emblem, still: (o) => registry.renderEmblem(ref, o), frame: (o) => registry.emblemFrame(ref, o) });
28
+ /** The registry, once something has loaded it: later ids are bound at once. */
29
+ let loaded;
30
+ let loading;
31
+ const load = () => {
32
+ loading ??= import("./turn-registry.js").then((api) => (loaded = api));
33
+ return loading;
34
+ };
29
35
  /**
30
- * Binds an emblem to an element so that it can be turned: it draws `emblemFrame` into `container`, and a drag turns it (left and right are yaw, up and down are pitch, and Shift on a ball is roll), the arrow keys turn
36
+ * Binds an emblem to an element so that it can be turned: it draws the emblem's `frame` into `container`, and a drag turns it (left and right are yaw, up and down are pitch, and Shift on a ball is roll), the arrow keys turn
31
37
  * it, Home or 0 puts it back, and Space starts and stops an auto-turn. An emblem that cannot turn is drawn as it is, and nothing is listened to. Nothing moves by itself: `autoPlay` is off by default and, under
32
38
  * `prefers-reduced-motion`, only starts when it is `"force"`. The element gets `touch-action: none`, `user-select: none`, a grab cursor, a tab stop, a role description and a hint; `destroy()` takes them away.
39
+ *
40
+ * Pass the emblem itself (`TROPHY_HOCKEY_CUP`, from its own module) and nothing but the turning and that one emblem is in the bundle. Pass an id (`"trophy:hockey-cup"`) and the registry is loaded on
41
+ * demand: the element is drawn when `ready` resolves, not before. The main entry's `bindTurn` has the registry already and draws an id at once.
33
42
  * @example
34
43
  * ```ts
35
44
  * import { bindTurn } from "@johnmorrisdotca/dougu-sports/turn";
36
- * const turner = bindTurn(document.getElementById("cup")!, "trophy:tennis-cup", { size: 160 });
45
+ * import { TROPHY_TENNIS_CUP } from "@johnmorrisdotca/dougu-sports/trophy/tennis-cup";
46
+ * const turner = bindTurn(document.getElementById("cup")!, TROPHY_TENNIS_CUP, { size: 160 });
37
47
  * turner.setAngles({ yaw: 40 });
38
48
  * console.log(turner.angles.yaw);
39
49
  * // 40
40
50
  * ```
41
51
  */
42
52
  export function bindTurn(container, ref, options = {}) {
53
+ return bindTurnWith(undefined, container, ref, options);
54
+ }
55
+ /** `bindTurn` with the registry in hand (or none, to load it when an id is bound): the main entry gives it, so that an id is drawn at once. An emblem object needs neither. */
56
+ export function bindTurnWith(registry, container, ref, options = {}) {
43
57
  let current = ref;
44
- let emblem = emblemOf(ref).emblem;
58
+ let drawer;
59
+ let version = 0;
60
+ let pending;
45
61
  let turnable = false;
46
62
  let sensitivity = 1.5;
47
63
  let range = [-60, 60];
@@ -95,7 +111,9 @@ export function bindTurn(container, ref, options = {}) {
95
111
  }
96
112
  const at = angles.yaw === 0 && angles.pitch === 0 && angles.roll === 0;
97
113
  // The resting view of an emblem that turns is drawn in the box of the turn too, so that the element is the same size at every angle and a drag does not make the emblem jump or zoom at its first step.
98
- container.innerHTML = !turnable ? renderEmblem(current, frame) : at ? renderEmblem(current, { turnBox: true, ...frame }) : emblemFrame(current, { ...frame, ...angles });
114
+ if (drawer === undefined)
115
+ return;
116
+ container.innerHTML = !turnable ? drawer.still(frame) : at ? drawer.still({ turnBox: true, ...frame }) : drawer.frame({ ...frame, ...angles });
99
117
  };
100
118
  /** One drawing for any number of changes within a frame. */
101
119
  const schedule = () => {
@@ -178,7 +196,7 @@ export function bindTurn(container, ref, options = {}) {
178
196
  drag.y = y;
179
197
  if (dx === 0 && dy === 0)
180
198
  return;
181
- if (event.shiftKey === true && emblem.kind === "ball")
199
+ if (event.shiftKey === true && drawer?.emblem.kind === "ball")
182
200
  place({ roll: angles.roll + dx, pitch: angles.pitch - dy });
183
201
  else
184
202
  place({ yaw: angles.yaw + dx, pitch: angles.pitch - dy });
@@ -287,24 +305,46 @@ export function bindTurn(container, ref, options = {}) {
287
305
  drag = undefined;
288
306
  held = false;
289
307
  };
290
- const settle = () => {
291
- emblem = emblemOf(current).emblem;
292
- turnable = canTurn(emblem);
308
+ const adopt = (next) => {
309
+ drawer = next;
310
+ turnable = canTurn(next.emblem);
311
+ detach();
293
312
  if (turnable)
294
313
  attach();
295
- else {
296
- detach();
314
+ else
297
315
  setPlaying(false);
298
- }
299
316
  };
300
- configure(options);
301
- place({ yaw: options.yaw, pitch: options.pitch, roll: options.roll });
302
- settle();
303
- draw();
304
- if (turnable && options.autoPlay !== undefined && options.autoPlay !== false) {
317
+ /** Binds what `current` names: at once for an emblem, or for an id when the registry is in hand; otherwise when it has loaded. Gives the wait, if there is one. */
318
+ const settle = (first) => {
319
+ const mine = ++version;
320
+ const now = current;
321
+ if (typeof now !== "string")
322
+ return void adopt(fromEmblem(now));
323
+ const known = registry ?? loaded;
324
+ if (known !== undefined)
325
+ return void adopt(fromId(known, now));
326
+ return load().then((api) => {
327
+ if (mine !== version || destroyed)
328
+ return;
329
+ adopt(fromId(api, now));
330
+ draw();
331
+ if (first)
332
+ autoStart();
333
+ });
334
+ };
335
+ const autoStart = () => {
336
+ if (!turnable || options.autoPlay === undefined || options.autoPlay === false)
337
+ return;
305
338
  const reduced = globals.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
306
339
  if (options.autoPlay === "force" || !reduced)
307
340
  setPlaying(true);
341
+ };
342
+ configure(options);
343
+ place({ yaw: options.yaw, pitch: options.pitch, roll: options.roll });
344
+ pending = settle(true);
345
+ if (pending === undefined) {
346
+ draw();
347
+ autoStart();
308
348
  }
309
349
  return {
310
350
  get angles() {
@@ -316,6 +356,9 @@ export function bindTurn(container, ref, options = {}) {
316
356
  get turnable() {
317
357
  return turnable;
318
358
  },
359
+ get ready() {
360
+ return pending ?? Promise.resolve();
361
+ },
319
362
  setAngles(partial) {
320
363
  if (destroyed)
321
364
  return;
@@ -331,9 +374,9 @@ export function bindTurn(container, ref, options = {}) {
331
374
  place({ yaw: o.yaw, pitch: o.pitch, roll: o.roll });
332
375
  // The pitch the new range allows.
333
376
  place({ pitch: angles.pitch });
334
- detach();
335
- settle();
336
- draw();
377
+ pending = settle(false);
378
+ if (pending === undefined)
379
+ draw();
337
380
  },
338
381
  play() {
339
382
  if (!turnable || destroyed)
@@ -355,6 +398,7 @@ export function bindTurn(container, ref, options = {}) {
355
398
  return;
356
399
  setPlaying(false);
357
400
  destroyed = true;
401
+ version++;
358
402
  if (renderId !== undefined)
359
403
  globals.cancelAnimationFrame?.(renderId);
360
404
  renderId = undefined;
@@ -0,0 +1,15 @@
1
+ import { type EmblemRef } from "./emblems/emblem.api.ts";
2
+ import { type TurnContainer, type TurnControlOptions, type TurnController } from "./turn-control.ts";
3
+ /**
4
+ * Binds an emblem to an element so that a drag, the arrow keys, Space or a clock turn it; see `bindTurn` of `@johnmorrisdotca/dougu-sports/turn`, which takes an emblem object and loads nothing else. This one is
5
+ * from the main entry: an id is read from the registry and drawn at once, with no `await`.
6
+ * @example
7
+ * ```ts
8
+ * import { bindTurn } from "@johnmorrisdotca/dougu-sports";
9
+ * const turner = bindTurn(document.getElementById("cup")!, "trophy:tennis-cup", { size: 160 });
10
+ * turner.setAngles({ yaw: 40 });
11
+ * console.log(turner.angles.yaw);
12
+ * // 40
13
+ * ```
14
+ */
15
+ export declare function bindTurn(container: TurnContainer, ref: EmblemRef, options?: TurnControlOptions): TurnController;
@@ -0,0 +1,19 @@
1
+ // `bindTurn` of the main entry: the same as the light one of `./turn`, with the registry already in hand, so an id is drawn at once (as it always was) and an emblem object works too. It is a function of its own, so
2
+ // a page that imports only emblems from the main entry does not pull it in; one that imports `bindTurn` from here has the registry in the bundle anyway.
3
+ import { emblemFrame, emblemOf, renderEmblem } from "./emblems/emblem.api.js";
4
+ import { bindTurnWith } from "./turn-control.js";
5
+ /**
6
+ * Binds an emblem to an element so that a drag, the arrow keys, Space or a clock turn it; see `bindTurn` of `@johnmorrisdotca/dougu-sports/turn`, which takes an emblem object and loads nothing else. This one is
7
+ * from the main entry: an id is read from the registry and drawn at once, with no `await`.
8
+ * @example
9
+ * ```ts
10
+ * import { bindTurn } from "@johnmorrisdotca/dougu-sports";
11
+ * const turner = bindTurn(document.getElementById("cup")!, "trophy:tennis-cup", { size: 160 });
12
+ * turner.setAngles({ yaw: 40 });
13
+ * console.log(turner.angles.yaw);
14
+ * // 40
15
+ * ```
16
+ */
17
+ export function bindTurn(container, ref, options = {}) {
18
+ return bindTurnWith({ emblemOf, renderEmblem, emblemFrame }, container, ref, options);
19
+ }
@@ -0,0 +1 @@
1
+ export { emblemFrame, emblemOf, renderEmblem } from "./emblems/emblem.api.ts";
@@ -0,0 +1,3 @@
1
+ // What `bindTurn` loads when it is given an id: the three functions that read the registry. A module of its own, so that the dynamic import has a target no static import of `./turn` shares, and a bundler
2
+ // makes it a chunk of its own.
3
+ export { emblemFrame, emblemOf, renderEmblem } from "./emblems/emblem.api.js";
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.7.2";
1
+ export declare const VERSION = "0.8.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.7.2";
1
+ export const VERSION = "0.8.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johnmorrisdotca/dougu-sports",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "Dougu Sports 道具, sports emblems as SVG strings: trophies, medals, crests, sport icons, and the ball and gear of every sport, in metal, line or flat colour, with optional 3D turning. Light and dark, accessible, tree-shakeable. Zero dependencies.",
5
5
  "keywords": [
6
6
  "sports",
@@ -113,18 +113,18 @@
113
113
  "scripts": {
114
114
  "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json",
115
115
  "lint": "eslint .",
116
- "typecheck": "tsc -p tsconfig.check.json",
117
- "test": "node --test test/*.test.ts test/*.test.js",
118
- "test:package": "pnpm build && node scripts/check-package.mjs",
119
- "test:readme": "pnpm build && node scripts/make-gallery.mjs --check && node scripts/check-readme-examples.mjs",
120
- "check": "pnpm lint && pnpm typecheck && pnpm test && pnpm build && node scripts/check-package.mjs",
116
+ "typecheck": "tsc -p tsconfig.check.json && tsc -p tsconfig.tools.json && tsc -p tsconfig.demo.json",
117
+ "test": "node --test test/*.test.ts",
118
+ "test:package": "pnpm build && node scripts/check-package.ts",
119
+ "test:readme": "pnpm build && node scripts/make-gallery.ts --check && node scripts/check-readme-examples.ts",
120
+ "check": "pnpm lint && pnpm typecheck && pnpm test && pnpm build && node scripts/check-package.ts",
121
121
  "prepublishOnly": "pnpm check",
122
122
  "family:readme": "node scripts/family-readme.mjs",
123
- "dev": "pnpm build && node scripts/dev.mjs",
124
- "gallery": "pnpm build && node scripts/make-gallery.mjs",
125
- "site": "pnpm build && node scripts/site.mjs",
123
+ "dev": "pnpm build && node scripts/dev.ts",
124
+ "gallery": "pnpm build && node scripts/make-gallery.ts",
125
+ "site": "pnpm build && node scripts/site.ts",
126
126
  "test:demo": "pnpm site && playwright test",
127
- "screenshots": "pnpm site && node scripts/screenshots.mjs"
127
+ "screenshots": "pnpm site && node scripts/screenshots.ts"
128
128
  },
129
129
  "devDependencies": {
130
130
  "@eslint/js": "^9.0.0",