or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

commands.mdindex.mdplugins.mdstate-and-ui.mdtypes-and-config.md
tile.json

state-and-ui.mddocs/

State Management and UI

Observable state management with comprehensive UI components for find and replace functionality. The system provides automatic synchronization between state changes and UI updates.

Capabilities

FindAndReplaceState

Observable state object that manages all find and replace state with automatic UI synchronization.

/**
 * Observable state object managing search and replace state.
 * All properties are observable and trigger UI updates when changed.
 */
class FindAndReplaceState extends ObservableMixin {
  /**
   * Creates a new state instance
   * @param model - The CKEditor model instance
   */
  constructor(model: Model);
  
  /** Collection of current search results */
  results: Collection<FindResultType>;
  
  /** Currently highlighted/selected result */
  highlightedResult: FindResultType | null;
  
  /** 1-indexed offset of the highlighted result */
  highlightedOffset: number;
  
  /** Current search text */
  searchText: string;
  
  /** Current replacement text */
  replaceText: string;
  
  /** Last used search callback function */
  lastSearchCallback: FindCallback | null;
  
  /** Whether case-sensitive matching is enabled */
  matchCase: boolean;
  
  /** Whether whole word matching is enabled */
  matchWholeWords: boolean;
  
  /**
   * Cleans up state and removes search markers from the model
   * @param model - The CKEditor model instance
   */
  clear(model: Model): void;
  
  /**
   * Recalculates the highlight offset based on current model state
   * @param model - The CKEditor model instance
   */
  refreshHighlightOffset(model: Model): void;
}

Usage Examples:

// Access the state
const editing = editor.plugins.get('FindAndReplaceEditing');
const state = editing.state;

// Listen to state changes
state.on('change:searchText', (evt, propertyName, newValue, oldValue) => {
  console.log(`Search text changed from "${oldValue}" to "${newValue}"`);
});

state.on('change:results', () => {
  console.log(`Found ${state.results.length} results`);
});

// Modify state programmatically
state.searchText = 'new search';
state.matchCase = true;
state.matchWholeWords = false;

// Clear all results and markers
state.clear(editor.model);

FindAndReplaceFormView

Main form view component providing the complete find and replace user interface.

/**
 * Main form view component for find and replace interface.
 * Provides input fields, controls, and handles user interactions.
 */
class FindAndReplaceFormView extends View {
  /**
   * Creates a new form view instance
   * @param locale - The locale instance for internationalization
   */
  constructor(locale: Locale);
  
  /** Child views collection */
  children: ViewCollection;
  
  /** Focus management for keyboard navigation */
  focusCycler: FocusCycler;
  
  /** Number of search results found */
  matchCount: number;
  
  /** Current highlight offset (1-indexed) */
  highlightOffset: number;
  
  /** Whether search parameters have changed since last search */
  isDirty: boolean;
  
  /** Display text for the results counter */
  _resultsCounterText: string;
  
  /** Whether case matching is enabled */
  _matchCase: boolean;
  
  /** Whether whole words only matching is enabled */
  _wholeWordsOnly: boolean;
  
  /** Whether search results were found */
  _searchResultsFound: boolean;
  
  /** Command enabled states */
  _areCommandsEnabled: Record<string, boolean>;
  
  /** Renders the form and all its components */
  render(): void;
  
  /** Cleans up the form and removes event listeners */
  destroy(): void;
  
  /**
   * Focuses the form, optionally specifying direction for focus cycling
   * @param direction - Focus direction (1 for forward, -1 for backward)
   */
  focus(direction?: 1 | -1): void;
  
  /** Resets the form to its initial state */
  reset(): void;
}

Usage Examples:

// Access the form view
const ui = editor.plugins.get('FindAndReplaceUI');
const formView = ui.formView;

if (formView) {
  // Listen to form events
  formView.on('findNext', (evt, data) => {
    console.log('Find next triggered:', data);
  });
  
  formView.on('replace', (evt, data) => {
    console.log('Replace triggered:', data);
  });
  
  // Update form state
  formView.matchCount = 5;
  formView.highlightOffset = 2;
  formView._matchCase = true;
  
  // Control form behavior
  formView.focus(); // Focus the form
  formView.reset(); // Reset to initial state
}

State Synchronization

Automatic UI Updates

The state object automatically synchronizes with UI components:

// State changes automatically update UI
const state = editor.plugins.get('FindAndReplaceEditing').state;

