Skip to content

Conversation

@eli7pm
Copy link
Contributor

@eli7pm eli7pm commented Sep 24, 2025

Overview

This PR introduces a new signing demo implementation that leverages Custom Overlays instead of the traditional CustomRenderer callback approach, providing a more robust and developer-friendly solution for signing workflows.

Key Features

  • 🖋️ Interactive Signature Widgets - Click-to-sign functionality with visual feedback
    • 📝 Editable Text Fields - Double-click to edit with inline editing capabilities
    • 📅 Date Picker Widgets - Calendar interface for date selection
    • 🎯 Drag & Drop Interface - Intuitive widget positioning in form creator mode
    • ⚡ React Hook Architecture - Clean, reusable useNutrientViewer hook
    • 🎨 Extensible Widget System - Easy to add new widget types

Why Custom Overlays Beat CustomRenderer?

The Problem with CustomRenderer:

  • Complex annotation lifecycle management
  • Fragile drag & drop with DOM manipulation bugs
  • Mixed React/vanilla JS patterns causing maintenance headaches
  • Multiple state synchronization points between UI and annotations

The Custom Overlay Solution:

  • ✅ 90% Less Code - Clean React components vs complex custom renderers
  • ✅ Zero Drag & Drop Bugs - Overlays independent of annotation state
  • ✅ Atomic State Updates - Simple data flow instead of complex synchronization
  • ✅ Standard React Patterns - Familiar development experience

Key Technical Improvements

Aspect CustomRenderer (Complete Demo) Custom Overlay (This PR)
Code Complexity 906-line monolith 18 modular files
State Management Multiple refs + manual sync Single custom hook
Widget Creation Complex DOM manipulation Simple React components
Drag & Drop Manual bounding box calculations Built-in collision detection
Developer Experience Mixed paradigms Pure React patterns

Demo Capabilities

  • Form Creator Mode - Drag and drop widgets from sidebar
    • Multiple Widget Types - Signature, initials, text, and date fields
    • Real-time Interaction - Immediate visual feedback for user actions
    • Collision Detection - Prevents widget overlap with 8px gap enforcement
    • Boundary Validation - Widgets constrain to document boundaries

This approach provides a more maintainable and scalable foundation for document signing workflows while significantly reducing implementation complexity.

@eli7pm eli7pm requested a review from InosRahul September 24, 2025 05:14
@eli7pm eli7pm self-assigned this Sep 24, 2025
@eli7pm eli7pm added the enhancement New feature or request label Sep 24, 2025
@eli7pm eli7pm requested a review from veroo-m September 26, 2025 02:01
@veroo-m veroo-m requested a review from a team September 26, 2025 14:23
@InosRahul
Copy link
Contributor

Did some QA, below are my findings:

  1. The sidebar isn't responsive and doesn't have scroll, so the instructions below get cut off.
CleanShot.2025-10-28.at.21.49.15.mp4
  1. The signature modal is not centered.
CleanShot 2025-10-28 at 21 52 54@2x
  1. No way to delete Widget Annotation, either by clicking Delete on Keyboard or through UI
    https://github.com/user-attachments/assets/64aad58e-8193-4732-8753-09f0248ffb1a

  2. Date picker is not being shown and Date Field take any input, no input validation is present.

CleanShot.2025-10-28.at.21.57.33.mp4
  1. Dragging widgets through the page or repositioning them is very slow.
CleanShot.2025-10-28.at.22.03.20.mp4

@eli7pm
Copy link
Contributor Author

eli7pm commented Oct 28, 2025

Did some QA, below are my findings:

  1. The sidebar isn't responsive and doesn't have scroll, so the instructions below get cut off.

CleanShot.2025-10-28.at.21.49.15.mp4
2. The signature modal is not centered.

CleanShot 2025-10-28 at 21 52 54@2x 3. No way to delete Widget Annotation, either by clicking Delete on Keyboard or through UI https://github.com/user-attachments/assets/64aad58e-8193-4732-8753-09f0248ffb1a 4. Date picker is not being shown and Date Field take any input, no input validation is present.

CleanShot.2025-10-28.at.21.57.33.mp4
5. Dragging widgets through the page or repositioning them is very slow.

