# Transfer

A dual-panel selection component that allows users to move items between two lists. Features include multiple selection, bulk transfer operations, custom item rendering, and search filtering. Perfect for role assignments, permission management, and data organization tasks.

## Installation

```bash
npm install @ticatec/uniface-element
```

## Import

```typescript
import Transfer, { type TransferData } from "@ticatec/uniface-element/Transfer";
```

## Basic Usage

```svelte
<script>
  import Transfer from "@ticatec/uniface-element/Transfer";
  
  let availableUsers = [
    { id: 1, name: "John Doe", email: "john@example.com" },
    { id: 2, name: "Jane Smith", email: "jane@example.com" },
    { id: 3, name: "Bob Wilson", email: "bob@example.com" },
    { id: 4, name: "Alice Brown", email: "alice@example.com" }
  ];
  
  let selectedUsers = [
    { id: 5, name: "Charlie Davis", email: "charlie@example.com" }
  ];
  
  async function handleTransfer(items, direction) {
    console.log(`Moving ${items.length} items ${direction}`);
    // Add your transfer logic here
    return true; // Return true to allow the transfer
  }
</script>

<Transfer 
  sourceList={availableUsers}
  sourceTitle="Available Users"
  targetList={selectedUsers}
  targetTitle="Selected Users"
  onchange={handleTransfer}
/>
```

## Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `sourceList` | `Array<any>` | Required | Items available for selection |
| `sourceTitle` | `string` | Required | Title for the source panel |
| `targetList` | `Array<any>` | Required | Currently selected items |
| `targetTitle` | `string` | Required | Title for the target panel |
| `onchange` | `TransferData` | `null` | Callback when items are transferred |
| `onfocus` | `((event: FocusEvent) => void) \| null` | `null` | Callback when component gains focus |
| `onblur` | `((event: FocusEvent) => void) \| null` | `null` | Callback when component loses focus |
| `itemRender` | `ComponentType \| null` | `null` | Custom component for rendering items |
| `filter` | `FunFilter \| null` | `null` | Search filter function |
| `style` | `string` | `""` | Additional CSS styles |
| `footer$style` | `string` | Default footer styling | Custom footer styles |

## Type Definitions

### TransferData
```typescript
type TransferData = (list: Array<any>, direction: "left" | "right") => Promise<boolean>;
```

The transfer callback function receives:
- `list`: Array of items being transferred
- `direction`: "left" (to source) or "right" (to target)
- Returns: Promise<boolean> - true to allow transfer, false to prevent

## Examples

### User Role Management

```svelte
<script>
  import Transfer from "@ticatec/uniface-element/Transfer";
  
  let availableUsers = [
    { id: 1, name: "John Doe", role: "Developer", department: "Engineering" },
    { id: 2, name: "Jane Smith", role: "Designer", department: "Product" },
    { id: 3, name: "Bob Wilson", role: "Manager", department: "Sales" },
    { id: 4, name: "Alice Brown", role: "Analyst", department: "Marketing" }
  ];
  
  let adminUsers = [
    { id: 5, name: "Charlie Davis", role: "Admin", department: "IT" }
  ];
  
  async function handleRoleAssignment(users, direction) {
    console.log(`${direction === 'right' ? 'Granting' : 'Revoking'} admin access for:`, users);
    
    // Simulate API call
    try {
      await new Promise(resolve => setTimeout(resolve, 1000));
      
      if (direction === 'right') {
        // Grant admin access
        users.forEach(user => {
          console.log(`Granted admin access to ${user.name}`);
        });
      } else {
        // Revoke admin access
        users.forEach(user => {
          console.log(`Revoked admin access from ${user.name}`);
        });
      }
      
      return true;
    } catch (error) {
      console.error('Failed to update user roles:', error);
      return false;
    }
  }
</script>

<div class="role-management">
  <h3>Admin Role Management</h3>
  <Transfer 
    sourceList={availableUsers}
    sourceTitle="Available Users"
    targetList={adminUsers}
    targetTitle="Admin Users"
    onchange={handleRoleAssignment}
  />
</div>

<style>
  .role-management {
    max-width: 800px;
    margin: 20px auto;
    padding: 20px;
  }
  
  h3 {
    margin-bottom: 20px;
    text-align: center;
  }
</style>
```

