stimulus-pdf-viewer-rails 0.2.1 → 0.3.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2b8580f37e1f69672150d18e71bda47daff6b3eb2b082becc5f0090bea57d43
4
- data.tar.gz: 6f3ecaf5db5c8248a6037c0f416149df1af21980e83236a9ffe9a9d395aa5e99
3
+ metadata.gz: 6fe6b668cbffa8acaf9ade9ef15614aad4d7677e3dc505bebe4065ba8ccdb726
4
+ data.tar.gz: f8f51d4e310e36beb6dd51efc9dd3501daaa15067b2a853912d074427a04b9f7
5
5
  SHA512:
6
- metadata.gz: 27b13a4566eefe38263b236ba1fd96a0bec05651a27f5e333147b2f6635e9dac5e100b58ff4a67db6f36360895fc1b2ca8facdd58e091aa8b1c2053accf76f28
7
- data.tar.gz: 65a0c1284f86ffc83680d9ac95c6e20f64cec6dad84f3f5edb9ea6ec6e4cd1f6003f865d4d959400e05eab4f8e56b2db39494b2cec520506c1a8078770d3d605
6
+ metadata.gz: ad37d63e9f0c26f373b293737818fd85121bf78945f8e97ab06371f75d2aa3ac6b8b4b9976a27cb5553b5cb3f738b51258a82541761bb78531116e34fa27e90f
7
+ data.tar.gz: 41b654cae115f12ef9a8f33c80d5d0645657cbccc7b70ee94651dd557b2ea416b38ace501b7a0e43cf9387dc8b16237c2f7e442915d338e1c03dd0ee3108ad45
data/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.3.0] - 2026-04-01
6
+
7
+ ### Added
8
+ - Updated stimulus-pdf-viewer to 0.3.0
9
+
5
10
  ## [0.2.0] - 2026-01-11
6
11
 
7
12
  ### Added
@@ -2578,12 +2578,12 @@ class AnnotationEditToolbar {
2578
2578
  });
2579
2579
 
2580
2580
  // Handle keyboard shortcuts
2581
- document.addEventListener("keydown", (e) => {
2581
+ this._keydownHandler = (e) => {
2582
2582
  if (this.element.classList.contains("hidden")) return
2583
2583
 
2584
2584
  // Don't intercept if user is typing
2585
2585
  const activeEl = document.activeElement;
2586
- if (activeEl && (activeEl.tagName === "INPUT" || activeEl.tagName === "TEXTAREA")) return
2586
+ if (activeEl && (activeEl.tagName === "INPUT" || activeEl.tagName === "TEXTAREA" || activeEl.isContentEditable)) return
2587
2587
 
2588
2588
  if (e.key === "Escape") {
2589
2589
  if (this.colorDropdownOpen) {
@@ -2613,7 +2613,8 @@ class AnnotationEditToolbar {
2613
2613
  this.onComment(this.currentAnnotation);
2614
2614
  }
2615
2615
  }
2616
- });
2616
+ };
2617
+ document.addEventListener("keydown", this._keydownHandler);
2617
2618
  }
2618
2619
 
