sketchmark 1.1.1 → 1.1.2

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/dist/index.cjs CHANGED
@@ -10567,13 +10567,13 @@ function exportHTML(svg, dslSource, opts = {}) {
10567
10567
  </head>
10568
10568
  <body>
10569
10569
  <div class="diagram">${svgStr}</div>
10570
- <details class="dsl"><summary style="cursor:pointer;color:#f0c96a">DSL source</summary><pre>${escapeHtml(dslSource)}</pre></details>
10570
+ <details class="dsl"><summary style="cursor:pointer;color:#f0c96a">DSL source</summary><pre>${escapeHtml$1(dslSource)}</pre></details>
10571
10571
  </body>
10572
10572
  </html>`;
10573
10573
  const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
10574
10574
  download(blob, opts.filename ?? 'diagram.html');
10575
10575
  }
10576
- function escapeHtml(s) {
10576
+ function escapeHtml$1(s) {
10577
10577
  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
10578
10578
  }
10579
10579
  // ── GIF stub (requires gifshot or gif.js at runtime) ──────
@@ -10799,7 +10799,7 @@ const CANVAS_CSS = `
10799
10799
  .skm-canvas__viewport.is-panning{cursor:grabbing}
10800
10800
  .skm-canvas--dark .skm-canvas__viewport{background:#12100a}
10801
10801
  .skm-canvas__grid{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
10802
- .skm-canvas__world{position:absolute;top:0;left:0;transform-origin:0 0;will-change:transform}
10802
+ .skm-canvas__world{position:absolute;top:0;left:0;transform-origin:0 0;}
10803
10803
  .skm-canvas__controls{position:absolute;right:14px;bottom:14px;display:flex;flex-direction:column;align-items:center;gap:4px;z-index:2}
10804
10804
  .skm-canvas__zoom{min-width:40px;text-align:center;color:#8a6040;font-size:10px}
10805
10805
  .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}
@@ -11449,20 +11449,44 @@ const EDITOR_CSS = `
11449
11449
  color: #fff;
11450
11450
  }
11451
11451
 
11452
- .skm-editor__input {
11452
+ .skm-editor__surface {
11453
+ position: relative;
11453
11454
  flex: 1;
11454
- width: 100%;
11455
11455
  min-height: 0;
11456
- border: 0;
11457
- outline: 0;
11458
- resize: none;
11459
11456
  background: #1c1608;
11460
- color: #e0c898;
11457
+ overflow: hidden;
11458
+ }
11459
+
11460
+ .skm-editor__highlight,
11461
+ .skm-editor__input {
11462
+ position: absolute;
11463
+ inset: 0;
11464
+ width: 100%;
11465
+ height: 100%;
11461
11466
  padding: 12px 14px;
11462
11467
  font: inherit;
11463
11468
  font-size: 12px;
11464
11469
  line-height: 1.7;
11465
11470
  tab-size: 2;
11471
+ white-space: pre-wrap;
11472
+ overflow: auto;
11473
+ }
11474
+
11475
+ .skm-editor__highlight {
11476
+ margin: 0;
11477
+ border: 0;
11478
+ background: #1c1608;
11479
+ color: #e0c898;
11480
+ pointer-events: none;
11481
+ word-break: break-word;
11482
+ }
11483
+
11484
+ .skm-editor__input {
11485
+ border: 0;
11486
+ outline: 0;
11487
+ resize: none;
11488
+ background: transparent;
11489
+ color: transparent;
11466
11490
  caret-color: #f0c96a;
11467
11491
  }
11468
11492
 
@@ -11470,6 +11494,40 @@ const EDITOR_CSS = `
11470
11494
  color: #80633b;
11471
11495
  }
11472
11496
 
11497
+ .skm-editor__input::selection {
11498
+ background: rgba(240, 201, 106, 0.22);
11499
+ }
11500
+
11501
+ .skm-editor__token--keyword {
11502
+ color: #e07040;
11503
+ }
11504
+
11505
+ .skm-editor__token--property {
11506
+ color: #70a8d0;
11507
+ }
11508
+
11509
+ .skm-editor__token--string {
11510
+ color: #8db870;
11511
+ }
11512
+
11513
+ .skm-editor__token--number {
11514
+ color: #d4a020;
11515
+ }
11516
+
11517
+ .skm-editor__token--comment {
11518
+ color: #6a5a3a;
11519
+ }
11520
+
11521
+ .skm-editor__token--connector {
11522
+ color: #c8b070;
11523
+ }
11524
+
11525
+ .skm-editor__token--color {
11526
+ color: var(--skm-editor-color, #f0c96a);
11527
+ box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.08);
11528
+ font-weight: 600;
11529
+ }
11530
+
11473
11531
  .skm-editor__error {
11474
11532
  display: none;
11475
11533
  flex-shrink: 0;
@@ -11486,12 +11544,112 @@ const EDITOR_CSS = `
11486
11544
  display: block;
11487
11545
  }
11488
11546
  `;
11547
+ const CONNECTORS = ["<-->", "<->", "-->", "<--", "---", "--", "->", "<-"];
11548
+ const HEX_COLOR_RE = /#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})\b/g;
11489
11549
  function defaultFormatter(value) {
11490
11550
  return normalizeNewlines(value)
11491
11551
  .split("\n")
11492
11552
  .map((line) => line.replace(/[ \t]+$/g, ""))
11493
11553
  .join("\n");
11494
11554
  }
11555
+ function escapeHtml(value) {
11556
+ return value
11557
+ .replace(/&/g, "&amp;")
11558
+ .replace(/</g, "&lt;")
11559
+ .replace(/>/g, "&gt;")
11560
+ .replace(/"/g, "&quot;");
11561
+ }
11562
+ function wrapToken(kind, value) {
11563
+ return `<span class="skm-editor__token skm-editor__token--${kind}">${escapeHtml(value)}</span>`;
11564
+ }
11565
+ function renderColorLiteral(value) {
11566
+ return `<span class="skm-editor__token skm-editor__token--color" style="--skm-editor-color:${value}">${escapeHtml(value)}</span>`;
11567
+ }
11568
+ function renderStringToken(value) {
11569
+ HEX_COLOR_RE.lastIndex = 0;
11570
+ if (!HEX_COLOR_RE.test(value)) {
11571
+ return wrapToken("string", value);
11572
+ }
11573
+ HEX_COLOR_RE.lastIndex = 0;
11574
+ let html = "";
11575
+ let lastIndex = 0;
11576
+ let match = null;
11577
+ while ((match = HEX_COLOR_RE.exec(value))) {
11578
+ if (match.index > lastIndex) {
11579
+ html += wrapToken("string", value.slice(lastIndex, match.index));
11580
+ }
11581
+ html += renderColorLiteral(match[0]);
11582
+ lastIndex = match.index + match[0].length;
11583
+ }
11584
+ if (lastIndex < value.length) {
11585
+ html += wrapToken("string", value.slice(lastIndex));
11586
+ }
11587
+ return html;
11588
+ }
11589
+ function renderPlainToken(value, nextChar) {
11590
+ if (/^-?\d/.test(value)) {
11591
+ return wrapToken("number", value);
11592
+ }
11593
+ if (nextChar === "=") {
11594
+ return wrapToken("property", value);
11595
+ }
11596
+ if (KEYWORDS.has(value)) {
11597
+ return wrapToken("keyword", value);
11598
+ }
11599
+ return escapeHtml(value);
11600
+ }
11601
+ function highlightLine(line) {
11602
+ let html = "";
11603
+ let index = 0;
11604
+ while (index < line.length) {
11605
+ const rest = line.slice(index);
11606
+ if (rest.startsWith("//") || rest.startsWith("#")) {
11607
+ html += wrapToken("comment", rest);
11608
+ break;
11609
+ }
11610
+ if (line[index] === "\"") {
11611
+ let end = index + 1;
11612
+ while (end < line.length) {
11613
+ if (line[end] === "\"" && line[end - 1] !== "\\") {
11614
+ end += 1;
11615
+ break;
11616
+ }
11617
+ end += 1;
11618
+ }
11619
+ html += renderStringToken(line.slice(index, end));
11620
+ index = end;
11621
+ continue;
11622
+ }
11623
+ const connector = CONNECTORS.find((candidate) => line.startsWith(candidate, index));
11624
+ if (connector) {
11625
+ html += wrapToken("connector", connector);
11626
+ index += connector.length;
11627
+ continue;
11628
+ }
11629
+ const wordMatch = /^[A-Za-z_][A-Za-z0-9_-]*/.exec(rest);
11630
+ if (wordMatch) {
11631
+ const word = wordMatch[0];
11632
+ const nextChar = line[index + word.length] ?? "";
11633
+ html += renderPlainToken(word, nextChar);
11634
+ index += word.length;
11635
+ continue;
11636
+ }
11637
+ const numberMatch = /^-?\d+(?:\.\d+)?/.exec(rest);
11638
+ if (numberMatch) {
11639
+ html += wrapToken("number", numberMatch[0]);
11640
+ index += numberMatch[0].length;
11641
+ continue;
11642
+ }
11643
+ html += escapeHtml(line[index]);
11644
+ index += 1;
11645
+ }
11646
+ return html;
11647
+ }
11648
+ function renderHighlightedValue(value) {
11649
+ const normalized = normalizeNewlines(value);
11650
+ const html = normalized.split("\n").map(highlightLine).join("\n");
11651
+ return html || " ";
11652
+ }
11495
11653
  class SketchmarkEditor {
11496
11654
  constructor(options) {
11497
11655
  this.emitter = new EventEmitter();
@@ -11528,16 +11686,23 @@ class SketchmarkEditor {
11528
11686
  if (options.showClearButton !== false)
11529
11687
  this.toolbar.appendChild(clearButton);
11530
11688
  this.toolbar.appendChild(hint);
11689
+ this.surface = document.createElement("div");
11690
+ this.surface.className = "skm-editor__surface";
11691
+ this.highlightElement = document.createElement("pre");
11692
+ this.highlightElement.className = "skm-editor__highlight";
11693
+ this.highlightElement.setAttribute("aria-hidden", "true");
11531
11694
  this.textarea = document.createElement("textarea");
11532
11695
  this.textarea.className = "skm-editor__input";
11533
11696
  this.textarea.spellcheck = false;
11534
11697
  this.textarea.placeholder = options.placeholder ?? "diagram\nbox a label=\"Hello\"\nend";
11535
11698
  this.textarea.value = normalizeNewlines(options.value ?? DEFAULT_CLEAR_VALUE);
11536
11699
  this.textarea.addEventListener("input", () => {
11700
+ this.syncHighlight();
11537
11701
  const payload = { value: this.getValue(), editor: this };
11538
11702
  options.onChange?.(payload.value, this);
11539
11703
  this.emitter.emit("change", payload);
11540
11704
  });
11705
+ this.textarea.addEventListener("scroll", () => this.syncScroll());
11541
11706
  this.textarea.addEventListener("keydown", (event) => {
11542
11707
  if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
11543
11708
  event.preventDefault();
@@ -11549,9 +11714,12 @@ class SketchmarkEditor {
11549
11714
  if (options.showToolbar !== false) {
11550
11715
  this.root.appendChild(this.toolbar);
11551
11716
  }
11552
- this.root.appendChild(this.textarea);
11717
+ this.surface.appendChild(this.highlightElement);
11718
+ this.surface.appendChild(this.textarea);
11719
+ this.root.appendChild(this.surface);
11553
11720
  this.root.appendChild(this.errorElement);
11554
11721
  host.appendChild(this.root);
11722
+ this.syncHighlight();
11555
11723
  if (options.autoFocus) {
11556
11724
  this.focus();
11557
11725
  }
@@ -11561,6 +11729,7 @@ class SketchmarkEditor {
11561
11729
  }
11562
11730
  setValue(value, emitChange = false) {
11563
11731
  this.textarea.value = normalizeNewlines(value);
11732
+ this.syncHighlight();
11564
11733
  if (emitChange) {
11565
11734
  const payload = { value: this.getValue(), editor: this };
11566
11735
  this.options.onChange?.(payload.value, this);
@@ -11602,6 +11771,14 @@ class SketchmarkEditor {
11602
11771
  destroy() {
11603
11772
  this.root.remove();
11604
11773
  }
11774
+ syncHighlight() {
11775
+ this.highlightElement.innerHTML = renderHighlightedValue(this.textarea.value);
11776
+ this.syncScroll();
11777
+ }
11778
+ syncScroll() {
11779
+ this.highlightElement.scrollTop = this.textarea.scrollTop;
11780
+ this.highlightElement.scrollLeft = this.textarea.scrollLeft;
11781
+ }
11605
11782
  }
11606
11783
 
11607
11784
  const EMBED_STYLE_ID = "sketchmark-embed-ui";