### With Custom Item Rendering

```svelte
<script>
  import Transfer from "@ticatec/uniface-element/Transfer";
  import UserCard from "./UserCard.svelte"; // Custom component
  
  let candidates = [
    { 
      id: 1, 
      name: "John Doe", 
      email: "john@example.com",
      skills: ["JavaScript", "React", "Node.js"],
      experience: 5
    },
    { 
      id: 2, 
      name: "Jane Smith", 
      email: "jane@example.com",
      skills: ["Python", "Django", "PostgreSQL"],
      experience: 3
    },
    { 
      id: 3, 
      name: "Bob Wilson", 
      email: "bob@example.com",
      skills: ["Java", "Spring", "MySQL"],
      experience: 7
    }
  ];
  
  let shortlisted = [];
  
  async function handleShortlist(candidates, direction) {
    // Add validation logic
    if (direction === 'right' && shortlisted.length + candidates.length > 5) {
      alert('Cannot shortlist more than 5 candidates');
      return false;
    }
    
    return true;
  }
</script>

<!-- UserCard.svelte -->
<script>
  export let item;
</script>

<div class="user-card">
  <div class="user-header">
    <h4>{item.name}</h4>
    <span class="experience">{item.experience} years</span>
  </div>
  <p class="email">{item.email}</p>
  <div class="skills">
    {#each item.skills as skill}
      <span class="skill-tag">{skill}</span>
    {/each}
  </div>
</div>

<style>
  .user-card {
    padding: 12px;
    border: 1px solid #eee;
    border-radius: 4px;
    margin: 4px 0;
  }
  
  .user-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 8px;
  }
  
  .user-header h4 {
    margin: 0;
    font-size: 16px;
  }
  
  .experience {
    background: #007bff;
    color: white;
    padding: 2px 8px;
    border-radius: 12px;
    font-size: 12px;
  }
  
  .email {
    margin: 0 0 8px 0;
    color: #666;
    font-size: 14px;
  }
  
  .skills {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
  }
  
  .skill-tag {
    background: #f8f9fa;
    border: 1px solid #dee2e6;
    padding: 2px 6px;
    border-radius: 4px;
    font-size: 12px;
  }
</style>

<!-- Main component -->
<div class="candidate-selection">
  <h3>Candidate Shortlisting</h3>
  <Transfer 
    sourceList={candidates}
    sourceTitle="All Candidates"
    targetList={shortlisted}
    targetTitle="Shortlisted"
    itemRender={UserCard}
    onchange={handleShortlist}
  />
</div>

<style>
  .candidate-selection {
    max-width: 900px;
    margin: 20px auto;
    padding: 20px;
  }
</style>
```

### With Search Filter

