yafowil.widget.autocomplete 2.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,64 @@
1
+ from yafowil.base import factory
2
+ from yafowil.utils import entry_point
3
+ import os
4
+ import webresource as wr
5
+
6
+
7
+ resources_dir = os.path.join(os.path.dirname(__file__), 'resources')
8
+
9
+
10
+ ##############################################################################
11
+ # Default
12
+ ##############################################################################
13
+
14
+ # webresource ################################################################
15
+
16
+ resources = wr.ResourceGroup(
17
+ name='yafowil.widget.autocomplete',
18
+ directory=resources_dir,
19
+ path='yafowil-autocomplete'
20
+ )
21
+ resources.add(wr.ScriptResource(
22
+ name='yafowil-autocomplete-js',
23
+ depends='jquery-js',
24
+ resource='widget.js',
25
+ compressed='widget.min.js'
26
+ ))
27
+ resources.add(wr.StyleResource(
28
+ name='yafowil-autocomplete-css',
29
+ resource='widget.css'
30
+ ))
31
+
32
+ # B/C resources ##############################################################
33
+
34
+ js = [{
35
+ 'group': 'yafowil.widget.autocomplete.common',
36
+ 'resource': 'widget.js',
37
+ 'order': 21,
38
+ }]
39
+ css = [{
40
+ 'group': 'yafowil.widget.autocomplete.common',
41
+ 'resource': 'widget.css',
42
+ 'order': 21,
43
+ }]
44
+
45
+
46
+ ##############################################################################
47
+ # Registration
48
+ ##############################################################################
49
+
50
+ @entry_point(order=10)
51
+ def register():
52
+ from yafowil.widget.autocomplete import widget # noqa
53
+
54
+ widget_name = 'yafowil.widget.autocomplete'
55
+
56
+ # Default
57
+ factory.register_theme(
58
+ 'default',
59
+ widget_name,
60
+ resources_dir,
61
+ js=js,
62
+ css=css
63
+ )
64
+ factory.register_resources('default', widget_name, resources)
@@ -0,0 +1,127 @@
1
+ from yafowil.base import factory
2
+ from yafowil.compat import IS_PY2
3
+ import json
4
+
5
+
6
+ if IS_PY2:
7
+ from urlparse import urlparse
8
+ from urlparse import parse_qs
9
+ else:
10
+ from urllib.parse import urlparse
11
+ from urllib.parse import parse_qs
12
+
13
+
14
+ lipsum = """Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
15
+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
16
+ veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
17
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
18
+ dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident,
19
+ sunt in culpa qui officia deserunt mollit anim id est laborum."""
20
+ lipsum = sorted(set(lipsum.lower().replace('.', '').replace(',', '').split()))
21
+
22
+
23
+ def json_response(url):
24
+ purl = urlparse(url)
25
+ qs = parse_qs(purl.query)
26
+ data = json_data(qs.get('term', [''])[0])
27
+ return {
28
+ 'body': json.dumps(data),
29
+ 'header': [('Content-Type', 'application/json')]
30
+ }
31
+
32
+
33
+ def json_data(term):
34
+ data = lipsum
35
+ if term:
36
+ data = [_ for _ in data if _.startswith(term)]
37
+ return data
38
+
39
+
40
+ DOC_STATIC = """\
41
+ Autocomplete with static vocabulary
42
+ -----------------------------------
43
+
44
+ Autocomplete can make use of a static vocabulary source. Here words of blind
45
+ text Lorem Ipsum are used. First the static list of words is generated
46
+
47
+ .. code-block:: python
48
+
49
+ lipsum = '''Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
50
+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
51
+ veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
52
+ commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit
53
+ esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
54
+ cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
55
+ est laborum.'''
56
+ lipsum = sorted(set(lipsum.lower().replace('.', '').replace(',', '').split()))
57
+
58
+ Next, the field is defined as
59
+
60
+ .. code-block:: python
61
+
62
+ field = factory('#field:autocomplete', props={
63
+ 'label': 'Enter some text (local, lorem ipsum)',
64
+ 'value': '',
65
+ 'source': lipsum,
66
+ })
67
+ """
68
+
69
+ DOC_JSON = """\
70
+ Autocomplete with dynamic json vocabulary
71
+ -----------------------------------------
72
+
73
+ Instead of static lists autocomplete may ask the server for a list of words
74
+ matching a given term. The source is a string and as such interpreted as a
75
+ absolute or relative url
76
+
77
+ .. code-block:: python
78
+
79
+ field = factory('#field:autocomplete', props={
80
+ 'label': 'Enter some text (remote listdir)',
81
+ 'value': '',
82
+ 'source': 'yafowil.widget.autocomplete.json',
83
+ 'minLength': 1,
84
+ })
85
+
86
+ The server answers with a JSON response, here the example does it using WSGI
87
+ and ``webob`` way. This code needs modification depending on the framework
88
+ used
89
+
90
+ .. code-block:: python
91
+
92
+ def json_response(environ, start_response):
93
+ data = lipsum
94
+ if environ['QUERY_STRING'].startswith('term='):
95
+ qsts = environ['QUERY_STRING'][5:]
96
+ data = [_ for _ in data if _.startswith(qsts)]
97
+ response = Response(content_type='application/json',
98
+ body=json.dumps(data))
99
+ return response(environ, start_response)
100
+ """
101
+
102
+
103
+ def get_example():
104
+ static_ac = factory('#field:autocomplete', name='static', props={
105
+ 'label': 'Enter some text (local, lorem ipsum)',
106
+ 'value': '',
107
+ 'source': lipsum,
108
+ })
109
+ json_ac = factory('#field:autocomplete', name='json', props={
110
+ 'label': 'Enter some text (remote listdir)',
111
+ 'value': '',
112
+ 'source': 'yafowil.widget.autocomplete.json',
113
+ 'minLength': 1,
114
+ })
115
+ routes = {
116
+ 'yafowil.widget.autocomplete.json': json_response,
117
+ }
118
+ return [{
119
+ 'widget': static_ac,
120
+ 'doc': DOC_STATIC,
121
+ 'title': 'Static autocomplete',
122
+ }, {
123
+ 'widget': json_ac,
124
+ 'routes': routes,
125
+ 'doc': DOC_JSON,
126
+ 'title': 'JSON data autocomplete',
127
+ }]
@@ -0,0 +1,29 @@
1
+ div.yafowil-widget-autocomplete .hiddenStructure {
2
+ display: none;
3
+ }
4
+
5
+ div.autocomplete-dropdown {
6
+ display: none;
7
+ position: absolute;
8
+ z-index: 2000;
9
+ min-width: 200px;
10
+ max-height: 320px;
11
+ overflow: hidden;
12
+ overflow-y: auto;
13
+ background: white;
14
+ border: 1px solid rgb(0 0 0 / 20%);
15
+ border-radius: 4px;
16
+ padding: 0;
17
+ box-shadow: 1px 1px 4px rgb(0 0 0 / 20%);
18
+ }
19
+
20
+ .autocomplete-suggestion {
21
+ display: block;
22
+ cursor: default;
23
+ padding: 5px 15px;
24
+ }
25
+
26
+ .autocomplete-suggestion.selected {
27
+ background: var(--yafowil-accent-color, #0d6efd);
28
+ color: var(--yafowil-accent-font-color, #fff);
29
+ }
@@ -0,0 +1,340 @@
1
+ var yafowil_autocomplete = (function (exports, $) {
2
+ 'use strict';
3
+
4
+ class AutocompleteSuggestion {
5
+ constructor(widget, source, val) {
6
+ this.widget = widget;
7
+ this.elem = $('<div />')
8
+ .addClass('autocomplete-suggestion')
9
+ .appendTo(this.widget.dd_elem);
10
+ let index = source.toUpperCase().indexOf(val.toUpperCase());
11
+ $(`<span />`)
12
+ .text(source.substring(0, index))
13
+ .appendTo(this.elem);
14
+ $(`<strong />`)
15
+ .text(source.substring(index, index + val.length))
16
+ .appendTo(this.elem);
17
+ $(`<span />`)
18
+ .text(source.substring(index + val.length))
19
+ .appendTo(this.elem);
20
+ this.value = source;
21
+ this.selected = false;
22
+ this.select = this.select.bind(this);
23
+ this.elem.off('mousedown', this.select).on('mousedown', this.select);
24
+ }
25
+ get selected() {
26
+ return this._selected;
27
+ }
28
+ set selected(selected) {
29
+ if (selected) {
30
+ this._selected = true;
31
+ this.elem.addClass('selected');
32
+ } else {
33
+ this._selected = false;
34
+ this.elem.removeClass('selected');
35
+ }
36
+ }
37
+ select() {
38
+ this.selected = true;
39
+ this.widget.select_suggestion(this.value);
40
+ }
41
+ }
42
+ class AutocompleteWidget {
43
+ static initialize(context) {
44
+ $('div.yafowil-widget-autocomplete', context).each(function() {
45
+ let elem = $(this);
46
+ if (window.yafowil_array !== undefined &&
47
+ window.yafowil_array.inside_template(elem)) {
48
+ return;
49
+ }
50
+ new AutocompleteWidget(elem);
51
+ });
52
+ }
53
+ constructor(elem) {
54
+ elem.data('yafowil-autocomplete', this);
55
+ this.elem = elem;
56
+ this.input_elem = $('input.autocomplete', this.elem)
57
+ .attr('spellcheck', false)
58
+ .attr('autocomplete', 'off');
59
+ this.dd_elem = $(`<div />`)
60
+ .addClass('autocomplete-dropdown')
61
+ .appendTo('body');
62
+ this.suggestions = [];
63
+ this.current_focus = 0;
64
+ let options = this.parse_options();
65
+ this.sourcetype = options.type;
66
+ this.delay = options.delay;
67
+ this.min_length = options.minLength;
68
+ this.parse_source();
69
+ this.on_input = this.on_input.bind(this);
70
+ this.hide_dropdown = this.hide_dropdown.bind(this);
71
+ this.on_keydown = this.on_keydown.bind(this);
72
+ this.input_elem
73
+ .on('focusout', this.hide_dropdown)
74
+ .on('focus input', this.on_input)
75
+ .on('keydown', this.on_keydown);
76
+ this.autocomplete = this.autocomplete.bind(this);
77
+ }
78
+ unload() {
79
+ clearTimeout(this.timeout);
80
+ this.input_elem
81
+ .off('focusout', this.hide_dropdown)
82
+ .off('focus input', this.on_input)
83
+ .off('keydown', this.on_keydown);
84
+ }
85
+ parse_options() {
86
+ let rawparams = $('.autocomplete-params', this.elem).text().split('|'),
87
+ options = [];
88
+ for (let i = 0; i < rawparams.length; i++) {
89
+ let pair = rawparams[i].split(',');
90
+ let value = pair[1].replace(/^\s+|\s+$/g, "");
91
+ if (!isNaN(value)) {
92
+ value = parseInt(value);
93
+ } else
94
+ if (value === 'True') {
95
+ value = true;
96
+ } else
97
+ if (value === 'False') {
98
+ value = false;
99
+ }
100
+ let key = pair[0].replace(/^\s+|\s+$/g, "");
101
+ options[key] = value;
102
+ }
103
+ return options;
104
+ }
105
+ parse_source() {
106
+ let source = $('.autocomplete-source', this.elem).text();
107
+ if (source.indexOf('javascript:') === 0) {
108
+ let src = source.substring(11, source.length).split('.');
109
+ let window_src = window;
110
+ for (let part of src) {
111
+ window_src = window_src[part];
112
+ if (window_src === undefined) {
113
+ throw new Error('Cannot locate source function: ' + source);
114
+ }
115
+ }
116
+ this.source = function(request, response) {
117
+ window_src(request, response);
118
+ };
119
+ } else if (this.sourcetype === 'local') {
120
+ if (source === '') {
121
+ throw new Error('Local source is empty');
122
+ }
123
+ this.source = function(request, response) {
124
+ let src = source.split('|'),
125
+ term = request.term,
126
+ data = [];
127
+ for (let item of src) {
128
+ if (item.toUpperCase().indexOf(term.toUpperCase()) > -1) {
129
+ data.push(item);
130
+ }
131
+ }
132
+ response(data);
133
+ };
134
+ } else if (this.sourcetype === 'remote') {
135
+ this.source = function(request, response) {
136
+ $.ajax({
137
+ url: source,
138
+ data: {term: request.term},
139
+ dataType: "json",
140
+ success: function(data) {
141
+ response(data);
142
+ },
143
+ error: function() {
144
+ response([]);
145
+ throw new Error('Cannot locate JSON at: ' + source);
146
+ }
147
+ });
148
+ };
149
+ }
150
+ }
151
+ on_input(e) {
152
+ clearTimeout(this.timeout);
153
+ this.dd_elem.empty().hide();
154
+ this.suggestions = [];
155
+ this.current_focus = -1;
156
+ if (this.input_elem.val().length >= this.min_length) {
157
+ this.timeout = setTimeout(this.autocomplete, this.delay);
158
+ }
159
+ }
160
+ autocomplete() {
161
+ let val = this.input_elem.val();
162
+ this.source({term: val}, (data) => {
163
+ if(!data.length) {
164
+ return;
165
+ }
166
+ for (let item of data) {
167
+ this.suggestions.push(new AutocompleteSuggestion(this, item, val));
168
+ }
169
+ let scrolltop = $(document).scrollTop(),
170
+ input_top = this.elem.offset().top,
171
+ input_left = this.elem.offset().left,
172
+ input_height = this.elem.outerHeight(),
173
+ dd_height = this.dd_elem.outerHeight(),
174
+ top;
175
+ let viewport_edge = scrolltop + $(window).outerHeight();
176
+ let dd_bottom = input_top + input_height + dd_height;
177
+ if (dd_bottom >= viewport_edge) {
178
+ top = input_top - dd_height;
179
+ } else {
180
+ top = input_top + input_height;
181
+ }
182
+ this.dd_elem.css({
183
+ top: `${top}px`,
184
+ left: `${input_left}px`
185
+ });
186
+ this.dd_elem.show();
187
+ });
188
+ }
189
+ on_keydown(e) {
190
+ let scrolltop = this.dd_elem.scrollTop();
191
+ switch (e.key) {
192
+ case "ArrowDown":
193
+ this.current_focus++;
194
+ this.add_active(true);
195
+ break;
196
+ case "ArrowUp":
197
+ this.current_focus--;
198
+ this.add_active(false);
199
+ break;
200
+ case "Enter":
201
+ e.preventDefault();
202
+ if (this.current_focus > -1) {
203
+ let selected_elem = this.suggestions[this.current_focus];
204
+ selected_elem.selected = true;
205
+ this.input_elem.val(selected_elem.value);
206
+ this.hide_dropdown();
207
+ this.input_elem.trigger('blur');
208
+ }
209
+ break;
210
+ case "Escape":
211
+ this.hide_dropdown();
212
+ this.input_elem.trigger('blur');
213
+ break;
214
+ case "Tab":
215
+ this.hide_dropdown();
216
+ if (this.current_focus > -1) {
217
+ let selected_elem = this.suggestions[this.current_focus];
218
+ this.input_elem.val(selected_elem.value);
219
+ this.hide_dropdown();
220
+ this.input_elem.trigger('blur');
221
+ }
222
+ break;
223
+ case "PageDown":
224
+ e.preventDefault();
225
+ this.dd_elem.scrollTop(scrolltop + this.dd_elem.height());
226
+ if (this.current_focus > -1) {
227
+ let index = 0;
228
+ for (let i in this.suggestions) {
229
+ let elem = this.suggestions[i].elem;
230
+ if (elem.offset().top < this.dd_elem.offset().top) {
231
+ index++;
232
+ }
233
+ }
234
+ this.current_focus = index;
235
+ let selected_elem = this.suggestions[index];
236
+ this.unselect_all();
237
+ selected_elem.selected = true;
238
+ }
239
+ break;
240
+ case "PageUp":
241
+ e.preventDefault();
242
+ this.dd_elem.scrollTop(scrolltop - this.dd_elem.height());
243
+ if (this.current_focus > -1) {
244
+ let index = 0;
245
+ for (let i in this.suggestions) {
246
+ let elem = this.suggestions[i].elem;
247
+ if (elem.offset().top < this.dd_elem.offset().top) {
248
+ index++;
249
+ }
250
+ }
251
+ this.current_focus = index;
252
+ let selected_elem = this.suggestions[index];
253
+ this.unselect_all();
254
+ selected_elem.selected = true;
255
+ }
256
+ break;
257
+ }
258
+ }
259
+ select_suggestion(val) {
260
+ this.hide_dropdown();
261
+ this.input_elem.val(val);
262
+ }
263
+ unselect_all() {
264
+ for (let suggestion of this.suggestions) {
265
+ suggestion.selected = false;
266
+ }
267
+ }
268
+ add_active(dir) {
269
+ if (this.suggestions.length === 0) {
270
+ return;
271
+ }
272
+ this.unselect_all();
273
+ if (this.current_focus >= this.suggestions.length) {
274
+ this.current_focus = 0;
275
+ } else if (this.current_focus < 0) {
276
+ this.current_focus = (this.suggestions.length - 1);
277
+ }
278
+ let active_elem = this.suggestions[this.current_focus];
279
+ active_elem.selected = true;
280
+ let scrolltop = this.dd_elem.scrollTop();
281
+ let elem_top = active_elem.elem.offset().top;
282
+ let elem_height = active_elem.elem.outerHeight();
283
+ let dd_top = this.dd_elem.offset().top;
284
+ let dd_height = this.dd_elem.outerHeight();
285
+ if (dir) {
286
+ if (this.current_focus === 0) {
287
+ this.dd_elem.scrollTop(0);
288
+ } else if (elem_top + elem_height > dd_top + dd_height) {
289
+ this.dd_elem.scrollTop(scrolltop + elem_height);
290
+ }
291
+ } else {
292
+ if (this.current_focus >= this.suggestions.length - 1) {
293
+ this.dd_elem.scrollTop(elem_height * this.suggestions.length);
294
+ } else if (elem_top < dd_top) {
295
+ this.dd_elem.scrollTop(scrolltop - elem_height);
296
+ }
297
+ }
298
+ }
299
+ hide_dropdown() {
300
+ this.dd_elem.hide();
301
+ }
302
+ }
303
+ function autocomplete_on_array_add(inst, context) {
304
+ AutocompleteWidget.initialize(context);
305
+ }
306
+ function register_array_subscribers() {
307
+ if (window.yafowil_array !== undefined) {
308
+ window.yafowil_array.on_array_event('on_add', autocomplete_on_array_add);
309
+ } else if (yafowil.array !== undefined) {
310
+ $.extend(yafowil.array.hooks.add, {
311
+ autocomplete_binder: AutocompleteWidget.initialize
312
+ });
313
+ }
314
+ }
315
+
316
+ $(function() {
317
+ if (window.ts !== undefined) {
318
+ ts.ajax.register(AutocompleteWidget.initialize, true);
319
+ } else if (window.bdajax !== undefined) {
320
+ bdajax.register(AutocompleteWidget.initialize, true);
321
+ } else {
322
+ AutocompleteWidget.initialize();
323
+ }
324
+ register_array_subscribers();
325
+ });
326
+
327
+ exports.AutocompleteSuggestion = AutocompleteSuggestion;
328
+ exports.AutocompleteWidget = AutocompleteWidget;
329
+ exports.register_array_subscribers = register_array_subscribers;
330
+
331
+ Object.defineProperty(exports, '__esModule', { value: true });
332
+
333
+
334
+ window.yafowil = window.yafowil || {};
335
+ window.yafowil.autocomplete = exports;
336
+
337
+
338
+ return exports;
339
+
340
+ })({}, jQuery);
@@ -0,0 +1 @@
1
+ var yafowil_autocomplete=function(e,t){"use strict";class s{constructor(e,s,i){this.widget=e,this.elem=t("<div />").addClass("autocomplete-suggestion").appendTo(this.widget.dd_elem);let o=s.toUpperCase().indexOf(i.toUpperCase());t("<span />").text(s.substring(0,o)).appendTo(this.elem),t("<strong />").text(s.substring(o,o+i.length)).appendTo(this.elem),t("<span />").text(s.substring(o+i.length)).appendTo(this.elem),this.value=s,this.selected=!1,this.select=this.select.bind(this),this.elem.off("mousedown",this.select).on("mousedown",this.select)}get selected(){return this._selected}set selected(e){e?(this._selected=!0,this.elem.addClass("selected")):(this._selected=!1,this.elem.removeClass("selected"))}select(){this.selected=!0,this.widget.select_suggestion(this.value)}}class i{static initialize(e){t("div.yafowil-widget-autocomplete",e).each((function(){let e=t(this);void 0!==window.yafowil_array&&window.yafowil_array.inside_template(e)||new i(e)}))}constructor(e){e.data("yafowil-autocomplete",this),this.elem=e,this.input_elem=t("input.autocomplete",this.elem).attr("spellcheck",!1).attr("autocomplete","off"),this.dd_elem=t("<div />").addClass("autocomplete-dropdown").appendTo("body"),this.suggestions=[],this.current_focus=0;let s=this.parse_options();this.sourcetype=s.type,this.delay=s.delay,this.min_length=s.minLength,this.parse_source(),this.on_input=this.on_input.bind(this),this.hide_dropdown=this.hide_dropdown.bind(this),this.on_keydown=this.on_keydown.bind(this),this.input_elem.on("focusout",this.hide_dropdown).on("focus input",this.on_input).on("keydown",this.on_keydown),this.autocomplete=this.autocomplete.bind(this)}unload(){clearTimeout(this.timeout),this.input_elem.off("focusout",this.hide_dropdown).off("focus input",this.on_input).off("keydown",this.on_keydown)}parse_options(){let e=t(".autocomplete-params",this.elem).text().split("|"),s=[];for(let t=0;t<e.length;t++){let i=e[t].split(","),o=i[1].replace(/^\s+|\s+$/g,"");isNaN(o)?"True"===o?o=!0:"False"===o&&(o=!1):o=parseInt(o),s[i[0].replace(/^\s+|\s+$/g,"")]=o}return s}parse_source(){let e=t(".autocomplete-source",this.elem).text();if(0===e.indexOf("javascript:")){let t=e.substring(11,e.length).split("."),s=window;for(let i of t)if(s=s[i],void 0===s)throw new Error("Cannot locate source function: "+e);this.source=function(e,t){s(e,t)}}else if("local"===this.sourcetype){if(""===e)throw new Error("Local source is empty");this.source=function(t,s){let i=e.split("|"),o=t.term,l=[];for(let e of i)e.toUpperCase().indexOf(o.toUpperCase())>-1&&l.push(e);s(l)}}else"remote"===this.sourcetype&&(this.source=function(s,i){t.ajax({url:e,data:{term:s.term},dataType:"json",success:function(e){i(e)},error:function(){throw i([]),new Error("Cannot locate JSON at: "+e)}})})}on_input(e){clearTimeout(this.timeout),this.dd_elem.empty().hide(),this.suggestions=[],this.current_focus=-1,this.input_elem.val().length>=this.min_length&&(this.timeout=setTimeout(this.autocomplete,this.delay))}autocomplete(){let e=this.input_elem.val();this.source({term:e},(i=>{if(!i.length)return;for(let t of i)this.suggestions.push(new s(this,t,e));let o,l=t(document).scrollTop(),n=this.elem.offset().top,r=this.elem.offset().left,h=this.elem.outerHeight(),u=this.dd_elem.outerHeight();o=n+h+u>=l+t(window).outerHeight()?n-u:n+h,this.dd_elem.css({top:`${o}px`,left:`${r}px`}),this.dd_elem.show()}))}on_keydown(e){let t=this.dd_elem.scrollTop();switch(e.key){case"ArrowDown":this.current_focus++,this.add_active(!0);break;case"ArrowUp":this.current_focus--,this.add_active(!1);break;case"Enter":if(e.preventDefault(),this.current_focus>-1){let e=this.suggestions[this.current_focus];e.selected=!0,this.input_elem.val(e.value),this.hide_dropdown(),this.input_elem.trigger("blur")}break;case"Escape":this.hide_dropdown(),this.input_elem.trigger("blur");break;case"Tab":if(this.hide_dropdown(),this.current_focus>-1){let e=this.suggestions[this.current_focus];this.input_elem.val(e.value),this.hide_dropdown(),this.input_elem.trigger("blur")}break;case"PageDown":if(e.preventDefault(),this.dd_elem.scrollTop(t+this.dd_elem.height()),this.current_focus>-1){let e=0;for(let t in this.suggestions){this.suggestions[t].elem.offset().top<this.dd_elem.offset().top&&e++}this.current_focus=e;let t=this.suggestions[e];this.unselect_all(),t.selected=!0}break;case"PageUp":if(e.preventDefault(),this.dd_elem.scrollTop(t-this.dd_elem.height()),this.current_focus>-1){let e=0;for(let t in this.suggestions){this.suggestions[t].elem.offset().top<this.dd_elem.offset().top&&e++}this.current_focus=e;let t=this.suggestions[e];this.unselect_all(),t.selected=!0}}}select_suggestion(e){this.hide_dropdown(),this.input_elem.val(e)}unselect_all(){for(let e of this.suggestions)e.selected=!1}add_active(e){if(0===this.suggestions.length)return;this.unselect_all(),this.current_focus>=this.suggestions.length?this.current_focus=0:this.current_focus<0&&(this.current_focus=this.suggestions.length-1);let t=this.suggestions[this.current_focus];t.selected=!0;let s=this.dd_elem.scrollTop(),i=t.elem.offset().top,o=t.elem.outerHeight(),l=this.dd_elem.offset().top,n=this.dd_elem.outerHeight();e?0===this.current_focus?this.dd_elem.scrollTop(0):i+o>l+n&&this.dd_elem.scrollTop(s+o):this.current_focus>=this.suggestions.length-1?this.dd_elem.scrollTop(o*this.suggestions.length):i<l&&this.dd_elem.scrollTop(s-o)}hide_dropdown(){this.dd_elem.hide()}}function o(e,t){i.initialize(t)}function l(){void 0!==window.yafowil_array?window.yafowil_array.on_array_event("on_add",o):void 0!==yafowil.array&&t.extend(yafowil.array.hooks.add,{autocomplete_binder:i.initialize})}return t((function(){void 0!==window.ts?ts.ajax.register(i.initialize,!0):void 0!==window.bdajax?bdajax.register(i.initialize,!0):i.initialize(),l()})),e.AutocompleteSuggestion=s,e.AutocompleteWidget=i,e.register_array_subscribers=l,Object.defineProperty(e,"__esModule",{value:!0}),window.yafowil=window.yafowil||{},window.yafowil.autocomplete=e,e}({},jQuery);
@@ -0,0 +1,165 @@
1
+ from node.utils import UNSET
2
+ from yafowil.base import ExtractionError
3
+ from yafowil.base import factory
4
+ from yafowil.compat import IS_PY2
5
+ from yafowil.tests import fxml
6
+ from yafowil.tests import YafowilTestCase
7
+ import os
8
+ import unittest
9
+
10
+
11
+ if not IS_PY2:
12
+ from importlib import reload
13
+
14
+
15
+ def np(path):
16
+ return path.replace('/', os.path.sep)
17
+
18
+
19
+ class TestAutocompleteWidget(YafowilTestCase):
20
+
21
+ def setUp(self):
22
+ super(TestAutocompleteWidget, self).setUp()
23
+ from yafowil.widget import autocomplete
24
+ from yafowil.widget.autocomplete import widget
25
+ reload(widget)
26
+ autocomplete.register()
27
+
28
+ def test_source_is_string(self):
29
+ # Render plain, source is string
30
+ widget = factory(
31
+ 'autocomplete',
32
+ name='root',
33
+ props={
34
+ 'source': 'http://www.foo.bar/baz'
35
+ })
36
+ self.checkOutput("""
37
+ <div class="yafowil-widget-autocomplete">
38
+ <input class="autocomplete" id="input-root" name="root" type="text"/>
39
+ <div class="autocomplete-source hiddenStructure">http://www.foo.bar/baz</div>
40
+ <div class="autocomplete-params hiddenStructure">delay,300|minLength,1|type,remote</div>
41
+ </div>
42
+ """, fxml(widget()))
43
+
44
+ def test_source_is_list(self):
45
+ # Render plain, source is list
46
+ widget = factory(
47
+ 'autocomplete',
48
+ name='root',
49
+ props={
50
+ 'source': ['foo', 'bar']
51
+ })
52
+ self.checkOutput("""
53
+ <div class="yafowil-widget-autocomplete">
54
+ <input class="autocomplete" id="input-root" name="root" type="text"/>
55
+ <div class="autocomplete-source hiddenStructure">foo|bar</div>
56
+ <div class="autocomplete-params hiddenStructure">delay,300|minLength,1|type,local</div>
57
+ </div>
58
+ """, fxml(widget()))
59
+
60
+ def test_source_is_callable(self):
61
+ # Render plain, source is callable
62
+ def test_source(widget, data):
63
+ return 'http://from.callable/'
64
+
65
+ widget = factory(
66
+ 'autocomplete',
67
+ name='root',
68
+ props={
69
+ 'source': test_source
70
+ })
71
+ self.checkOutput("""
72
+ <div class="yafowil-widget-autocomplete">
73
+ <input class="autocomplete" id="input-root" name="root" type="text"/>
74
+ <div class="autocomplete-source hiddenStructure">http://from.callable/</div>
75
+ <div class="autocomplete-params hiddenStructure">delay,300|minLength,1|type,remote</div>
76
+ </div>""", fxml(widget()))
77
+
78
+ def test_extraction(self):
79
+ def test_source(widget, data):
80
+ return 'http://from.callable/'
81
+
82
+ widget = factory(
83
+ 'autocomplete',
84
+ name='root',
85
+ props={
86
+ 'source': test_source
87
+ })
88
+ data = widget.extract({'root': 'abc'})
89
+ self.assertEqual(
90
+ [data.name, data.value, data.extracted, data.errors],
91
+ ['root', UNSET, 'abc', []]
92
+ )
93
+
94
+ widget = factory(
95
+ 'error:autocomplete',
96
+ name='root',
97
+ props={
98
+ 'source': test_source,
99
+ 'required': 'Autocomplete widget is required'
100
+ })
101
+ data = widget.extract({'root': ''})
102
+ error = ExtractionError('Autocomplete widget is required')
103
+ self.assertEqual(
104
+ [data.name, data.value, data.extracted, data.errors],
105
+ ['root', UNSET, '', [error]]
106
+ )
107
+
108
+ self.checkOutput("""
109
+ <div class="error">
110
+ <div class="errormessage">Autocomplete widget is required</div>
111
+ <div class="yafowil-widget-autocomplete">
112
+ <input class="autocomplete required" id="input-root" name="root"
113
+ required="required" type="text" value=""/>
114
+ <div class="autocomplete-source hiddenStructure">http://from.callable/</div>
115
+ <div class="autocomplete-params hiddenStructure">delay,300|minLength,1|type,remote</div>
116
+ </div>
117
+ </div>
118
+ """, fxml(widget(data)))
119
+
120
+ def test_invalid_source_type(self):
121
+ widget = factory(
122
+ 'autocomplete',
123
+ name='root',
124
+ props={
125
+ 'source': None
126
+ })
127
+ with self.assertRaises(ValueError) as arc:
128
+ widget()
129
+ self.assertEqual(
130
+ str(arc.exception),
131
+ 'resulting source must be tuple/list or string'
132
+ )
133
+
134
+ def test_resources(self):
135
+ factory.theme = 'default'
136
+ resources = factory.get_resources('yafowil.widget.autocomplete')
137
+ self.assertTrue(
138
+ resources.directory.endswith(np('/autocomplete/resources'))
139
+ )
140
+ self.assertEqual(resources.name, 'yafowil.widget.autocomplete')
141
+ self.assertEqual(resources.path, 'yafowil-autocomplete')
142
+
143
+ scripts = resources.scripts
144
+ self.assertEqual(len(scripts), 1)
145
+
146
+ self.assertTrue(
147
+ scripts[0].directory.endswith(np('/autocomplete/resources'))
148
+ )
149
+ self.assertEqual(scripts[0].path, 'yafowil-autocomplete')
150
+ self.assertEqual(scripts[0].file_name, 'widget.min.js')
151
+ self.assertTrue(os.path.exists(scripts[0].file_path))
152
+
153
+ styles = resources.styles
154
+ self.assertEqual(len(styles), 1)
155
+
156
+ self.assertTrue(
157
+ styles[0].directory.endswith(np('/autocomplete/resources'))
158
+ )
159
+ self.assertEqual(styles[0].path, 'yafowil-autocomplete')
160
+ self.assertEqual(styles[0].file_name, 'widget.css')
161
+ self.assertTrue(os.path.exists(styles[0].file_path))
162
+
163
+
164
+ if __name__ == '__main__':
165
+ unittest.main()
@@ -0,0 +1,82 @@
1
+ from yafowil.base import factory
2
+ from yafowil.common import generic_extractor
3
+ from yafowil.common import generic_required_extractor
4
+ from yafowil.common import input_generic_renderer
5
+ from yafowil.compat import STR_TYPE
6
+ from yafowil.utils import attr_value
7
+ from yafowil.utils import managedprops
8
+
9
+
10
+ @managedprops('source', 'delay', 'minLength')
11
+ def autocomplete_renderer(widget, data):
12
+ result = data.rendered
13
+ tag = data.tag
14
+ source = attr_value('source', widget, data)
15
+ if isinstance(source, (list, tuple)):
16
+ source = '|'.join(source)
17
+ source_type = 'local'
18
+ elif isinstance(source, STR_TYPE):
19
+ source_type = 'remote'
20
+ else:
21
+ raise ValueError('resulting source must be tuple/list or string')
22
+ result += tag('div', source, **{
23
+ 'class': 'autocomplete-source hiddenStructure'
24
+ })
25
+ params = [
26
+ ('%s,%s' % (_, attr_value(_, widget, data)))
27
+ for _ in ['delay', 'minLength']
28
+ ]
29
+ params.append('type,%s' % source_type)
30
+ result += tag('div', '|'.join(params), **{
31
+ 'class': 'autocomplete-params hiddenStructure'
32
+ })
33
+ return tag('div', result, **{'class': 'yafowil-widget-autocomplete'})
34
+
35
+
36
+ def autocomplete_extractor(widget, data):
37
+ return data.extracted
38
+
39
+
40
+ factory.register(
41
+ 'autocomplete',
42
+ extractors=[
43
+ generic_extractor,
44
+ generic_required_extractor,
45
+ autocomplete_extractor
46
+ ],
47
+ edit_renderers=[
48
+ input_generic_renderer,
49
+ autocomplete_renderer
50
+ ]
51
+ )
52
+
53
+ factory.doc['blueprint']['autocomplete'] = """\
54
+ Add-on blueprint `yafowil.widget.autocomplete
55
+ <http://github.com/conestack/yafowil.widget.autocomplete/>`_ utilizing
56
+ ``jquery.ui.autocomplete`` to offer the user a selection based on the input
57
+ given so far.
58
+ """
59
+
60
+ factory.defaults['autocomplete.type'] = 'text'
61
+
62
+ factory.defaults['autocomplete.class'] = 'autocomplete'
63
+
64
+ factory.defaults['autocomplete.required_class'] = 'required'
65
+
66
+ factory.defaults['autocomplete.disabled'] = False
67
+
68
+ factory.defaults['autocomplete.size'] = None
69
+
70
+ factory.defaults['autocomplete.delay'] = '300'
71
+ factory.doc['props']['autocomplete.delay'] = """\
72
+ Delay in milliseconds.
73
+ """
74
+
75
+ factory.defaults['autocomplete.minLength'] = '1'
76
+ factory.doc['props']['autocomplete.minLength'] = """\
77
+ Minimum input length to trigger autocomplete.
78
+ """
79
+
80
+ factory.doc['props']['autocomplete.source'] = """\
81
+ Autocomplete source as python iterable or string defining JSON view callback.
82
+ """
@@ -0,0 +1,2 @@
1
+ import sys, types, os;p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('yafowil',));importlib = __import__('importlib.util');__import__('importlib.machinery');m = sys.modules.setdefault('yafowil', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('yafowil', [os.path.dirname(p)])));m = m or sys.modules.setdefault('yafowil', types.ModuleType('yafowil'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p)
2
+ import sys, types, os;p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('yafowil', 'widget'));importlib = __import__('importlib.util');__import__('importlib.machinery');m = sys.modules.setdefault('yafowil.widget', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('yafowil.widget', [os.path.dirname(p)])));m = m or sys.modules.setdefault('yafowil.widget', types.ModuleType('yafowil.widget'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p);m and setattr(sys.modules['yafowil'], 'widget', m)
@@ -0,0 +1,27 @@
1
+ License
2
+ =======
3
+
4
+ Copyright (c) 2010-2021, BlueDynamics Alliance, Austria, Germany, Switzerland
5
+ Copyright (c) 2021-2022, Yafowil Contributors
6
+ All rights reserved.
7
+
8
+ Redistribution and use in source and binary forms, with or without
9
+ modification, are permitted provided that the following conditions are met:
10
+
11
+ * Redistributions of source code must retain the above copyright notice, this
12
+ list of conditions and the following disclaimer.
13
+
14
+ * Redistributions in binary form must reproduce the above copyright notice, this
15
+ list of conditions and the following disclaimer in the documentation and/or
16
+ other materials provided with the distribution.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,186 @@
1
+ Metadata-Version: 2.1
2
+ Name: yafowil.widget.autocomplete
3
+ Version: 2.0a1
4
+ Summary: Autocomplete Widget for YAFOWIL
5
+ Home-page: http://github.com/conestack/yafowil.widget.autocomplete
6
+ Author: Yafowil Contributors
7
+ Author-email: dev@conestack.org
8
+ License: Simplified BSD
9
+ Keywords: jquery jquery.ui.autocomplete widget yafowil
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Topic :: Software Development
13
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
14
+ Classifier: License :: OSI Approved :: BSD License
15
+ Classifier: Programming Language :: Python :: 2.7
16
+ Classifier: Programming Language :: Python :: 3.5
17
+ Classifier: Programming Language :: Python :: 3.6
18
+ Classifier: Programming Language :: Python :: 3.7
19
+ License-File: LICENSE.rst
20
+ Requires-Dist: setuptools
21
+ Requires-Dist: yafowil >2.1.99
22
+ Provides-Extra: test
23
+ Requires-Dist: lxml ; extra == 'test'
24
+ Requires-Dist: zope.testrunner ; extra == 'test'
25
+
26
+ This is a **autocomplete widget** for for `YAFOWIL
27
+ <http://pypi.python.org/pypi/yafowil>`_ - Yet Another Form WIdget Library.
28
+
29
+ - `Documentation <http://docs.yafowil.info/en/latest/blueprints.html#autocomplete>`_
30
+ - `DEMO - see it Live <http://docs.demo.yafowil.info/++widget++yafowil.widget.autocomplete/index.html>`_
31
+
32
+
33
+ Source Code
34
+ ===========
35
+
36
+ The sources are in a GIT DVCS with its main branches at
37
+ `github <http://github.com/conestack/yafowil.widget.autocomplete>`_.
38
+
39
+ We'd be happy to see many forks and pull-requests to make YAFOWIL even better.
40
+
41
+
42
+ Contributors
43
+ ============
44
+
45
+ - Jens Klein
46
+
47
+ - Robert Niederreiter
48
+
49
+ - Georg Bernhard
50
+
51
+ - Lena Daxenbichler
52
+
53
+
54
+ Changes
55
+ =======
56
+
57
+ 2.0a1 (2024-06-02)
58
+ ------------------
59
+
60
+ - Extend JS by ``autocomplete_on_array_add`` and ``register_array_subscribers``
61
+ to enable usage in ``yafowil.widget.array``.
62
+ [lenadax]
63
+
64
+ - Get rid of jQuery UI. Rewrite Jacascript widget using ES6.
65
+ [lenadax]
66
+
67
+
68
+ 1.7 (2018-07-16)
69
+ ----------------
70
+
71
+ - Python 3 compatibility.
72
+ [rnix]
73
+
74
+ - Convert doctests to unittests.
75
+ [rnix]
76
+
77
+
78
+ 1.6 (2017-03-01)
79
+ ----------------
80
+
81
+ - Add dedicated CSS for ``plone5`` theme provided by ``yafowil.plone``.
82
+ [rnix, 2016-06-28]
83
+
84
+ - Use ``yafowil.utils.entry_point`` decorator.
85
+ [rnix, 2016-06-28]
86
+
87
+
88
+ 1.5.1 (2015-06-25)
89
+ ------------------
90
+
91
+ - Resolve JSHint errors and warnings.
92
+ [thet]
93
+
94
+
95
+ 1.5 (2015-01-23)
96
+ ----------------
97
+
98
+ - Update jquery UI autocomplete to 1.10.3 and use latest jquery ui boostrap
99
+ styles.
100
+ [rnix, 2014-07-05]
101
+
102
+ 1.4.1
103
+ -----
104
+
105
+ - use ``yafowil.utils.attr_value`` wherever possible.
106
+ [rnix, 2012-10-25]
107
+
108
+ 1.4
109
+ ---
110
+
111
+ - Add ``generic_required_extractor`` to extraction chain
112
+ [rnix, 2012-07-25]
113
+
114
+ - Adopt resource providing
115
+ [rnix, 2012-06-12]
116
+
117
+ - Remove example app
118
+ [rnix, 2012-06-12]
119
+
120
+ 1.3
121
+ ---
122
+
123
+ - Support JavaScript callback functions in source attribute
124
+ [rnix, 2011-10-03]
125
+
126
+ - register autocomplete binder in ``yafowil.widget.array`` hooks if installed
127
+ [rnix, 2011-10-03]
128
+
129
+ - register plan and depend on yafowil 1.3
130
+ [jensens, 2011-09-30]
131
+
132
+ 1.2
133
+ ---
134
+
135
+ - adopt to yafowil 1.2
136
+ [jensens, 2011-09-20]
137
+
138
+ - resource available in plain Zope2
139
+ [jensens, 2011-09-20]
140
+
141
+ 1.1
142
+ ---
143
+
144
+ - adopt to yafowil 1.1
145
+ [rnix, 2011-07-08]
146
+
147
+ 1.0.1
148
+ -----
149
+
150
+ - test coverage
151
+ [rnix, 2011-05-07]
152
+
153
+ 1.0
154
+ ---
155
+
156
+ - made it work
157
+ [jensens, 2010-11-24]
158
+
159
+
160
+ License
161
+ =======
162
+
163
+ Copyright (c) 2010-2021, BlueDynamics Alliance, Austria, Germany, Switzerland
164
+ Copyright (c) 2021-2022, Yafowil Contributors
165
+ All rights reserved.
166
+
167
+ Redistribution and use in source and binary forms, with or without
168
+ modification, are permitted provided that the following conditions are met:
169
+
170
+ * Redistributions of source code must retain the above copyright notice, this
171
+ list of conditions and the following disclaimer.
172
+
173
+ * Redistributions in binary form must reproduce the above copyright notice, this
174
+ list of conditions and the following disclaimer in the documentation and/or
175
+ other materials provided with the distribution.
176
+
177
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
178
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
179
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
180
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
181
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
182
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
183
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
184
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
185
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
186
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,15 @@
1
+ yafowil.widget.autocomplete-2.0a1-py3.9-nspkg.pth,sha256=qMztS1qcT3Izik2RG40diPoBu5UBjZngpO4B5vlyj2I,1032
2
+ yafowil/widget/autocomplete/__init__.py,sha256=Td_ploHAruVP8scuFhcpTrh5gb8VNIEKZzzhYFGv08I,1644
3
+ yafowil/widget/autocomplete/example.py,sha256=XfxLIZZr-1vchDMQBAw4pl5bW2cwbJgKPyC1QWQnsU8,4055
4
+ yafowil/widget/autocomplete/tests.py,sha256=Zeo63EpkxAiTcrrvJsDwuZpWVKQ02LPri5x3unQ1Yv0,5637
5
+ yafowil/widget/autocomplete/widget.py,sha256=K1VXmLlyxkWy74xXQCpCTRon-GyxmiMTFPd_9yO9hY0,2432
6
+ yafowil/widget/autocomplete/resources/widget.css,sha256=T2WDambBG4WZnGNxN_07EMCIFZ574J5QeeTCmR6TNDs,625
7
+ yafowil/widget/autocomplete/resources/widget.js,sha256=P35IQ3P5YHWy1zxtzy6aaxOSUvmf7wXXMP_r-zUYX7w,13530
8
+ yafowil/widget/autocomplete/resources/widget.min.js,sha256=BdjBUTg83ReDi0sj1uMiIRfzC35dLMxe7J_3B_zWyXM,5967
9
+ yafowil.widget.autocomplete-2.0a1.dist-info/LICENSE.rst,sha256=voySO5bColCbsIXQyWy4yWhSk5DULUYHeiQ121Vh3fU,1403
10
+ yafowil.widget.autocomplete-2.0a1.dist-info/METADATA,sha256=yzK9GCyjTLnMiYIVoqNpoeO4le9D604c1rcFKycRZcw,4629
11
+ yafowil.widget.autocomplete-2.0a1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
12
+ yafowil.widget.autocomplete-2.0a1.dist-info/entry_points.txt,sha256=OqVe3M8KB75CHP8cJicoLRsFWtv12qlibX1P1PYCXRU,123
13
+ yafowil.widget.autocomplete-2.0a1.dist-info/namespace_packages.txt,sha256=oTPEdRfMCGNcZRcF2Y0ULt8JBOUbIGRQHpcQ3vb_vXQ,23
14
+ yafowil.widget.autocomplete-2.0a1.dist-info/top_level.txt,sha256=cJz5CZEzEKmMv88hkEh5t8fW1bRvd7Dv6VZ7l6RpqUA,8
15
+ yafowil.widget.autocomplete-2.0a1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.43.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [yafowil.plugin]
2
+ example = yafowil.widget.autocomplete.example:get_example
3
+ register = yafowil.widget.autocomplete:register
@@ -0,0 +1,2 @@
1
+ yafowil
2
+ yafowil.widget