Преглед изворни кода

Use jQuery UI autocomplete instead of typeahead.js.

typeahead.js is unmaintained, and every release of it and of its fork
calls $.isArray and $.isFunction, which jQuery 4 removed; rendering the
spotlight failed, so Helios stopped at startup. jQuery UI, which Helios
already loads, has autocomplete.

The key-binding input now really offers its completions. It used to
call typeahead with options alone, the Bootstrap 2 shape, which gave
typeahead.js no source to suggest from.

jQuery UI appends the menu to the body, so re-rendering the spotlight
first destroys the previous one, and helios.less styles the menu, as
Helios does not load the jQuery UI stylesheet. The grey inline hint
typeahead drew in the spotlight has no counterpart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Herby Vojčík пре 1 дан
родитељ
комит
45aa93559a

+ 1 - 0
Gruntfile.js

@@ -72,6 +72,7 @@ module.exports = function (grunt) {
                     'src/Helios-Transcript.st', 'src/Helios-Workspace.st', 'src/Helios-Debugger.st',
                     'src/Helios-SUnit.st',
                     // list all tests in dependency order
+                    'src/Helios-Core-Tests.st', 'src/Helios-KeyBindings-Tests.st',
                     'src/Helios-Browser-Tests.st', 'src/Helios-Workspace-Tests.st', 'src/Helios-SUnit-Tests.st'
                 ],
                 libraries: ['amber/core/SUnit', 'amber/web/Web'],

+ 0 - 1
app.js