```svelte
<script>
  import Transfer from "@ticatec/uniface-element/Transfer";
  
  let allProducts = [
    { id: 1, name: "Laptop Pro", category: "Electronics", price: 1299 },
    { id: 2, name: "Wireless Mouse", category: "Electronics", price: 25 },
    { id: 3, name: "Coffee Maker", category: "Kitchen", price: 89 },
    { id: 4, name: "Desk Chair", category: "Furniture", price: 199 },
    { id: 5, name: "Monitor Stand", category: "Electronics", price: 45 },
    { id: 6, name: "Blender", category: "Kitchen", price: 120 }
  ];
  
  let cartItems = [];
  
  // Custom filter function
  function productFilter(item, searchTerm) {
    if (!searchTerm) return true;
    
    const term = searchTerm.toLowerCase();
    return item.name.toLowerCase().includes(term) ||
           item.category.toLowerCase().includes(term) ||
           item.price.toString().includes(term);
  }
  
  async function handleAddToCart(products, direction) {
    if (direction === 'right') {
      console.log('Adding to cart:', products);
    } else {
      console.log('Removing from cart:', products);
    }
    return true;
  }
</script>

<div class="product-transfer">
  <h3>Product Selection</h3>
  <Transfer 
    sourceList={allProducts}
    sourceTitle="Available Products"
    targetList={cartItems}
    targetTitle="Shopping Cart"
    filter={productFilter}
    onchange={handleAddToCart}
  />
  
  {#if cartItems.length > 0}
    <div class="cart-summary">
      <h4>Cart Summary</h4>
      <p>Items: {cartItems.length}</p>
      <p>Total: ${cartItems.reduce((sum, item) => sum + item.price, 0)}</p>
    </div>
  {/if}
</div>

<style>
  .product-transfer {
    max-width: 800px;
    margin: 20px auto;
    padding: 20px;
  }
  
  .cart-summary {
    margin-top: 20px;
    padding: 16px;
    background: #f8f9fa;
    border-radius: 8px;
    text-align: center;
  }
  
  .cart-summary h4 {
    margin: 0 0 12px 0;
  }
  
  .cart-summary p {
    margin: 4px 0;
    font-size: 16px;
  }
</style>
```

### Permission Management

```svelte
<script>
  import Transfer from "@ticatec/uniface-element/Transfer";
  
  let availablePermissions = [
    { id: 1, name: "Read Users", module: "User Management", level: "Read" },
    { id: 2, name: "Create Users", module: "User Management", level: "Write" },
    { id: 3, name: "Delete Users", module: "User Management", level: "Admin" },
    { id: 4, name: "View Reports", module: "Analytics", level: "Read" },
    { id: 5, name: "Export Data", module: "Analytics", level: "Write" },
    { id: 6, name: "System Settings", module: "Administration", level: "Admin" }
  ];
  
  let rolePermissions = [];
  let selectedRole = "Editor";
  
  async function handlePermissionChange(permissions, direction) {
    console.log(`${direction === 'right' ? 'Granting' : 'Revoking'} permissions:`, permissions);
    
    // Validation: Admin permissions require approval
    const adminPermissions = permissions.filter(p => p.level === 'Admin');
    if (direction === 'right' && adminPermissions.length > 0) {
      const confirmed = confirm(
        `You are about to grant admin-level permissions. Continue?`
      );
      if (!confirmed) return false;
    }
    
    return true;
  }
  
  function resetPermissions() {
    rolePermissions = [];
    availablePermissions = [...availablePermissions, ...rolePermissions];
  }
</script>

<div class="permission-management">
  <div class="role-header">
    <h3>Role: {selectedRole}</h3>
    <select bind:value={selectedRole}>
      <option value="Viewer">Viewer</option>
      <option value="Editor">Editor</option>
      <option value="Admin">Admin</option>
    </select>
    <button on:click={resetPermissions}>Reset</button>
  </div>
  
  <Transfer 
    sourceList={availablePermissions}
    sourceTitle="Available Permissions"
    targetList={rolePermissions}
    targetTitle="Role Permissions"
    onchange={handlePermissionChange}
  />
  
  <div class="permission-summary">
    <h4>Permission Summary</h4>
    {#each ['Read', 'Write', 'Admin'] as level}
      {@const count = rolePermissions.filter(p => p.level === level).length}
      <div class="permission-level">
        <span class="level-name">{level}:</span>
        <span class="level-count">{count}</span>
      </div>
    {/each}
  </div>
</div>

<style>
  .permission-management {
    max-width: 900px;
    margin: 20px auto;
    padding: 20px;
  }
  
  .role-header {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-bottom: 20px;
  }
  
  .role-header h3 {
    margin: 0;
    flex: 1;
  }
  
  .role-header select,
  .role-header button {
    padding: 8px 12px;
    border: 1px solid #ccc;
    border-radius: 4px;
  }
  
  .permission-summary {
    margin-top: 20px;
    padding: 16px;
    background: #f8f9fa;
    border-radius: 8px;
  }
  
  .permission-summary h4 {
    margin: 0 0 12px 0;
  }
  
  .permission-level {
    display: flex;
    justify-content: space-between;
    margin: 8px 0;
  }
  
  .level-name {
    font-weight: 500;
  }
  
  .level-count {
    background: #007bff;
    color: white;
    padding: 2px 8px;
    border-radius: 12px;
    font-size: 14px;
  }
</style>
```

