markstream-angular 2.0.12 → 2.0.13

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.
@@ -3,8 +3,8 @@ import { CommonModule } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
4
  import { Input, ChangeDetectionStrategy, Component, forwardRef, inject, ChangeDetectorRef, ViewChild, ViewContainerRef, ElementRef, Renderer2, Directive, HostListener, Injectable, InjectionToken, EventEmitter, Output } from '@angular/core';
5
5
  import { normalizeCustomHtmlTags, normalizeCustomHtmlTagName, parseMarkdownToStructure, mergeCustomHtmlTags, getMarkdown, sanitizeHtmlAttrs, VOID_HTML_TAGS, BLOCKED_HTML_TAGS, isHtmlTagHardBlocked, isHtmlTagBlocked, isUnsafeHtmlUrl, shouldOpenLinkInNewTab, sanitizeImageSrc, NON_STRUCTURING_HTML_TAGS, toSafeMermaidSvgMarkup, getHtmlTagFromContent, stripCustomHtmlWrapper, STANDARD_HTML_TAGS, hasCompleteHtmlTagContent } from 'stream-markdown-parser';
6
+ import { createPanGesture, createSmoothMarkdownStream } from 'markstream-core';
6
7
  import { computePosition, offset, flip, shift, autoUpdate } from '@floating-ui/dom';
7
- import { createSmoothMarkdownStream } from 'markstream-core';
8
8
 
9
9
  const TAG_TOKEN_RE = /<\/?([A-Z][\w:-]*)(?:\s[^<>]*)?>/gi;
