Customizing the annotation inspector in our viewer
You can customize the Nutrient SDK to show an annotation inspector in a popup whenever you select an annotation. If you want to see an example, check out our demo.
In this guide, you’ll implement an inspector for shape annotations that has the option to change the color of the stroke. You’ll use NutrientViewer.Container#annotationTooltipCallback to add a tooltip that’s visible when selecting any shape annotation:
let instance;const seenAnnotations = new Set();NutrientViewer.load({ ...baseOptions, theme: NutrientViewer.Theme.DARK, annotationTooltipCallback: (annotation) => { if (!(annotation instanceof NutrientViewer.Annotations.ShapeAnnotation)) { return []; }
if (!annotation.id || !seenAnnotations.has(annotation.id)) { if (annotation.id) { seenAnnotations.add(annotation.id); } return []; }
const doc = instance.contentDocument instanceof Document ? instance.contentDocument : instance.contentDocument.ownerDocument; const wrapper = doc.createElement("div"); wrapper.classList.add("stroke-color-control");
const text = doc.createElement("span"); text.classList.add("stroke-color-text"); text.textContent = "Stroke Color"; wrapper.appendChild(text);
const swatchContainer = doc.createElement("div"); swatchContainer.classList.add("stroke-color-swatch-container"); wrapper.appendChild(swatchContainer);
const swatchButton = doc.createElement("button"); swatchButton.type = "button"; swatchButton.classList.add("stroke-color-button"); swatchContainer.appendChild(swatchButton);
const palette = doc.createElement("div"); palette.classList.add("stroke-color-palette"); palette.hidden = true; swatchContainer.appendChild(palette);
const swatchColors = [ "#F44336", "#FF9800", "#FFEB3B", "#4CAF50", "#3F83F8", "#D946EF", "#F8B4B4", "#FBD38D", "#FCE96A", "#B7F0A4", "#C7D2FE", "#FBCFE8", "#E5E7EB", "#9CA3AF", "#6B7280", "#374151", "#111827", null, ];
const updateButtonColor = (color) => { swatchButton.style.setProperty("--stroke-color", color || "transparent"); swatchButton.classList.toggle("is-transparent", !color); };
updateButtonColor(annotation.strokeColor?.toHex() ?? null);
swatchColors.forEach((color) => { const swatch = doc.createElement("button"); swatch.type = "button"; swatch.classList.add("stroke-color-swatch"); if (!color) { swatch.classList.add("is-transparent"); swatch.dataset.color = ""; } else { swatch.dataset.color = color; swatch.style.setProperty("--swatch-color", color); }
swatch.addEventListener("click", async () => { const selectedColor = swatch.dataset.color; const updatedAnnotation = annotation.set( "strokeColor", selectedColor ? NutrientViewer.Color.fromHex(selectedColor) : null ); await instance.update(updatedAnnotation); updateButtonColor(selectedColor || null); palette.hidden = true; });
palette.appendChild(swatch); });
swatchButton.addEventListener("click", () => { palette.hidden = !palette.hidden; });
return [ { type: "custom", node: wrapper, onPress: () => { console.log(annotation); }, }, ]; }, initialViewState: new NutrientViewer.ViewState({ enableAnnotationToolbar: false, }),}).then((_instance) => { instance = _instance;
const styleRoot = instance.contentDocument instanceof Document ? instance.contentDocument.head : instance.contentDocument;
if (styleRoot && !styleRoot.querySelector("style[data-stroke-tooltip]")) { const styleTag = styleRoot.ownerDocument.createElement("style"); styleTag.dataset.strokeTooltip = "true"; styleTag.textContent = strokeTooltipStyles; styleRoot.appendChild(styleTag); }
console.log("Nutrient loaded!"); console.log("API docs: https://www.nutrient.io/api/web/"); console.log("Guides: https://www.nutrient.io/guides/web/");
return instance;});const strokeTooltipStyles = `.stroke-color-control { position: relative; display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 200px; padding: 10px; border-radius: 3px; font: 600 14px/1.2 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: #ffffff;}
.stroke-color-text { min-width: 90px;}
.stroke-color-swatch-container { position: relative; display: flex; align-items: center;}
.stroke-color-button { width: 44px; height: 44px; padding: 6px; border-radius: 10px; border: 2px solid #4a53f5; background: #242833; cursor: pointer; box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35); display: flex; align-items: center; justify-content: center;}
.stroke-color-button::before { content: ""; width: 24px; height: 24px; border-radius: 50%; background: var(--stroke-color, transparent); border: 2px solid rgba(0, 0, 0, 0.1);}
.stroke-color-button.is-transparent::before { background: transparent; border: 2px solid #c9ced8; position: relative;}
.stroke-color-button.is-transparent::after { content: ""; position: absolute; width: 26px; height: 2px; background: #ef4444; transform: rotate(-35deg);}
.stroke-color-palette { position: absolute; top: calc(100% + 8px); left: -8px; padding: 12px 16px; border-radius: 10px; background: #f5f6f8; box-shadow: 0 10px 24px rgba(15, 23, 42, 0.2); display: grid; grid-template-columns: repeat(6, 28px); gap: 12px; z-index: 10;}
.stroke-color-swatch { width: 28px; height: 28px; border-radius: 50%; border: 2px solid rgba(0, 0, 0, 0.12); background: var(--swatch-color, transparent); cursor: pointer;}
.stroke-color-swatch.is-transparent { background: #fff; position: relative;}
.stroke-color-swatch.is-transparent::after { content: ""; position: absolute; top: 12px; left: -2px; width: 32px; height: 2px; background: #ef4444; transform: rotate(-35deg);}
.PSPDFKit-Annotation-Tooltip { overflow: visible;}`;}The code above will result in the following.
Similarly, you can add other elements that control the different properties of the annotation.