@@ -11,7 +11,85 @@ import 'package:collection/collection.dart';
1111import 'package:http/http.dart' deferred as http show read;
1212import 'package:web/web.dart' ;
1313
14- import 'web_util.dart' ;
14+ import '../../web_util.dart' ;
15+
16+ /// Create a [_CompletionWidget] on [element] .
17+ ///
18+ /// Here [element] must:
19+ /// * be an `<input>` element, with
20+ /// * `type="text"`, or,
21+ /// * `type="search".
22+ /// * have properties:
23+ /// * `data-completion-src`, URL from which completion data should be
24+ /// loaded.
25+ /// * `data-completion-class` (optional), class that should be applied to
26+ /// the dropdown that provides completion options.
27+ /// Useful if styling multiple completer widgets.
28+ ///
29+ /// The dropdown that provides completions will be appended to
30+ /// `document.body` and given the following classes:
31+ /// * `completion-dropdown` for the completion dropdown.
32+ /// * `completion-option` for each option in the dropdown, and,
33+ /// * `completion-option-select` is applied to selected options.
34+ void create (Element element, Map <String , String > options) {
35+ if (! element.isA <HTMLInputElement >()) {
36+ throw UnsupportedError ('Must be <input> element' );
37+ }
38+ final input = element as HTMLInputElement ;
39+
40+ if (input.type != 'text' && input.type != 'search' ) {
41+ throw UnsupportedError ('Must have type="text" or type="search"' );
42+ }
43+
44+ final src = options['src' ] ?? '' ;
45+ if (src.isEmpty) {
46+ throw UnsupportedError ('Must have completion-src="<url>"' );
47+ }
48+ final srcUri = Uri .tryParse (src);
49+ if (srcUri == null ) {
50+ throw UnsupportedError ('completion-src="$src " must be a valid URI' );
51+ }
52+ final completionClass = options['class' ] ?? '' ;
53+
54+ // Setup attributes
55+ input.autocomplete = 'off' ;
56+ input.autocapitalize = 'off' ;
57+ input.spellcheck = false ;
58+ input.setAttribute ('autocorrect' , 'off' ); // safari only
59+
60+ scheduleMicrotask (() async {
61+ // Don't do anymore setup before input has focus
62+ if (document.activeElement != input) {
63+ await input.onFocus.first;
64+ }
65+
66+ final _CompletionData data;
67+ try {
68+ data = await _CompletionWidget ._completionDataFromUri (srcUri);
69+ } on Exception catch (e) {
70+ throw Exception (
71+ 'Unable to load autocompletion-src="$src ", error: $e ' ,
72+ );
73+ }
74+
75+ // Create and style the dropdown element
76+ final dropdown = HTMLDivElement ()
77+ ..style.display = 'none'
78+ ..style.position = 'absolute'
79+ ..classList.add ('completion-dropdown' );
80+ if (completionClass.isNotEmpty) {
81+ dropdown.classList.add (completionClass);
82+ }
83+
84+ _CompletionWidget ._(
85+ input: input,
86+ dropdown: dropdown,
87+ data: data,
88+ );
89+ // Add dropdown after the <input>
90+ document.body! .after (dropdown);
91+ });
92+ }
1593
1694typedef _CompletionData = List <
1795 ({
@@ -93,7 +171,7 @@ final class _State {
93171 '_State(forced: $forced , triggered: $triggered , caret: $caret , text: $text , selected: $selectedIndex )' ;
94172}
95173
96- final class CompletionWidget {
174+ final class _CompletionWidget {
97175 static final _whitespace = RegExp (r'\s' );
98176 static final optionClass = 'completion-option' ;
99177 static final selectedOptionClass = 'completion-option-selected' ;
@@ -103,7 +181,7 @@ final class CompletionWidget {
103181 final _CompletionData data;
104182 var state = _State ();
105183
106- CompletionWidget ._({
184+ _CompletionWidget ._({
107185 required this .input,
108186 required this .dropdown,
109187 required this .data,
@@ -343,83 +421,6 @@ final class CompletionWidget {
343421 trackState ();
344422 }
345423
346- /// Create a [CompletionWidget] on [element] .
347- ///
348- /// Here [element] must:
349- /// * be an `<input>` element, with
350- /// * `type="text"`, or,
351- /// * `type="search".
352- /// * have properties:
353- /// * `data-completion-src`, URL from which completion data should be
354- /// loaded.
355- /// * `data-completion-class` (optional), class that should be applied to
356- /// the dropdown that provides completion options.
357- /// Useful if styling multiple completer widgets.
358- ///
359- /// The dropdown that provides completions will be appended to
360- /// `document.body` and given the following classes:
361- /// * `completion-dropdown` for the completion dropdown.
362- /// * `completion-option` for each option in the dropdown, and,
363- /// * `completion-option-select` is applied to selected options.
364- static void create (Element element) {
365- if (! element.isA <HTMLInputElement >()) {
366- throw UnsupportedError ('Must be <input> element' );
367- }
368- final input = element as HTMLInputElement ;
369-
370- if (input.type != 'text' && input.type != 'search' ) {
371- throw UnsupportedError ('Must have type="text" or type="search"' );
372- }
373- final src = input.getAttribute ('data-completion-src' ) ?? '' ;
374- if (src.isEmpty) {
375- throw UnsupportedError ('Must have completion-src="<url>"' );
376- }
377- final srcUri = Uri .tryParse (src);
378- if (srcUri == null ) {
379- throw UnsupportedError ('completion-src="$src " must be a valid URI' );
380- }
381- final completionClass = input.getAttribute ('data-completion-class' ) ?? '' ;
382-
383- // Setup attributes
384- input.autocomplete = 'off' ;
385- input.autocapitalize = 'off' ;
386- input.spellcheck = false ;
387- input.setAttribute ('autocorrect' , 'off' ); // safari only
388-
389- scheduleMicrotask (() async {
390- // Don't do anymore setup before input has focus
391- if (document.activeElement != input) {
392- await input.onFocus.first;
393- }
394-
395- final _CompletionData data;
396- try {
397- data = await _completionDataFromUri (srcUri);
398- } on Exception catch (e) {
399- throw Exception (
400- 'Unable to load autocompletion-src="$src ", error: $e ' ,
401- );
402- }
403-
404- // Create and style the dropdown element
405- final dropdown = HTMLDivElement ()
406- ..style.display = 'none'
407- ..style.position = 'absolute'
408- ..classList.add ('completion-dropdown' );
409- if (completionClass.isNotEmpty) {
410- dropdown.classList.add (completionClass);
411- }
412-
413- CompletionWidget ._(
414- input: input,
415- dropdown: dropdown,
416- data: data,
417- );
418- // Add dropdown after the <input>
419- document.body! .after (dropdown);
420- });
421- }
422-
423424 /// Load completion data from [src] .
424425 ///
425426 /// Completion data must be a JSON response on the form:
@@ -607,7 +608,7 @@ final class CompletionWidget {
607608 trigger: trigger,
608609 suggestions: completion.options
609610 .map ((option) {
610- final overlap = lcs (prefix, option);
611+ final overlap = _lcs (prefix, option);
611612 var html = option;
612613 if (overlap.isNotEmpty) {
613614 html = html.replaceAll (overlap, '<strong>$overlap </strong>' );
@@ -632,7 +633,7 @@ final class CompletionWidget {
632633}
633634
634635/// The longest common substring
635- String lcs (String S , String T ) {
636+ String _lcs (String S , String T ) {
636637 final r = S .length;
637638 final n = T .length;
638639 var Lp = List .filled (n, 0 ); // ignore: non_constant_identifier_names
0 commit comments