@@ -7,7 +7,6 @@ define([
     'css!codemirror/theme/ambiance',
     'css!codemirror/lib/codemirror',
     'css!codemirror/addon/hint/show-hint',
-    'typeahead',
     'jquery-ui',
     'bootstrap/js/dropdown',
     './showdown',

+ 1 - 2
package.json

@@ -33,8 +33,7 @@
     "codemirror": "^5.44.0",
     "jquery-ui": ">=1.14.2",
     "require-css": "^0.1.10",
-    "showdown": "^0.5.0",
-    "typeahead.js": "^0.10.5"
+    "showdown": "^0.5.0"
   },
   "devDependencies": {
     "@ambers/sdk": "^0.13.0",

+ 21 - 15
resources/helios.css

@@ -294,18 +294,12 @@ body[id="helios"] .dropdown-menu {
   padding: 0;
   margin: 3px;
 }
-body[id="helios"] .twitter-typeahead {
+body[id="helios"] .spotlight {
   float: right;
+  position: relative;
   top: 1px;
   right: 26px;
   height: 22px;
-}
-body[id="helios"] .typeahead {
-  position: fixed;
-  top: 0px;
-  float: right;
-  right: 0px;
-  height: 100%;
   z-index: 1100;
   border: 1px solid #999;
   font-size: 11px;
@@ -314,27 +308,39 @@ body[id="helios"] .typeahead {
   width: 200px;
   box-shadow: inset 0 0 2px 0 #ddd;
 }
-body[id="helios"] .typeahead:focus {
+body[id="helios"] .spotlight:focus {
   box-shadow: inset 0 0 2px 0 #08c;
   border-color: #08c;
 }
-body[id="helios"] .tt-dropdown-menu {
+body[id="helios"] .ui-autocomplete {
+  position: absolute;
+  z-index: 2002;
+  list-style: none;
+  margin: 0;
+  padding: 0;
   background-color: #ddd;
   width: 300px;
   max-height: 350px;
   overflow-y: auto;
 }
-body[id="helios"] .tt-hint {
-  color: #999;
-}
-body[id="helios"] .tt-suggestion p {
+body[id="helios"] .ui-menu-item-wrapper {
   margin: 2px;
   font-size: 11px;
 }
-body[id="helios"] .tt-suggestion.tt-cursor {
+body[id="helios"] .ui-menu-item-wrapper.ui-state-active {
   color: #fff;
   background-color: #0097cf;
 }
+body[id="helios"] .ui-helper-hidden-accessible {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  margin: -1px;
+  padding: 0;
+  border: 0;
+  overflow: hidden;
+  clip: rect(0 0 0 0);
+}
 body[id="helios"] .nav-pills > .active > a {
   background-color: #ddd;
   color: #fff;

+ 21 - 17
resources/helios.less

@@ -347,21 +347,12 @@ body[id="helios"] {
 	}
 
 	// this is the spotlight searchbox
-	// it is a span created around the actual
-	// typahead inputs
-	.twitter-typeahead {
+	.spotlight {
 		float: right;
+		position: relative;
 		top: 1px;
 		right: 26px;
 		height: 22px;
-	}
-
-	.typeahead {
-		position: fixed;
-		top: 0px;
-		float: right;
-		right: 0px;
-		height: 100%;
 		z-index: 1100;
 		border: 1px solid #999;
 		font-size: 11px;
@@ -376,23 +367,36 @@ body[id="helios"] {
                 }
         }
 
-	.tt-dropdown-menu {
+	// jQuery UI autocomplete, without the jQuery UI stylesheet
+	.ui-autocomplete {
+		position: absolute;
+		z-index: 2002;
+		list-style: none;
+		margin: 0;
+		padding: 0;
 		background-color: #ddd;
 		width: 300px;
 		max-height: 350px;
 		overflow-y: auto;
 	}
-	.tt-hint {
-		color: #999;
-	}
-	.tt-suggestion p {
+	.ui-menu-item-wrapper {
 		margin: 2px;
 		font-size: 11px;
 	}
-	.tt-suggestion.tt-cursor {
+	.ui-menu-item-wrapper.ui-state-active {
 		color: #fff;
 		background-color: #0097cf;
 	}
+	.ui-helper-hidden-accessible {
+		position: absolute;
+		width: 1px;
+		height: 1px;
+		margin: -1px;
+		padding: 0;
+		border: 0;
+		overflow: hidden;
+		clip: rect(0 0 0 0);
+	}
 
 	.nav-pills > .active > a {
 		background-color: #ddd;

+ 219 - 0
src/Helios-Core-Tests.js

@@ -0,0 +1,219 @@
+define(["amber/boot", "require", "amber/core/SUnit"], function($boot,requirejs){"use strict";
+var $core=$boot.api,nil=$boot.nilAsValue,$nil=$boot.nilAsReceiver,$recv=$boot.asReceiver,$globals=$boot.globals;
+var $pkg = $core.addPackage("Helios-Core-Tests");
+$pkg.transport = {"type":"amd","amdNamespace":"helios"};
+
+$core.addClass("HLCompletionTest", $globals.TestCase, "Helios-Core-Tests");
+//>>excludeStart("ide", pragmas.excludeIdeData);
+$globals.HLCompletionTest.comment="I am an abstract test of the completions a rendered widget offers in its input.";
+//>>excludeEnd("ide");
+$core.addMethod(
+$core.method({
+selector: "inFixtureDo:",
+protocol: "accessing",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aBlock"],
+source: "inFixtureDo: aBlock\x0a\x09| fixture |\x0a\x09fixture := (HTMLCanvas onJQuery: 'body' asJQuery) div.\x0a\x09^ [ aBlock value: fixture asJQuery ]\x0a\x09\x09ensure: [ fixture asJQuery remove ]",
+referencedClasses: ["HTMLCanvas"],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["div", "onJQuery:", "asJQuery", "ensure:", "value:", "remove"]
+}, function ($methodClass){ return function (aBlock){
+var self=this,$self=this;
+var fixture;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+fixture=$recv($recv($globals.HTMLCanvas)._onJQuery_(["body"._asJQuery()
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx1.sendIdx["asJQuery"]=1
+//>>excludeEnd("ctx");
+][0]))._div();
+return $recv((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $recv(aBlock)._value_([$recv(fixture)._asJQuery()
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx2.sendIdx["asJQuery"]=2
+//>>excludeEnd("ctx");
+][0]);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)});
+//>>excludeEnd("ctx");
+}))._ensure_((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $recv($recv(fixture)._asJQuery())._remove();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,2)});
+//>>excludeEnd("ctx");
+}));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"inFixtureDo:",{aBlock:aBlock,fixture:fixture})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.HLCompletionTest);
+
+$core.addMethod(
+$core.method({
+selector: "suggestionsOf:for:",
+protocol: "accessing",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aWidget", "aString"],
+source: "suggestionsOf: aWidget for: aString\x0a\x09^ self inFixtureDo: [ :fixture |\x0a\x09\x09| input |\x0a\x09\x09aWidget appendToJQuery: fixture.\x0a\x09\x09input := fixture find: 'input'.\x0a\x09\x09input val: aString; autocomplete: 'search'.\x0a\x09\x09(input autocomplete: 'widget') children toArray collect: [ :each | each textContent ] ]",
+referencedClasses: [],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["inFixtureDo:", "appendToJQuery:", "find:", "val:", "autocomplete:", "collect:", "toArray", "children", "textContent"]
+}, function ($methodClass){ return function (aWidget,aString){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1;
+return $self._inFixtureDo_((function(fixture){
+var input;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+$recv(aWidget)._appendToJQuery_(fixture);
+input=$recv(fixture)._find_("input");
+$1=input;
+$recv($1)._val_(aString);
+[$recv($1)._autocomplete_("search")
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx2.sendIdx["autocomplete:"]=1
+//>>excludeEnd("ctx");
+][0];
+return $recv($recv($recv($recv(input)._autocomplete_("widget"))._children())._toArray())._collect_((function(each){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx3) {
+//>>excludeEnd("ctx");
+return $recv(each)._textContent();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx3) {$ctx3.fillBlock({each:each},$ctx2,2)});
+//>>excludeEnd("ctx");
+}));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({fixture:fixture,input:input},$ctx1,1)});
+//>>excludeEnd("ctx");
+}));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"suggestionsOf:for:",{aWidget:aWidget,aString:aString})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.HLCompletionTest);
+
+
+$core.addMethod(
+$core.method({
+selector: "isAbstract",
+protocol: "testing",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "isAbstract\x0a\x09^ self name = HLCompletionTest name",
+referencedClasses: ["HLCompletionTest"],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["=", "name"]
+}, function ($methodClass){ return function (){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+return $recv([$self._name()
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx1.sendIdx["name"]=1
+//>>excludeEnd("ctx");
+][0]).__eq($recv($globals.HLCompletionTest)._name());
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"isAbstract",{})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.HLCompletionTest.a$cls);
+
+
+$core.addClass("HLSpotlightWidgetTest", $globals.HLCompletionTest, "Helios-Core-Tests");
+$core.addMethod(
+$core.method({
+selector: "testOffersMatchingClassNames",
+protocol: "tests",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "testOffersMatchingClassNames\x0a\x09PlatformDom isFeasible ifTrue: [\x0a\x09\x09self assert: ((self suggestionsOf: HLSpotlightWidget new for: 'HLSpotlightWid') includes: 'HLSpotlightWidget') ]",
+referencedClasses: ["PlatformDom", "HLSpotlightWidget"],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["ifTrue:", "isFeasible", "assert:", "includes:", "suggestionsOf:for:", "new"]
+}, function ($methodClass){ return function (){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+if($core.assert($recv($globals.PlatformDom)._isFeasible())){
+$self._assert_($recv($self._suggestionsOf_for_($recv($globals.HLSpotlightWidget)._new(),"HLSpotlightWid"))._includes_("HLSpotlightWidget"));
+}
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"testOffersMatchingClassNames",{})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.HLSpotlightWidgetTest);
+
+$core.addMethod(
+$core.method({
+selector: "testRefreshKeepsOneCompletionMenu",
+protocol: "tests",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "testRefreshKeepsOneCompletionMenu\x0a\x09PlatformDom isFeasible ifTrue: [\x0a\x09\x09| spotlight menus |\x0a\x09\x09spotlight := HLSpotlightWidget new.\x0a\x09\x09menus := [ '.ui-autocomplete' asJQuery length ].\x0a\x09\x09self inFixtureDo: [ :fixture |\x0a\x09\x09\x09| rendered |\x0a\x09\x09\x09spotlight appendToJQuery: fixture.\x0a\x09\x09\x09rendered := menus value.\x0a\x09\x09\x09spotlight refresh.\x0a\x09\x09\x09self assert: menus value equals: rendered ] ]",
+referencedClasses: ["PlatformDom", "HLSpotlightWidget"],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["ifTrue:", "isFeasible", "new", "length", "asJQuery", "inFixtureDo:", "appendToJQuery:", "value", "refresh", "assert:equals:"]
+}, function ($methodClass){ return function (){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+if($core.assert($recv($globals.PlatformDom)._isFeasible())){
+var spotlight,menus;
+spotlight=$recv($globals.HLSpotlightWidget)._new();
+menus=(function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $recv(".ui-autocomplete"._asJQuery())._length();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,2)});
+//>>excludeEnd("ctx");
+});
+$self._inFixtureDo_((function(fixture){
+var rendered;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+$recv(spotlight)._appendToJQuery_(fixture);
+rendered=[$recv(menus)._value()
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx2.sendIdx["value"]=1
+//>>excludeEnd("ctx");
+][0];
+$recv(spotlight)._refresh();
+return $self._assert_equals_($recv(menus)._value(),rendered);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({fixture:fixture,rendered:rendered},$ctx1,3)});
+//>>excludeEnd("ctx");
+}));
+}
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"testRefreshKeepsOneCompletionMenu",{})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.HLSpotlightWidgetTest);
+
+
+});

+ 54 - 0
src/Helios-Core-Tests.st

@@ -0,0 +1,54 @@
+Smalltalk createPackage: 'Helios-Core-Tests'!
+TestCase subclass: #HLCompletionTest
+	slots: {}
+	package: 'Helios-Core-Tests'!
+!HLCompletionTest commentStamp!
+I am an abstract test of the completions a rendered widget offers in its input.!
+
+!HLCompletionTest methodsFor: 'accessing'!
+
+inFixtureDo: aBlock
+	| fixture |
+	fixture := (HTMLCanvas onJQuery: 'body' asJQuery) div.
+	^ [ aBlock value: fixture asJQuery ]
+		ensure: [ fixture asJQuery remove ]
+!
+
+suggestionsOf: aWidget for: aString
+	^ self inFixtureDo: [ :fixture |
+		| input |
+		aWidget appendToJQuery: fixture.
+		input := fixture find: 'input'.
+		input val: aString; autocomplete: 'search'.
+		(input autocomplete: 'widget') children toArray collect: [ :each | each textContent ] ]
+! !
+
+!HLCompletionTest class methodsFor: 'testing'!
+
+isAbstract
+	^ self name = HLCompletionTest name
+! !
+
+HLCompletionTest subclass: #HLSpotlightWidgetTest
+	slots: {}
+	package: 'Helios-Core-Tests'!
+
+!HLSpotlightWidgetTest methodsFor: 'tests'!
+
+testOffersMatchingClassNames
+	PlatformDom isFeasible ifTrue: [
+		self assert: ((self suggestionsOf: HLSpotlightWidget new for: 'HLSpotlightWid') includes: 'HLSpotlightWidget') ]
+!
+
+testRefreshKeepsOneCompletionMenu
+	PlatformDom isFeasible ifTrue: [
+		| spotlight menus |
+		spotlight := HLSpotlightWidget new.
+		menus := [ '.ui-autocomplete' asJQuery length ].
+		self inFixtureDo: [ :fixture |
+			| rendered |
+			spotlight appendToJQuery: fixture.
+			rendered := menus value.
+			spotlight refresh.
+			self assert: menus value equals: rendered ] ]
+! !

+ 28 - 20
src/Helios-Core.js

@@ -8552,50 +8552,58 @@ selector: "renderContentOn:",
 protocol: "rendering",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["html"],
-source: "renderContentOn: html\x0a\x09input := html input\x0a\x09\x09class: 'spotlight typeahead';\x0a\x09\x09placeholder: self ghostText;\x0a\x09\x09onKeyDown: [ :event | \x0a\x09\x09\x09event which = 13 ifTrue: [\x0a\x09\x09\x09\x09self search: input asJQuery val ] ]\x0a\x09\x09yourself.\x0a\x09\x09\x09\x0a\x09input asJQuery \x0a\x09\x09typeahead: #{ 'hint' -> true }\x0a\x09\x09value: #{ 'name' -> 'classesAndSelectors'.\x0a\x09\x09\x09'displayKey' -> [ :suggestion | suggestion asString ].\x0a\x09\x09\x09'source' -> [ :query :callback | self findMatches: query andRender: callback ]}.\x0a\x09\x09\x22use additional datasets for grouping into classes and selectors\x22",
+source: "renderContentOn: html\x0a\x09input ifNotNil: [ :old | old asJQuery autocomplete: 'destroy' ].\x0a\x09input := html input\x0a\x09\x09class: 'spotlight';\x0a\x09\x09placeholder: self ghostText;\x0a\x09\x09onKeyDown: [ :event | \x0a\x09\x09\x09event which = 13 ifTrue: [\x0a\x09\x09\x09\x09self search: input asJQuery val ] ]\x0a\x09\x09yourself.\x0a\x09\x09\x09\x0a\x09input asJQuery \x0a\x09\x09autocomplete: #{ 'source' -> [ :request :response | self findMatches: request term andRender: response ] }",
 referencedClasses: [],
 //>>excludeEnd("ide");
 pragmas: [],
-messageSends: ["class:", "input", "placeholder:", "ghostText", "onKeyDown:", "yourself", "ifTrue:", "=", "which", "search:", "val", "asJQuery", "typeahead:value:", "asString", "findMatches:andRender:"]
+messageSends: ["ifNotNil:", "autocomplete:", "asJQuery", "class:", "input", "placeholder:", "ghostText", "onKeyDown:", "yourself", "ifTrue:", "=", "which", "search:", "val", "findMatches:andRender:", "term"]
 }, function ($methodClass){ return function (html){
 var self=this,$self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-var $1;
-$1=$recv(html)._input();
-$recv($1)._class_("spotlight typeahead");
-$recv($1)._placeholder_($self._ghostText());
-$self.input=$recv($1)._onKeyDown_($recv((function(event){
+var $1,$2;
+$1=$self.input;
+if($1 == null || $1.a$nil){
+$1;
+} else {
+var old;
+old=$1;
+[$recv([$recv(old)._asJQuery()
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx1.sendIdx["asJQuery"]=1
+//>>excludeEnd("ctx");
+][0])._autocomplete_("destroy")
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx1.sendIdx["autocomplete:"]=1
+//>>excludeEnd("ctx");
+][0];
+}
+$2=$recv(html)._input();
+$recv($2)._class_("spotlight");
+$recv($2)._placeholder_($self._ghostText());
+$self.input=$recv($2)._onKeyDown_($recv((function(event){
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx2) {
 //>>excludeEnd("ctx");
 if($core.assert($recv($recv(event)._which()).__eq((13)))){
 return $self._search_($recv([$recv($self.input)._asJQuery()
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-,$ctx2.sendIdx["asJQuery"]=1
+,$ctx2.sendIdx["asJQuery"]=2
 //>>excludeEnd("ctx");
 ][0])._val());
 }
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx2) {$ctx2.fillBlock({event:event},$ctx1,1)});
+}, function($ctx2) {$ctx2.fillBlock({event:event},$ctx1,2)});
 //>>excludeEnd("ctx");
 }))._yourself());
-$recv($recv($self.input)._asJQuery())._typeahead_value_($globals.HashedCollection._newFromPairs_(["hint",true]),$globals.HashedCollection._newFromPairs_(["name","classesAndSelectors","displayKey",(function(suggestion){
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-return $core.withContext(function($ctx2) {
-//>>excludeEnd("ctx");
-return $recv(suggestion)._asString();
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx2) {$ctx2.fillBlock({suggestion:suggestion},$ctx1,3)});
-//>>excludeEnd("ctx");
-}),"source",(function(query,callback){
+$recv($recv($self.input)._asJQuery())._autocomplete_($globals.HashedCollection._newFromPairs_(["source",(function(request,response){
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx2) {
 //>>excludeEnd("ctx");
-return $self._findMatches_andRender_(query,callback);
+return $self._findMatches_andRender_($recv(request)._term(),response);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx2) {$ctx2.fillBlock({query:query,callback:callback},$ctx1,4)});
+}, function($ctx2) {$ctx2.fillBlock({request:request,response:response},$ctx1,4)});
 //>>excludeEnd("ctx");
 })]));
 return self;

+ 3 - 6
src/Helios-Core.st

@@ -2028,8 +2028,9 @@ search: aString
 !HLSpotlightWidget methodsFor: 'rendering'!
 
 renderContentOn: html
+	input ifNotNil: [ :old | old asJQuery autocomplete: 'destroy' ].
 	input := html input
-		class: 'spotlight typeahead';
+		class: 'spotlight';
 		placeholder: self ghostText;
 		onKeyDown: [ :event | 
 			event which = 13 ifTrue: [
@@ -2037,11 +2038,7 @@ renderContentOn: html
 		yourself.
 			
 	input asJQuery 
-		typeahead: #{ 'hint' -> true }
-		value: #{ 'name' -> 'classesAndSelectors'.
-			'displayKey' -> [ :suggestion | suggestion asString ].
-			'source' -> [ :query :callback | self findMatches: query andRender: callback ]}.
-		"use additional datasets for grouping into classes and selectors"
+		autocomplete: #{ 'source' -> [ :request :response | self findMatches: request term andRender: response ] }
 ! !
 
 HLWidget subclass: #HLTabWidget

+ 37 - 0
src/Helios-KeyBindings-Tests.js

@@ -0,0 +1,37 @@
+define(["amber/boot", "require", "helios/Helios-Core-Tests"], function($boot,requirejs){"use strict";
+var $core=$boot.api,nil=$boot.nilAsValue,$nil=$boot.nilAsReceiver,$recv=$boot.asReceiver,$globals=$boot.globals;
+var $pkg = $core.addPackage("Helios-KeyBindings-Tests");
+$pkg.transport = {"type":"amd","amdNamespace":"helios"};
+
+$core.addClass("HLBindingActionInputWidgetTest", $globals.HLCompletionTest, "Helios-KeyBindings-Tests");
+$core.addMethod(
+$core.method({
+selector: "testOffersInputCompletion",
+protocol: "tests",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "testOffersInputCompletion\x0a\x09PlatformDom isFeasible ifTrue: [\x0a\x09\x09self\x0a\x09\x09\x09assert: (self suggestionsOf: (HLBindingActionInputWidget new inputCompletion: #('alpha' 'beta' 'gamma'); yourself) for: 'al')\x0a\x09\x09\x09equals: #('alpha') ]",
+referencedClasses: ["PlatformDom", "HLBindingActionInputWidget"],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["ifTrue:", "isFeasible", "assert:equals:", "suggestionsOf:for:", "inputCompletion:", "new", "yourself"]
+}, function ($methodClass){ return function (){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1;
+if($core.assert($recv($globals.PlatformDom)._isFeasible())){
+$1=$recv($globals.HLBindingActionInputWidget)._new();
+$recv($1)._inputCompletion_(["alpha", "beta", "gamma"]);
+$self._assert_equals_($self._suggestionsOf_for_($recv($1)._yourself(),"al"),["alpha"]);
+}
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"testOffersInputCompletion",{})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.HLBindingActionInputWidgetTest);
+
+
+});

+ 13 - 0
src/Helios-KeyBindings-Tests.st

@@ -0,0 +1,13 @@
+Smalltalk createPackage: 'Helios-KeyBindings-Tests'!
+HLCompletionTest subclass: #HLBindingActionInputWidgetTest
+	slots: {}
+	package: 'Helios-KeyBindings-Tests'!
+
+!HLBindingActionInputWidgetTest methodsFor: 'tests'!
+
+testOffersInputCompletion
+	PlatformDom isFeasible ifTrue: [
+		self
+			assert: (self suggestionsOf: (HLBindingActionInputWidget new inputCompletion: #('alpha' 'beta' 'gamma'); yourself) for: 'al')
+			equals: #('alpha') ]
+! !

+ 3 - 3
src/Helios-KeyBindings.js

@@ -1248,11 +1248,11 @@ selector: "renderOn:",
 protocol: "rendering",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["html"],
-source: "renderOn: html\x0a\x09wrapper ifNil: [ wrapper := html span ].\x0a\x0a\x09wrapper \x0a\x09\x09class: self status;\x0a\x09\x09with: [\x0a\x09\x09\x09input := html input\x0a\x09\x09\x09\x09placeholder: self ghostText;\x0a\x09\x09\x09\x09value: self defaultValue;\x0a\x09\x09\x09\x09onKeyDown: [ :event | \x0a\x09\x09\x09\x09\x09event which = 13 ifTrue: [\x0a\x09\x09\x09\x09\x09\x09self evaluate: input asJQuery val ] ]\x0a\x09\x09\x09\x09yourself.\x0a\x09\x09\x09input asJQuery \x0a\x09\x09\x09\x09typeahead: #{ 'source' -> self inputCompletion }.\x0a\x09\x09\x09messageTag := (html span\x0a\x09\x09\x09\x09class: 'help-inline';\x0a\x09\x09\x09\x09with: self message;\x0a\x09\x09\x09\x09yourself) ].\x0a\x09\x0a\x09\x22Evaluate with a timeout to ensure focus.\x0a\x09Commands can be executed from a menu, clicking on the menu to\x0a\x09evaluate the command would give it the focus otherwise\x22\x0a\x09\x0a\x09[ input asJQuery focus; select ] fork",
+source: "renderOn: html\x0a\x09wrapper ifNil: [ wrapper := html span ].\x0a\x0a\x09wrapper \x0a\x09\x09class: self status;\x0a\x09\x09with: [\x0a\x09\x09\x09input := html input\x0a\x09\x09\x09\x09placeholder: self ghostText;\x0a\x09\x09\x09\x09value: self defaultValue;\x0a\x09\x09\x09\x09onKeyDown: [ :event | \x0a\x09\x09\x09\x09\x09event which = 13 ifTrue: [\x0a\x09\x09\x09\x09\x09\x09self evaluate: input asJQuery val ] ]\x0a\x09\x09\x09\x09yourself.\x0a\x09\x09\x09input asJQuery \x0a\x09\x09\x09\x09autocomplete: #{ 'source' -> self inputCompletion }.\x0a\x09\x09\x09messageTag := (html span\x0a\x09\x09\x09\x09class: 'help-inline';\x0a\x09\x09\x09\x09with: self message;\x0a\x09\x09\x09\x09yourself) ].\x0a\x09\x0a\x09\x22Evaluate with a timeout to ensure focus.\x0a\x09Commands can be executed from a menu, clicking on the menu to\x0a\x09evaluate the command would give it the focus otherwise\x22\x0a\x09\x0a\x09[ input asJQuery focus; select ] fork",
 referencedClasses: [],
 //>>excludeEnd("ide");
 pragmas: [],
-messageSends: ["ifNil:", "span", "class:", "status", "with:", "placeholder:", "input", "ghostText", "value:", "defaultValue", "onKeyDown:", "yourself", "ifTrue:", "=", "which", "evaluate:", "val", "asJQuery", "typeahead:", "inputCompletion", "message", "fork", "focus", "select"]
+messageSends: ["ifNil:", "span", "class:", "status", "with:", "placeholder:", "input", "ghostText", "value:", "defaultValue", "onKeyDown:", "yourself", "ifTrue:", "=", "which", "evaluate:", "val", "asJQuery", "autocomplete:", "inputCompletion", "message", "fork", "focus", "select"]
 }, function ($methodClass){ return function (html){
 var self=this,$self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
@@ -1306,7 +1306,7 @@ $recv([$recv($self.input)._asJQuery()
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 ,$ctx2.sendIdx["asJQuery"]=2
 //>>excludeEnd("ctx");
-][0])._typeahead_($globals.HashedCollection._newFromPairs_(["source",$self._inputCompletion()]));
+][0])._autocomplete_($globals.HashedCollection._newFromPairs_(["source",$self._inputCompletion()]));
 $4=$recv(html)._span();
 $recv($4)._class_("help-inline");
 $recv($4)._with_($self._message());

+ 1 - 1
src/Helios-KeyBindings.st

@@ -325,7 +325,7 @@ renderOn: html
 						self evaluate: input asJQuery val ] ]
 				yourself.
 			input asJQuery 
-				typeahead: #{ 'source' -> self inputCompletion }.
+				autocomplete: #{ 'source' -> self inputCompletion }.
 			messageTag := (html span
 				class: 'help-inline';
 				with: self message;

+ 2 - 0
src/all.js

@@ -1,5 +1,6 @@
 define([
 	'./Helios-Core',
+	'./Helios-Core-Tests',
 	'./Helios-Exceptions',
 	'./Helios-Helpers',
 	'./Helios-Commands-Core',
@@ -8,6 +9,7 @@ define([
 	'./Helios-Commands-SUnit',
 	'./Helios-Layout',
 	'./Helios-KeyBindings',
+	'./Helios-KeyBindings-Tests',
 	'./Helios-Browser',
 	'./Helios-Browser-Tests',
 	'./Helios-Workspace',

+ 0 - 13
typeahead.js.amd.json

@@ -1,13 +0,0 @@
-{
-  "paths": {
-    "typeahead": "dist/typeahead.jquery"
-  },
-  "shim": {
-    "typeahead": {
-      "deps": [
-        "jquery"
-      ],
-      "exports": "jQuery.fn.typeahead"
-    }
-  }
-}