CleanShot.2025-10-28.at.22.03.20.mp4

@InosRahul thanks a lot.

  1. isn't responsive in what sense? I'll add scroll.
  2. Wouldn't that be a problem with the Web SDK itself?
CleanShot 2025-10-28 at 12 41 41@2x 3. will add 4. will add 5. I'll check it out

## Support

For issues related to:
- **Nutrient Viewer**: Check the [Nutrient documentation](https://docs.nutrient.io/)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This URL is incorrect. Should be https://www.nutrient.io/guides/web/

The demo now supports all available NutrientViewer FormField types:
- **SignatureFormField**: Signature and Initials
- **TextFormField**: Text, Name, Email, Date (with format validation)
- **CheckBoxFormField**: Checkbox controls
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if I'm wrong but the UI didn't show this,

Comment on lines +188 to +192
### Developer-Friendly Features
- **TypeScript-Ready**: Structured for easy TypeScript integration
- **Modular Architecture**: Separated concerns with dedicated utility modules
- **Comprehensive Error Handling**: Graceful degradation when SDK features are unavailable
- **Performance Monitoring**: Built-in performance tracking utilities
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These lines can be removed.

"widget",
"overlay"
],
"author": "Your Name",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be Nutrient or Eli

"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/yourusername/nutrient-signing-demo.git"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

URL should be corrected.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should commit this file. Can be kept in .gitignore.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completely disagree. Always keep the package-lock.json. That's the only thing that actually freezes the versions of the dependencies.

https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json#description

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh yes, I forgot about this. My comment can be ignored in this case, thank you for correcting.

};

// Widget Overlay Manager Class
class WidgetOverlayManager {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there a class inside a hook file? 😲 You can probably extract this into a different file.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, definitely to be extracted

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, This could be a nightmare to debug. This should be simplified.

Copy link

@sc0 sc0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All in all it looks solid. Few comments, but mostly me being annoying and/or a smartass. All in all good job! Happy to approve once @InosRahul is happy.

One thing to consider - you have a lot of CSS text just baked in the code. Might be worth to "unbloat" the code by extracting it to classes and applying those to elements, but that's just a personal taste kind of thing.

};

// Widget Overlay Manager Class
class WidgetOverlayManager {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, definitely to be extracted

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completely disagree. Always keep the package-lock.json. That's the only thing that actually freezes the versions of the dependencies.

https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json#description

Comment on lines +275 to +280
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ?
`${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}` :
"0, 123, 255";
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Sorry, I have natural allergy to regex -- if you're happy with this, ignore this comment.

This helper's not really required, since you're only using it with element.style.backgroundColor. CSS can take color in a lot of formats, including hex. That also includes opacity, so if you have a color #aaffee and want to change it to 10% opacity, you can just write it as #aaffee1a (where 1a is 0.1 * 255 ≈ 26). So lines like fieldElement.style.backgroundColor = 'rgba(${hexToRgb(fieldType.color)}, 0.1)'; can be simplified to fieldElement.style.backgroundColor = fieldType.color + "1a";. It's also a little more performant for the browser to render, but that's a totally marginal gain.

Also this helper function kinda doesn't really belong here, but I'm just being annoying, it doesn't matter.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get the idea and I understand ui customization guides use dynamic element creation, but I feel like this is taking it to extreme, resulting in very hard to maintain code. Since you're using React, I'd consider hacking it around a bit, creating a proper React component and just returning it as HTMLElement. I did not test this approach and can't guarantee it'll work, but think it's worth exploring. Here's a short snippet that might help, by the courtesy of GPT 5-mini:

import { createRoot } from 'react-dom/client';

function createInteractiveElement(props) {
  const container = document.createElement('div'); // not in DOM unless you append it
  const root = createRoot(container);
  root.render(<MyComplexComponent {...props} />);

  // Return the element the library expects.
  // If the library will insert this into the document, you may want to return container.firstElementChild
  // or append `container` itself where needed.
  return {
    element: container.firstElementChild,
    cleanup: () => root.unmount(), // caller can call when done
  };
}

Please feel free to reach out if you need help with that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants