sketchmark 1.1.1 → 1.1.3

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.

Potentially problematic release.


This version of sketchmark might be problematic. Click here for more details.

package/dist/index.js CHANGED
@@ -9246,11 +9246,13 @@ class AnimationController {
9246
9246
  this._config = _config;
9247
9247
  this._step = -1;
9248
9248
  this._pendingStepTimers = new Set();
9249
+ this._pendingNarrationTimers = new Set();
9249
9250
  this._transforms = new Map();
9250
9251
  this._listeners = [];
9251
9252
  // ── Narration caption ──
9252
9253
  this._captionEl = null;
9253
9254
  this._captionTextEl = null;
9255
+ this._narrationRunId = 0;
9254
9256
  // ── Annotations ──
9255
9257
  this._annotationLayer = null;
9256
9258
  this._annotations = [];
@@ -9513,20 +9515,30 @@ class AnimationController {
9513
9515
  }
9514
9516
  this.emit("step-change");
9515
9517
  }
9518
+ _clearTimerBucket(bucket) {
9519
+ bucket.forEach((id) => window.clearTimeout(id));
9520
+ bucket.clear();
9521
+ }
9516
9522
  _clearPendingStepTimers() {
9517
- this._pendingStepTimers.forEach((id) => window.clearTimeout(id));
9518
- this._pendingStepTimers.clear();
9523
+ this._clearTimerBucket(this._pendingStepTimers);
9519
9524
  }
