or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration.mdcore-usage.mddata-management.mdevents.mdindex.mdtemplates.mdtypes.md
tile.json

events.mddocs/

Events

Complete reference for Choices.js event system with detailed event types, data structures, and handling patterns for monitoring user interactions and state changes.

Event Types

{ .api }
type EventType = 
  | 'addItem' 
  | 'removeItem' 
  | 'highlightItem' 
  | 'unhighlightItem'
  | 'choice' 
  | 'change' 
  | 'search' 
  | 'showDropdown' 
  | 'hideDropdown'
  | 'highlightChoice';

Event Constants

{ .api }
const EVENTS = {
  showDropdown: 'showDropdown',
  hideDropdown: 'hideDropdown', 
  change: 'change',
  choice: 'choice',
  search: 'search',
  addItem: 'addItem',
  removeItem: 'removeItem',
  highlightItem: 'highlightItem',
  highlightChoice: 'highlightChoice',
  unhighlightItem: 'unhighlightItem'
};

Event Listening

Basic Event Handling

// Listen on the original element
const element = document.querySelector('#my-select');
const choices = new Choices(element);

element.addEventListener('change', (event) => {
  console.log('Selection changed:', event.detail);
});

// Or listen on choices.passedElement.element
choices.passedElement.element.addEventListener('addItem', (event) => {
  console.log('Item added:', event.detail);
});

Multiple Event Listeners

// Listen to multiple events
['addItem', 'removeItem', 'change'].forEach(eventType => {
  element.addEventListener(eventType, (event) => {
    console.log(`Event: ${eventType}`, event.detail);
  });
});

Event Details

change Event

Fired when the overall selection changes.

{ .api }
interface ChangeEventDetail {
  value: string | string[];
}

Usage:

element.addEventListener('change', (event) => {
  console.log('New value:', event.detail.value);
  
  // For single select: event.detail.value is string
  // For multi-select: event.detail.value is string[]
});

Example:

// Single select change
// event.detail = { value: "selected-option" }

// Multi-select change  
// event.detail = { value: ["option1", "option2"] }

addItem Event

Fired when an item is added to the selection.

{ .api }
interface AddItemEventDetail {
  id: number;
  value: string;
  label: string;
  customProperties?: Record<string, any>;
  keyCode?: number;
}

Usage:

element.addEventListener('addItem', (event) => {
  const { id, value, label, customProperties, keyCode } = event.detail;
  
  console.log(`Added: ${label} (${value})`);
  
  if (customProperties) {
    console.log('Custom data:', customProperties);
  }
  
  if (keyCode) {
    console.log('Added via keyboard:', keyCode);
  }
});

Example:

// event.detail = {
//   id: 123,
//   value: "premium-plan",
//   label: "Premium Plan",
//   customProperties: { price: 29.99, features: [...] },
//   keyCode: 13 // Enter key
// }

removeItem Event

Fired when an item is removed from the selection.

{ .api }
interface RemoveItemEventDetail {
  id: number;
  value: string;
  label: string;
  customProperties?: Record<string, any>;
}

Usage:

element.addEventListener('removeItem', (event) => {
  const { id, value, label, customProperties } = event.detail;
  
  console.log(`Removed: ${label} (${value})`);
  
  // Handle cleanup for custom properties
  if (customProperties?.tempFile) {
    cleanupTempFile(customProperties.tempFile);
  }
});

choice Event

Fired when a choice is selected from the dropdown (before becoming an item).

{ .api }
interface ChoiceEventDetail {
  choice: {
    id: number;
    value: string;
    label: string;
    customProperties?: Record<string, any>;
  };
}

Usage:

element.addEventListener('choice', (event) => {
  const choice = event.detail.choice;
  
  console.log('Choice selected:', choice.label);
  
  // Can be used for analytics or validation before addition
  if (choice.customProperties?.requiresConfirmation) {
    if (!confirm(`Add ${choice.label}?`)) {
      // Prevention would need to be handled in addItem event
    }
  }
});

search Event

Fired when user performs a search.

{ .api }
interface SearchEventDetail {
  value: string;
  resultCount: number;
}

Usage:

element.addEventListener('search', (event) => {
  const { value, resultCount } = event.detail;
  
  console.log(`Searched for: "${value}" - ${resultCount} results`);
  
  // Trigger external search or analytics
  if (value.length >= 3) {
    trackSearchQuery(value);
  }
});

highlightItem Event

Fired when an item is highlighted (keyboard navigation or hover).

{ .api }
interface HighlightItemEventDetail {
  id: number;
  value: string;
  label: string;
}

Usage:

element.addEventListener('highlightItem', (event) => {
  const { id, value, label } = event.detail;
  
  console.log(`Highlighted item: ${label}`);
  
  // Update UI or show additional information
  showItemPreview(value);
});

unhighlightItem Event

Fired when an item highlight is removed.

{ .api }
interface UnhighlightItemEventDetail {
  id: number;
  value: string;  
  label: string;
}

Usage:

element.addEventListener('unhighlightItem', (event) => {
  const { id, value, label } = event.detail;
  
  console.log(`Unhighlighted item: ${label}`);
  
  // Clean up UI
  hideItemPreview();
});

highlightChoice Event

Fired when a choice in the dropdown is highlighted.

{ .api }
interface HighlightChoiceEventDetail {
  choice: {
    id: number;
    value: string;
    label: string;
  };
}

showDropdown Event