state.results.add({
  id: 'result1',
  label: 'Result 1',
  start: 0,
  end: 10
});

// UI automatically shows updated count
console.log(formView.matchCount); // Reflects new count

state.highlightedOffset = 3;
// UI automatically updates highlight indicator

Bidirectional Binding

UI interactions automatically update state:

// User typing in search field updates state
formView._findInputView.fieldView.value = 'new search';
// This triggers: state.searchText = 'new search'

// Checkbox interactions update state
formView._matchCaseSwitch.isOn = true;
// This triggers: state.matchCase = true

Event System

Form View Events

The form view fires events for user interactions:

interface FindNextEvent {
  name: 'findNext';
  args: [data?: FindNextEventData];
}

interface FindPreviousEvent {
  name: 'findPrevious';
  args: [data?: FindEventBaseData];
}

interface ReplaceEvent {
  name: 'replace';
  args: [data: ReplaceEventData];
}

interface ReplaceAllEvent {
  name: 'replaceAll';
  args: [data: ReplaceEventData];
}

Event Data Types:

interface FindNextEventData extends FindEventBaseData {
  /** Whether case matching is enabled */
  matchCase: boolean;
  /** Whether whole words matching is enabled */
  wholeWords: boolean;
}

interface FindEventBaseData {
  /** The search text */
  searchText: string;
}

interface ReplaceEventData extends FindEventBaseData {
  /** The replacement text */
  replaceText: string;
}

Usage Example:

// Listen to all form events
formView.on('findNext', (evt, data) => {
  editor.execute('find', data.searchText, {
    matchCase: data.matchCase,
    wholeWords: data.wholeWords
  });
  editor.execute('findNext');
});

formView.on('replace', (evt, data) => {
  const state = editor.plugins.get('FindAndReplaceEditing').state;
  editor.execute('replace', data.replaceText, state.highlightedResult);
});

formView.on('replaceAll', (evt, data) => {
  editor.execute('replaceAll', data.replaceText, data.searchText);
});

State Change Events

State objects fire change events for all observable properties:

state.on('change:results', (evt, propertyName, newResults, oldResults) => {
  console.log(`Results changed: ${newResults.length} results`);
  
  // Update UI accordingly
  if (newResults.length === 0) {
    formView._searchResultsFound = false;
  }
});

state.on('change:highlightedResult', (evt, propertyName, newResult) => {
  if (newResult) {
    console.log(`Highlighted result: ${newResult.id}`);
    // Scroll to result, update UI indicators, etc.
  }
});

Utility Functions

Result Sorting

/**
 * Sorts search results by their position markers in the document.
 * Exported as _sortFindResultsByMarkerPositions (internal use indicator).
 * @param model - The CKEditor model instance
 * @param results - Array of find results to sort
 * @returns Sorted array of results
 */
function sortSearchResultsByMarkerPositions(
  model: Model, 
  results: Array<FindResultType>
): Array<FindResultType>;

Usage Example:

import { _sortFindResultsByMarkerPositions } from '@ckeditor/ckeditor5-find-and-replace';

// Sort results by document position
const sortedResults = _sortFindResultsByMarkerPositions(editor.model, results);

Advanced State Management

Custom State Observers

// Create custom observers for complex state changes
class FindAndReplaceAnalytics {
  constructor(state) {
    this.state = state;
    this.setupObservers();
  }
  
  setupObservers() {
    // Track search patterns
    this.state.on('change:searchText', (evt, prop, newValue) => {
      this.logSearchPattern(newValue);
    });
    
    // Track replacement patterns
    this.state.on('change:results', (evt, prop, results) => {
      this.logResultCount(results.length);
    });
  }
  
  logSearchPattern(pattern) {
    console.log('Search pattern used:', pattern);
  }
  
  logResultCount(count) {
    console.log('Results found:', count);
  }
}

State Persistence

// Save and restore state
class StatePersistence {
  saveState(state) {
    const stateData = {
      searchText: state.searchText,
      replaceText: state.replaceText,
      matchCase: state.matchCase,
      matchWholeWords: state.matchWholeWords
    };
    localStorage.setItem('findReplaceState', JSON.stringify(stateData));
  }
  
  restoreState(state) {
    const savedState = localStorage.getItem('findReplaceState');
    if (savedState) {
      const stateData = JSON.parse(savedState);
      Object.assign(state, stateData);
    }
  }
}