9520
- _scheduleStep(fn, delayMs) {
9525
+ _cancelNarrationTyping() {
9526
+ this._narrationRunId += 1;
9527
+ this._clearTimerBucket(this._pendingNarrationTimers);
9528
+ }
9529
+ _scheduleTimer(fn, delayMs, bucket = this._pendingStepTimers) {
9521
9530
  if (delayMs <= 0) {
9522
9531
  fn();
9523
9532
  return;
9524
9533
  }
9525
9534
  const id = window.setTimeout(() => {
9526
- this._pendingStepTimers.delete(id);
9535
+ bucket.delete(id);
9527
9536
  fn();
9528
9537
  }, delayMs);
9529
- this._pendingStepTimers.add(id);
9538
+ bucket.add(id);
9539
+ }
9540
+ _scheduleStep(fn, delayMs) {
9541
+ this._scheduleTimer(fn, delayMs, this._pendingStepTimers);
9530
9542
  }
9531
9543
  _stepWaitMs(step, fallbackMs) {
9532
9544
  const delay = Math.max(0, step.delay ?? 0);
@@ -9570,6 +9582,7 @@ class AnimationController {
9570
9582
  }
9571
9583
  _clearAll() {
9572
9584
  this._clearPendingStepTimers();
9585
+ this._cancelNarrationTyping();
9573
9586
  this._cancelSpeech();
9574
9587
  this._transforms.clear();
9575
9588
  // Nodes
@@ -10124,6 +10137,7 @@ class AnimationController {
10124
10137
  _doNarrate(text, silent) {
10125
10138
  if (!this._captionEl || !this._captionTextEl)
10126
10139
  return;
10140
+ this._cancelNarrationTyping();
10127
10141
  this._captionEl.style.opacity = "1";
10128
10142
  if (silent || !text) {
10129
10143
  this._captionTextEl.textContent = text;
@@ -10134,12 +10148,16 @@ class AnimationController {
10134
10148
  this._speak(text);
10135
10149
  // Typing effect
10136
10150
  this._captionTextEl.textContent = "";
10151
+ const narrationRunId = this._narrationRunId;
10137
10152
  let charIdx = 0;
10138
10153
  const typeNext = () => {
10154
+ if (this._narrationRunId !== narrationRunId || !this._captionTextEl)
10155
+ return;
10139
10156
  if (charIdx < text.length) {
10140
10157
  this._captionTextEl.textContent += text[charIdx++];
10141
- const id = window.setTimeout(typeNext, ANIMATION.narrationTypeMs);
10142
- this._pendingStepTimers.add(id);
10158
+ if (charIdx < text.length) {
10159
+ this._scheduleTimer(typeNext, ANIMATION.narrationTypeMs, this._pendingNarrationTimers);
10160
+ }
10143
10161
  }
10144
10162
  };
10145
10163
  typeNext();
@@ -10249,12 +10267,11 @@ class AnimationController {
10249
10267
  requestAnimationFrame(animate);
10250
10268
  }
10251
10269
  // After guide finishes: reveal rough.js element, remove guide
10252
- const id = window.setTimeout(() => {
10270
+ this._scheduleTimer(() => {
10253
10271
  roughEl.style.transition = `opacity 120ms ease`;
10254
10272
  roughEl.style.opacity = "1";
10255
10273
  guide.remove();
10256
10274
  }, dur + 30);
10257
- this._pendingStepTimers.add(id);
10258
10275
  }));
10259
10276
  }
10260
10277
  _doAnnotationCircle(target, silent) {
@@ -10565,13 +10582,13 @@ function exportHTML(svg, dslSource, opts = {}) {
10565
10582
  </head>
10566
10583
  <body>
10567
10584
  <div class="diagram">${svgStr}</div>
10568
- <details class="dsl"><summary style="cursor:pointer;color:#f0c96a">DSL source</summary><pre>${escapeHtml(dslSource)}</pre></details>
10585
+ <details class="dsl"><summary style="cursor:pointer;color:#f0c96a">DSL source</summary><pre>${escapeHtml$1(dslSource)}</pre></details>
10569
10586
  </body>
10570
10587
  </html>`;
10571
10588
  const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
10572
10589
  download(blob, opts.filename ?? 'diagram.html');
10573
10590
  }
10574
- function escapeHtml(s) {
10591
+ function escapeHtml$1(s) {
10575
10592
  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
10576
10593
  }
10577
10594
  // ── GIF stub (requires gifshot or gif.js at runtime) ──────
@@ -10797,7 +10814,7 @@ const CANVAS_CSS = `
10797
10814
  .skm-canvas__viewport.is-panning{cursor:grabbing}
10798
10815
  .skm-canvas--dark .skm-canvas__viewport{background:#12100a}
10799
10816
  .skm-canvas__grid{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
10800
- .skm-canvas__world{position:absolute;top:0;left:0;transform-origin:0 0;will-change:transform}
10817
+ .skm-canvas__world{position:absolute;top:0;left:0;transform-origin:0 0;}
10801
10818
  .skm-canvas__controls{position:absolute;right:14px;bottom:14px;display:flex;flex-direction:column;align-items:center;gap:4px;z-index:2}
10802
10819
  .skm-canvas__zoom{min-width:40px;text-align:center;color:#8a6040;font-size:10px}
10803
10820
  .skm-canvas__minimap{position:absolute;left:14px;bottom:14px;width:120px;height:80px;background:rgba(255,248,234,.94);border:1px solid #caba98;border-radius:6px;overflow:hidden;z-index:2}
@@ -11447,20 +11464,44 @@ const EDITOR_CSS = `
11447
11464
  color: #fff;
11448
11465
  }
11449
11466
 
11450
- .skm-editor__input {
11467
+ .skm-editor__surface {
11468
+ position: relative;
11451
11469
  flex: 1;
11452
- width: 100%;
11453
11470
  min-height: 0;
11454
- border: 0;
11455
- outline: 0;
11456
- resize: none;
11457
11471
  background: #1c1608;
11458
- color: #e0c898;
11472
+ overflow: hidden;
11473
+ }
11474
+
11475
+ .skm-editor__highlight,
11476
+ .skm-editor__input {
11477
+ position: absolute;
11478
+ inset: 0;
11479
+ width: 100%;
11480
+ height: 100%;
11459
11481
  padding: 12px 14px;
11460
11482
  font: inherit;
11461
11483
  font-size: 12px;
11462
11484
  line-height: 1.7;
11463
11485
  tab-size: 2;
11486
+ white-space: pre-wrap;
11487
+ overflow: auto;
11488
+ }
11489
+
11490
+ .skm-editor__highlight {
11491
+ margin: 0;
11492
+ border: 0;
11493
+ background: #1c1608;
11494
+ color: #e0c898;
11495
+ pointer-events: none;
11496
+ word-break: break-word;
11497
+ }
11498
+
11499
+ .skm-editor__input {
11500
+ border: 0;
11501
+ outline: 0;
11502
+ resize: none;
11503
+ background: transparent;
11504
+ color: transparent;
11464
11505
  caret-color: #f0c96a;
11465
11506
  }
11466
11507
 
@@ -11468,6 +11509,40 @@ const EDITOR_CSS = `
11468
11509
  color: #80633b;
11469
11510
  }
11470
11511
 
11512
+ .skm-editor__input::selection {
11513
+ background: rgba(240, 201, 106, 0.22);
11514
+ }
11515
+
11516
+ .skm-editor__token--keyword {
11517
+ color: #e07040;
11518
+ }
11519
+
11520
+ .skm-editor__token--property {
11521
+ color: #70a8d0;
11522
+ }
11523
+
11524
+ .skm-editor__token--string {
11525
+ color: #8db870;
11526
+ }
11527
+
11528
+ .skm-editor__token--number {
11529
+ color: #d4a020;
11530
+ }
11531
+
11532
+ .skm-editor__token--comment {
11533
+ color: #6a5a3a;
11534
+ }
11535
+
11536
+ .skm-editor__token--connector {
11537
+ color: #c8b070;
11538
+ }
11539
+
11540
+ .skm-editor__token--color {
11541
+ color: var(--skm-editor-color, #f0c96a);
11542
+ box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.08);
11543
+ font-weight: 600;
11544
+ }
11545
+
11471
11546
  .skm-editor__error {
11472
11547
  display: none;
11473
11548
  flex-shrink: 0;
@@ -11484,12 +11559,112 @@ const EDITOR_CSS = `
11484
11559
  display: block;
11485
11560
  }
11486
11561
  `;
11562
+ const CONNECTORS = ["<-->", "<->", "-->", "<--", "---", "--", "->", "<-"];
11563
+ const HEX_COLOR_RE = /#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})\b/g;
11487
11564
  function defaultFormatter(value) {
11488
11565
  return normalizeNewlines(value)
11489
11566
  .split("\n")
11490
11567
  .map((line) => line.replace(/[ \t]+$/g, ""))
11491
11568
  .join("\n");
11492
11569
  }
11570
+ function escapeHtml(value) {
11571
+ return value
11572
+ .replace(/&/g, "&amp;")
11573
+ .replace(/</g, "&lt;")
11574
+ .replace(/>/g, "&gt;")
11575
+ .replace(/"/g, "&quot;");
11576
+ }
11577
+ function wrapToken(kind, value) {
11578
+ return `<span class="skm-editor__token skm-editor__token--${kind}">${escapeHtml(value)}</span>`;
11579
+ }
11580
+ function renderColorLiteral(value) {
11581
+ return `<span class="skm-editor__token skm-editor__token--color" style="--skm-editor-color:${value}">${escapeHtml(value)}</span>`;
11582
+ }
11583
+ function renderStringToken(value) {
11584
+ HEX_COLOR_RE.lastIndex = 0;
11585
+ if (!HEX_COLOR_RE.test(value)) {
11586
+ return wrapToken("string", value);
11587
+ }
11588
+ HEX_COLOR_RE.lastIndex = 0;
11589
+ let html = "";
11590
+ let lastIndex = 0;
11591
+ let match = null;
11592
+ while ((match = HEX_COLOR_RE.exec(value))) {
11593
+ if (match.index > lastIndex) {
11594
+ html += wrapToken("string", value.slice(lastIndex, match.index));
11595
+ }
11596
+ html += renderColorLiteral(match[0]);
11597
+ lastIndex = match.index + match[0].length;
11598
+ }
11599
+ if (lastIndex < value.length) {
11600
+ html += wrapToken("string", value.slice(lastIndex));
11601
+ }
11602
+ return html;
11603
+ }
11604
+ function renderPlainToken(value, nextChar) {
11605
+ if (/^-?\d/.test(value)) {
11606
+ return wrapToken("number", value);
11607
+ }
11608
+ if (nextChar === "=") {
11609
+ return wrapToken("property", value);
11610
+ }
11611
+ if (KEYWORDS.has(value)) {
11612
+ return wrapToken("keyword", value);
11613
+ }
11614
+ return escapeHtml(value);
11615
+ }
11616
+ function highlightLine(line) {
11617
+ let html = "";
11618
+ let index = 0;
11619
+ while (index < line.length) {
11620
+ const rest = line.slice(index);
11621
+ if (rest.startsWith("//") || rest.startsWith("#")) {
11622
+ html += wrapToken("comment", rest);
11623
+ break;
11624
+ }
11625
+ if (line[index] === "\"") {
11626
+ let end = index + 1;
11627
+ while (end < line.length) {
11628
+ if (line[end] === "\"" && line[end - 1] !== "\\") {
11629
+ end += 1;
11630
+ break;
11631
+ }
11632
+ end += 1;
11633
+ }
11634
+ html += renderStringToken(line.slice(index, end));
11635
+ index = end;
11636
+ continue;
11637
+ }
11638
+ const connector = CONNECTORS.find((candidate) => line.startsWith(candidate, index));
11639
+ if (connector) {
11640
+ html += wrapToken("connector", connector);
11641
+ index += connector.length;
11642
+ continue;
11643
+ }
11644
+ const wordMatch = /^[A-Za-z_][A-Za-z0-9_-]*/.exec(rest);
11645
+ if (wordMatch) {
11646
+ const word = wordMatch[0];
11647
+ const nextChar = line[index + word.length] ?? "";
11648
+ html += renderPlainToken(word, nextChar);
11649
+ index += word.length;
11650
+ continue;
11651
+ }
11652
+ const numberMatch = /^-?\d+(?:\.\d+)?/.exec(rest);
11653
+ if (numberMatch) {
11654
+ html += wrapToken("number", numberMatch[0]);
11655
+ index += numberMatch[0].length;
11656
+ continue;
11657
+ }
11658
+ html += escapeHtml(line[index]);
11659
+ index += 1;
11660
+ }
11661
+ return html;
11662
+ }
11663
+ function renderHighlightedValue(value) {
11664
+ const normalized = normalizeNewlines(value);
11665
+ const html = normalized.split("\n").map(highlightLine).join("\n");
11666
+ return html || " ";
11667
+ }
11493
11668
  class SketchmarkEditor {
11494
11669
  constructor(options) {
11495
11670
  this.emitter = new EventEmitter();
@@ -11526,16 +11701,23 @@ class SketchmarkEditor {
11526
11701
  if (options.showClearButton !== false)
11527
11702
  this.toolbar.appendChild(clearButton);
11528
11703
  this.toolbar.appendChild(hint);
11704
+ this.surface = document.createElement("div");
11705
+ this.surface.className = "skm-editor__surface";
11706
+ this.highlightElement = document.createElement("pre");
11707
+ this.highlightElement.className = "skm-editor__highlight";
11708
+ this.highlightElement.setAttribute("aria-hidden", "true");
11529
11709
  this.textarea = document.createElement("textarea");
11530
11710
  this.textarea.className = "skm-editor__input";
11531
11711
  this.textarea.spellcheck = false;
11532
11712
  this.textarea.placeholder = options.placeholder ?? "diagram\nbox a label=\"Hello\"\nend";
11533
11713
  this.textarea.value = normalizeNewlines(options.value ?? DEFAULT_CLEAR_VALUE);
11534
11714
  this.textarea.addEventListener("input", () => {
11715
+ this.syncHighlight();
11535
11716
  const payload = { value: this.getValue(), editor: this };
11536
11717
  options.onChange?.(payload.value, this);
11537
11718
  this.emitter.emit("change", payload);
11538
11719
  });
11720
+ this.textarea.addEventListener("scroll", () => this.syncScroll());
11539
11721
  this.textarea.addEventListener("keydown", (event) => {
11540
11722
  if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
11541
11723
  event.preventDefault();
@@ -11547,9 +11729,12 @@ class SketchmarkEditor {
11547
11729
  if (options.showToolbar !== false) {
11548
11730
  this.root.appendChild(this.toolbar);
11549
11731
  }
11550
- this.root.appendChild(this.textarea);
11732
+ this.surface.appendChild(this.highlightElement);
11733
+ this.surface.appendChild(this.textarea);
11734
+ this.root.appendChild(this.surface);
11551
11735
  this.root.appendChild(this.errorElement);
11552
11736
  host.appendChild(this.root);
11737
+ this.syncHighlight();
11553
11738
  if (options.autoFocus) {
11554
11739
  this.focus();
11555
11740
  }
@@ -11559,6 +11744,7 @@ class SketchmarkEditor {
11559
11744
  }
11560
11745
  setValue(value, emitChange = false) {
11561
11746
  this.textarea.value = normalizeNewlines(value);
11747
+ this.syncHighlight();
11562
11748
  if (emitChange) {
11563
11749
  const payload = { value: this.getValue(), editor: this };
11564
11750
  this.options.onChange?.(payload.value, this);
@@ -11600,6 +11786,14 @@ class SketchmarkEditor {
11600
11786
  destroy() {
11601
11787
  this.root.remove();
11602
11788
  }
11789
+ syncHighlight() {
11790
+ this.highlightElement.innerHTML = renderHighlightedValue(this.textarea.value);
11791
+ this.syncScroll();
11792
+ }
11793
+ syncScroll() {
11794
+ this.highlightElement.scrollTop = this.textarea.scrollTop;
11795
+ this.highlightElement.scrollLeft = this.textarea.scrollLeft;
11796
+ }
11603
11797
  }
11604
11798
 
11605
11799
  const EMBED_STYLE_ID = "sketchmark-embed-ui";