2619
2620
  _toggleColorDropdown() {
@@ -2722,6 +2723,347 @@ class AnnotationEditToolbar {
2722
2723
  }
2723
2724
 
2724
2725
  destroy() {
2726
+ if (this._keydownHandler) {
2727
+ document.removeEventListener("keydown", this._keydownHandler);
2728
+ }
2729
+ this.element.remove();
2730
+ }
2731
+ }
2732
+
2733
+ class AnnotationDetailPanel {
2734
+ constructor(options = {}) {
2735
+ this.container = options.container;
2736
+ this.onColorChange = options.onColorChange;
2737
+ this.onDelete = options.onDelete;
2738
+ this.onEdit = options.onEdit;
2739
+ this.onComment = options.onComment;
2740
+ this.onClose = options.onClose;
2741
+ this.colors = options.colors || ColorPicker.COLORS.map(c => c.value);
2742
+
2743
+ this.currentAnnotation = null;
2744
+ this.anchorElement = null;
2745
+ this.colorDropdownOpen = false;
2746
+
2747
+ this._createPanel();
2748
+ this._setupEventListeners();
2749
+ }
2750
+
2751
+ _createPanel() {
2752
+ this.element = document.createElement("div");
2753
+ this.element.className = "annotation-detail-panel hidden";
2754
+ this.element.innerHTML = `
2755
+ <div class="annotation-detail-header">
2756
+ <div class="annotation-detail-header-info">
2757
+ <span class="annotation-detail-type"></span>
2758
+ </div>
2759
+ <div class="annotation-detail-header-actions">
2760
+ <button class="toolbar-btn comment-btn hidden" title="Add Comment (C)">
2761
+ ${Icons.comment}
2762
+ </button>
2763
+ <button class="color-picker-btn" title="Change color" aria-haspopup="true" aria-expanded="false">
2764
+ <span class="color-swatch"></span>
2765
+ ${Icons.chevronDown}
2766
+ </button>
2767
+ <div class="color-dropdown hidden">
2768
+ ${this.colors.map(color => `
2769
+ <button class="color-option" data-color="${color}" aria-selected="false">
2770
+ <span class="color-swatch" style="background-color: ${color}"></span>
2771
+ </button>
2772
+ `).join("")}
2773
+ </div>
2774
+ <button class="toolbar-btn edit-btn hidden" title="Edit (E)">
2775
+ ${Icons.edit}
2776
+ </button>
2777
+ <div class="toolbar-divider"></div>
2778
+ <button class="toolbar-btn delete-btn" title="Delete (Delete)">
2779
+ ${Icons.delete}
2780
+ </button>
2781
+ <div class="toolbar-divider"></div>
2782
+ <button class="annotation-detail-close" title="Close (Escape)">
2783
+ ${Icons.close}
2784
+ </button>
2785
+ </div>
2786
+ </div>
2787
+ <div class="annotation-detail-body">
2788
+ <div class="annotation-detail-text hidden"></div>
2789
+ <div class="annotation-detail-content-slot"></div>
2790
+ </div>
2791
+ `;
2792
+
2793
+ this.commentBtn = this.element.querySelector(".comment-btn");
2794
+ this.editBtn = this.element.querySelector(".edit-btn");
2795
+ this.typeLabel = this.element.querySelector(".annotation-detail-type");
2796
+ this.textContent = this.element.querySelector(".annotation-detail-text");
2797
+ this.contentSlot = this.element.querySelector(".annotation-detail-content-slot");
2798
+ }
2799
+
2800
+ _setupEventListeners() {
2801
+ // Prevent pointer/click events inside the panel from triggering annotation tools
2802
+ // Tools listen on pointerdown on the pages container, so we must stop all phases
2803
+ this.element.addEventListener("pointerdown", (e) => {
2804
+ e.stopPropagation();
2805
+ });
2806
+ this.element.addEventListener("click", (e) => {
2807
+ e.stopPropagation();
2808
+ });
2809
+
2810
+ // Close button
2811
+ this.element.querySelector(".annotation-detail-close").addEventListener("click", (e) => {
2812
+ e.stopPropagation();
2813
+ this.onClose?.();
2814
+ });
2815
+
2816
+ // Color picker button
2817
+ const colorBtn = this.element.querySelector(".color-picker-btn");
2818
+ colorBtn.addEventListener("click", (e) => {
2819
+ e.stopPropagation();
2820
+ this._toggleColorDropdown();
2821
+ });
2822
+
2823
+ // Color options
2824
+ this.element.querySelectorAll(".color-option").forEach(option => {
2825
+ option.addEventListener("click", (e) => {
2826
+ e.stopPropagation();
2827
+ this._selectColor(option.dataset.color);
2828
+ });
2829
+ });
2830
+
2831
+ // Comment button
2832
+ this.commentBtn.addEventListener("click", (e) => {
2833
+ e.stopPropagation();
2834
+ if (this.currentAnnotation && this.onComment) {
2835
+ this.onComment(this.currentAnnotation);
2836
+ }
2837
+ });
2838
+
2839
+ // Edit button
2840
+ this.editBtn.addEventListener("click", (e) => {
2841
+ e.stopPropagation();
2842
+ if (this.currentAnnotation && this.onEdit) {
2843
+ this.onEdit(this.currentAnnotation);
2844
+ }
2845
+ });
2846
+
2847
+ // Delete button (hide is handled by _deselectAnnotation cascade)
2848
+ this.element.querySelector(".delete-btn").addEventListener("click", (e) => {
2849
+ e.stopPropagation();
2850
+ if (this.currentAnnotation && this.onDelete) {
2851
+ this.onDelete(this.currentAnnotation);
2852
+ }
2853
+ });
2854
+
2855
+ // Close color dropdown on outside click
2856
+ document.addEventListener("click", (e) => {
2857
+ if (this.colorDropdownOpen && !this.element.contains(e.target)) {
2858
+ this._closeColorDropdown();
2859
+ }
2860
+ });
2861
+
2862
+ // Keyboard shortcuts
2863
+ this._keydownHandler = (e) => {
2864
+ if (this.element.classList.contains("hidden")) return
2865
+
2866
+ const activeEl = document.activeElement;
2867
+ if (activeEl && (activeEl.tagName === "INPUT" || activeEl.tagName === "TEXTAREA" || activeEl.isContentEditable)) return
2868
+
2869
+ if (e.key === "Escape") {
2870
+ if (this.colorDropdownOpen) {
2871
+ this._closeColorDropdown();
2872
+ } else {
2873
+ this.onClose?.();
2874
+ }
2875
+ e.preventDefault();
2876
+ } else if (e.key === "Delete" || e.key === "Backspace") {
2877
+ e.preventDefault();
2878
+ if (this.currentAnnotation && this.onDelete) {
2879
+ this.onDelete(this.currentAnnotation);
2880
+ }
2881
+ } else if (e.key === "e" || e.key === "E") {
2882
+ if (this.currentAnnotation?.annotation_type === "note" && this.onEdit) {
2883
+ e.preventDefault();
2884
+ this.onEdit(this.currentAnnotation);
2885
+ }
2886
+ } else if (e.key === "c" || e.key === "C") {
2887
+ const supportsComment = ["highlight", "line", "ink"].includes(this.currentAnnotation?.annotation_type);
2888
+ if (supportsComment && this.onComment) {
2889
+ e.preventDefault();
2890
+ this.onComment(this.currentAnnotation);
2891
+ }
2892
+ }
2893
+ };
2894
+ document.addEventListener("keydown", this._keydownHandler);
2895
+ }
2896
+
2897
+ _toggleColorDropdown() {
2898
+ if (this.colorDropdownOpen) {
2899
+ this._closeColorDropdown();
2900
+ } else {
2901
+ this._openColorDropdown();
2902
+ }
2903
+ }
2904
+
2905
+ _openColorDropdown() {
2906
+ const dropdown = this.element.querySelector(".color-dropdown");
2907
+ const btn = this.element.querySelector(".color-picker-btn");
2908
+ dropdown.classList.remove("hidden");
2909
+ btn.setAttribute("aria-expanded", "true");
2910
+ this.colorDropdownOpen = true;
2911
+ }
2912
+
2913
+ _closeColorDropdown() {
2914
+ const dropdown = this.element.querySelector(".color-dropdown");
2915
+ const btn = this.element.querySelector(".color-picker-btn");
2916
+ dropdown.classList.add("hidden");
2917
+ btn.setAttribute("aria-expanded", "false");
2918
+ this.colorDropdownOpen = false;
2919
+ }
2920
+
2921
+ _selectColor(color) {
2922
+ if (this.currentAnnotation && this.onColorChange) {
2923
+ this.onColorChange(this.currentAnnotation, color);
2924
+ }
2925
+ this._updateSelectedColor(color);
2926
+ this._closeColorDropdown();
2927
+ }
2928
+
2929
+ _updateSelectedColor(color) {
2930
+ const swatch = this.element.querySelector(".color-picker-btn .color-swatch");
2931
+ swatch.style.backgroundColor = color;
2932
+
2933
+ this.element.querySelectorAll(".color-option").forEach(option => {
2934
+ option.setAttribute("aria-selected", option.dataset.color === color ? "true" : "false");
2935
+ });
2936
+ }
2937
+
2938
+ _getTypeLabel(annotationType) {
2939
+ const labels = {
2940
+ highlight: "Highlight",
2941
+ line: "Underline",
2942
+ note: "Note",
2943
+ ink: "Drawing"
2944
+ };
2945
+ return labels[annotationType] || "Annotation"
2946
+ }
2947
+
2948
+ show(annotation, anchorElement, options = {}) {
2949
+ this.currentAnnotation = annotation;
2950
+ this.anchorElement = anchorElement;
2951
+
2952
+ // Update header info
2953
+ const page = annotation.page || 1;
2954
+ this.typeLabel.textContent = `${this._getTypeLabel(annotation.annotation_type)} \u2014 Page ${page}`;
2955
+
2956
+ // Update color swatch
2957
+ const color = annotation.color || ColorPicker.DEFAULT_HIGHLIGHT_COLOR;
2958
+ this._updateSelectedColor(color);
2959
+
2960
+ // Show/hide buttons based on annotation type
2961
+ const isNote = annotation.annotation_type === "note";
2962
+ const supportsComment = ["highlight", "line", "ink"].includes(annotation.annotation_type);
2963
+ this.commentBtn.classList.toggle("hidden", !supportsComment);
2964
+ this.editBtn.classList.toggle("hidden", !isNote);
2965
+
2966
+ if (supportsComment) {
2967
+ const hasComment = annotation.contents && annotation.contents.trim();
2968
+ this.commentBtn.title = hasComment ? "Edit Comment (C)" : "Add Comment (C)";
2969
+ }
2970
+
2971
+ // Show annotation text content if present
2972
+ if (annotation.contents) {
2973
+ this.textContent.textContent = annotation.contents;
2974
+ this.textContent.classList.remove("hidden");
2975
+ } else {
2976
+ this.textContent.textContent = "";
2977
+ this.textContent.classList.add("hidden");
2978
+ }
2979
+
2980
+ // Inject custom content if provided (HTML string or DOM element)
2981
+ if (options.content) {
2982
+ if (typeof options.content === "string") {
2983
+ this.contentSlot.innerHTML = options.content;
2984
+ } else {
2985
+ this.contentSlot.innerHTML = "";
2986
+ this.contentSlot.appendChild(options.content);
2987
+ }
2988
+ }
2989
+
2990
+ // Position and show the panel
2991
+ this._positionPanel(anchorElement);
2992
+ this.element.classList.remove("hidden");
2993
+ }
2994
+
2995
+ _positionPanel(anchorElement) {
2996
+ const pageContainer = anchorElement.closest(".pdf-page");
2997
+ if (!pageContainer) return
2998
+
2999
+ // Append to page container so it scrolls with the page
3000
+ pageContainer.appendChild(this.element);
3001
+
3002
+ // Get annotation position relative to the page
3003
+ const anchorRect = anchorElement.getBoundingClientRect();
3004
+ const pageRect = pageContainer.getBoundingClientRect();
3005
+
3006
+ const anchorTop = anchorRect.top - pageRect.top;
3007
+ const anchorLeft = anchorRect.left - pageRect.left;
3008
+ const anchorRight = anchorLeft + anchorRect.width;
3009
+ const pageWidth = pageRect.width;
3010
+
3011
+ const panelWidth = parseInt(
3012
+ getComputedStyle(this.element).getPropertyValue("--panel-width") || "320", 10
3013
+ );
3014
+
3015
+ // Try to position to the right of the annotation
3016
+ if (anchorRight + panelWidth + 12 <= pageWidth) {
3017
+ this.element.style.left = `${anchorRight + 8}px`;
3018
+ this.element.style.right = "auto";
3019
+ }
3020
+ // Fall back to the left side
3021
+ else if (anchorLeft - panelWidth - 12 >= 0) {
3022
+ this.element.style.left = `${anchorLeft - panelWidth - 8}px`;
3023
+ this.element.style.right = "auto";
3024
+ }
3025
+ // Fall back to below the annotation (centered)
3026
+ else {
3027
+ const centerX = anchorLeft + anchorRect.width / 2;
3028
+ this.element.style.left = `${Math.max(8, centerX - panelWidth / 2)}px`;
3029
+ this.element.style.right = "auto";
3030
+ }
3031
+
3032
+ // Vertical alignment: align top of panel with top of annotation
3033
+ this.element.style.top = `${anchorTop}px`;
3034
+ }
3035
+
3036
+ hide() {
3037
+ this._closeColorDropdown();
3038
+ this.element.classList.add("hidden");
3039
+ this.currentAnnotation = null;
3040
+ this.anchorElement = null;
3041
+
3042
+ // Clear text content
3043
+ this.textContent.textContent = "";
3044
+ this.textContent.classList.add("hidden");
3045
+
3046
+ // Clear injected content (but preserve the slot container)
3047
+ this.contentSlot.innerHTML = "";
3048
+
3049
+ // Remove from parent when hidden
3050
+ if (this.element.parentNode) {
3051
+ this.element.parentNode.removeChild(this.element);
3052
+ }
3053
+ }
3054
+
3055
+ isVisible() {
3056
+ return !this.element.classList.contains("hidden")
3057
+ }
3058
+
3059
+ getContentContainer() {
3060
+ return this.contentSlot
3061
+ }
3062
+
3063
+ destroy() {
3064
+ if (this._keydownHandler) {
3065
+ document.removeEventListener("keydown", this._keydownHandler);
3066
+ }
2725
3067
  this.element.remove();
2726
3068
  }
2727
3069
  }
