@grundyjs/algiviz 0.8.3 → 0.9.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
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.0 � 2026-10-27
4
+
5
+ - Add player.nextStep() for exact manual stepping in generic and array players.
6
+ - Finish an in-progress transition without reading another snapshot; at a boundary,
7
+ consume one next snapshot. Complete terminal holds without extra source reads.
8
+ - Preserve rendering, disposal and error handling contracts; avoid rounding errors
9
+ when mixing timed advancement with manual steps.
10
+ - Update the minimal example, English/Russian READMEs and highlighted GIF to use nextStep().
11
+
3
12
  ## 0.8.3 — 2026-10-27
4
13
 
5
14
  - Streamline English and Russian READMEs around installation and a minimal example.
package/README.md CHANGED
@@ -81,11 +81,11 @@ const player = createPlayer(steps(), {
81
81
  renderer.render(ctx, frame);
82
82
  },
83
83
  });
84
- document.querySelector('#next').onclick = () => player.advance(1000);
84
+ document.querySelector('#next').onclick = () => player.nextStep();
85
85
  ```
86
86
 
87
87
  The first frame (`x = 40`) is drawn immediately inside `createPlayer(...)`. Each
88
- click advances one 1000 ms step: the player resumes the loop until the next
88
+ click requests the next state: the player resumes the loop until the next
89
89
  `yield`, then renders. There is no interpolation here: the handler draws only
90
90
  `current`. Each snapshot is created afresh and never mutated. Call
91
91
  `player.dispose()` when removing the visualization.
@@ -5,6 +5,9 @@ export interface Player<S, E> {
5
5
  readonly finished: boolean;
6
6
  /** Advance by playback time; pass zero to redraw without consuming steps. */
7
7
  advance(deltaMs: number): Frame<S, E>;
8
+ /** Finish the current transition, or consume one next step at a boundary.
9
+ * At the terminal boundary, finish the hold. Renders once, even if finished. */
10
+ nextStep(): Frame<S, E>;
8
11
  /** Close the source early. The last frame remains readable. */
9
12
  dispose(): void;
10
13
  }
@@ -13,6 +16,6 @@ export declare function createPlayer<S, E>(steps: Iterable<Step<S, E>>, options:
13
16
  stepDurationMs: number;
14
17
  finalHoldMs: number;
15
18
  isTerminal: (step: Step<S, E>) => boolean;
16
- /** Called initially and on each advance, including advance(0). */
19
+ /** Called initially and on each advance or nextStep, including after completion. */
17
20
  render?: (frame: Frame<S, E>) => void;
18
21
  }): Player<S, E>;
@@ -59,6 +59,38 @@ export function createPlayer(steps, options) {
59
59
  return Object.freeze({
60
60
  get frame() { return frame(); },
61
61
  get finished() { return finished; },
62
+ nextStep() {
63
+ if (disposed)
64
+ throw new Error("Player has been disposed");
65
+ try {
66
+ if (!finished) {
67
+ if (elapsed === stepDurationMs) {
68
+ if (options.isTerminal(current)) {
69
+ hold = finalHoldMs;
70
+ }
71
+ else {
72
+ const next = iterator.next();
73
+ if (next.done)
74
+ throw new RangeError("Step source ended without a terminal step");
75
+ previous = current;
76
+ current = next.value;
77
+ }
78
+ }
79
+ // Assign the endpoint directly: time arithmetic can undershoot or
80
+ // spill a rounding remainder into an unwanted additional step.
81
+ elapsed = stepDurationMs;
82
+ complete();
83
+ }
84
+ const result = frame();
85
+ options.render?.(result);
86
+ return result;
87
+ }
88
+ catch (error) {
89
+ disposed = true;
90
+ closeAfterFailure();
91
+ throw error;
92
+ }
93
+ },
62
94
  advance(deltaMs) {
63
95
  if (disposed)
64
96
  throw new Error("Player has been disposed");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grundyjs/algiviz",
3
- "version": "0.8.3",
3
+ "version": "0.9.0",
4
4
  "description": "TypeScript toolkit for custom algorithm visualizations with step-by-step playback and extensible rendering",
5
5
  "type": "module",
6
6
  "files": [
package/ru/README.md CHANGED
@@ -80,11 +80,11 @@ const player = createPlayer(steps(), {
80
80
  renderer.render(ctx, frame);
81
81
  },
82
82
  });
83
- document.querySelector('#next').onclick = () => player.advance(1000);
83
+ document.querySelector('#next').onclick = () => player.nextStep();
84
84
  ```
85
85
 
86
86
  Первый кадр (`x = 40`) рисуется сразу внутри `createPlayer(...)`. Каждый клик
87
- проходит один шаг длительностью 1000 мс: плеер возобновляет цикл до следующего
87
+ запрашивает следующее состояние: плеер возобновляет цикл до следующего
88
88
  `yield`, затем вызывает отрисовку. Здесь нет плавного перехода: обработчик рисует
89
89
  только `current`. Каждый снимок создаётся заново и больше не изменяется. При
90
90
  удалении визуализации вызовите `player.dispose()`.