blob: fdab6ad3b582dbcc091a1f8c5d7808b7f27d67bd [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:html' as html;
import 'text_editing.dart';
/// Provides default functionality for listening to HTML composition events.
///
/// A class with this mixin generally calls [determineCompositionState] in order to update
/// an [EditingState] with new composition values; namely, [EditingState.composingBaseOffset]
/// and [EditingState.composingExtentOffset].
///
/// A class with this mixin should call [addCompositionEventHandlers] on initalization, and
/// [removeCompositionEventHandlers] on deinitalization.
///
/// See also:
///
/// * [EditingState], the state of a text field that [CompositionAwareMixin] updates.
/// * [DefaultTextEditingStrategy], the primary implementer of [CompositionAwareMixin].
mixin CompositionAwareMixin {
/// The name of the HTML composition event type that triggers on starting a composition.
static const String _kCompositionStart = 'compositionstart';
/// The name of the browser composition event type that triggers on updating a composition.
static const String _kCompositionUpdate = 'compositionupdate';
/// The name of the browser composition event type that triggers on ending a composition.
static const String _kCompositionEnd = 'compositionend';
late final html.EventListener _compositionStartListener = _handleCompositionStart;
late final html.EventListener _compositionUpdateListener = _handleCompositionUpdate;
late final html.EventListener _compositionEndListener = _handleCompositionEnd;
/// The currently composing text in the `domElement`.
///
/// Will be null if composing just started, ended, or no composing is being done.
/// This member is kept up to date provided compositionEventHandlers are in place,
/// so it is safe to reference it to get the current composingText.
String? composingText;
void addCompositionEventHandlers(html.HtmlElement domElement) {
domElement.addEventListener(_kCompositionStart, _compositionStartListener);
domElement.addEventListener(_kCompositionUpdate, _compositionUpdateListener);
domElement.addEventListener(_kCompositionEnd, _compositionEndListener);
}
void removeCompositionEventHandlers(html.HtmlElement domElement) {
domElement.removeEventListener(_kCompositionStart, _compositionStartListener);
domElement.removeEventListener(_kCompositionUpdate, _compositionUpdateListener);
domElement.removeEventListener(_kCompositionEnd, _compositionEndListener);
}
void _handleCompositionStart(html.Event event) {
composingText = null;
}
void _handleCompositionUpdate(html.Event event) {
if (event is html.CompositionEvent) {
composingText = event.data;
}
}
void _handleCompositionEnd(html.Event event) {
composingText = null;
}
EditingState determineCompositionState(EditingState editingState) {
if (editingState.baseOffset == null || composingText == null || editingState.text == null) {
return editingState;
}
final int composingBase = editingState.baseOffset! - composingText!.length;
if (composingBase < 0) {
return editingState;
}
return editingState.copyWith(
composingBaseOffset: composingBase,
composingExtentOffset: composingBase + composingText!.length,
);
}
}