### With Event Handlers

```svelte
<script>
  import Transfer from "@ticatec/uniface-element/Transfer";
  
  let sourceItems = ["Item 1", "Item 2", "Item 3", "Item 4"];
  let targetItems = ["Item 5"];
  let isFocused = false;
  let transferLog = [];
  
  async function handleTransfer(items, direction) {
    const action = direction === 'right' ? 'moved to target' : 'moved to source';
    const logEntry = `${new Date().toLocaleTimeString()}: ${items.length} item(s) ${action}`;
    transferLog = [...transferLog, logEntry];
    
    return true;
  }
  
  function handleFocus(event) {
    console.log('Transfer component focused');
    isFocused = true;
  }
  
  function handleBlur(event) {
    console.log('Transfer component blurred');
    isFocused = false;
  }
</script>

<div class="event-demo">
  <h3>Transfer with Event Handling</h3>
  <p>Component is {isFocused ? 'focused' : 'not focused'}</p>
  
  <Transfer 
    sourceList={sourceItems}
    sourceTitle="Source"
    targetList={targetItems}
    targetTitle="Target"
    onchange={handleTransfer}
    onfocus={handleFocus}
    onblur={handleBlur}
  />
  
  <div class="transfer-log">
    <h4>Transfer Log:</h4>
    {#each transferLog.slice(-5) as entry}
      <p>{entry}</p>
    {/each}
  </div>
</div>

<style>
  .event-demo {
    max-width: 800px;
    margin: 20px auto;
    padding: 20px;
  }
  
  .transfer-log {
    margin-top: 20px;
    padding: 16px;
    background: #f8f9fa;
    border-radius: 8px;
    max-height: 200px;
    overflow-y: auto;
  }
  
  .transfer-log h4 {
    margin: 0 0 12px 0;
  }
  
  .transfer-log p {
    margin: 4px 0;
    font-family: monospace;
    font-size: 14px;
  }
</style>
```

### Bulk Operations