10
10
  function hydrateCustomTagContent(nodes, source, customHtmlTags) {
@@ -4413,6 +4413,9 @@ class InfographicBlockNodeComponent {
4413
4413
  this.showSource = false;
4414
4414
  this.modalOpen = false;
4415
4415
  this.zoom = 1;
4416
+ this.translateX = 0;
4417
+ this.translateY = 0;
4418
+ this.isDragging = false;
4416
4419
  this.error = '';
4417
4420
  this.containerMinHeight = '';
4418
4421
  this.svgMarkup = '';
@@ -4422,6 +4425,31 @@ class InfographicBlockNodeComponent {
4422
4425
  this.renderScheduled = false;
4423
4426
  this.copyTimer = null;
4424
4427
  this.infographicInstance = null;
4428
+ // Drag functionality. The gesture lifecycle (window listeners, pointer identity,
4429
+ // lost releases) lives in markstream-core so every package pans identically.
4430
+ this.panGesture = createPanGesture({
4431
+ getTranslate: () => ({ x: this.translateX, y: this.translateY }),
4432
+ setTranslate: (next) => {
4433
+ this.translateX = next.x;
4434
+ this.translateY = next.y;
4435
+ this.cdr.markForCheck();
4436
+ },
4437
+ canStart: (event) => {
4438
+ // Nothing to pan until the diagram exists. Without this the pending/error
4439
+ // source panel would be claimed by the gesture, and `preventDefault` would
4440
+ // take text selection away from it.
4441
+ if (!this.hasDiagramSvg())
4442
+ return false;
4443
+ // Touch pans only where the surface claims the gesture. Elsewhere the page
4444
+ // keeps the swipe for scrolling, and panning along with it would shift the
4445
+ // diagram by a few pixels before the browser takes the gesture over.
4446
+ return event.pointerType !== 'touch' || this.panSurfaceClaimsTouch;
4447
+ },
4448
+ onActiveChange: (active) => {
4449
+ this.isDragging = active;
4450
+ this.cdr.markForCheck();
4451
+ },
4452
+ });
4425
4453
  }
4426
4454
  get mergedProps() {
4427
4455
  return {
@@ -4508,8 +4536,31 @@ class InfographicBlockNodeComponent {
4508
4536
  get resolvedProgressiveRender() {
4509
4537
  return this.mergedProps.progressiveRender === true;
4510
4538
  }
4539
+ /**
4540
+ * The diagram is scaled to fit the preview box, so at fit zoom nothing is
4541
+ * off-screen and panning only shifts it inside the box. Touch therefore keeps
4542
+ * scrolling the page at that zoom (a chat is mostly scrolling) and only claims
4543
+ * the gesture once the diagram is zoomed in and actually overflows the box.
4544
+ * The fullscreen modal always claims it: the surface fills the viewport, panning
4545
+ * is the intended gesture there, and the page behind it is locked anyway.
4546
+ */
4547
+ get panSurfaceClaimsTouch() {
4548
+ return this.modalOpen || this.zoom > 1;
4549
+ }
4550
+ get previewTransform() {
4551
+ return `translate(${this.translateX}px, ${this.translateY}px) scale(${this.zoom})`;
4552
+ }
4511
4553
  get modalTransform() {
4512
- return `scale(${this.zoom})`;
4554
+ return `translate(${this.translateX}px, ${this.translateY}px) scale(${this.zoom})`;
4555
+ }
4556
+ /**
4557
+ * Whether a pan surface holds a rendered diagram. The modal renders its own
4558
+ * copy of the SVG, so it is checked as well: the source panel may have taken
4559
+ * the inline preview's place while the modal is open.
4560
+ */
4561
+ hasDiagramSvg() {
4562
+ return !!this.previewHost?.nativeElement?.querySelector('svg')
4563
+ || !!this.modalHost?.nativeElement?.querySelector('svg');
4513
4564
  }
4514
4565
  ngAfterViewInit() {
4515
4566
  this.viewReady = true;
@@ -4525,6 +4576,7 @@ class InfographicBlockNodeComponent {
4525
4576
  this.destroyed = true;
4526
4577
  this.renderToken += 1;
4527
4578
  this.renderScheduled = false;
4579
+ this.panGesture.stop();
4528
4580
  if (this.copyTimer != null && typeof window !== 'undefined')
4529
4581
  window.clearTimeout(this.copyTimer);
4530
4582
  this.destroyInfographic();
@@ -4591,8 +4643,13 @@ class InfographicBlockNodeComponent {
4591
4643
  }
4592
4644
  resetZoom() {
4593
4645
  this.zoom = 1;
4646
+ this.translateX = 0;
4647
+ this.translateY = 0;
4594
4648
  this.cdr.markForCheck();
4595
4649
  }
4650
+ onPanStart(event) {
4651
+ this.panGesture.start(event);
4652
+ }
4596
4653
  scheduleInfographicRender() {
4597
4654
  if (this.destroyed || this.renderScheduled)
4598
4655
  return;
@@ -4831,6 +4888,10 @@ class InfographicBlockNodeComponent {
4831
4888
  #previewHost
4832
4889
  class="infographic-render"
4833
4890
  [style.minHeight]="resolvedContainerMinHeight"
4891
+ [style.transform]="previewTransform"
4892
+ [style.touch-action]="panSurfaceClaimsTouch ? 'none' : null"
4893
+ [style.cursor]="isDragging ? 'grabbing' : 'grab'"
4894
+ (pointerdown)="onPanStart($event)"
4834
4895
  >
4835
4896
  <pre *ngIf="!svgMarkup && !error" class="markstream-angular-enhanced-block__source infographic-pending-source"><code translate="no">{{ code }}</code></pre>
4836
4897
  </div>
@@ -4860,7 +4921,13 @@ class InfographicBlockNodeComponent {
4860
4921
  <button type="button" class="mermaid-modal-close" (click)="closeModal()">Close</button>
4861
4922
  </div>
4862
4923
  <div class="mermaid-modal-body">
4863
- <div class="mermaid-modal-content" [style.transform]="modalTransform">
4924
+ <div
4925
+ class="mermaid-modal-content"
4926
+ [style.transform]="modalTransform"
4927
+ [style.touch-action]="panSurfaceClaimsTouch ? 'none' : null"
4928
+ [style.cursor]="isDragging ? 'grabbing' : 'grab'"
4929
+ (pointerdown)="onPanStart($event)"
4930
+ >
4864
4931
  <div #modalHost class="fullscreen"></div>
4865
4932
  </div>
4866
4933
  </div>
@@ -4966,6 +5033,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImpo
4966
5033
  #previewHost
4967
5034
  class="infographic-render"
4968
5035
  [style.minHeight]="resolvedContainerMinHeight"
5036
+ [style.transform]="previewTransform"
5037
+ [style.touch-action]="panSurfaceClaimsTouch ? 'none' : null"
5038
+ [style.cursor]="isDragging ? 'grabbing' : 'grab'"
5039
+ (pointerdown)="onPanStart($event)"
4969
5040
  >
4970
5041
  <pre *ngIf="!svgMarkup && !error" class="markstream-angular-enhanced-block__source infographic-pending-source"><code translate="no">{{ code }}</code></pre>
4971
5042
  </div>
@@ -4995,7 +5066,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImpo
4995
5066
  <button type="button" class="mermaid-modal-close" (click)="closeModal()">Close</button>
4996
5067
  </div>
4997
5068
  <div class="mermaid-modal-body">
4998
- <div class="mermaid-modal-content" [style.transform]="modalTransform">
5069
+ <div
5070
+ class="mermaid-modal-content"
5071
+ [style.transform]="modalTransform"
5072
+ [style.touch-action]="panSurfaceClaimsTouch ? 'none' : null"
5073
+ [style.cursor]="isDragging ? 'grabbing' : 'grab'"
5074
+ (pointerdown)="onPanStart($event)"
5075
+ >
4999
5076
  <div #modalHost class="fullscreen"></div>
5000
5077
  </div>
5001
5078
  </div>
@@ -6791,6 +6868,9 @@ class MermaidBlockNodeComponent {
6791
6868
  this.showSource = false;
6792
6869
  this.modalOpen = false;
6793
6870
  this.zoom = 1;
6871
+ this.translateX = 0;
6872
+ this.translateY = 0;
6873
+ this.isDragging = false;
6794
6874
  this.svgMarkup = '';
6795
6875
  this.error = '';
6796
6876
  this.viewReady = false;
@@ -6798,6 +6878,31 @@ class MermaidBlockNodeComponent {
6798
6878
  this.renderToken = 0;
6799
6879
  this.copyTimer = null;
6800
6880
  this.lastMermaidBindFunctions = null;
6881
+ // Drag functionality. The gesture lifecycle (window listeners, pointer identity,
6882
+ // lost releases) lives in markstream-core so every package pans identically.
6883
+ this.panGesture = createPanGesture({
6884
+ getTranslate: () => ({ x: this.translateX, y: this.translateY }),
6885
+ setTranslate: (next) => {
6886
+ this.translateX = next.x;
6887
+ this.translateY = next.y;
6888
+ this.cdr.markForCheck();
6889
+ },
6890
+ canStart: (event) => {
6891
+ // Nothing to pan until the diagram exists. Without this the error message the
6892
+ // block renders in the preview would be claimed by the gesture, and
6893
+ // `preventDefault` would take text selection away from it.
6894
+ if (!this.hasDiagramSvg())
6895
+ return false;
6896
+ // Touch pans only where the surface claims the gesture. Elsewhere the page
6897
+ // keeps the swipe for scrolling, and panning along with it would shift the
6898
+ // diagram by a few pixels before the browser takes the gesture over.
6899
+ return event.pointerType !== 'touch' || this.panSurfaceClaimsTouch;
6900
+ },
6901
+ onActiveChange: (active) => {
6902
+ this.isDragging = active;
6903
+ this.cdr.markForCheck();
6904
+ },
6905
+ });
6801
6906
  }
6802
6907
  get mergedProps() {
6803
6908
  return {
@@ -6888,11 +6993,31 @@ class MermaidBlockNodeComponent {
6888
6993
  get resolvedProgressiveRender() {
6889
6994
  return this.mergedProps.progressiveRender === true;
6890
6995
  }
6996
+ /**
6997
+ * The diagram is scaled to fit the preview box, so at fit zoom nothing is
6998
+ * off-screen and panning only shifts it inside the box. Touch therefore keeps
6999
+ * scrolling the page at that zoom (a chat is mostly scrolling) and only claims
7000
+ * the gesture once the diagram is zoomed in and actually overflows the box.
7001
+ * The fullscreen modal always claims it: the surface fills the viewport, panning
7002
+ * is the intended gesture there, and the page behind it is locked anyway.
7003
+ */
7004
+ get panSurfaceClaimsTouch() {
7005
+ return this.modalOpen || this.zoom > 1;
7006
+ }
6891
7007
  get previewTransform() {
6892
- return `scale(${this.zoom})`;
7008
+ return `translate(${this.translateX}px, ${this.translateY}px) scale(${this.zoom})`;
6893
7009
  }
6894
7010
  get modalTransform() {
6895
- return `scale(${Math.max(1, this.zoom)})`;
7011
+ return `translate(${this.translateX}px, ${this.translateY}px) scale(${Math.max(1, this.zoom)})`;
7012
+ }
7013
+ /**
7014
+ * Whether a pan surface holds a rendered diagram. The modal renders its own
7015
+ * copy of the SVG, so it is checked as well: the source panel may have taken
7016
+ * the inline preview's place while the modal is open.
7017
+ */
7018
+ hasDiagramSvg() {
7019
+ return !!this.previewHost?.nativeElement?.querySelector('svg')
7020
+ || !!this.modalHost?.nativeElement?.querySelector('svg');
6896
7021
  }
6897
7022
  ngAfterViewInit() {
6898
7023
  this.viewReady = true;
@@ -6906,6 +7031,7 @@ class MermaidBlockNodeComponent {
6906
7031
  ngOnDestroy() {
6907
7032
  this.destroyed = true;
6908
7033
  this.renderToken += 1;
7034
+ this.panGesture.stop();
6909
7035
  if (this.copyTimer != null && typeof window !== 'undefined')
6910
7036
  window.clearTimeout(this.copyTimer);
6911
7037
  }
@@ -6968,8 +7094,13 @@ class MermaidBlockNodeComponent {
6968
7094
  }
6969
7095
  resetZoom() {
6970
7096
  this.zoom = 1;
7097
+ this.translateX = 0;
7098
+ this.translateY = 0;
6971
7099
  this.cdr.markForCheck();
6972
7100
  }
7101
+ onPanStart(event) {
7102
+ this.panGesture.start(event);
7103
+ }
6973
7104
  handleModalWheel(event) {
6974
7105
  if (this.mergedProps.enableWheelZoom !== true)
6975
7106
  return;
@@ -7371,6 +7502,9 @@ class MermaidBlockNodeComponent {
7371
7502
  [style.minHeight.px]="estimatedPreviewHeightPx"
7372
7503
  [style.height.px]="estimatedPreviewHeightPx"
7373
7504
  [style.transform]="previewTransform"
7505
+ [style.touch-action]="panSurfaceClaimsTouch ? 'none' : null"
7506
+ [style.cursor]="isDragging ? 'grabbing' : 'grab'"
7507
+ (pointerdown)="onPanStart($event)"
7374
7508
  >
7375
7509
  <div #previewHost class="markstream-angular-mermaid" [class.is-empty]="!svgMarkup"></div>
7376
7510
  </div>
@@ -7403,6 +7537,9 @@ class MermaidBlockNodeComponent {
7403
7537
  <div
7404
7538
  class="mermaid-modal-content"
7405
7539
  [style.transform]="modalTransform"
7540
+ [style.touch-action]="panSurfaceClaimsTouch ? 'none' : null"
7541
+ [style.cursor]="isDragging ? 'grabbing' : 'grab'"
7542
+ (pointerdown)="onPanStart($event)"
7406
7543
  (wheel)="handleModalWheel($event)"
7407
7544
  >
7408
7545
  <div #modalHost class="markstream-angular-mermaid fullscreen"></div>
@@ -7516,6 +7653,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImpo
7516
7653
  [style.minHeight.px]="estimatedPreviewHeightPx"
7517
7654
  [style.height.px]="estimatedPreviewHeightPx"
7518
7655
  [style.transform]="previewTransform"
7656
+ [style.touch-action]="panSurfaceClaimsTouch ? 'none' : null"
7657
+ [style.cursor]="isDragging ? 'grabbing' : 'grab'"
7658
+ (pointerdown)="onPanStart($event)"
7519
7659
  >
7520
7660
  <div #previewHost class="markstream-angular-mermaid" [class.is-empty]="!svgMarkup"></div>
7521
7661
  </div>
@@ -7548,6 +7688,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImpo
7548
7688
  <div
7549
7689
  class="mermaid-modal-content"
7550
7690
  [style.transform]="modalTransform"
7691
+ [style.touch-action]="panSurfaceClaimsTouch ? 'none' : null"
7692
+ [style.cursor]="isDragging ? 'grabbing' : 'grab'"
7693
+ (pointerdown)="onPanStart($event)"
7551
7694
  (wheel)="handleModalWheel($event)"
7552
7695
  >
7553
7696
  <div #modalHost class="markstream-angular-mermaid fullscreen"></div>