diff --git a/playground/annotations/bulk-delete-annotations/index.ts b/playground/annotations/bulk-delete-annotations/index.ts new file mode 100644 index 0000000..da25b72 --- /dev/null +++ b/playground/annotations/bulk-delete-annotations/index.ts @@ -0,0 +1,40 @@ +import { baseOptions } from "../../shared/base-options"; + +// Bulk delete ALL annotations +window.NutrientViewer.load({ + ...baseOptions, + theme: window.NutrientViewer.Theme.DARK, +}).then((instance) => { + const items = instance.toolbarItems; + + const deleteButton = { + type: "custom", + id: "delete-annotations", + title: "Delete", // or "Remove" + onPress: async () => { + try { + const pagesAnnotations = await Promise.all( + Array.from({ length: instance.totalPageCount }).map((_, pageIndex) => + instance.getAnnotations(pageIndex), + ), + ); + + const annotationIds = pagesAnnotations.flatMap((pageAnnotations) => + pageAnnotations.map((annotation) => annotation.id).toArray(), + ); + + if (annotationIds.length === 0) { + console.info("No annotations to delete."); + return; + } + + await instance.delete(annotationIds); + console.info(`Deleted ${annotationIds.length} annotations.`); + } catch (e) { + console.error("Failed to delete annotations:", e); + } + }, + }; + + instance.setToolbarItems([...items, deleteButton]); +}); diff --git a/playground/annotations/bulk-delete-annotations/playground.mdx b/playground/annotations/bulk-delete-annotations/playground.mdx new file mode 100644 index 0000000..c7d1d9e --- /dev/null +++ b/playground/annotations/bulk-delete-annotations/playground.mdx @@ -0,0 +1,6 @@ +--- +category: annotations +title: Bulk Delete Annotations +description: Example to bulk delete annotations from the entire document. After the viewer loads, it gathers annotations from every page, flattens their IDs, and deletes them in a single operation.. +keywords: [annoatations, delete] +--- diff --git a/playground/annotations/custom-ink-color-buttons/index.ts b/playground/annotations/custom-ink-color-buttons/index.ts new file mode 100644 index 0000000..cbd2ed3 --- /dev/null +++ b/playground/annotations/custom-ink-color-buttons/index.ts @@ -0,0 +1,104 @@ +import type { Color, ToolbarItem } from "@nutrient-sdk/viewer"; +import { baseOptions } from "../../shared/base-options"; + +// Initialize window.NutrientViewer with custom configuration +window.NutrientViewer.load({ + ...baseOptions, + theme: window.NutrientViewer.Theme.DARK, +}).then((instance) => { + // SVG template for pen icon - used for both red and blue pens + const PEN_ICON_SVG = ` + + + `; + + // Creates a pen icon DOM node with specified color + function createPenIconNode(color: string): HTMLDivElement { + const node = document.createElement("div"); + node.innerHTML = PEN_ICON_SVG.replace("currentColor", color); + node.querySelector("path")?.setAttribute("fill", color); + const svg = node.querySelector("svg"); + if (svg) { + svg.style.color = color; + } + return node; + } + + // Creates a custom toolbar item + function createInkToolbarItem( + title: string, + node: HTMLDivElement, + color: Color, + ): ToolbarItem { + return { + type: "custom", + title, + node, + onPress: async () => { + const { interactionMode } = instance.viewState; + + if (interactionMode === "INK" || interactionMode === null) { + // Disable current interaction mode + instance.setViewState((viewState) => + viewState.set("interactionMode", null), + ); + + // Update ink preset with selected color + instance.setAnnotationPresets((presets) => ({ + ...presets, + ink: { + ...presets.ink, + strokeColor: color, + }, + })); + + // Enable ink mode with new color + instance.setCurrentAnnotationPreset("ink"); + instance.setViewState((viewState) => + viewState.set( + "interactionMode", + window.NutrientViewer.InteractionMode.INK, + ), + ); + } else { + // Disable interaction mode + instance.setViewState((viewState) => + viewState.set("interactionMode", null), + ); + } + }, + }; + } + + // Create pen icon nodes for each color + const redPenNode = createPenIconNode("red"); + const bluePenNode = createPenIconNode("blue"); + + // Create toolbar items for red and blue ink + const redInkTool = createInkToolbarItem( + "Red Ink", + redPenNode, + window.NutrientViewer.Color.RED, + ); + const blueInkTool = createInkToolbarItem( + "Blue Ink", + bluePenNode, + window.NutrientViewer.Color.BLUE, + ); + + // Add custom tools to toolbar next to the default ink tool + instance.setToolbarItems((items) => + items.reduce( + (toolbar, item) => + item.type === "ink" + ? [...toolbar, item, redInkTool, blueInkTool] + : [...toolbar, item], + [], + ), + ); +}); diff --git a/playground/annotations/custom-ink-color-buttons/playground.mdx b/playground/annotations/custom-ink-color-buttons/playground.mdx new file mode 100644 index 0000000..a5fdfe3 --- /dev/null +++ b/playground/annotations/custom-ink-color-buttons/playground.mdx @@ -0,0 +1,6 @@ +--- +category: annotations +title: Custom Ink Color Buttons +description: Adds custom toolbar buttons for red and blue ink annotations. When clicked, these buttons update the ink preset color and activate ink drawing mode, allowing quick color switching without navigating through menus. +keywords: [annotations, ink, toolbar, custom, color] +--- diff --git a/playground/annotations/custom-stamp-toolbar-items/index.ts b/playground/annotations/custom-stamp-toolbar-items/index.ts new file mode 100644 index 0000000..1514324 --- /dev/null +++ b/playground/annotations/custom-stamp-toolbar-items/index.ts @@ -0,0 +1,124 @@ +import type { Instance, ToolbarItem } from "@nutrient-sdk/viewer"; +import { baseOptions } from "../../shared/base-options"; + +let selectedStamp: string | null = null; +let instance: Instance | null = null; + +function updateCursor(inst: Instance) { + const contentDoc = inst.contentDocument; + if (!("host" in contentDoc)) return; + + const container = contentDoc.host as HTMLElement; + if (selectedStamp) { + container.style.cursor = "move"; + } else { + container.style.cursor = "default"; + } +} + +// Function to apply stamp to the currently visible page +async function applyStampToCurrentPage( + inst: Instance, + stampType: string, + top = 50, + left = 50, + width = 150, + height = 50, +) { + // Get the current page index from the view state + const currentPageIndex = inst.viewState.currentPageIndex; + + const stamp = new window.NutrientViewer.Annotations.StampAnnotation({ + pageIndex: currentPageIndex, + stampType: stampType, + boundingBox: new window.NutrientViewer.Geometry.Rect({ + left: left - width / 2, + top: top - height / 2, + width: width, + height: height, + }), + }); + + // Create the stamp annotation + await inst.create(stamp); +} + +// Function to remove stamp annotations from the current page +async function removeStampFromCurrentPage() { + if (!instance) return; + + const currentPageIndex = instance.viewState.currentPageIndex; + + // Get all annotations on the current page + const annotations = await instance.getAnnotations(currentPageIndex); + + // Filter for stamp annotations only + const stampAnnotations = annotations.filter( + (annotation) => + annotation instanceof window.NutrientViewer.Annotations.StampAnnotation, + ); + await instance.delete(stampAnnotations); +} + +const customStampItems: ToolbarItem[] = [ + { + type: "custom", + id: "no-stamp", + title: "No Stamp", + dropdownGroup: "custom-stamps", + onPress: () => removeStampFromCurrentPage(), + }, + { + type: "custom", + id: "stamp-approved", + title: "Approved Stamp", + dropdownGroup: "custom-stamps", + onPress: () => { + selectedStamp = "Approved"; + if (instance) updateCursor(instance); + }, + }, + { + type: "custom", + id: "stamp-rejected", + title: "Rejected Stamp", + dropdownGroup: "custom-stamps", + onPress: () => { + selectedStamp = "Rejected"; + if (instance) updateCursor(instance); + }, + }, + { + type: "custom", + id: "stamp-draft", + title: "Draft Stamp", + dropdownGroup: "custom-stamps", + onPress: () => { + selectedStamp = "Draft"; + if (instance) updateCursor(instance); + }, + }, +]; + +window.NutrientViewer.load({ + ...baseOptions, + theme: window.NutrientViewer.Theme.DARK, + toolbarItems: [ + ...window.NutrientViewer.defaultToolbarItems, + ...customStampItems, + ], +}).then((_instance) => { + instance = _instance; + instance.addEventListener("page.press", async (event) => { + if (selectedStamp) { + await applyStampToCurrentPage( + _instance, + selectedStamp, + event.point.y, + event.point.x, + ); + selectedStamp = null; // Reset after applying + updateCursor(_instance); + } + }); +}); diff --git a/playground/annotations/custom-stamp-toolbar-items/playground.mdx b/playground/annotations/custom-stamp-toolbar-items/playground.mdx new file mode 100644 index 0000000..d46eef0 --- /dev/null +++ b/playground/annotations/custom-stamp-toolbar-items/playground.mdx @@ -0,0 +1,6 @@ +--- +category: annotations +title: Custom Stamp Toolbar Items +description: Adds custom toolbar dropdown items for stamp annotations (Approved, Rejected, Draft). Select a stamp type, then click anywhere on the page to place it. Also includes a "No Stamp" option to remove all stamps from the current page. +keywords: [annotations, stamp, toolbar, dropdown, custom] +--- diff --git a/playground/annotations/cut-copy-paste-annotations/index.ts b/playground/annotations/cut-copy-paste-annotations/index.ts new file mode 100644 index 0000000..959b7b5 --- /dev/null +++ b/playground/annotations/cut-copy-paste-annotations/index.ts @@ -0,0 +1,115 @@ +import type { ToolbarItem } from "@nutrient-sdk/viewer"; +import { baseOptions } from "../../shared/base-options"; + +window.NutrientViewer.load({ + ...baseOptions, + theme: window.NutrientViewer.Theme.DARK, + enableClipboardActions: true, +}).then((instance) => { + // Clipboard actions + const copy: ToolbarItem = { + type: "custom", + title: "Copy", + onPress: async () => { + const event = /(Mac)/i.test(navigator.platform) + ? new KeyboardEvent("keydown", { key: "c", metaKey: true }) + : new KeyboardEvent("keydown", { key: "c", ctrlKey: true }); + document.dispatchEvent(event); + }, + }; + + const paste: ToolbarItem = { + type: "custom", + title: "Paste", + onPress: async () => { + const event = /(Mac)/i.test(navigator.platform) + ? new KeyboardEvent("keydown", { key: "v", metaKey: true }) + : new KeyboardEvent("keydown", { key: "v", ctrlKey: true }); + document.dispatchEvent(event); + }, + }; + + const cut: ToolbarItem = { + type: "custom", + title: "Cut", + onPress: async () => { + const event = /(Mac)/i.test(navigator.platform) + ? new KeyboardEvent("keydown", { key: "x", metaKey: true }) + : new KeyboardEvent("keydown", { key: "x", ctrlKey: true }); + document.dispatchEvent(event); + }, + }; + + instance.setToolbarItems((items) => { + items.push(cut); + items.push(paste); + items.push(copy); + items.push({ type: "comment" }); + return items; + }); + + let isProcessingPaste = false; // Declared outside to persist across events + + document.addEventListener("paste", async (event) => { + if (isProcessingPaste) return; + + isProcessingPaste = true; + + try { + const clipboardData = event.clipboardData; + if (!clipboardData) return; + + const items = clipboardData.items; + const item = items[0]; // Only processing the first clipboard item + + const contentType = item.type; + const currentPage = instance.viewState.currentPageIndex; + + if (item.kind === "file" && item.type.startsWith("image")) { + const file = item.getAsFile(); + if (!file) return; + + const imageAttachmentId = await instance.createAttachment(file); + const annotation = + new window.NutrientViewer.Annotations.ImageAnnotation({ + pageIndex: currentPage, + contentType: contentType, + imageAttachmentId, + description: "Pasted Image Annotation", + boundingBox: new window.NutrientViewer.Geometry.Rect({ + left: 10, + top: 50, + width: 150, + height: 150, + }), + }); + await instance.create(annotation); + } else if (item.kind === "string") { + item.getAsString(async (pastedText: string) => { + const textAnnotation = + new window.NutrientViewer.Annotations.TextAnnotation({ + pageIndex: currentPage, + fontSize: 10, + strokeColor: window.NutrientViewer.Color.GREEN, + fontColor: window.NutrientViewer.Color.BLUE, + text: { + format: "plain", + value: pastedText, + }, + boundingBox: new window.NutrientViewer.Geometry.Rect({ + left: 10, + top: 50, + width: 150, + height: 50, + }), + }); + await instance.create(textAnnotation); + }); + } else { + console.log("Unsupported clipboard item"); + } + } finally { + isProcessingPaste = false; + } + }); +}); diff --git a/playground/annotations/cut-copy-paste-annotations/playground.mdx b/playground/annotations/cut-copy-paste-annotations/playground.mdx new file mode 100644 index 0000000..5d328c5 --- /dev/null +++ b/playground/annotations/cut-copy-paste-annotations/playground.mdx @@ -0,0 +1,6 @@ +--- +category: annotations +title: Cut Copy Paste Annotations +description: Adds custom Cut, Copy, and Paste toolbar buttons that trigger keyboard shortcuts. Also handles system paste events to create Image or Text annotations from clipboard content on the current page. +keywords: [annotations, clipboard, copy, paste, cut, toolbar] +--- diff --git a/playground/annotations/highlight-annotations-from-search/index.ts b/playground/annotations/highlight-annotations-from-search/index.ts new file mode 100644 index 0000000..74636d4 --- /dev/null +++ b/playground/annotations/highlight-annotations-from-search/index.ts @@ -0,0 +1,41 @@ +import type { HighlightAnnotation } from "@nutrient-sdk/viewer"; +import { baseOptions } from "../../shared/base-options"; + +// Load window.NutrientViewer with dark theme +window.NutrientViewer.load({ + ...baseOptions, + theme: window.NutrientViewer.Theme.DARK, +}).then(async (instance) => { + // List of terms to search for in the document + const namesList = ["primate", "shellfish", "monkey", "macaque"]; + + // Creates an array of promises that return the searchResult of each search + const annotations = ( + await Promise.all(namesList.map((name) => instance.search(name))) + ).flatMap((searchResults) => + searchResults.reduce((acc, searchResult) => { + // Extract location information for the found text + const { rectsOnPage, pageIndex } = searchResult; + + const firstRect = rectsOnPage.first(); + if (!firstRect) return acc; + + // Create bounding box for the highlight + const bbox = new window.NutrientViewer.Geometry.Rect(firstRect); + + // Create a new highlight annotation + return acc.concat( + new window.NutrientViewer.Annotations.HighlightAnnotation({ + id: window.NutrientViewer.generateInstantId(), + pageIndex: pageIndex, + boundingBox: bbox, + rects: rectsOnPage, + color: window.NutrientViewer.Color.YELLOW, + }), + ); + }, []), + ); + + // Apply all highlight annotations to the document + await instance.create(annotations); +}); diff --git a/playground/annotations/highlight-annotations-from-search/playground.mdx b/playground/annotations/highlight-annotations-from-search/playground.mdx new file mode 100644 index 0000000..dc27d11 --- /dev/null +++ b/playground/annotations/highlight-annotations-from-search/playground.mdx @@ -0,0 +1,6 @@ +--- +category: annotations +title: Highlight Annotations From Search +description: Searches the document for a list of terms and automatically creates yellow highlight annotations for each match found. Demonstrates combining the search API with annotation creation. +keywords: [annotations, highlight, search, text] +--- diff --git a/playground/annotations/prevent-annotations-dragging/index.ts b/playground/annotations/prevent-annotations-dragging/index.ts new file mode 100644 index 0000000..9e4bfc3 --- /dev/null +++ b/playground/annotations/prevent-annotations-dragging/index.ts @@ -0,0 +1,40 @@ +import { List } from "@nutrient-sdk/viewer"; +import { baseOptions } from "../../shared/base-options"; + +window.NutrientViewer.load({ + ...baseOptions, + theme: window.NutrientViewer.Theme.DARK, +}).then((instance) => { + instance.addEventListener("annotations.press", (event) => { + const { annotation } = event; + + // Handling special case for Text Annotations + if ( + instance.getSelectedAnnotations() !== null && + annotation instanceof window.NutrientViewer.Annotations.TextAnnotation + ) { + return instance.setEditingAnnotation(annotation, false); + } + + // Handling special case for Notes slightly different than Text Annotations + if ( + annotation instanceof window.NutrientViewer.Annotations.NoteAnnotation + ) { + event.preventDefault?.(); + return instance.setEditingAnnotation(annotation, false); + } + + // Ignoring Forms and Links to allow default focus and redirect behaviors respectively + if ( + annotation instanceof + window.NutrientViewer.Annotations.WidgetAnnotation || + annotation instanceof window.NutrientViewer.Annotations.LinkAnnotation + ) { + return; + } + + // Preventing dragging for the rest of Annotation Types + event.preventDefault?.(); + instance.setSelectedAnnotations(List([annotation])); + }); +}); diff --git a/playground/annotations/prevent-annotations-dragging/playground.mdx b/playground/annotations/prevent-annotations-dragging/playground.mdx new file mode 100644 index 0000000..62455a2 --- /dev/null +++ b/playground/annotations/prevent-annotations-dragging/playground.mdx @@ -0,0 +1,6 @@ +--- +category: annotations +title: Prevent Annotations Dragging +description: Prevents drag behavior when clicking on annotations. Text and Note annotations enter edit mode instead, Widget and Link annotations retain default behavior, and all other annotations are selected without dragging. +keywords: [annotations, dragging, selection, preventDefault] +--- diff --git a/playground/annotations/shape-annotations-custom-outline/index.ts b/playground/annotations/shape-annotations-custom-outline/index.ts new file mode 100644 index 0000000..1fb0cb3 --- /dev/null +++ b/playground/annotations/shape-annotations-custom-outline/index.ts @@ -0,0 +1,28 @@ +import { List } from "@nutrient-sdk/viewer"; +import { baseOptions } from "../../shared/base-options"; + +window.NutrientViewer.load({ + ...baseOptions, + theme: window.NutrientViewer.Theme.DARK, +}).then((instance) => { + instance.addEventListener("page.press", async (event) => { + const { point, nativeEvent } = event; + + // Find annotation where click is within the outline area (outer - inner bounding box) + const annotation = (await instance.getAnnotations(0)).find((annotation) => { + const outerRect = annotation.boundingBox.grow(30); + const innerRect = annotation.boundingBox.grow(-30); + return outerRect.isPointInside(point) && !innerRect.isPointInside(point); + }); + + const isSelected = instance.getSelectedAnnotations(); + + if (annotation && !isSelected) { + nativeEvent.stopImmediatePropagation(); + setTimeout( + () => instance.setSelectedAnnotations(List([annotation.id])), + 0, + ); + } + }); +}); diff --git a/playground/annotations/shape-annotations-custom-outline/playground.mdx b/playground/annotations/shape-annotations-custom-outline/playground.mdx new file mode 100644 index 0000000..1dbe31b --- /dev/null +++ b/playground/annotations/shape-annotations-custom-outline/playground.mdx @@ -0,0 +1,6 @@ +--- +category: annotations +title: Shape Annotations Custom Outline Selection +description: Enables selecting shape annotations by clicking on their outline border. Detects clicks within a 30px margin around the annotation bounding box, allowing selection without clicking directly on the annotation content. +keywords: [annotations, shape, selection, outline, bounding box] +--- diff --git a/playground/annotations/shape-annotations-custom-outline/styles.css b/playground/annotations/shape-annotations-custom-outline/styles.css new file mode 100644 index 0000000..bce54ed --- /dev/null +++ b/playground/annotations/shape-annotations-custom-outline/styles.css @@ -0,0 +1,42 @@ +/* Add your CSS here */ +.PSPDFKit-Shape-Annotation { + pointer-events: auto; + outline: none !important; +} + +.PSPDFKit-Shape-Annotation:not(:has(svg ellipse))::after { + cursor: default !important; + pointer-events: auto; + content: ""; + position: absolute; + top: 1.25rem; + bottom: 1.25rem; + left: 1.25rem; + right: 1.25rem; + outline: 1px solid red !important; +} + +.PSPDFKit-Shape-Annotation:has(svg ellipse)::after { + cursor: default !important; + pointer-events: auto; + content: ""; + position: absolute; + top: 1.25rem; + bottom: 1.25rem; + left: 1.25rem; + right: 1.25rem; + outline: 1px solid red !important; + border-radius: 50%; +} + +.PSPDFKit-Shape-Annotation::before { + cursor: pointer; + pointer-events: auto; + content: ""; + position: absolute; + top: -25px; + bottom: -25px; + left: -25px; + right: -25px; + outline: 2px solid pink; +} diff --git a/playground/annotations/skip-delete-confirmation-dialog/index.ts b/playground/annotations/skip-delete-confirmation-dialog/index.ts new file mode 100644 index 0000000..037e28c --- /dev/null +++ b/playground/annotations/skip-delete-confirmation-dialog/index.ts @@ -0,0 +1,29 @@ +import type { Annotation } from "@nutrient-sdk/viewer"; +import { baseOptions } from "../../shared/base-options"; + +// Skip the Delete confirmation pop-up dialog for Annotations +window.NutrientViewer.load({ + ...baseOptions, + theme: window.NutrientViewer.Theme.DARK, +}).then((instance) => { + // Skip annotation delete confirmation popup + instance.addEventListener("annotations.willChange", (event) => { + // Check if this is a delete start action + if ( + event.reason === + window.NutrientViewer.AnnotationsWillChangeReason.DELETE_START + ) { + const annotation = event.annotations.first() as Annotation | undefined; + if (!annotation) return; + + // Delete the annotation immediately + instance.delete(annotation.id); + + // Click the cancel button to prevent the dialog from showing + const cancelButton = instance.contentDocument.querySelector( + ".PSPDFKit-Confirm-Dialog-Button-Cancel", + ) as HTMLElement | null; + cancelButton?.click(); + } + }); +}); diff --git a/playground/annotations/skip-delete-confirmation-dialog/playground.mdx b/playground/annotations/skip-delete-confirmation-dialog/playground.mdx new file mode 100644 index 0000000..8c5d68e --- /dev/null +++ b/playground/annotations/skip-delete-confirmation-dialog/playground.mdx @@ -0,0 +1,6 @@ +--- +category: annotations +title: Skip Delete Confirmation Dialog +description: Bypasses the default delete confirmation dialog when removing annotations. Listens for DELETE_START events and immediately deletes the annotation while dismissing the confirmation popup. +keywords: [annotations, delete, dialog, confirmation] +--- diff --git a/playground/annotations/toggle-annotations-visibility/index.ts b/playground/annotations/toggle-annotations-visibility/index.ts new file mode 100644 index 0000000..665752c --- /dev/null +++ b/playground/annotations/toggle-annotations-visibility/index.ts @@ -0,0 +1,37 @@ +import type { Annotation, ToolbarItem } from "@nutrient-sdk/viewer"; +import { baseOptions } from "../../shared/base-options"; + +let annotationsHidden = true; + +window.NutrientViewer.load({ + ...baseOptions, + theme: window.NutrientViewer.Theme.DARK, + toolbarItems: [...window.NutrientViewer.defaultToolbarItems], +}).then(async (instance) => { + const toggleAnnotationsButton: ToolbarItem = { + type: "custom", + id: "toggle-annotations", + title: "Toggle Annotations", + onPress: async () => { + const totalPages = instance.totalPageCount; + const updatedAnnotations: Annotation[] = []; + + for (let i = 0; i < totalPages; i++) { + const annotations = await instance.getAnnotations(i); + + annotations.forEach((annotation: Annotation) => { + const updatedAnnotation = annotation.set("noView", annotationsHidden); + updatedAnnotations.push(updatedAnnotation); + }); + } + + await instance.update(updatedAnnotations); + annotationsHidden = !annotationsHidden; + }, + }; + + instance.setToolbarItems((items) => { + items.push(toggleAnnotationsButton); + return items; + }); +}); diff --git a/playground/annotations/toggle-annotations-visibility/playground.mdx b/playground/annotations/toggle-annotations-visibility/playground.mdx new file mode 100644 index 0000000..6883411 --- /dev/null +++ b/playground/annotations/toggle-annotations-visibility/playground.mdx @@ -0,0 +1,6 @@ +--- +category: annotations +title: Toggle Annotations Visibility +description: Adds a toolbar button to toggle the visibility of all annotations in the document. Uses the noView property to show or hide annotations across all pages. +keywords: [annotations, visibility, toggle, noView, toolbar] +--- diff --git a/playground/package-lock.json b/playground/package-lock.json index 1b0add4..a4912e2 100644 --- a/playground/package-lock.json +++ b/playground/package-lock.json @@ -1,63 +1,63 @@ -{ - "name": "playground-examples", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "playground-examples", - "devDependencies": { - "@nutrient-sdk/viewer": "^1.9.1", - "typescript": "5.9.2" - } - }, - "node_modules/@nutrient-sdk/viewer": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@nutrient-sdk/viewer/-/viewer-1.9.1.tgz", - "integrity": "sha512-QODpcjobRvbwBlHwcFN3yKqmXgzr04zFKbqTyRfyYFwun1JiXwE4z5zhep0QSxQlavFJvnwz0aABZBz5/LrkNg==", - "dev": true, - "license": "SEE LICENSE IN https://www.nutrient.io/legal/Nutrient_SDK_User_Evaluation_Subscription_Agreement", - "dependencies": { - "@types/react": "^18.0.0" - } - }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "18.3.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", - "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.2.2" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - } - } -} +{ + "name": "playground-examples", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "playground-examples", + "devDependencies": { + "@nutrient-sdk/viewer": "^1.9.1", + "typescript": "5.9.2" + } + }, + "node_modules/@nutrient-sdk/viewer": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@nutrient-sdk/viewer/-/viewer-1.9.1.tgz", + "integrity": "sha512-QODpcjobRvbwBlHwcFN3yKqmXgzr04zFKbqTyRfyYFwun1JiXwE4z5zhep0QSxQlavFJvnwz0aABZBz5/LrkNg==", + "dev": true, + "license": "SEE LICENSE IN https://www.nutrient.io/legal/Nutrient_SDK_User_Evaluation_Subscription_Agreement", + "dependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", + "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +}