npm package: @smarterservices/smarter-elements
Description: Embeddable UI components for SmarterServices
Author: SmarterServices
Latest version: 1.0.25

# SmarterElements

Embeddable UI components for SmarterServices that can be integrated into any web application. Built with TypeScript for type safety and better developer experience.

## Installation

### For Node.js/React Applications

```bash
npm install @smarterservices/smarter-elements
```

or

```bash
yarn add @smarterservices/smarter-elements
```

This will install the package with React as an external dependency, which means it will use your application's React instance.

### For Direct Browser Usage

For direct usage in browsers without a build step, include the browser bundle script from unpkg:

```html
<!-- Latest version -->  
<script src="https://unpkg.com/@smarterservices/smarter-elements/dist/browser.js"></script>

<!-- Specific version (recommended for production) -->  
<script src="https://unpkg.com/@smarterservices/smarter-elements@1.0.24/dist/browser.js"></script>

<!-- Latest in a major version -->  
<script src="https://unpkg.com/@smarterservices/smarter-elements@1/dist/browser.js"></script>

<!-- Latest in a minor version -->  
<script src="https://unpkg.com/@smarterservices/smarter-elements@1.0/dist/browser.js"></script>
```

### TypeScript Support

TypeScript type definitions are included in the package. No additional type installations are required.

```typescript
import SmarterElements from '@smarterservices/smarter-elements';

// Create a new instance with TypeScript type checking
const elements = SmarterElements({
  baseUrl: 'https://your-smarterproctoring-url.com',
});

// Create an element with type checking
export const examCard = elements.create('examCard', {
  exam: {
    id: 'exam123',
    title: 'Biology 101',
    // TypeScript will enforce the correct property types
  },
});
```

## Browser Support

SmarterElements supports all modern browsers.

## Usage

### TypeScript Support

All components and utilities are fully typed. You'll get autocompletion and type checking when using TypeScript in your project.

#### TypeScript Configuration

Ensure your `tsconfig.json` has the following compiler options:

```json
{
  "compilerOptions": {
    "esModuleInterop": true,
    "moduleResolution": "node",
    "target": "es2018"
  }
}
```

#### Available Types

```typescript
import type {
  SmarterElements,
  ElementInstance,
  ElementOptions,
  EventHandler
} from '@smarterservices/smarter-elements';
```

### Examples

Examples of how to use SmarterElements can be found in the UI application at:

```
http://localhost:4000/elements-examples/elements-integration.html
```

When running the UI application locally, you can access these examples to see how different components work and how to integrate them into your application.

### JavaScript Integration

#### Using the Browser Bundle

The browser bundle is specifically designed for direct usage in browsers without a build step. It includes React internally, so you don't need to include React separately.

```html
<!-- Using the browser bundle from unpkg CDN -->  
<script src="https://unpkg.com/@smarterservices/smarter-elements/dist/browser.js"></script>

<script>
  // Create a new instance with clean API
  const elements = new SmarterElements({
    baseUrl: 'https://your-smarterproctoring-url.com',
  });

  // Create an exam card
  const examCard = elements.create('examCard', {
    exam: {
      id: 'exam123',
      title: 'Biology 101',
      course: 'BIO 101 - Introduction to Biology',
      date: '2025-06-15',
      startTime: '10:00 AM',
      duration: 90,
    },
  });
</script>
```

#### Using a Specific Version

You can specify a particular version of the browser bundle:

```html
<!-- Using a specific major version (1.x.x - latest in 1.x range) -->
<script src="https://unpkg.com/@smarterservices/smarter-elements@1/dist/browser.js"></script>

<!-- Using a specific minor version (1.0.x - latest patch in 1.0 range) -->
<script src="https://unpkg.com/@smarterservices/smarter-elements@1.0/dist/browser.js"></script>

<!-- Using an exact version (1.0.24) -->
<script src="https://unpkg.com/@smarterservices/smarter-elements@1.0.24/dist/browser.js"></script>

<script>
  // Create a new instance
  const elements = new SmarterElements({
    baseUrl: 'https://your-smarterproctoring-url.com',
  });

  // Create an exam card
  const examCard = elements.create('examCard', {
    exam: {
      id: 'exam123',
      title: 'Biology 101',
      course: 'BIO 101 - Introduction to Biology',
      date: '2025-06-15',
      startTime: '10:00 AM',
      duration: 90,
    },
  });

  // Mount the exam card to a container
  examCard.mount('#exam-card-container');

  // Add event listeners
  examCard.on('start-exam', data => {
    console.log('Start exam clicked:', data);
  });

  examCard.on('view-details', data => {
    console.log('View details clicked:', data);
  });
</script>
```

### React Integration

#### Installing a Specific Version

```bash
# Install the latest version
npm install @smarterservices/smarter-elements

# Install a specific major version (latest in 1.x range)
npm install @smarterservices/smarter-elements@1

# Install a specific minor version (latest patch in 1.0 range)
npm install @smarterservices/smarter-elements@1.0

# Install an exact version
npm install @smarterservices/smarter-elements@1.0.24
```

#### Basic Integration

When using the package in a React application, the library will use your application's React instance. This avoids duplicate React instances and prevents "Invalid hook call" errors.

```jsx
import React, { useEffect, useRef } from 'react';
import SmarterElements from '@smarterservices/smarter-elements';

function ExamCardComponent() {
  const containerRef = useRef(null);
  const examCardRef = useRef(null);

  useEffect(() => {
    // Create a new instance
    const elements = SmarterElements({
      baseUrl: 'https://your-smarterproctoring-url.com',
    });

    // Create an exam card
    const examCard = elements.create('examCard', {
      exam: {
        id: 'exam123',
        title: 'Biology 101',
        course: 'BIO 101 - Introduction to Biology',
        date: '2025-06-15',
        startTime: '10:00 AM',
        duration: 90,
      },
    });

    // Mount the exam card to the container
    examCard.mount(containerRef.current);

    // Add event listeners
    examCard.on('start-exam', data => {
      console.log('Start exam clicked:', data);
    });

    examCard.on('view-details', data => {
      console.log('View details clicked:', data);
    });

    // Save the reference for cleanup
    examCardRef.current = examCard;

    // Cleanup
    return () => {
      if (examCardRef.current) {
        examCardRef.current.unmount();
      }
    };
  }, []);

  return <div ref={containerRef} className="exam-card-container"></div>;
}

export default ExamCardComponent;
```

## Available Elements

- `examCard`: Displays exam information with actions
- `proctorSchedule`: Shows proctor schedule with calendar view
- `examVerification`: Handles exam verification process
- `identityCheck`: Manages identity verification
- `elementsLayout`: Provides a layout container for elements

## Events

Each element emits events that you can listen to:

### ExamCard Events

- `start-exam`: Triggered when the start exam button is clicked
- `view-details`: Triggered when the view details button is clicked
- `resize`: Triggered when the element's size changes

### ProctorSchedule Events

- `date-changed`: Triggered when the selected date changes
- `session-selected`: Triggered when a session is selected

## Modal Support

SmarterElements provides built-in modal support for displaying elements in a centered dialog:

```javascript
// Create an element instance
const elements = SmarterElements({
  baseUrl: 'https://your-smarterproctoring-url.com',
});

const examCard = elements.create('examCard', {
  exam: {
    id: 'exam456',
    title: 'Physics 202',
    course: 'PHYS 202 - Quantum Mechanics',
    date: '2025-10-15',
    startTime: '2:00 PM',
    duration: 120,
  },
});

// Open the element in a modal
examCard.openModal({
  width: '800px',
  maxWidth: '90vw',
  maxHeight: '90vh'
});

// Close the modal programmatically
examCard.closeModal();

// Listen for modal events
examCard.on('modal:open', (data) => {
  console.log('Modal opened:', data);
});

examCard.on('modal:close', (data) => {
  console.log('Modal closed:', data);
});
```

## Element Constraints & Resizing

SmarterElements includes a unified resizing system with support for min/max constraints and smooth animations.

### Setting Constraints

```javascript
// Create an element
const examCard = elements.create('examCard', { exam: {...} });

// Set constraints after creation
examCard.updateConstraints({
  minHeight: 200,
  maxHeight: 600,
  minWidth: 300,
  maxWidth: 800
});

// Get current constraints
const constraints = examCard.getConstraints();
console.log('Current constraints:', constraints);

// Listen for constraint updates
examCard.on('constraints:update', ({ constraints }) => {
  console.log('Constraints updated:', constraints);
});
```

### Automatic Resizing Setup

#### In the parent page (hosting the iframe):

```javascript
import { setupUnifiedParentResize } from '@smarterservices/smarter-elements';

// Set up resize handling
const cleanup = setupUnifiedParentResize(
  iframe,
  ({ height, width }) => {
    iframe.style.height = `${height}px`;
    console.log('New dimensions:', { height, width });
  },
  {
    elementId: 'my-element',
    minHeight: 100,
    maxHeight: 800
  }
);

// Cleanup when done
cleanup();
```

#### In the iframe content (React example):

```jsx
import { setupUnifiedContentResize } from '@smarterservices/smarter-elements';
import { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    // Set up automatic resizing with constraints
    const cleanup = setupUnifiedContentResize({
      minHeight: 100,      // Minimum height in pixels
      maxHeight: 600,      // Maximum height in pixels
      elementId: 'my-element',  // Element ID for targeted messages
      debounceMs: 16       // Debounce delay (60fps)
    });
    
    return cleanup;
  }, []);
  
  return (
    <div>
      {/* Your component content */}
    </div>
  );
}
```