@@ -3665,6 +4007,17 @@ class AnnotationSidebar {
3665
4007
  this.listContainer.appendChild(item);
3666
4008
  }
3667
4009
  }
4010
+
4011
+ // Re-fire selected event after rebuild so consumers can re-attach detail panels
4012
+ if (this.selectedAnnotationId) {
4013
+ const annotation = this.annotationManager.getAnnotation(this.selectedAnnotationId);
4014
+ if (annotation) {
4015
+ this.element.dispatchEvent(new CustomEvent("pdf-sidebar:annotation-selected", {
4016
+ bubbles: true,
4017
+ detail: { annotationId: this.selectedAnnotationId, annotation }
4018
+ }));
4019
+ }
4020
+ }
3668
4021
  }
3669
4022
 
3670
4023
  _matchesFilter(annotation) {
@@ -3887,18 +4240,36 @@ class AnnotationSidebar {
3887
4240
  }
3888
4241
 
3889
4242
  _selectItem(annotationId) {
4243
+ const previousId = this.selectedAnnotationId;
4244
+
4245
+ // Skip if already selected
4246
+ if (previousId === annotationId) return
4247
+
3890
4248
  // Deselect previous
3891
4249
  const prev = this.listContainer.querySelector(".annotation-list-item.selected");
3892
4250
  if (prev) {
3893
4251
  prev.classList.remove("selected");
3894
4252
  }
3895
4253
 
4254
+ if (previousId) {
4255
+ this.element.dispatchEvent(new CustomEvent("pdf-sidebar:annotation-deselected", {
4256
+ bubbles: true,
4257
+ detail: { annotationId: previousId }
4258
+ }));
4259
+ }
4260
+
3896
4261
  // Select new
3897
4262
  this.selectedAnnotationId = annotationId;
3898
4263
  const item = this.listContainer.querySelector(`[data-annotation-id="${annotationId}"]`);
3899
4264
  if (item) {
3900
4265
  item.classList.add("selected");
3901
4266
  }
4267
+
4268
+ const annotation = this.annotationManager.getAnnotation(annotationId);
4269
+ this.element.dispatchEvent(new CustomEvent("pdf-sidebar:annotation-selected", {
4270
+ bubbles: true,
4271
+ detail: { annotationId, annotation }
4272
+ }));
3902
4273
  }
3903
4274
 
3904
4275
  /**
@@ -3926,7 +4297,12 @@ class AnnotationSidebar {
3926
4297
  if (this.isOpen) {
3927
4298
  // Clear selection if deleted annotation was selected
3928
4299
  if (this.selectedAnnotationId === annotation.id) {
4300
+ const previousId = this.selectedAnnotationId;
3929
4301
  this.selectedAnnotationId = null;
4302
+ this.element.dispatchEvent(new CustomEvent("pdf-sidebar:annotation-deselected", {
4303
+ bubbles: true,
4304
+ detail: { annotationId: previousId }
4305
+ }));
3930
4306
  }
3931
4307
  this._refreshList();
3932
4308
  }
@@ -3954,6 +4330,10 @@ class AnnotationSidebar {
3954
4330
  this.element.classList.add("open");
3955
4331
  this.container.classList.add("annotation-sidebar-open");
3956
4332
  this._refreshList();
4333
+
4334
+ this.element.dispatchEvent(new CustomEvent("pdf-sidebar:annotation-sidebar-opened", {
4335
+ bubbles: true
4336
+ }));
3957
4337
  }
3958
4338
 
3959
4339
  /**
@@ -3963,6 +4343,10 @@ class AnnotationSidebar {
3963
4343
  this.isOpen = false;
3964
4344
  this.element.classList.remove("open");
3965
4345
  this.container.classList.remove("annotation-sidebar-open");
4346
+
4347
+ this.element.dispatchEvent(new CustomEvent("pdf-sidebar:annotation-sidebar-closed", {
4348
+ bubbles: true
4349
+ }));
3966
4350
  }
3967
4351
 
3968
4352
  /**
@@ -6730,6 +7114,18 @@ class PdfViewer {
6730
7114
  onDeselect: this._deselectAnnotation.bind(this)
6731
7115
  });
6732
7116
 
7117
+ // Detail panel (opt-in: replaces edit toolbar when enabled)
7118
+ if (this.options.detailPanel && this.bodyContainer) {
7119
+ this.annotationDetailPanel = new AnnotationDetailPanel({
7120
+ container: this.bodyContainer,
7121
+ onColorChange: this._onAnnotationColorChange.bind(this),
7122
+ onDelete: this._onAnnotationDelete.bind(this),
7123
+ onEdit: this._onAnnotationEdit.bind(this),
7124
+ onComment: this._onAnnotationComment.bind(this),
7125
+ onClose: this._deselectAnnotation.bind(this)
7126
+ });
7127
+ }
7128
+
6733
7129
  this.undoBar = new UndoBar(this.undoBarContainer, {
6734
7130
  onUndo: this._onAnnotationUndo.bind(this)
6735
7131
  });
@@ -6869,8 +7265,12 @@ class PdfViewer {
6869
7265
 
6870
7266
  // Deselect annotation when clicking outside
6871
7267
  this.pagesContainer.addEventListener("click", (e) => {
6872
- // Don't deselect if clicking on an annotation or the edit toolbar
6873
- if (e.target.closest(".annotation") || e.target.closest(".annotation-edit-toolbar")) {
7268
+ // Don't deselect if clicking on an annotation, edit toolbar, or detail panel
7269
+ if (e.target.closest(".annotation") || e.target.closest(".annotation-edit-toolbar") || e.target.closest(".annotation-detail-panel")) {
7270
+ return
7271
+ }
7272
+ // Skip if an annotation was just created (click follows pointerup from text selection)
7273
+ if (this._suppressClickDeselect) {
6874
7274
  return
6875
7275
  }
6876
7276
  this._deselectAnnotation();
@@ -6922,6 +7322,12 @@ class PdfViewer {
6922
7322
  // Render annotations on all rendered pages
6923
7323
  this._renderAnnotations();
6924
7324
 
7325
+ const annotations = this.annotationManager.getAllAnnotations();
7326
+ this.container.dispatchEvent(new CustomEvent("pdf-viewer:annotations-loaded", {
7327
+ bubbles: true,
7328
+ detail: { annotations, count: annotations.length }
7329
+ }));
7330
+
6925
7331
  // Navigate to initial page if specified
6926
7332
  if (this.initialPage > 1) {
6927
7333
  this.viewer.goToPage(this.initialPage);
@@ -7049,11 +7455,15 @@ class PdfViewer {
7049
7455
 
7050
7456
  // Auto-select the newly created annotation
7051
7457
  const pageContainer = this.viewer.getPageContainer(annotation.page);
7052
- const element = pageContainer?.querySelector(`[data-annotation-id="${annotation.id}"]`);
7458
+ const element = pageContainer?.querySelector(`.annotation[data-annotation-id="${annotation.id}"]`);
7053
7459
  if (element) {
7054
7460
  this._selectAnnotation(annotation, element);
7055
7461
  }
7056
7462
 
7463
+ // Suppress the click-to-deselect that follows pointerup after text selection
7464
+ this._suppressClickDeselect = true;
7465
+ setTimeout(() => { this._suppressClickDeselect = false; }, 100);
7466
+
7057
7467
  // Notify annotation sidebar
7058
7468
  this.annotationSidebar?.onAnnotationCreated(annotation);
7059
7469
 
@@ -7071,9 +7481,9 @@ class PdfViewer {
7071
7481
  // Remember if this annotation was selected
7072
7482
  const wasSelected = this.selectedAnnotation && this.selectedAnnotation.id === annotation.id;
7073
7483
 
7074
- // Hide toolbar before re-render (it will be re-shown after)
7484
+ // Hide annotation UI before re-render (it will be re-shown after)
7075
7485
  if (wasSelected) {
7076
- this.annotationEditToolbar.hide();
7486
+ this._hideAnnotationUI();
7077
7487
  }
7078
7488
 
7079
7489
  this._renderAnnotationsForPage(annotation.page, this.viewer.getPageContainer(annotation.page));
@@ -7081,7 +7491,7 @@ class PdfViewer {
7081
7491
  // Re-select the annotation after re-render
7082
7492
  if (wasSelected) {
7083
7493
  const pageContainer = this.viewer.getPageContainer(annotation.page);
7084
- const element = pageContainer?.querySelector(`[data-annotation-id="${annotation.id}"]`);
7494
+ const element = pageContainer?.querySelector(`.annotation[data-annotation-id="${annotation.id}"]`);
7085
7495
  if (element) {
7086
7496
  // Get the fresh annotation data from the manager
7087
7497
  const updatedAnnotation = this.annotationManager.getAnnotation(annotation.id);
@@ -7091,7 +7501,7 @@ class PdfViewer {
7091
7501
  this.selectedAnnotation = updatedAnnotation;
7092
7502
  this.selectedAnnotationElement = element;
7093
7503
  element.classList.add("selected");
7094
- this.annotationEditToolbar.show(updatedAnnotation, element);
7504
+ this._showAnnotationUI(updatedAnnotation, element);
7095
7505
  }
7096
7506
  }
7097
7507
  }
@@ -7102,6 +7512,11 @@ class PdfViewer {
7102
7512
  // Announce to screen readers
7103
7513
  const typeLabel = this._getAnnotationTypeLabel(annotation.annotation_type);
7104
7514
  getAnnouncer().announce(`${typeLabel} updated`);
7515
+
7516
+ this.container.dispatchEvent(new CustomEvent("pdf-viewer:annotation-updated", {
7517
+ bubbles: true,
7518
+ detail: { annotation }
7519
+ }));
7105
7520
  }
7106
7521
 
7107
7522
  _onAnnotationDeleted(annotation) {
@@ -7733,7 +8148,7 @@ class PdfViewer {
7733
8148
  if (this.selectedAnnotationElement !== element) {
7734
8149
  this.selectedAnnotationElement = element;
7735
8150
  element.classList.add("selected");
7736
- this.annotationEditToolbar.show(annotation, element, pageHeight);
8151
+ this._showAnnotationUI(annotation, element, pageHeight);
7737
8152
  }
7738
8153
  return
7739
8154
  }
@@ -7746,8 +8161,8 @@ class PdfViewer {
7746
8161
  this.selectedAnnotationElement = element;
7747
8162
  element.classList.add("selected");
7748
8163
 
7749
- // Show the edit toolbar below the annotation (includes note content for notes)
7750
- this.annotationEditToolbar.show(annotation, element, pageHeight);
8164
+ // Show the appropriate annotation UI
8165
+ this._showAnnotationUI(annotation, element, pageHeight);
7751
8166
 
7752
8167
  this.container.dispatchEvent(new CustomEvent("pdf-viewer:annotation-selected", {
7753
8168
  bubbles: true,
@@ -7755,15 +8170,51 @@ class PdfViewer {
7755
8170
  }));
7756
8171
  }
7757
8172
 
8173
+ _showAnnotationUI(annotation, element, pageHeight) {
8174
+ if (this.annotationDetailPanel) {
8175
+ this.annotationDetailPanel.show(annotation, element);
8176
+ this.container.dispatchEvent(new CustomEvent("pdf-viewer:detail-panel-opened", {
8177
+ bubbles: true,
8178
+ detail: { annotation, element }
8179
+ }));
8180
+ } else {
8181
+ this.annotationEditToolbar.show(annotation, element, pageHeight);
8182
+ }
8183
+ }
8184
+
8185
+ _hideAnnotationUI() {
8186
+ if (this.annotationDetailPanel) {
8187
+ const annotation = this.annotationDetailPanel.currentAnnotation;
8188
+ this.annotationDetailPanel.hide();
8189
+ if (annotation) {
8190
+ this.container.dispatchEvent(new CustomEvent("pdf-viewer:detail-panel-closed", {
8191
+ bubbles: true,
8192
+ detail: { annotation, annotationId: annotation.id }
8193
+ }));
8194
+ }
8195
+ } else {
8196
+ this.annotationEditToolbar.hide();
8197
+ }
8198
+ }
8199
+
7758
8200
  _deselectAnnotation() {
8201
+ const previousAnnotation = this.selectedAnnotation;
8202
+
7759
8203
  if (this.selectedAnnotationElement) {
7760
8204
  this.selectedAnnotationElement.classList.remove("selected");
7761
8205
  }
7762
8206
  this.selectedAnnotation = null;
7763
8207
  this.selectedAnnotationElement = null;
7764
8208
 
7765
- // Hide the edit toolbar
7766
- this.annotationEditToolbar.hide();
8209
+ // Hide the annotation UI (detail panel or edit toolbar)
8210
+ this._hideAnnotationUI();
8211
+
8212
+ if (previousAnnotation) {
8213
+ this.container.dispatchEvent(new CustomEvent("pdf-viewer:annotation-deselected", {
8214
+ bubbles: true,
8215
+ detail: { annotationId: previousAnnotation.id }
8216
+ }));
8217
+ }
7767
8218
  }
7768
8219
 
7769
8220
  async _onAnnotationColorChange(annotation, color) {
@@ -7931,6 +8382,7 @@ class PdfViewer {
7931
8382
 
7932
8383
  this.viewer.destroy();
7933
8384
  this.annotationEditToolbar.destroy();
8385
+ this.annotationDetailPanel?.destroy();
7934
8386
  this.undoBar.destroy();
7935
8387
  this.thumbnailSidebar?.destroy();
7936
8388
  this.annotationSidebar?.destroy();
@@ -7957,7 +8409,8 @@ class pdf_viewer_controller extends Controller {
7957
8409
  trackingUrl: String,
7958
8410
  initialPage: Number,
7959
8411
  initialAnnotation: String,
7960
- autoHeight: { type: Boolean, default: true }
8412
+ autoHeight: { type: Boolean, default: true },
8413
+ detailPanel: { type: Boolean, default: false }
7961
8414
  }
7962
8415
 
7963
8416
  initialize() {
@@ -7978,7 +8431,8 @@ class pdf_viewer_controller extends Controller {
7978
8431
  userName: this.userNameValue,
7979
8432
  documentId: this.documentIdValue,
7980
8433
  initialPage: this.initialPageValue || 1,
7981
- initialAnnotation: this.initialAnnotationValue
8434
+ initialAnnotation: this.initialAnnotationValue,
8435
+ detailPanel: this.detailPanelValue
7982
8436
  });
7983
8437
 
7984
8438
  // Set up the toolbar