Fired when the dropdown is shown.

element.addEventListener('showDropdown', (event) => {
  console.log('Dropdown opened');
  
  // Load dynamic content, track analytics
  loadAdditionalChoices();
  trackDropdownOpen();
});

hideDropdown Event

Fired when the dropdown is hidden.

element.addEventListener('hideDropdown', (event) => {
  console.log('Dropdown closed');
  
  // Cleanup, save state
  saveCurrentState();
});

Event Handling Patterns

Form Validation

element.addEventListener('addItem', (event) => {
  const { value } = event.detail;
  
  // Validate new item
  if (!isValidSelection(value)) {
    // Remove invalid item
    choices.removeActiveItemsByValue(value);
    showValidationError('Invalid selection');
  }
});

Real-time Updates

// Sync with other components
element.addEventListener('change', (event) => {
  updateRelatedFields(event.detail.value);
  calculateTotal();
  validateForm();
});

// Update counters
element.addEventListener('addItem', () => {
  updateSelectionCounter(choices.getValue().length);
});

element.addEventListener('removeItem', () => {
  updateSelectionCounter(choices.getValue().length);
});

Analytics and Tracking

// Track user behavior
element.addEventListener('search', (event) => {
  analytics.track('search_performed', {
    query: event.detail.value,
    results: event.detail.resultCount
  });
});

element.addEventListener('choice', (event) => {
  analytics.track('option_selected', {
    option: event.detail.choice.value,
    label: event.detail.choice.label
  });
});

// Track interaction patterns
let interactionStart;
element.addEventListener('showDropdown', () => {
  interactionStart = Date.now();
});

element.addEventListener('hideDropdown', () => {
  const duration = Date.now() - interactionStart;
  analytics.track('dropdown_interaction', { duration });
});

Dynamic Content Loading

// Load choices based on search
let searchTimeout;
element.addEventListener('search', (event) => {
  const { value } = event.detail;
  
  clearTimeout(searchTimeout);
  searchTimeout = setTimeout(async () => {
    if (value.length >= 2) {
      const results = await searchAPI(value);
      choices.setChoices(results, 'id', 'name', true);
    }
  }, 300);
});

// Load more choices when dropdown opens
element.addEventListener('showDropdown', async () => {
  if (!hasLoadedInitialChoices) {
    const initialChoices = await loadInitialChoices();
    choices.setChoices(initialChoices);
    hasLoadedInitialChoices = true;
  }
});

Custom Validation

// Maximum selection validation
element.addEventListener('addItem', (event) => {
  const currentCount = choices.getValue().length;
  const maxAllowed = 3;
  
  if (currentCount > maxAllowed) {
    choices.removeActiveItemsByValue(event.detail.value);
    showError(`Maximum ${maxAllowed} selections allowed`);
  }
});

// Custom business logic
element.addEventListener('addItem', (event) => {
  const { value, customProperties } = event.detail;
  
  if (customProperties?.requiresPermission) {
    if (!hasPermission(value)) {
      choices.removeActiveItemsByValue(value);
      showError('Insufficient permissions for this selection');
    }
  }
});

State Synchronization

// Two-way binding with framework state
element.addEventListener('change', (event) => {
  // Update React state
  setSelectedItems(event.detail.value);
  
  // Update Vue data
  this.selectedItems = event.detail.value;
  
  // Update Angular model
  this.selectedItems = event.detail.value;
});

// Sync with localStorage
element.addEventListener('change', (event) => {
  localStorage.setItem('choices_selection', JSON.stringify(event.detail.value));
});

// Restore from localStorage
window.addEventListener('load', () => {
  const saved = localStorage.getItem('choices_selection');
  if (saved) {
    choices.setValue(JSON.parse(saved));
  }
});

Accessibility Enhancements

// Announce changes to screen readers
element.addEventListener('addItem', (event) => {
  announceToScreenReader(`Added ${event.detail.label}`);
});

element.addEventListener('removeItem', (event) => {
  announceToScreenReader(`Removed ${event.detail.label}`);
});

function announceToScreenReader(message) {
  const announcement = document.createElement('div');
  announcement.setAttribute('aria-live', 'polite');
  announcement.setAttribute('aria-atomic', 'true');
  announcement.className = 'sr-only';
  announcement.textContent = message;
  
  document.body.appendChild(announcement);
  setTimeout(() => document.body.removeChild(announcement), 1000);
}

Error Handling

// Handle event errors gracefully
element.addEventListener('addItem', (event) => {
  try {
    processNewItem(event.detail);
  } catch (error) {
    console.error('Error processing new item:', error);
    choices.removeActiveItemsByValue(event.detail.value);
    showUserError('Failed to add item. Please try again.');
  }
});

// Validate event data
element.addEventListener('change', (event) => {
  if (!event.detail || typeof event.detail.value === 'undefined') {
    console.warn('Invalid change event data:', event.detail);
    return;
  }
  
  handleValidChange(event.detail.value);
});

Event Prevention

Note: Choices.js events cannot be prevented using preventDefault() as they are custom events fired after actions complete. To prevent actions, handle them in the event and reverse the action:

// "Prevent" item addition by immediately removing it
element.addEventListener('addItem', (event) => {
  if (shouldPreventAddition(event.detail.value)) {
    choices.removeActiveItemsByValue(event.detail.value);
  }
});

This comprehensive event system enables deep integration with application logic, validation, analytics, and user experience enhancements.