### HTML/JS Usage (No Build Step)

For iframe content without a build step, you can use the auto-initialization feature:

```html
<script src="https://unpkg.com/@smarterservices/smarter-elements/dist/browser.js" 
        data-auto-resize 
        data-min-height="100"
        data-max-height="600"
        data-element-id="your-element-id">
</script>
```

Or initialize manually after loading the script:

```html
<script src="https://unpkg.com/@smarterservices/smarter-elements/dist/browser.js"></script>
<script>
  // Initialize resize handling
  const cleanup = SmarterElements.setupUnifiedContentResize({
    minHeight: 100,
    maxHeight: 600,
    elementId: 'my-element'
  });
  
  // Cleanup when page unloads
  window.addEventListener('beforeunload', cleanup);
</script>
```

### Observing Specific Elements

To ensure specific elements trigger a resize when they change, add the `data-observe-resize` attribute:

```html
<div data-observe-resize>
  <!-- Content that might change size -->
</div>
```

### Resize Events

You can listen for resize events on the element instance:

```javascript
// Listen for resize events (includes both dimensions)
element.on('resize', ({ height, width, elementId }) => {
  console.log('Element resized:', { height, width, elementId });
});

// Listen for constraint updates
element.on('constraints:update', ({ constraints, elementId }) => {
  console.log('Constraints updated:', constraints);
});
```

### Best Practices

1. **Use a minimum height** to prevent the iframe from collapsing when content is loading
2. **Add padding/margin to the offset** if your content has space that isn't captured by the height calculation
3. **Use element IDs** when embedding multiple iframes to ensure messages don't get mixed up
4. **Clean up** event listeners when unmounting components to prevent memory leaks

## Troubleshooting

### Iframe Not Resizing

1. **Check the console** for any error messages
2. **Verify the iframe URL** - The parent and iframe must be on the same domain or have proper CORS headers
3. **Check content security policy** - Ensure your CSP allows `postMessage` communication
4. **Verify the iframe is loaded** - Wait for the iframe's `load` event before setting up resizing

### Common Issues

- **Content jumps on load**: Set a reasonable `minHeight` to prevent layout shifts
- **Resize not working with dynamic content**: Ensure you've added `data-observe-resize` to dynamic content containers
- **Messages not received**: Check that the `elementId` matches between parent and iframe if using targeted messages

## Build System

SmarterElements uses a dual build approach to support different usage scenarios:

### NPM Package Builds (ESM and CommonJS)

When installed via npm, the package provides:

- **ESM build** (`dist/index.mjs`): For modern bundlers and environments that support ES modules
- **CommonJS build** (`dist/index.js`): For Node.js and older bundlers

Both of these builds externalize React, meaning they expect React to be provided by your application. This prevents duplicate React instances and avoids "Invalid hook call" errors.

### Browser Bundle (IIFE)

For direct browser usage without a build step, the package includes:

- **Browser bundle** (`dist/browser.js`): An IIFE build that includes React internally

This bundle exposes `SmarterElements` as a global constructor and can be used directly in a browser without any build tools or separate React installation.

## Versioning

This package follows [Semantic Versioning](https://semver.org/):

- **MAJOR** version changes indicate incompatible API changes
- **MINOR** version changes add functionality in a backward-compatible manner
- **PATCH** version changes include backward-compatible bug fixes

### Version Compatibility

To ensure your integration remains stable, we recommend the following versioning strategies:

#### For Production Applications

- **Lock to an exact version** in your package.json: `"@smarterservices/smarter-elements": "1.2.3"`
- Update versions manually after testing with your application

#### For Development/Staging

- **Lock to a minor version** using the caret syntax: `"@smarterservices/smarter-elements": "^1.2.0"`
- This allows automatic updates for patch releases (bug fixes) but prevents potentially breaking changes

#### Version Compatibility Matrix

| SmarterElements Version | React Compatibility | Browser Support | Notes                                 |
| ----------------------- | ------------------- | --------------- | ------------------------------------- |
| 1.x.x                   | React 18+           | Modern browsers | Initial stable release series         |
| 2.x.x (future)          | React 18+           | Modern browsers | May contain breaking changes from 1.x |

#### Choosing the Right Version

When selecting a version for your application:

1. **For new projects**: Use the latest version available
2. **For existing projects**:
   - Check the CHANGELOG.md for breaking changes before upgrading major versions
   - Test thoroughly in a staging environment before deploying to production
3. **For critical applications**: Pin to an exact version and only upgrade after thorough testing

## Browser Support

SmarterElements supports all modern browsers:

- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)

## License

MIT