```svelte
<script>
  import Transfer from "@ticatec/uniface-element/Transfer";
  
  let employees = [
    { id: 1, name: "John Doe", department: "Engineering", salary: 75000 },
    { id: 2, name: "Jane Smith", department: "Marketing", salary: 65000 },
    { id: 3, name: "Bob Wilson", department: "Sales", salary: 70000 },
    { id: 4, name: "Alice Brown", department: "HR", salary: 60000 },
    { id: 5, name: "Charlie Davis", department: "Engineering", salary: 80000 }
  ];
  
  let projectTeam = [];
  let maxTeamSize = 3;
  
  async function handleTeamAssignment(members, direction) {
    if (direction === 'right') {
      // Check team size limit
      if (projectTeam.length + members.length > maxTeamSize) {
        alert(`Team size cannot exceed ${maxTeamSize} members`);
        return false;
      }
      
      // Check budget constraint
      const newBudget = [...projectTeam, ...members]
        .reduce((sum, member) => sum + member.salary, 0);
      
      if (newBudget > 200000) {
        const confirmed = confirm(
          `Adding these members will exceed budget ($${newBudget.toLocaleString()}). Continue?`
        );
        if (!confirmed) return false;
      }
    }
    
    return true;
  }
  
  function moveAllToTarget() {
    if (employees.length + projectTeam.length <= maxTeamSize) {
      projectTeam = [...projectTeam, ...employees];
      employees = [];
    } else {
      alert(`Cannot add all employees. Team size limit: ${maxTeamSize}`);
    }
  }
  
  function moveAllToSource() {
    employees = [...employees, ...projectTeam];
    projectTeam = [];
  }
  
  $: currentBudget = projectTeam.reduce((sum, member) => sum + member.salary, 0);
</script>

<div class="team-builder">
  <div class="controls">
    <h3>Project Team Builder</h3>
    <div class="bulk-actions">
      <button on:click={moveAllToTarget}>Add All</button>
      <button on:click={moveAllToSource}>Remove All</button>
    </div>
  </div>
  
  <Transfer 
    sourceList={employees}
    sourceTitle="Available Employees"
    targetList={projectTeam}
    targetTitle="Project Team"
    onchange={handleTeamAssignment}
  />
  
  <div class="team-stats">
    <div class="stat">
      <span>Team Size:</span>
      <span>{projectTeam.length}/{maxTeamSize}</span>
    </div>
    <div class="stat">
      <span>Budget:</span>
      <span>${currentBudget.toLocaleString()}</span>
    </div>
    <div class="stat">
      <span>Avg Salary:</span>
      <span>${projectTeam.length > 0 ? Math.round(currentBudget / projectTeam.length).toLocaleString() : 0}</span>
    </div>
  </div>
</div>

<style>
  .team-builder {
    max-width: 900px;
    margin: 20px auto;
    padding: 20px;
  }
  
  .controls {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
  }
  
  .controls h3 {
    margin: 0;
  }
  
  .bulk-actions {
    display: flex;
    gap: 8px;
  }
  
  .bulk-actions button {
    padding: 8px 16px;
    border: 1px solid #007bff;
    background: white;
    color: #007bff;
    border-radius: 4px;
    cursor: pointer;
  }
  
  .bulk-actions button:hover {
    background: #007bff;
    color: white;
  }
  
  .team-stats {
    margin-top: 20px;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 16px;
  }
  
  .stat {
    padding: 16px;
    background: #f8f9fa;
    border-radius: 8px;
    text-align: center;
  }
  
  .stat span:first-child {
    display: block;
    font-weight: 500;
    margin-bottom: 8px;
  }
  
  .stat span:last-child {
    font-size: 18px;
    color: #007bff;
    font-weight: bold;
  }
</style>
```

## Features

- **Dual Panel Layout**: Clean source and target lists with transfer controls
- **Multiple Selection**: Select multiple items for bulk transfer operations
- **Custom Rendering**: Use custom components to display items
- **Search & Filter**: Built-in search functionality with custom filter functions
- **Transfer Validation**: Async callback system for validating transfers
- **Event Handling**: Focus, blur, and change event support
- **Keyboard Accessibility**: Full keyboard navigation support
- **Responsive Design**: Adapts to different screen sizes
- **Internationalization**: Built-in i18n support for labels

## Styling

The Transfer component uses CSS Grid layout and can be customized with CSS:

```css
/* Custom transfer styling */
.uniface-transfer-container {
  /* Your custom styles */
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 16px;
  min-height: 400px;
}

.list-container {
  /* Custom list box styles */
}

.icon-button-container {
  /* Custom button container styles */
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 8px;
}
```

## Accessibility

- Keyboard navigation with Tab and Enter keys
- ARIA attributes for screen readers
- Focus management and visual indicators
- Semantic HTML structure
- High contrast support

## Best Practices

1. **Validation**: Always implement transfer validation in the `onchange` callback
2. **Custom Rendering**: Use `itemRender` for complex item display needs
3. **Search**: Implement appropriate filter functions for large datasets
4. **Error Handling**: Provide clear feedback when transfers fail
5. **Performance**: Consider pagination for very large lists
6. **Accessibility**: Ensure proper keyboard navigation and screen reader support

## Browser Support

- Modern browsers with full event support
- Compatible with Svelte 5+
- CSS Grid layout support
- Touch-friendly interface
- Full TypeScript support

## Related Components

- `ListBox` - Single list selection component
- `OptionsSelect` - Dropdown selection component
- `CheckboxGroup` - Multiple checkbox selection