diff --git a/app/assets/javascripts/trln_argon/advanced_search_scope.js b/app/assets/javascripts/trln_argon/advanced_search_scope.js index 094369cf..b5c2f53a 100644 --- a/app/assets/javascripts/trln_argon/advanced_search_scope.js +++ b/app/assets/javascripts/trln_argon/advanced_search_scope.js @@ -16,13 +16,5 @@ Blacklight.onLoad(function() { var action = $(this).val(); $(".advanced").attr("action", "/" + action); }); - - // fix labeling for fields generated by Chosen jquery library (https://harvesthq.github.io/chosen/) - $("[id$='_chosen']").each(function() { - $the_id = $(this).attr('id'); - $aria_label = $(this).attr('id').replace('_chosen', '_label'); - $('#' + $the_id + ' .chosen-search-input').attr('aria-labelledby', $aria_label); - }); - }); }); \ No newline at end of file diff --git a/app/assets/javascripts/trln_argon/advanced_search_select.js b/app/assets/javascripts/trln_argon/advanced_search_select.js deleted file mode 100644 index 3c61408a..00000000 --- a/app/assets/javascripts/trln_argon/advanced_search_select.js +++ /dev/null @@ -1,7 +0,0 @@ -Blacklight.onLoad(function() { - $('.advanced-search-facet-select').chosen({ - allow_single_deselect: true, - no_results_text: 'No results matched', - width: '100%' - }); -}); diff --git a/app/assets/javascripts/trln_argon/controllers/multi_select_controller.js b/app/assets/javascripts/trln_argon/controllers/multi_select_controller.js new file mode 100644 index 00000000..2b256104 --- /dev/null +++ b/app/assets/javascripts/trln_argon/controllers/multi_select_controller.js @@ -0,0 +1,27 @@ +document.addEventListener("DOMContentLoaded", function () { + if (window.Stimulus) { + // Only register the controller if Stimulus is loaded + const application = window.Stimulus.Application.start(); + + application.register("multi-select", class extends Stimulus.Controller { + static values = { plugins: Array }; + + connect() { + this.select = new TomSelect(this.element, { + plugins: this.pluginsValue, + render: { + item: function (data, escape) { + return `
${escape(data.text)}
`; + }, + }, + }); + } + + disconnect() { + this.select?.destroy(); + } + }); + } else { + console.error("Stimulus is not loaded yet."); + } + }); diff --git a/app/assets/javascripts/trln_argon/trln_argon.js b/app/assets/javascripts/trln_argon/trln_argon.js index d48ac739..c7572493 100644 --- a/app/assets/javascripts/trln_argon/trln_argon.js +++ b/app/assets/javascripts/trln_argon/trln_argon.js @@ -4,10 +4,8 @@ //= require trln_argon/progressive_links.js //= require trln_argon/physical_media_facet.js //= require bootstrap -//= require chosen.jquery.js //= require blacklight/hierarchy/hierarchy.js //= require trln_argon/location_facet.js -//= require trln_argon/advanced_search_select.js //= require trln_argon/results_count_for_toggle.js //= require trln_argon/google_books_preview.js //= require trln_argon/fulltext_links.js @@ -16,3 +14,6 @@ //= require trln_argon/typeahead.bundle.js //= require trln_argon/advanced_search_tt_menu.js //= require trln_argon/autocomplete.js +//= require tom-select/tom-select.complete.js +//= require trln_argon/controllers/multi_select_controller.js +//= require stimulus/stimulus.umd.js diff --git a/app/assets/stylesheets/trln_argon/blacklight_advanced_search.scss b/app/assets/stylesheets/trln_argon/blacklight_advanced_search.scss index bdc70df4..b99fae12 100644 --- a/app/assets/stylesheets/trln_argon/blacklight_advanced_search.scss +++ b/app/assets/stylesheets/trln_argon/blacklight_advanced_search.scss @@ -11,10 +11,6 @@ h2.query-criteria-heading label { .col-form-label { text-align: left; } - - .chosen-search-input { - color: #767676; - } } #advanced_search { @@ -58,3 +54,14 @@ h2.query-criteria-heading label { font-weight: 700; } } + +.card { + margin-top: 1rem !important; /* Override with higher value */ + margin-bottom: 1rem !important; /* Override with higher value */ +} + +.ts-wrapper.multi .ts-control [data-value] { + background-color: #eeeeee !important;; + background-image: linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%) !important; + text-shadow: none !important; /* Remove text-shadow */ +} diff --git a/app/assets/stylesheets/trln_argon/trln_argon_dependencies.scss b/app/assets/stylesheets/trln_argon/trln_argon_dependencies.scss index 3c361d47..86f87fbd 100644 --- a/app/assets/stylesheets/trln_argon/trln_argon_dependencies.scss +++ b/app/assets/stylesheets/trln_argon/trln_argon_dependencies.scss @@ -10,4 +10,7 @@ @import 'font-awesome'; -@import 'chosen'; +// @import 'chosen'; + +@import 'tom-select/tom-select.default.min.css'; +@import 'tom-select/tom-select.bootstrap5.min.css'; diff --git a/app/components/trln_argon/multi_select_facet_component.html.erb b/app/components/trln_argon/multi_select_facet_component.html.erb new file mode 100644 index 00000000..6b1e6c12 --- /dev/null +++ b/app/components/trln_argon/multi_select_facet_component.html.erb @@ -0,0 +1,17 @@ +<%= render(@layout.new(facet_field: @facet_field)) do |component| %> + <% component.with_label do %> + <%= @facet_field.label %> + <% end %> + <% component.with_body do %> +
+ +
+ <% end %> + <% end %> + \ No newline at end of file diff --git a/app/components/trln_argon/multi_select_facet_component.rb b/app/components/trln_argon/multi_select_facet_component.rb new file mode 100644 index 00000000..6d5d3276 --- /dev/null +++ b/app/components/trln_argon/multi_select_facet_component.rb @@ -0,0 +1,59 @@ +module TrlnArgon + # Multi select facet component using TomSelect + class MultiSelectFacetComponent < Blacklight::Component + def initialize(facet_field:, layout: nil) + super() + @facet_field = facet_field + @layout = layout == false ? FacetFieldNoLayoutComponent : Blacklight::FacetFieldComponent + end + + # @return [Boolean] whether to render the component + def render? + presenters.any? + end + + # @return [Array] array of facet field presenters + def presenters + return [] unless @facet_field.paginator + + return to_enum(:presenters) unless block_given? + + @facet_field.paginator.items.each do |item| + yield @facet_field.facet_field + .item_presenter + .new(item, @facet_field.facet_field, helpers, @facet_field.key, @facet_field.search_state) + end + end + + # @return [Hash] HTML attributes for the select element + def select_attributes + { + class: "#{@facet_field.key}-select", + name: "f_inclusive[#{@facet_field.key}][]", + placeholder: I18n.t('facets.advanced_search.placeholder'), + multiple: true, + data: { + controller: 'multi-select', + multi_select_plugins_value: select_plugins.to_json + } + } + end + + # @return [Hash] HTML attributes for the option elements within the select element + def option_attributes(presenter:) + { + value: presenter.value, + selected: presenter.selected? ? 'selected' : nil + } + end + + # TomSelect functionality can be expanded with plugins. `checkbox_options` + # allow us to use the existing advanced search facet logic by using checkboxes. + # More plugins can be found here: https://tom-select.js.org/plugins/ + # + # @return [Array] array of TomSelect plugins + def select_plugins + %w[checkbox_options caret_position input_autogrow clear_button] + end + end +end diff --git a/config/locales/trln_argon.en.yml b/config/locales/trln_argon.en.yml index 0179e33e..4a759cd2 100644 --- a/config/locales/trln_argon.en.yml +++ b/config/locales/trln_argon.en.yml @@ -66,6 +66,7 @@ en: sort_label: "Sort results by" start_over: "Start over" search_btn: 'Search' + placeholder: 'Select facets...' trln_argon: diff --git a/lib/trln_argon/controller_override.rb b/lib/trln_argon/controller_override.rb index 08ca1a55..3c9a7d56 100644 --- a/lib/trln_argon/controller_override.rb +++ b/lib/trln_argon/controller_override.rb @@ -205,13 +205,13 @@ module ControllerOverride collapse: false, show: true, component: TrlnArgon::FacetFieldCheckboxesComponent, - advanced_search_component: TrlnArgon::AdvancedSearchFacetFieldComponent + advanced_search_component: TrlnArgon::MultiSelectFacetComponent config.add_facet_field TrlnArgon::Fields::AVAILABLE_FACET.to_s, label: TrlnArgon::Fields::AVAILABLE_FACET.label, limit: true, collapse: false, show: true, - advanced_search_component: TrlnArgon::AdvancedSearchFacetFieldComponent + advanced_search_component: TrlnArgon::MultiSelectFacetComponent config.add_facet_field TrlnArgon::Fields::LOCATION_HIERARCHY_FACET.to_s, label: TrlnArgon::Fields::LOCATION_HIERARCHY_FACET.label, limit: -1, @@ -221,17 +221,17 @@ module ControllerOverride # This helper is still needed for the label in constraints helper_method: :location_filter_display, component: Blacklight::Hierarchy::FacetFieldListComponent, - advanced_search_component: TrlnArgon::AdvancedSearchFacetFieldComponent + advanced_search_component: TrlnArgon::MultiSelectFacetComponent config.add_facet_field TrlnArgon::Fields::RESOURCE_TYPE_FACET.to_s, label: TrlnArgon::Fields::RESOURCE_TYPE_FACET.label, limit: true, collapse: false, - advanced_search_component: TrlnArgon::AdvancedSearchFacetFieldComponent + advanced_search_component: TrlnArgon::MultiSelectFacetComponent config.add_facet_field TrlnArgon::Fields::PHYSICAL_MEDIA_FACET.to_s, label: TrlnArgon::Fields::PHYSICAL_MEDIA_FACET.label, limit: true, collapse: false, - advanced_search_component: TrlnArgon::AdvancedSearchFacetFieldComponent + advanced_search_component: TrlnArgon::MultiSelectFacetComponent config.add_facet_field TrlnArgon::Fields::SUBJECT_TOPICAL_FACET.to_s, label: TrlnArgon::Fields::SUBJECT_TOPICAL_FACET.label, limit: true, @@ -244,11 +244,11 @@ module ControllerOverride # This helper is still needed for the label in constraints helper_method: :call_number_filter_display, component: Blacklight::Hierarchy::FacetFieldListComponent, - advanced_search_component: TrlnArgon::AdvancedSearchFacetFieldComponent + advanced_search_component: TrlnArgon::MultiSelectFacetComponent config.add_facet_field TrlnArgon::Fields::LANGUAGE_FACET.to_s, label: TrlnArgon::Fields::LANGUAGE_FACET.label, limit: true, - advanced_search_component: TrlnArgon::AdvancedSearchFacetFieldComponent + advanced_search_component: TrlnArgon::MultiSelectFacetComponent # See Range Facet Configuration options: # https://github.com/projectblacklight/blacklight_range_limit?tab=readme-ov-file#range-facet-configuration @@ -284,7 +284,7 @@ module ControllerOverride fq: "#{TrlnArgon::Fields::DATE_CATALOGED_FACET}:[NOW-3MONTH/DAY TO NOW]" } }, label: TrlnArgon::Fields::DATE_CATALOGED_FACET.label, limit: true, - advanced_search_component: TrlnArgon::AdvancedSearchFacetFieldComponent + advanced_search_component: TrlnArgon::MultiSelectFacetComponent # Hierarchical facet configuration # See: https://github.com/sul-dlss/blacklight-hierarchy/blob/main/README.md diff --git a/vendor/assets/javascripts/chosen.jquery.js b/vendor/assets/javascripts/chosen.jquery.js deleted file mode 100644 index 5f2df671..00000000 --- a/vendor/assets/javascripts/chosen.jquery.js +++ /dev/null @@ -1,1359 +0,0 @@ -/*! -Chosen, a Select Box Enhancer for jQuery and Prototype -by Patrick Filler for Harvest, http://getharvest.com - -Version 1.8.7 -Full source at https://github.com/harvesthq/chosen -Copyright (c) 2011-2018 Harvest http://getharvest.com - -MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md -This file is generated by `grunt build`, do not edit it by hand. -*/ - -(function() { - var $, AbstractChosen, Chosen, SelectParser, - bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, - extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty; - - SelectParser = (function() { - function SelectParser() { - this.options_index = 0; - this.parsed = []; - } - - SelectParser.prototype.add_node = function(child) { - if (child.nodeName.toUpperCase() === "OPTGROUP") { - return this.add_group(child); - } else { - return this.add_option(child); - } - }; - - SelectParser.prototype.add_group = function(group) { - var group_position, i, len, option, ref, results1; - group_position = this.parsed.length; - this.parsed.push({ - array_index: group_position, - group: true, - label: group.label, - title: group.title ? group.title : void 0, - children: 0, - disabled: group.disabled, - classes: group.className - }); - ref = group.childNodes; - results1 = []; - for (i = 0, len = ref.length; i < len; i++) { - option = ref[i]; - results1.push(this.add_option(option, group_position, group.disabled)); - } - return results1; - }; - - SelectParser.prototype.add_option = function(option, group_position, group_disabled) { - if (option.nodeName.toUpperCase() === "OPTION") { - if (option.text !== "") { - if (group_position != null) { - this.parsed[group_position].children += 1; - } - this.parsed.push({ - array_index: this.parsed.length, - options_index: this.options_index, - value: option.value, - text: option.text, - html: option.innerHTML, - title: option.title ? option.title : void 0, - selected: option.selected, - disabled: group_disabled === true ? group_disabled : option.disabled, - group_array_index: group_position, - group_label: group_position != null ? this.parsed[group_position].label : null, - classes: option.className, - style: option.style.cssText - }); - } else { - this.parsed.push({ - array_index: this.parsed.length, - options_index: this.options_index, - empty: true - }); - } - return this.options_index += 1; - } - }; - - return SelectParser; - - })(); - - SelectParser.select_to_array = function(select) { - var child, i, len, parser, ref; - parser = new SelectParser(); - ref = select.childNodes; - for (i = 0, len = ref.length; i < len; i++) { - child = ref[i]; - parser.add_node(child); - } - return parser.parsed; - }; - - AbstractChosen = (function() { - function AbstractChosen(form_field, options1) { - this.form_field = form_field; - this.options = options1 != null ? options1 : {}; - this.label_click_handler = bind(this.label_click_handler, this); - if (!AbstractChosen.browser_is_supported()) { - return; - } - this.is_multiple = this.form_field.multiple; - this.set_default_text(); - this.set_default_values(); - this.setup(); - this.set_up_html(); - this.register_observers(); - this.on_ready(); - } - - AbstractChosen.prototype.set_default_values = function() { - this.click_test_action = (function(_this) { - return function(evt) { - return _this.test_active_click(evt); - }; - })(this); - this.activate_action = (function(_this) { - return function(evt) { - return _this.activate_field(evt); - }; - })(this); - this.active_field = false; - this.mouse_on_container = false; - this.results_showing = false; - this.result_highlighted = null; - this.is_rtl = this.options.rtl || /\bchosen-rtl\b/.test(this.form_field.className); - this.allow_single_deselect = (this.options.allow_single_deselect != null) && (this.form_field.options[0] != null) && this.form_field.options[0].text === "" ? this.options.allow_single_deselect : false; - this.disable_search_threshold = this.options.disable_search_threshold || 0; - this.disable_search = this.options.disable_search || false; - this.enable_split_word_search = this.options.enable_split_word_search != null ? this.options.enable_split_word_search : true; - this.group_search = this.options.group_search != null ? this.options.group_search : true; - this.search_contains = this.options.search_contains || false; - this.single_backstroke_delete = this.options.single_backstroke_delete != null ? this.options.single_backstroke_delete : true; - this.max_selected_options = this.options.max_selected_options || Infinity; - this.inherit_select_classes = this.options.inherit_select_classes || false; - this.display_selected_options = this.options.display_selected_options != null ? this.options.display_selected_options : true; - this.display_disabled_options = this.options.display_disabled_options != null ? this.options.display_disabled_options : true; - this.include_group_label_in_selected = this.options.include_group_label_in_selected || false; - this.max_shown_results = this.options.max_shown_results || Number.POSITIVE_INFINITY; - this.case_sensitive_search = this.options.case_sensitive_search || false; - return this.hide_results_on_select = this.options.hide_results_on_select != null ? this.options.hide_results_on_select : true; - }; - - AbstractChosen.prototype.set_default_text = function() { - if (this.form_field.getAttribute("data-placeholder")) { - this.default_text = this.form_field.getAttribute("data-placeholder"); - } else if (this.is_multiple) { - this.default_text = this.options.placeholder_text_multiple || this.options.placeholder_text || AbstractChosen.default_multiple_text; - } else { - this.default_text = this.options.placeholder_text_single || this.options.placeholder_text || AbstractChosen.default_single_text; - } - this.default_text = this.escape_html(this.default_text); - return this.results_none_found = this.form_field.getAttribute("data-no_results_text") || this.options.no_results_text || AbstractChosen.default_no_result_text; - }; - - AbstractChosen.prototype.choice_label = function(item) { - if (this.include_group_label_in_selected && (item.group_label != null)) { - return "" + (this.escape_html(item.group_label)) + "" + item.html; - } else { - return item.html; - } - }; - - AbstractChosen.prototype.mouse_enter = function() { - return this.mouse_on_container = true; - }; - - AbstractChosen.prototype.mouse_leave = function() { - return this.mouse_on_container = false; - }; - - AbstractChosen.prototype.input_focus = function(evt) { - if (this.is_multiple) { - if (!this.active_field) { - return setTimeout(((function(_this) { - return function() { - return _this.container_mousedown(); - }; - })(this)), 50); - } - } else { - if (!this.active_field) { - return this.activate_field(); - } - } - }; - - AbstractChosen.prototype.input_blur = function(evt) { - if (!this.mouse_on_container) { - this.active_field = false; - return setTimeout(((function(_this) { - return function() { - return _this.blur_test(); - }; - })(this)), 100); - } - }; - - AbstractChosen.prototype.label_click_handler = function(evt) { - if (this.is_multiple) { - return this.container_mousedown(evt); - } else { - return this.activate_field(); - } - }; - - AbstractChosen.prototype.results_option_build = function(options) { - var content, data, data_content, i, len, ref, shown_results; - content = ''; - shown_results = 0; - ref = this.results_data; - for (i = 0, len = ref.length; i < len; i++) { - data = ref[i]; - data_content = ''; - if (data.group) { - data_content = this.result_add_group(data); - } else { - data_content = this.result_add_option(data); - } - if (data_content !== '') { - shown_results++; - content += data_content; - } - if (options != null ? options.first : void 0) { - if (data.selected && this.is_multiple) { - this.choice_build(data); - } else if (data.selected && !this.is_multiple) { - this.single_set_selected_text(this.choice_label(data)); - } - } - if (shown_results >= this.max_shown_results) { - break; - } - } - return content; - }; - - AbstractChosen.prototype.result_add_option = function(option) { - var classes, option_el; - if (!option.search_match) { - return ''; - } - if (!this.include_option_in_results(option)) { - return ''; - } - classes = []; - if (!option.disabled && !(option.selected && this.is_multiple)) { - classes.push("active-result"); - } - if (option.disabled && !(option.selected && this.is_multiple)) { - classes.push("disabled-result"); - } - if (option.selected) { - classes.push("result-selected"); - } - if (option.group_array_index != null) { - classes.push("group-option"); - } - if (option.classes !== "") { - classes.push(option.classes); - } - option_el = document.createElement("li"); - option_el.className = classes.join(" "); - if (option.style) { - option_el.style.cssText = option.style; - } - option_el.setAttribute("data-option-array-index", option.array_index); - option_el.innerHTML = option.highlighted_html || option.html; - if (option.title) { - option_el.title = option.title; - } - return this.outerHTML(option_el); - }; - - AbstractChosen.prototype.result_add_group = function(group) { - var classes, group_el; - if (!(group.search_match || group.group_match)) { - return ''; - } - if (!(group.active_options > 0)) { - return ''; - } - classes = []; - classes.push("group-result"); - if (group.classes) { - classes.push(group.classes); - } - group_el = document.createElement("li"); - group_el.className = classes.join(" "); - group_el.innerHTML = group.highlighted_html || this.escape_html(group.label); - if (group.title) { - group_el.title = group.title; - } - return this.outerHTML(group_el); - }; - - AbstractChosen.prototype.results_update_field = function() { - this.set_default_text(); - if (!this.is_multiple) { - this.results_reset_cleanup(); - } - this.result_clear_highlight(); - this.results_build(); - if (this.results_showing) { - return this.winnow_results(); - } - }; - - AbstractChosen.prototype.reset_single_select_options = function() { - var i, len, ref, result, results1; - ref = this.results_data; - results1 = []; - for (i = 0, len = ref.length; i < len; i++) { - result = ref[i]; - if (result.selected) { - results1.push(result.selected = false); - } else { - results1.push(void 0); - } - } - return results1; - }; - - AbstractChosen.prototype.results_toggle = function() { - if (this.results_showing) { - return this.results_hide(); - } else { - return this.results_show(); - } - }; - - AbstractChosen.prototype.results_search = function(evt) { - if (this.results_showing) { - return this.winnow_results(); - } else { - return this.results_show(); - } - }; - - AbstractChosen.prototype.winnow_results = function(options) { - var escapedQuery, fix, i, len, option, prefix, query, ref, regex, results, results_group, search_match, startpos, suffix, text; - this.no_results_clear(); - results = 0; - query = this.get_search_text(); - escapedQuery = query.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); - regex = this.get_search_regex(escapedQuery); - ref = this.results_data; - for (i = 0, len = ref.length; i < len; i++) { - option = ref[i]; - option.search_match = false; - results_group = null; - search_match = null; - option.highlighted_html = ''; - if (this.include_option_in_results(option)) { - if (option.group) { - option.group_match = false; - option.active_options = 0; - } - if ((option.group_array_index != null) && this.results_data[option.group_array_index]) { - results_group = this.results_data[option.group_array_index]; - if (results_group.active_options === 0 && results_group.search_match) { - results += 1; - } - results_group.active_options += 1; - } - text = option.group ? option.label : option.text; - if (!(option.group && !this.group_search)) { - search_match = this.search_string_match(text, regex); - option.search_match = search_match != null; - if (option.search_match && !option.group) { - results += 1; - } - if (option.search_match) { - if (query.length) { - startpos = search_match.index; - prefix = text.slice(0, startpos); - fix = text.slice(startpos, startpos + query.length); - suffix = text.slice(startpos + query.length); - option.highlighted_html = (this.escape_html(prefix)) + "" + (this.escape_html(fix)) + "" + (this.escape_html(suffix)); - } - if (results_group != null) { - results_group.group_match = true; - } - } else if ((option.group_array_index != null) && this.results_data[option.group_array_index].search_match) { - option.search_match = true; - } - } - } - } - this.result_clear_highlight(); - if (results < 1 && query.length) { - this.update_results_content(""); - return this.no_results(query); - } else { - this.update_results_content(this.results_option_build()); - if (!(options != null ? options.skip_highlight : void 0)) { - return this.winnow_results_set_highlight(); - } - } - }; - - AbstractChosen.prototype.get_search_regex = function(escaped_search_string) { - var regex_flag, regex_string; - regex_string = this.search_contains ? escaped_search_string : "(^|\\s|\\b)" + escaped_search_string + "[^\\s]*"; - if (!(this.enable_split_word_search || this.search_contains)) { - regex_string = "^" + regex_string; - } - regex_flag = this.case_sensitive_search ? "" : "i"; - return new RegExp(regex_string, regex_flag); - }; - - AbstractChosen.prototype.search_string_match = function(search_string, regex) { - var match; - match = regex.exec(search_string); - if (!this.search_contains && (match != null ? match[1] : void 0)) { - match.index += 1; - } - return match; - }; - - AbstractChosen.prototype.choices_count = function() { - var i, len, option, ref; - if (this.selected_option_count != null) { - return this.selected_option_count; - } - this.selected_option_count = 0; - ref = this.form_field.options; - for (i = 0, len = ref.length; i < len; i++) { - option = ref[i]; - if (option.selected) { - this.selected_option_count += 1; - } - } - return this.selected_option_count; - }; - - AbstractChosen.prototype.choices_click = function(evt) { - evt.preventDefault(); - this.activate_field(); - if (!(this.results_showing || this.is_disabled)) { - return this.results_show(); - } - }; - - AbstractChosen.prototype.keydown_checker = function(evt) { - var ref, stroke; - stroke = (ref = evt.which) != null ? ref : evt.keyCode; - this.search_field_scale(); - if (stroke !== 8 && this.pending_backstroke) { - this.clear_backstroke(); - } - switch (stroke) { - case 8: - this.backstroke_length = this.get_search_field_value().length; - break; - case 9: - if (this.results_showing && !this.is_multiple) { - this.result_select(evt); - } - this.mouse_on_container = false; - break; - case 13: - if (this.results_showing) { - evt.preventDefault(); - } - break; - case 27: - if (this.results_showing) { - evt.preventDefault(); - } - break; - case 32: - if (this.disable_search) { - evt.preventDefault(); - } - break; - case 38: - evt.preventDefault(); - this.keyup_arrow(); - break; - case 40: - evt.preventDefault(); - this.keydown_arrow(); - break; - } - }; - - AbstractChosen.prototype.keyup_checker = function(evt) { - var ref, stroke; - stroke = (ref = evt.which) != null ? ref : evt.keyCode; - this.search_field_scale(); - switch (stroke) { - case 8: - if (this.is_multiple && this.backstroke_length < 1 && this.choices_count() > 0) { - this.keydown_backstroke(); - } else if (!this.pending_backstroke) { - this.result_clear_highlight(); - this.results_search(); - } - break; - case 13: - evt.preventDefault(); - if (this.results_showing) { - this.result_select(evt); - } - break; - case 27: - if (this.results_showing) { - this.results_hide(); - } - break; - case 9: - case 16: - case 17: - case 18: - case 38: - case 40: - case 91: - break; - default: - this.results_search(); - break; - } - }; - - AbstractChosen.prototype.clipboard_event_checker = function(evt) { - if (this.is_disabled) { - return; - } - return setTimeout(((function(_this) { - return function() { - return _this.results_search(); - }; - })(this)), 50); - }; - - AbstractChosen.prototype.container_width = function() { - if (this.options.width != null) { - return this.options.width; - } else { - return this.form_field.offsetWidth + "px"; - } - }; - - AbstractChosen.prototype.include_option_in_results = function(option) { - if (this.is_multiple && (!this.display_selected_options && option.selected)) { - return false; - } - if (!this.display_disabled_options && option.disabled) { - return false; - } - if (option.empty) { - return false; - } - return true; - }; - - AbstractChosen.prototype.search_results_touchstart = function(evt) { - this.touch_started = true; - return this.search_results_mouseover(evt); - }; - - AbstractChosen.prototype.search_results_touchmove = function(evt) { - this.touch_started = false; - return this.search_results_mouseout(evt); - }; - - AbstractChosen.prototype.search_results_touchend = function(evt) { - if (this.touch_started) { - return this.search_results_mouseup(evt); - } - }; - - AbstractChosen.prototype.outerHTML = function(element) { - var tmp; - if (element.outerHTML) { - return element.outerHTML; - } - tmp = document.createElement("div"); - tmp.appendChild(element); - return tmp.innerHTML; - }; - - AbstractChosen.prototype.get_single_html = function() { - return "\n " + this.default_text + "\n
\n
\n
\n
\n \n
\n
    \n
    "; - }; - - AbstractChosen.prototype.get_multi_html = function() { - return "\n
    \n
      \n
      "; - }; - - AbstractChosen.prototype.get_no_results_html = function(terms) { - return "
    • \n " + this.results_none_found + " " + (this.escape_html(terms)) + "\n
    • "; - }; - - AbstractChosen.browser_is_supported = function() { - if ("Microsoft Internet Explorer" === window.navigator.appName) { - return document.documentMode >= 8; - } - if (/iP(od|hone)/i.test(window.navigator.userAgent) || /IEMobile/i.test(window.navigator.userAgent) || /Windows Phone/i.test(window.navigator.userAgent) || /BlackBerry/i.test(window.navigator.userAgent) || /BB10/i.test(window.navigator.userAgent) || /Android.*Mobile/i.test(window.navigator.userAgent)) { - return false; - } - return true; - }; - - AbstractChosen.default_multiple_text = "Select Some Options"; - - AbstractChosen.default_single_text = "Select an Option"; - - AbstractChosen.default_no_result_text = "No results match"; - - return AbstractChosen; - - })(); - - $ = jQuery; - - $.fn.extend({ - chosen: function(options) { - if (!AbstractChosen.browser_is_supported()) { - return this; - } - return this.each(function(input_field) { - var $this, chosen; - $this = $(this); - chosen = $this.data('chosen'); - if (options === 'destroy') { - if (chosen instanceof Chosen) { - chosen.destroy(); - } - return; - } - if (!(chosen instanceof Chosen)) { - $this.data('chosen', new Chosen(this, options)); - } - }); - } - }); - - Chosen = (function(superClass) { - extend(Chosen, superClass); - - function Chosen() { - return Chosen.__super__.constructor.apply(this, arguments); - } - - Chosen.prototype.setup = function() { - this.form_field_jq = $(this.form_field); - return this.current_selectedIndex = this.form_field.selectedIndex; - }; - - Chosen.prototype.set_up_html = function() { - var container_classes, container_props; - container_classes = ["chosen-container"]; - container_classes.push("chosen-container-" + (this.is_multiple ? "multi" : "single")); - if (this.inherit_select_classes && this.form_field.className) { - container_classes.push(this.form_field.className); - } - if (this.is_rtl) { - container_classes.push("chosen-rtl"); - } - container_props = { - 'class': container_classes.join(' '), - 'title': this.form_field.title - }; - if (this.form_field.id.length) { - container_props.id = this.form_field.id.replace(/[^\w]/g, '_') + "_chosen"; - } - this.container = $("
      ", container_props); - this.container.width(this.container_width()); - if (this.is_multiple) { - this.container.html(this.get_multi_html()); - } else { - this.container.html(this.get_single_html()); - } - this.form_field_jq.hide().after(this.container); - this.dropdown = this.container.find('div.chosen-drop').first(); - this.search_field = this.container.find('input').first(); - this.search_results = this.container.find('ul.chosen-results').first(); - this.search_field_scale(); - this.search_no_results = this.container.find('li.no-results').first(); - if (this.is_multiple) { - this.search_choices = this.container.find('ul.chosen-choices').first(); - this.search_container = this.container.find('li.search-field').first(); - } else { - this.search_container = this.container.find('div.chosen-search').first(); - this.selected_item = this.container.find('.chosen-single').first(); - } - this.results_build(); - this.set_tab_index(); - return this.set_label_behavior(); - }; - - Chosen.prototype.on_ready = function() { - return this.form_field_jq.trigger("chosen:ready", { - chosen: this - }); - }; - - Chosen.prototype.register_observers = function() { - this.container.on('touchstart.chosen', (function(_this) { - return function(evt) { - _this.container_mousedown(evt); - }; - })(this)); - this.container.on('touchend.chosen', (function(_this) { - return function(evt) { - _this.container_mouseup(evt); - }; - })(this)); - this.container.on('mousedown.chosen', (function(_this) { - return function(evt) { - _this.container_mousedown(evt); - }; - })(this)); - this.container.on('mouseup.chosen', (function(_this) { - return function(evt) { - _this.container_mouseup(evt); - }; - })(this)); - this.container.on('mouseenter.chosen', (function(_this) { - return function(evt) { - _this.mouse_enter(evt); - }; - })(this)); - this.container.on('mouseleave.chosen', (function(_this) { - return function(evt) { - _this.mouse_leave(evt); - }; - })(this)); - this.search_results.on('mouseup.chosen', (function(_this) { - return function(evt) { - _this.search_results_mouseup(evt); - }; - })(this)); - this.search_results.on('mouseover.chosen', (function(_this) { - return function(evt) { - _this.search_results_mouseover(evt); - }; - })(this)); - this.search_results.on('mouseout.chosen', (function(_this) { - return function(evt) { - _this.search_results_mouseout(evt); - }; - })(this)); - this.search_results.on('mousewheel.chosen DOMMouseScroll.chosen', (function(_this) { - return function(evt) { - _this.search_results_mousewheel(evt); - }; - })(this)); - this.search_results.on('touchstart.chosen', (function(_this) { - return function(evt) { - _this.search_results_touchstart(evt); - }; - })(this)); - this.search_results.on('touchmove.chosen', (function(_this) { - return function(evt) { - _this.search_results_touchmove(evt); - }; - })(this)); - this.search_results.on('touchend.chosen', (function(_this) { - return function(evt) { - _this.search_results_touchend(evt); - }; - })(this)); - this.form_field_jq.on("chosen:updated.chosen", (function(_this) { - return function(evt) { - _this.results_update_field(evt); - }; - })(this)); - this.form_field_jq.on("chosen:activate.chosen", (function(_this) { - return function(evt) { - _this.activate_field(evt); - }; - })(this)); - this.form_field_jq.on("chosen:open.chosen", (function(_this) { - return function(evt) { - _this.container_mousedown(evt); - }; - })(this)); - this.form_field_jq.on("chosen:close.chosen", (function(_this) { - return function(evt) { - _this.close_field(evt); - }; - })(this)); - this.search_field.on('blur.chosen', (function(_this) { - return function(evt) { - _this.input_blur(evt); - }; - })(this)); - this.search_field.on('keyup.chosen', (function(_this) { - return function(evt) { - _this.keyup_checker(evt); - }; - })(this)); - this.search_field.on('keydown.chosen', (function(_this) { - return function(evt) { - _this.keydown_checker(evt); - }; - })(this)); - this.search_field.on('focus.chosen', (function(_this) { - return function(evt) { - _this.input_focus(evt); - }; - })(this)); - this.search_field.on('cut.chosen', (function(_this) { - return function(evt) { - _this.clipboard_event_checker(evt); - }; - })(this)); - this.search_field.on('paste.chosen', (function(_this) { - return function(evt) { - _this.clipboard_event_checker(evt); - }; - })(this)); - if (this.is_multiple) { - return this.search_choices.on('click.chosen', (function(_this) { - return function(evt) { - _this.choices_click(evt); - }; - })(this)); - } else { - return this.container.on('click.chosen', function(evt) { - evt.preventDefault(); - }); - } - }; - - Chosen.prototype.destroy = function() { - $(this.container[0].ownerDocument).off('click.chosen', this.click_test_action); - if (this.form_field_label.length > 0) { - this.form_field_label.off('click.chosen'); - } - if (this.search_field[0].tabIndex) { - this.form_field_jq[0].tabIndex = this.search_field[0].tabIndex; - } - this.container.remove(); - this.form_field_jq.removeData('chosen'); - return this.form_field_jq.show(); - }; - - Chosen.prototype.search_field_disabled = function() { - this.is_disabled = this.form_field.disabled || this.form_field_jq.parents('fieldset').is(':disabled'); - this.container.toggleClass('chosen-disabled', this.is_disabled); - this.search_field[0].disabled = this.is_disabled; - if (!this.is_multiple) { - this.selected_item.off('focus.chosen', this.activate_field); - } - if (this.is_disabled) { - return this.close_field(); - } else if (!this.is_multiple) { - return this.selected_item.on('focus.chosen', this.activate_field); - } - }; - - Chosen.prototype.container_mousedown = function(evt) { - var ref; - if (this.is_disabled) { - return; - } - if (evt && ((ref = evt.type) === 'mousedown' || ref === 'touchstart') && !this.results_showing) { - evt.preventDefault(); - } - if (!((evt != null) && ($(evt.target)).hasClass("search-choice-close"))) { - if (!this.active_field) { - if (this.is_multiple) { - this.search_field.val(""); - } - $(this.container[0].ownerDocument).on('click.chosen', this.click_test_action); - this.results_show(); - } else if (!this.is_multiple && evt && (($(evt.target)[0] === this.selected_item[0]) || $(evt.target).parents("a.chosen-single").length)) { - evt.preventDefault(); - this.results_toggle(); - } - return this.activate_field(); - } - }; - - Chosen.prototype.container_mouseup = function(evt) { - if (evt.target.nodeName === "ABBR" && !this.is_disabled) { - return this.results_reset(evt); - } - }; - - Chosen.prototype.search_results_mousewheel = function(evt) { - var delta; - if (evt.originalEvent) { - delta = evt.originalEvent.deltaY || -evt.originalEvent.wheelDelta || evt.originalEvent.detail; - } - if (delta != null) { - evt.preventDefault(); - if (evt.type === 'DOMMouseScroll') { - delta = delta * 40; - } - return this.search_results.scrollTop(delta + this.search_results.scrollTop()); - } - }; - - Chosen.prototype.blur_test = function(evt) { - if (!this.active_field && this.container.hasClass("chosen-container-active")) { - return this.close_field(); - } - }; - - Chosen.prototype.close_field = function() { - $(this.container[0].ownerDocument).off("click.chosen", this.click_test_action); - this.active_field = false; - this.results_hide(); - this.container.removeClass("chosen-container-active"); - this.clear_backstroke(); - this.show_search_field_default(); - this.search_field_scale(); - return this.search_field.blur(); - }; - - Chosen.prototype.activate_field = function() { - if (this.is_disabled) { - return; - } - this.container.addClass("chosen-container-active"); - this.active_field = true; - this.search_field.val(this.search_field.val()); - return this.search_field.focus(); - }; - - Chosen.prototype.test_active_click = function(evt) { - var active_container; - active_container = $(evt.target).closest('.chosen-container'); - if (active_container.length && this.container[0] === active_container[0]) { - return this.active_field = true; - } else { - return this.close_field(); - } - }; - - Chosen.prototype.results_build = function() { - this.parsing = true; - this.selected_option_count = null; - this.results_data = SelectParser.select_to_array(this.form_field); - if (this.is_multiple) { - this.search_choices.find("li.search-choice").remove(); - } else { - this.single_set_selected_text(); - if (this.disable_search || this.form_field.options.length <= this.disable_search_threshold) { - this.search_field[0].readOnly = true; - this.container.addClass("chosen-container-single-nosearch"); - } else { - this.search_field[0].readOnly = false; - this.container.removeClass("chosen-container-single-nosearch"); - } - } - this.update_results_content(this.results_option_build({ - first: true - })); - this.search_field_disabled(); - this.show_search_field_default(); - this.search_field_scale(); - return this.parsing = false; - }; - - Chosen.prototype.result_do_highlight = function(el) { - var high_bottom, high_top, maxHeight, visible_bottom, visible_top; - if (el.length) { - this.result_clear_highlight(); - this.result_highlight = el; - this.result_highlight.addClass("highlighted"); - maxHeight = parseInt(this.search_results.css("maxHeight"), 10); - visible_top = this.search_results.scrollTop(); - visible_bottom = maxHeight + visible_top; - high_top = this.result_highlight.position().top + this.search_results.scrollTop(); - high_bottom = high_top + this.result_highlight.outerHeight(); - if (high_bottom >= visible_bottom) { - return this.search_results.scrollTop((high_bottom - maxHeight) > 0 ? high_bottom - maxHeight : 0); - } else if (high_top < visible_top) { - return this.search_results.scrollTop(high_top); - } - } - }; - - Chosen.prototype.result_clear_highlight = function() { - if (this.result_highlight) { - this.result_highlight.removeClass("highlighted"); - } - return this.result_highlight = null; - }; - - Chosen.prototype.results_show = function() { - if (this.is_multiple && this.max_selected_options <= this.choices_count()) { - this.form_field_jq.trigger("chosen:maxselected", { - chosen: this - }); - return false; - } - this.container.addClass("chosen-with-drop"); - this.results_showing = true; - this.search_field.focus(); - this.search_field.val(this.get_search_field_value()); - this.winnow_results(); - return this.form_field_jq.trigger("chosen:showing_dropdown", { - chosen: this - }); - }; - - Chosen.prototype.update_results_content = function(content) { - return this.search_results.html(content); - }; - - Chosen.prototype.results_hide = function() { - if (this.results_showing) { - this.result_clear_highlight(); - this.container.removeClass("chosen-with-drop"); - this.form_field_jq.trigger("chosen:hiding_dropdown", { - chosen: this - }); - } - return this.results_showing = false; - }; - - Chosen.prototype.set_tab_index = function(el) { - var ti; - if (this.form_field.tabIndex) { - ti = this.form_field.tabIndex; - this.form_field.tabIndex = -1; - return this.search_field[0].tabIndex = ti; - } - }; - - Chosen.prototype.set_label_behavior = function() { - this.form_field_label = this.form_field_jq.parents("label"); - if (!this.form_field_label.length && this.form_field.id.length) { - this.form_field_label = $("label[for='" + this.form_field.id + "']"); - } - if (this.form_field_label.length > 0) { - return this.form_field_label.on('click.chosen', this.label_click_handler); - } - }; - - Chosen.prototype.show_search_field_default = function() { - if (this.is_multiple && this.choices_count() < 1 && !this.active_field) { - this.search_field.val(this.default_text); - return this.search_field.addClass("default"); - } else { - this.search_field.val(""); - return this.search_field.removeClass("default"); - } - }; - - Chosen.prototype.search_results_mouseup = function(evt) { - var target; - target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first(); - if (target.length) { - this.result_highlight = target; - this.result_select(evt); - return this.search_field.focus(); - } - }; - - Chosen.prototype.search_results_mouseover = function(evt) { - var target; - target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first(); - if (target) { - return this.result_do_highlight(target); - } - }; - - Chosen.prototype.search_results_mouseout = function(evt) { - if ($(evt.target).hasClass("active-result") || $(evt.target).parents('.active-result').first()) { - return this.result_clear_highlight(); - } - }; - - Chosen.prototype.choice_build = function(item) { - var choice, close_link; - choice = $('
    • ', { - "class": "search-choice" - }).html("" + (this.choice_label(item)) + ""); - if (item.disabled) { - choice.addClass('search-choice-disabled'); - } else { - close_link = $('', { - "class": 'search-choice-close', - 'data-option-array-index': item.array_index - }); - close_link.on('click.chosen', (function(_this) { - return function(evt) { - return _this.choice_destroy_link_click(evt); - }; - })(this)); - choice.append(close_link); - } - return this.search_container.before(choice); - }; - - Chosen.prototype.choice_destroy_link_click = function(evt) { - evt.preventDefault(); - evt.stopPropagation(); - if (!this.is_disabled) { - return this.choice_destroy($(evt.target)); - } - }; - - Chosen.prototype.choice_destroy = function(link) { - if (this.result_deselect(link[0].getAttribute("data-option-array-index"))) { - if (this.active_field) { - this.search_field.focus(); - } else { - this.show_search_field_default(); - } - if (this.is_multiple && this.choices_count() > 0 && this.get_search_field_value().length < 1) { - this.results_hide(); - } - link.parents('li').first().remove(); - return this.search_field_scale(); - } - }; - - Chosen.prototype.results_reset = function() { - this.reset_single_select_options(); - this.form_field.options[0].selected = true; - this.single_set_selected_text(); - this.show_search_field_default(); - this.results_reset_cleanup(); - this.trigger_form_field_change(); - if (this.active_field) { - return this.results_hide(); - } - }; - - Chosen.prototype.results_reset_cleanup = function() { - this.current_selectedIndex = this.form_field.selectedIndex; - return this.selected_item.find("abbr").remove(); - }; - - Chosen.prototype.result_select = function(evt) { - var high, item; - if (this.result_highlight) { - high = this.result_highlight; - this.result_clear_highlight(); - if (this.is_multiple && this.max_selected_options <= this.choices_count()) { - this.form_field_jq.trigger("chosen:maxselected", { - chosen: this - }); - return false; - } - if (this.is_multiple) { - high.removeClass("active-result"); - } else { - this.reset_single_select_options(); - } - high.addClass("result-selected"); - item = this.results_data[high[0].getAttribute("data-option-array-index")]; - item.selected = true; - this.form_field.options[item.options_index].selected = true; - this.selected_option_count = null; - if (this.is_multiple) { - this.choice_build(item); - } else { - this.single_set_selected_text(this.choice_label(item)); - } - if (this.is_multiple && (!this.hide_results_on_select || (evt.metaKey || evt.ctrlKey))) { - if (evt.metaKey || evt.ctrlKey) { - this.winnow_results({ - skip_highlight: true - }); - } else { - this.search_field.val(""); - this.winnow_results(); - } - } else { - this.results_hide(); - this.show_search_field_default(); - } - if (this.is_multiple || this.form_field.selectedIndex !== this.current_selectedIndex) { - this.trigger_form_field_change({ - selected: this.form_field.options[item.options_index].value - }); - } - this.current_selectedIndex = this.form_field.selectedIndex; - evt.preventDefault(); - return this.search_field_scale(); - } - }; - - Chosen.prototype.single_set_selected_text = function(text) { - if (text == null) { - text = this.default_text; - } - if (text === this.default_text) { - this.selected_item.addClass("chosen-default"); - } else { - this.single_deselect_control_build(); - this.selected_item.removeClass("chosen-default"); - } - return this.selected_item.find("span").html(text); - }; - - Chosen.prototype.result_deselect = function(pos) { - var result_data; - result_data = this.results_data[pos]; - if (!this.form_field.options[result_data.options_index].disabled) { - result_data.selected = false; - this.form_field.options[result_data.options_index].selected = false; - this.selected_option_count = null; - this.result_clear_highlight(); - if (this.results_showing) { - this.winnow_results(); - } - this.trigger_form_field_change({ - deselected: this.form_field.options[result_data.options_index].value - }); - this.search_field_scale(); - return true; - } else { - return false; - } - }; - - Chosen.prototype.single_deselect_control_build = function() { - if (!this.allow_single_deselect) { - return; - } - if (!this.selected_item.find("abbr").length) { - this.selected_item.find("span").first().after(""); - } - return this.selected_item.addClass("chosen-single-with-deselect"); - }; - - Chosen.prototype.get_search_field_value = function() { - return this.search_field.val(); - }; - - Chosen.prototype.get_search_text = function() { - return $.trim(this.get_search_field_value()); - }; - - Chosen.prototype.escape_html = function(text) { - return $('
      ').text(text).html(); - }; - - Chosen.prototype.winnow_results_set_highlight = function() { - var do_high, selected_results; - selected_results = !this.is_multiple ? this.search_results.find(".result-selected.active-result") : []; - do_high = selected_results.length ? selected_results.first() : this.search_results.find(".active-result").first(); - if (do_high != null) { - return this.result_do_highlight(do_high); - } - }; - - Chosen.prototype.no_results = function(terms) { - var no_results_html; - no_results_html = this.get_no_results_html(terms); - this.search_results.append(no_results_html); - return this.form_field_jq.trigger("chosen:no_results", { - chosen: this - }); - }; - - Chosen.prototype.no_results_clear = function() { - return this.search_results.find(".no-results").remove(); - }; - - Chosen.prototype.keydown_arrow = function() { - var next_sib; - if (this.results_showing && this.result_highlight) { - next_sib = this.result_highlight.nextAll("li.active-result").first(); - if (next_sib) { - return this.result_do_highlight(next_sib); - } - } else { - return this.results_show(); - } - }; - - Chosen.prototype.keyup_arrow = function() { - var prev_sibs; - if (!this.results_showing && !this.is_multiple) { - return this.results_show(); - } else if (this.result_highlight) { - prev_sibs = this.result_highlight.prevAll("li.active-result"); - if (prev_sibs.length) { - return this.result_do_highlight(prev_sibs.first()); - } else { - if (this.choices_count() > 0) { - this.results_hide(); - } - return this.result_clear_highlight(); - } - } - }; - - Chosen.prototype.keydown_backstroke = function() { - var next_available_destroy; - if (this.pending_backstroke) { - this.choice_destroy(this.pending_backstroke.find("a").first()); - return this.clear_backstroke(); - } else { - next_available_destroy = this.search_container.siblings("li.search-choice").last(); - if (next_available_destroy.length && !next_available_destroy.hasClass("search-choice-disabled")) { - this.pending_backstroke = next_available_destroy; - if (this.single_backstroke_delete) { - return this.keydown_backstroke(); - } else { - return this.pending_backstroke.addClass("search-choice-focus"); - } - } - } - }; - - Chosen.prototype.clear_backstroke = function() { - if (this.pending_backstroke) { - this.pending_backstroke.removeClass("search-choice-focus"); - } - return this.pending_backstroke = null; - }; - - Chosen.prototype.search_field_scale = function() { - var div, i, len, style, style_block, styles, width; - if (!this.is_multiple) { - return; - } - style_block = { - position: 'absolute', - left: '-1000px', - top: '-1000px', - display: 'none', - whiteSpace: 'pre' - }; - styles = ['fontSize', 'fontStyle', 'fontWeight', 'fontFamily', 'lineHeight', 'textTransform', 'letterSpacing']; - for (i = 0, len = styles.length; i < len; i++) { - style = styles[i]; - style_block[style] = this.search_field.css(style); - } - div = $('
      ').css(style_block); - div.text(this.get_search_field_value()); - $('body').append(div); - width = div.width() + 25; - div.remove(); - if (this.container.is(':visible')) { - width = Math.min(this.container.outerWidth() - 10, width); - } - return this.search_field.width(width); - }; - - Chosen.prototype.trigger_form_field_change = function(extra) { - this.form_field_jq.trigger("input", extra); - return this.form_field_jq.trigger("change", extra); - }; - - return Chosen; - - })(AbstractChosen); - -}).call(this); diff --git a/vendor/assets/javascripts/stimulus/stimulus.umd.js b/vendor/assets/javascripts/stimulus/stimulus.umd.js new file mode 100644 index 00000000..b066e587 --- /dev/null +++ b/vendor/assets/javascripts/stimulus/stimulus.umd.js @@ -0,0 +1,2588 @@ +/* +Stimulus 3.2.1 +Copyright © 2023 Basecamp, LLC + */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Stimulus = {})); +}(this, (function (exports) { 'use strict'; + + class EventListener { + constructor(eventTarget, eventName, eventOptions) { + this.eventTarget = eventTarget; + this.eventName = eventName; + this.eventOptions = eventOptions; + this.unorderedBindings = new Set(); + } + connect() { + this.eventTarget.addEventListener(this.eventName, this, this.eventOptions); + } + disconnect() { + this.eventTarget.removeEventListener(this.eventName, this, this.eventOptions); + } + bindingConnected(binding) { + this.unorderedBindings.add(binding); + } + bindingDisconnected(binding) { + this.unorderedBindings.delete(binding); + } + handleEvent(event) { + const extendedEvent = extendEvent(event); + for (const binding of this.bindings) { + if (extendedEvent.immediatePropagationStopped) { + break; + } + else { + binding.handleEvent(extendedEvent); + } + } + } + hasBindings() { + return this.unorderedBindings.size > 0; + } + get bindings() { + return Array.from(this.unorderedBindings).sort((left, right) => { + const leftIndex = left.index, rightIndex = right.index; + return leftIndex < rightIndex ? -1 : leftIndex > rightIndex ? 1 : 0; + }); + } + } + function extendEvent(event) { + if ("immediatePropagationStopped" in event) { + return event; + } + else { + const { stopImmediatePropagation } = event; + return Object.assign(event, { + immediatePropagationStopped: false, + stopImmediatePropagation() { + this.immediatePropagationStopped = true; + stopImmediatePropagation.call(this); + }, + }); + } + } + + class Dispatcher { + constructor(application) { + this.application = application; + this.eventListenerMaps = new Map(); + this.started = false; + } + start() { + if (!this.started) { + this.started = true; + this.eventListeners.forEach((eventListener) => eventListener.connect()); + } + } + stop() { + if (this.started) { + this.started = false; + this.eventListeners.forEach((eventListener) => eventListener.disconnect()); + } + } + get eventListeners() { + return Array.from(this.eventListenerMaps.values()).reduce((listeners, map) => listeners.concat(Array.from(map.values())), []); + } + bindingConnected(binding) { + this.fetchEventListenerForBinding(binding).bindingConnected(binding); + } + bindingDisconnected(binding, clearEventListeners = false) { + this.fetchEventListenerForBinding(binding).bindingDisconnected(binding); + if (clearEventListeners) + this.clearEventListenersForBinding(binding); + } + handleError(error, message, detail = {}) { + this.application.handleError(error, `Error ${message}`, detail); + } + clearEventListenersForBinding(binding) { + const eventListener = this.fetchEventListenerForBinding(binding); + if (!eventListener.hasBindings()) { + eventListener.disconnect(); + this.removeMappedEventListenerFor(binding); + } + } + removeMappedEventListenerFor(binding) { + const { eventTarget, eventName, eventOptions } = binding; + const eventListenerMap = this.fetchEventListenerMapForEventTarget(eventTarget); + const cacheKey = this.cacheKey(eventName, eventOptions); + eventListenerMap.delete(cacheKey); + if (eventListenerMap.size == 0) + this.eventListenerMaps.delete(eventTarget); + } + fetchEventListenerForBinding(binding) { + const { eventTarget, eventName, eventOptions } = binding; + return this.fetchEventListener(eventTarget, eventName, eventOptions); + } + fetchEventListener(eventTarget, eventName, eventOptions) { + const eventListenerMap = this.fetchEventListenerMapForEventTarget(eventTarget); + const cacheKey = this.cacheKey(eventName, eventOptions); + let eventListener = eventListenerMap.get(cacheKey); + if (!eventListener) { + eventListener = this.createEventListener(eventTarget, eventName, eventOptions); + eventListenerMap.set(cacheKey, eventListener); + } + return eventListener; + } + createEventListener(eventTarget, eventName, eventOptions) { + const eventListener = new EventListener(eventTarget, eventName, eventOptions); + if (this.started) { + eventListener.connect(); + } + return eventListener; + } + fetchEventListenerMapForEventTarget(eventTarget) { + let eventListenerMap = this.eventListenerMaps.get(eventTarget); + if (!eventListenerMap) { + eventListenerMap = new Map(); + this.eventListenerMaps.set(eventTarget, eventListenerMap); + } + return eventListenerMap; + } + cacheKey(eventName, eventOptions) { + const parts = [eventName]; + Object.keys(eventOptions) + .sort() + .forEach((key) => { + parts.push(`${eventOptions[key] ? "" : "!"}${key}`); + }); + return parts.join(":"); + } + } + + const defaultActionDescriptorFilters = { + stop({ event, value }) { + if (value) + event.stopPropagation(); + return true; + }, + prevent({ event, value }) { + if (value) + event.preventDefault(); + return true; + }, + self({ event, value, element }) { + if (value) { + return element === event.target; + } + else { + return true; + } + }, + }; + const descriptorPattern = /^(?:(?:([^.]+?)\+)?(.+?)(?:\.(.+?))?(?:@(window|document))?->)?(.+?)(?:#([^:]+?))(?::(.+))?$/; + function parseActionDescriptorString(descriptorString) { + const source = descriptorString.trim(); + const matches = source.match(descriptorPattern) || []; + let eventName = matches[2]; + let keyFilter = matches[3]; + if (keyFilter && !["keydown", "keyup", "keypress"].includes(eventName)) { + eventName += `.${keyFilter}`; + keyFilter = ""; + } + return { + eventTarget: parseEventTarget(matches[4]), + eventName, + eventOptions: matches[7] ? parseEventOptions(matches[7]) : {}, + identifier: matches[5], + methodName: matches[6], + keyFilter: matches[1] || keyFilter, + }; + } + function parseEventTarget(eventTargetName) { + if (eventTargetName == "window") { + return window; + } + else if (eventTargetName == "document") { + return document; + } + } + function parseEventOptions(eventOptions) { + return eventOptions + .split(":") + .reduce((options, token) => Object.assign(options, { [token.replace(/^!/, "")]: !/^!/.test(token) }), {}); + } + function stringifyEventTarget(eventTarget) { + if (eventTarget == window) { + return "window"; + } + else if (eventTarget == document) { + return "document"; + } + } + + function camelize(value) { + return value.replace(/(?:[_-])([a-z0-9])/g, (_, char) => char.toUpperCase()); + } + function namespaceCamelize(value) { + return camelize(value.replace(/--/g, "-").replace(/__/g, "_")); + } + function capitalize(value) { + return value.charAt(0).toUpperCase() + value.slice(1); + } + function dasherize(value) { + return value.replace(/([A-Z])/g, (_, char) => `-${char.toLowerCase()}`); + } + function tokenize(value) { + return value.match(/[^\s]+/g) || []; + } + + function isSomething(object) { + return object !== null && object !== undefined; + } + function hasProperty(object, property) { + return Object.prototype.hasOwnProperty.call(object, property); + } + + const allModifiers = ["meta", "ctrl", "alt", "shift"]; + class Action { + constructor(element, index, descriptor, schema) { + this.element = element; + this.index = index; + this.eventTarget = descriptor.eventTarget || element; + this.eventName = descriptor.eventName || getDefaultEventNameForElement(element) || error("missing event name"); + this.eventOptions = descriptor.eventOptions || {}; + this.identifier = descriptor.identifier || error("missing identifier"); + this.methodName = descriptor.methodName || error("missing method name"); + this.keyFilter = descriptor.keyFilter || ""; + this.schema = schema; + } + static forToken(token, schema) { + return new this(token.element, token.index, parseActionDescriptorString(token.content), schema); + } + toString() { + const eventFilter = this.keyFilter ? `.${this.keyFilter}` : ""; + const eventTarget = this.eventTargetName ? `@${this.eventTargetName}` : ""; + return `${this.eventName}${eventFilter}${eventTarget}->${this.identifier}#${this.methodName}`; + } + shouldIgnoreKeyboardEvent(event) { + if (!this.keyFilter) { + return false; + } + const filters = this.keyFilter.split("+"); + if (this.keyFilterDissatisfied(event, filters)) { + return true; + } + const standardFilter = filters.filter((key) => !allModifiers.includes(key))[0]; + if (!standardFilter) { + return false; + } + if (!hasProperty(this.keyMappings, standardFilter)) { + error(`contains unknown key filter: ${this.keyFilter}`); + } + return this.keyMappings[standardFilter].toLowerCase() !== event.key.toLowerCase(); + } + shouldIgnoreMouseEvent(event) { + if (!this.keyFilter) { + return false; + } + const filters = [this.keyFilter]; + if (this.keyFilterDissatisfied(event, filters)) { + return true; + } + return false; + } + get params() { + const params = {}; + const pattern = new RegExp(`^data-${this.identifier}-(.+)-param$`, "i"); + for (const { name, value } of Array.from(this.element.attributes)) { + const match = name.match(pattern); + const key = match && match[1]; + if (key) { + params[camelize(key)] = typecast(value); + } + } + return params; + } + get eventTargetName() { + return stringifyEventTarget(this.eventTarget); + } + get keyMappings() { + return this.schema.keyMappings; + } + keyFilterDissatisfied(event, filters) { + const [meta, ctrl, alt, shift] = allModifiers.map((modifier) => filters.includes(modifier)); + return event.metaKey !== meta || event.ctrlKey !== ctrl || event.altKey !== alt || event.shiftKey !== shift; + } + } + const defaultEventNames = { + a: () => "click", + button: () => "click", + form: () => "submit", + details: () => "toggle", + input: (e) => (e.getAttribute("type") == "submit" ? "click" : "input"), + select: () => "change", + textarea: () => "input", + }; + function getDefaultEventNameForElement(element) { + const tagName = element.tagName.toLowerCase(); + if (tagName in defaultEventNames) { + return defaultEventNames[tagName](element); + } + } + function error(message) { + throw new Error(message); + } + function typecast(value) { + try { + return JSON.parse(value); + } + catch (o_O) { + return value; + } + } + + class Binding { + constructor(context, action) { + this.context = context; + this.action = action; + } + get index() { + return this.action.index; + } + get eventTarget() { + return this.action.eventTarget; + } + get eventOptions() { + return this.action.eventOptions; + } + get identifier() { + return this.context.identifier; + } + handleEvent(event) { + const actionEvent = this.prepareActionEvent(event); + if (this.willBeInvokedByEvent(event) && this.applyEventModifiers(actionEvent)) { + this.invokeWithEvent(actionEvent); + } + } + get eventName() { + return this.action.eventName; + } + get method() { + const method = this.controller[this.methodName]; + if (typeof method == "function") { + return method; + } + throw new Error(`Action "${this.action}" references undefined method "${this.methodName}"`); + } + applyEventModifiers(event) { + const { element } = this.action; + const { actionDescriptorFilters } = this.context.application; + const { controller } = this.context; + let passes = true; + for (const [name, value] of Object.entries(this.eventOptions)) { + if (name in actionDescriptorFilters) { + const filter = actionDescriptorFilters[name]; + passes = passes && filter({ name, value, event, element, controller }); + } + else { + continue; + } + } + return passes; + } + prepareActionEvent(event) { + return Object.assign(event, { params: this.action.params }); + } + invokeWithEvent(event) { + const { target, currentTarget } = event; + try { + this.method.call(this.controller, event); + this.context.logDebugActivity(this.methodName, { event, target, currentTarget, action: this.methodName }); + } + catch (error) { + const { identifier, controller, element, index } = this; + const detail = { identifier, controller, element, index, event }; + this.context.handleError(error, `invoking action "${this.action}"`, detail); + } + } + willBeInvokedByEvent(event) { + const eventTarget = event.target; + if (event instanceof KeyboardEvent && this.action.shouldIgnoreKeyboardEvent(event)) { + return false; + } + if (event instanceof MouseEvent && this.action.shouldIgnoreMouseEvent(event)) { + return false; + } + if (this.element === eventTarget) { + return true; + } + else if (eventTarget instanceof Element && this.element.contains(eventTarget)) { + return this.scope.containsElement(eventTarget); + } + else { + return this.scope.containsElement(this.action.element); + } + } + get controller() { + return this.context.controller; + } + get methodName() { + return this.action.methodName; + } + get element() { + return this.scope.element; + } + get scope() { + return this.context.scope; + } + } + + class ElementObserver { + constructor(element, delegate) { + this.mutationObserverInit = { attributes: true, childList: true, subtree: true }; + this.element = element; + this.started = false; + this.delegate = delegate; + this.elements = new Set(); + this.mutationObserver = new MutationObserver((mutations) => this.processMutations(mutations)); + } + start() { + if (!this.started) { + this.started = true; + this.mutationObserver.observe(this.element, this.mutationObserverInit); + this.refresh(); + } + } + pause(callback) { + if (this.started) { + this.mutationObserver.disconnect(); + this.started = false; + } + callback(); + if (!this.started) { + this.mutationObserver.observe(this.element, this.mutationObserverInit); + this.started = true; + } + } + stop() { + if (this.started) { + this.mutationObserver.takeRecords(); + this.mutationObserver.disconnect(); + this.started = false; + } + } + refresh() { + if (this.started) { + const matches = new Set(this.matchElementsInTree()); + for (const element of Array.from(this.elements)) { + if (!matches.has(element)) { + this.removeElement(element); + } + } + for (const element of Array.from(matches)) { + this.addElement(element); + } + } + } + processMutations(mutations) { + if (this.started) { + for (const mutation of mutations) { + this.processMutation(mutation); + } + } + } + processMutation(mutation) { + if (mutation.type == "attributes") { + this.processAttributeChange(mutation.target, mutation.attributeName); + } + else if (mutation.type == "childList") { + this.processRemovedNodes(mutation.removedNodes); + this.processAddedNodes(mutation.addedNodes); + } + } + processAttributeChange(element, attributeName) { + if (this.elements.has(element)) { + if (this.delegate.elementAttributeChanged && this.matchElement(element)) { + this.delegate.elementAttributeChanged(element, attributeName); + } + else { + this.removeElement(element); + } + } + else if (this.matchElement(element)) { + this.addElement(element); + } + } + processRemovedNodes(nodes) { + for (const node of Array.from(nodes)) { + const element = this.elementFromNode(node); + if (element) { + this.processTree(element, this.removeElement); + } + } + } + processAddedNodes(nodes) { + for (const node of Array.from(nodes)) { + const element = this.elementFromNode(node); + if (element && this.elementIsActive(element)) { + this.processTree(element, this.addElement); + } + } + } + matchElement(element) { + return this.delegate.matchElement(element); + } + matchElementsInTree(tree = this.element) { + return this.delegate.matchElementsInTree(tree); + } + processTree(tree, processor) { + for (const element of this.matchElementsInTree(tree)) { + processor.call(this, element); + } + } + elementFromNode(node) { + if (node.nodeType == Node.ELEMENT_NODE) { + return node; + } + } + elementIsActive(element) { + if (element.isConnected != this.element.isConnected) { + return false; + } + else { + return this.element.contains(element); + } + } + addElement(element) { + if (!this.elements.has(element)) { + if (this.elementIsActive(element)) { + this.elements.add(element); + if (this.delegate.elementMatched) { + this.delegate.elementMatched(element); + } + } + } + } + removeElement(element) { + if (this.elements.has(element)) { + this.elements.delete(element); + if (this.delegate.elementUnmatched) { + this.delegate.elementUnmatched(element); + } + } + } + } + + class AttributeObserver { + constructor(element, attributeName, delegate) { + this.attributeName = attributeName; + this.delegate = delegate; + this.elementObserver = new ElementObserver(element, this); + } + get element() { + return this.elementObserver.element; + } + get selector() { + return `[${this.attributeName}]`; + } + start() { + this.elementObserver.start(); + } + pause(callback) { + this.elementObserver.pause(callback); + } + stop() { + this.elementObserver.stop(); + } + refresh() { + this.elementObserver.refresh(); + } + get started() { + return this.elementObserver.started; + } + matchElement(element) { + return element.hasAttribute(this.attributeName); + } + matchElementsInTree(tree) { + const match = this.matchElement(tree) ? [tree] : []; + const matches = Array.from(tree.querySelectorAll(this.selector)); + return match.concat(matches); + } + elementMatched(element) { + if (this.delegate.elementMatchedAttribute) { + this.delegate.elementMatchedAttribute(element, this.attributeName); + } + } + elementUnmatched(element) { + if (this.delegate.elementUnmatchedAttribute) { + this.delegate.elementUnmatchedAttribute(element, this.attributeName); + } + } + elementAttributeChanged(element, attributeName) { + if (this.delegate.elementAttributeValueChanged && this.attributeName == attributeName) { + this.delegate.elementAttributeValueChanged(element, attributeName); + } + } + } + + function add(map, key, value) { + fetch(map, key).add(value); + } + function del(map, key, value) { + fetch(map, key).delete(value); + prune(map, key); + } + function fetch(map, key) { + let values = map.get(key); + if (!values) { + values = new Set(); + map.set(key, values); + } + return values; + } + function prune(map, key) { + const values = map.get(key); + if (values != null && values.size == 0) { + map.delete(key); + } + } + + class Multimap { + constructor() { + this.valuesByKey = new Map(); + } + get keys() { + return Array.from(this.valuesByKey.keys()); + } + get values() { + const sets = Array.from(this.valuesByKey.values()); + return sets.reduce((values, set) => values.concat(Array.from(set)), []); + } + get size() { + const sets = Array.from(this.valuesByKey.values()); + return sets.reduce((size, set) => size + set.size, 0); + } + add(key, value) { + add(this.valuesByKey, key, value); + } + delete(key, value) { + del(this.valuesByKey, key, value); + } + has(key, value) { + const values = this.valuesByKey.get(key); + return values != null && values.has(value); + } + hasKey(key) { + return this.valuesByKey.has(key); + } + hasValue(value) { + const sets = Array.from(this.valuesByKey.values()); + return sets.some((set) => set.has(value)); + } + getValuesForKey(key) { + const values = this.valuesByKey.get(key); + return values ? Array.from(values) : []; + } + getKeysForValue(value) { + return Array.from(this.valuesByKey) + .filter(([_key, values]) => values.has(value)) + .map(([key, _values]) => key); + } + } + + class IndexedMultimap extends Multimap { + constructor() { + super(); + this.keysByValue = new Map(); + } + get values() { + return Array.from(this.keysByValue.keys()); + } + add(key, value) { + super.add(key, value); + add(this.keysByValue, value, key); + } + delete(key, value) { + super.delete(key, value); + del(this.keysByValue, value, key); + } + hasValue(value) { + return this.keysByValue.has(value); + } + getKeysForValue(value) { + const set = this.keysByValue.get(value); + return set ? Array.from(set) : []; + } + } + + class SelectorObserver { + constructor(element, selector, delegate, details) { + this._selector = selector; + this.details = details; + this.elementObserver = new ElementObserver(element, this); + this.delegate = delegate; + this.matchesByElement = new Multimap(); + } + get started() { + return this.elementObserver.started; + } + get selector() { + return this._selector; + } + set selector(selector) { + this._selector = selector; + this.refresh(); + } + start() { + this.elementObserver.start(); + } + pause(callback) { + this.elementObserver.pause(callback); + } + stop() { + this.elementObserver.stop(); + } + refresh() { + this.elementObserver.refresh(); + } + get element() { + return this.elementObserver.element; + } + matchElement(element) { + const { selector } = this; + if (selector) { + const matches = element.matches(selector); + if (this.delegate.selectorMatchElement) { + return matches && this.delegate.selectorMatchElement(element, this.details); + } + return matches; + } + else { + return false; + } + } + matchElementsInTree(tree) { + const { selector } = this; + if (selector) { + const match = this.matchElement(tree) ? [tree] : []; + const matches = Array.from(tree.querySelectorAll(selector)).filter((match) => this.matchElement(match)); + return match.concat(matches); + } + else { + return []; + } + } + elementMatched(element) { + const { selector } = this; + if (selector) { + this.selectorMatched(element, selector); + } + } + elementUnmatched(element) { + const selectors = this.matchesByElement.getKeysForValue(element); + for (const selector of selectors) { + this.selectorUnmatched(element, selector); + } + } + elementAttributeChanged(element, _attributeName) { + const { selector } = this; + if (selector) { + const matches = this.matchElement(element); + const matchedBefore = this.matchesByElement.has(selector, element); + if (matches && !matchedBefore) { + this.selectorMatched(element, selector); + } + else if (!matches && matchedBefore) { + this.selectorUnmatched(element, selector); + } + } + } + selectorMatched(element, selector) { + this.delegate.selectorMatched(element, selector, this.details); + this.matchesByElement.add(selector, element); + } + selectorUnmatched(element, selector) { + this.delegate.selectorUnmatched(element, selector, this.details); + this.matchesByElement.delete(selector, element); + } + } + + class StringMapObserver { + constructor(element, delegate) { + this.element = element; + this.delegate = delegate; + this.started = false; + this.stringMap = new Map(); + this.mutationObserver = new MutationObserver((mutations) => this.processMutations(mutations)); + } + start() { + if (!this.started) { + this.started = true; + this.mutationObserver.observe(this.element, { attributes: true, attributeOldValue: true }); + this.refresh(); + } + } + stop() { + if (this.started) { + this.mutationObserver.takeRecords(); + this.mutationObserver.disconnect(); + this.started = false; + } + } + refresh() { + if (this.started) { + for (const attributeName of this.knownAttributeNames) { + this.refreshAttribute(attributeName, null); + } + } + } + processMutations(mutations) { + if (this.started) { + for (const mutation of mutations) { + this.processMutation(mutation); + } + } + } + processMutation(mutation) { + const attributeName = mutation.attributeName; + if (attributeName) { + this.refreshAttribute(attributeName, mutation.oldValue); + } + } + refreshAttribute(attributeName, oldValue) { + const key = this.delegate.getStringMapKeyForAttribute(attributeName); + if (key != null) { + if (!this.stringMap.has(attributeName)) { + this.stringMapKeyAdded(key, attributeName); + } + const value = this.element.getAttribute(attributeName); + if (this.stringMap.get(attributeName) != value) { + this.stringMapValueChanged(value, key, oldValue); + } + if (value == null) { + const oldValue = this.stringMap.get(attributeName); + this.stringMap.delete(attributeName); + if (oldValue) + this.stringMapKeyRemoved(key, attributeName, oldValue); + } + else { + this.stringMap.set(attributeName, value); + } + } + } + stringMapKeyAdded(key, attributeName) { + if (this.delegate.stringMapKeyAdded) { + this.delegate.stringMapKeyAdded(key, attributeName); + } + } + stringMapValueChanged(value, key, oldValue) { + if (this.delegate.stringMapValueChanged) { + this.delegate.stringMapValueChanged(value, key, oldValue); + } + } + stringMapKeyRemoved(key, attributeName, oldValue) { + if (this.delegate.stringMapKeyRemoved) { + this.delegate.stringMapKeyRemoved(key, attributeName, oldValue); + } + } + get knownAttributeNames() { + return Array.from(new Set(this.currentAttributeNames.concat(this.recordedAttributeNames))); + } + get currentAttributeNames() { + return Array.from(this.element.attributes).map((attribute) => attribute.name); + } + get recordedAttributeNames() { + return Array.from(this.stringMap.keys()); + } + } + + class TokenListObserver { + constructor(element, attributeName, delegate) { + this.attributeObserver = new AttributeObserver(element, attributeName, this); + this.delegate = delegate; + this.tokensByElement = new Multimap(); + } + get started() { + return this.attributeObserver.started; + } + start() { + this.attributeObserver.start(); + } + pause(callback) { + this.attributeObserver.pause(callback); + } + stop() { + this.attributeObserver.stop(); + } + refresh() { + this.attributeObserver.refresh(); + } + get element() { + return this.attributeObserver.element; + } + get attributeName() { + return this.attributeObserver.attributeName; + } + elementMatchedAttribute(element) { + this.tokensMatched(this.readTokensForElement(element)); + } + elementAttributeValueChanged(element) { + const [unmatchedTokens, matchedTokens] = this.refreshTokensForElement(element); + this.tokensUnmatched(unmatchedTokens); + this.tokensMatched(matchedTokens); + } + elementUnmatchedAttribute(element) { + this.tokensUnmatched(this.tokensByElement.getValuesForKey(element)); + } + tokensMatched(tokens) { + tokens.forEach((token) => this.tokenMatched(token)); + } + tokensUnmatched(tokens) { + tokens.forEach((token) => this.tokenUnmatched(token)); + } + tokenMatched(token) { + this.delegate.tokenMatched(token); + this.tokensByElement.add(token.element, token); + } + tokenUnmatched(token) { + this.delegate.tokenUnmatched(token); + this.tokensByElement.delete(token.element, token); + } + refreshTokensForElement(element) { + const previousTokens = this.tokensByElement.getValuesForKey(element); + const currentTokens = this.readTokensForElement(element); + const firstDifferingIndex = zip(previousTokens, currentTokens).findIndex(([previousToken, currentToken]) => !tokensAreEqual(previousToken, currentToken)); + if (firstDifferingIndex == -1) { + return [[], []]; + } + else { + return [previousTokens.slice(firstDifferingIndex), currentTokens.slice(firstDifferingIndex)]; + } + } + readTokensForElement(element) { + const attributeName = this.attributeName; + const tokenString = element.getAttribute(attributeName) || ""; + return parseTokenString(tokenString, element, attributeName); + } + } + function parseTokenString(tokenString, element, attributeName) { + return tokenString + .trim() + .split(/\s+/) + .filter((content) => content.length) + .map((content, index) => ({ element, attributeName, content, index })); + } + function zip(left, right) { + const length = Math.max(left.length, right.length); + return Array.from({ length }, (_, index) => [left[index], right[index]]); + } + function tokensAreEqual(left, right) { + return left && right && left.index == right.index && left.content == right.content; + } + + class ValueListObserver { + constructor(element, attributeName, delegate) { + this.tokenListObserver = new TokenListObserver(element, attributeName, this); + this.delegate = delegate; + this.parseResultsByToken = new WeakMap(); + this.valuesByTokenByElement = new WeakMap(); + } + get started() { + return this.tokenListObserver.started; + } + start() { + this.tokenListObserver.start(); + } + stop() { + this.tokenListObserver.stop(); + } + refresh() { + this.tokenListObserver.refresh(); + } + get element() { + return this.tokenListObserver.element; + } + get attributeName() { + return this.tokenListObserver.attributeName; + } + tokenMatched(token) { + const { element } = token; + const { value } = this.fetchParseResultForToken(token); + if (value) { + this.fetchValuesByTokenForElement(element).set(token, value); + this.delegate.elementMatchedValue(element, value); + } + } + tokenUnmatched(token) { + const { element } = token; + const { value } = this.fetchParseResultForToken(token); + if (value) { + this.fetchValuesByTokenForElement(element).delete(token); + this.delegate.elementUnmatchedValue(element, value); + } + } + fetchParseResultForToken(token) { + let parseResult = this.parseResultsByToken.get(token); + if (!parseResult) { + parseResult = this.parseToken(token); + this.parseResultsByToken.set(token, parseResult); + } + return parseResult; + } + fetchValuesByTokenForElement(element) { + let valuesByToken = this.valuesByTokenByElement.get(element); + if (!valuesByToken) { + valuesByToken = new Map(); + this.valuesByTokenByElement.set(element, valuesByToken); + } + return valuesByToken; + } + parseToken(token) { + try { + const value = this.delegate.parseValueForToken(token); + return { value }; + } + catch (error) { + return { error }; + } + } + } + + class BindingObserver { + constructor(context, delegate) { + this.context = context; + this.delegate = delegate; + this.bindingsByAction = new Map(); + } + start() { + if (!this.valueListObserver) { + this.valueListObserver = new ValueListObserver(this.element, this.actionAttribute, this); + this.valueListObserver.start(); + } + } + stop() { + if (this.valueListObserver) { + this.valueListObserver.stop(); + delete this.valueListObserver; + this.disconnectAllActions(); + } + } + get element() { + return this.context.element; + } + get identifier() { + return this.context.identifier; + } + get actionAttribute() { + return this.schema.actionAttribute; + } + get schema() { + return this.context.schema; + } + get bindings() { + return Array.from(this.bindingsByAction.values()); + } + connectAction(action) { + const binding = new Binding(this.context, action); + this.bindingsByAction.set(action, binding); + this.delegate.bindingConnected(binding); + } + disconnectAction(action) { + const binding = this.bindingsByAction.get(action); + if (binding) { + this.bindingsByAction.delete(action); + this.delegate.bindingDisconnected(binding); + } + } + disconnectAllActions() { + this.bindings.forEach((binding) => this.delegate.bindingDisconnected(binding, true)); + this.bindingsByAction.clear(); + } + parseValueForToken(token) { + const action = Action.forToken(token, this.schema); + if (action.identifier == this.identifier) { + return action; + } + } + elementMatchedValue(element, action) { + this.connectAction(action); + } + elementUnmatchedValue(element, action) { + this.disconnectAction(action); + } + } + + class ValueObserver { + constructor(context, receiver) { + this.context = context; + this.receiver = receiver; + this.stringMapObserver = new StringMapObserver(this.element, this); + this.valueDescriptorMap = this.controller.valueDescriptorMap; + } + start() { + this.stringMapObserver.start(); + this.invokeChangedCallbacksForDefaultValues(); + } + stop() { + this.stringMapObserver.stop(); + } + get element() { + return this.context.element; + } + get controller() { + return this.context.controller; + } + getStringMapKeyForAttribute(attributeName) { + if (attributeName in this.valueDescriptorMap) { + return this.valueDescriptorMap[attributeName].name; + } + } + stringMapKeyAdded(key, attributeName) { + const descriptor = this.valueDescriptorMap[attributeName]; + if (!this.hasValue(key)) { + this.invokeChangedCallback(key, descriptor.writer(this.receiver[key]), descriptor.writer(descriptor.defaultValue)); + } + } + stringMapValueChanged(value, name, oldValue) { + const descriptor = this.valueDescriptorNameMap[name]; + if (value === null) + return; + if (oldValue === null) { + oldValue = descriptor.writer(descriptor.defaultValue); + } + this.invokeChangedCallback(name, value, oldValue); + } + stringMapKeyRemoved(key, attributeName, oldValue) { + const descriptor = this.valueDescriptorNameMap[key]; + if (this.hasValue(key)) { + this.invokeChangedCallback(key, descriptor.writer(this.receiver[key]), oldValue); + } + else { + this.invokeChangedCallback(key, descriptor.writer(descriptor.defaultValue), oldValue); + } + } + invokeChangedCallbacksForDefaultValues() { + for (const { key, name, defaultValue, writer } of this.valueDescriptors) { + if (defaultValue != undefined && !this.controller.data.has(key)) { + this.invokeChangedCallback(name, writer(defaultValue), undefined); + } + } + } + invokeChangedCallback(name, rawValue, rawOldValue) { + const changedMethodName = `${name}Changed`; + const changedMethod = this.receiver[changedMethodName]; + if (typeof changedMethod == "function") { + const descriptor = this.valueDescriptorNameMap[name]; + try { + const value = descriptor.reader(rawValue); + let oldValue = rawOldValue; + if (rawOldValue) { + oldValue = descriptor.reader(rawOldValue); + } + changedMethod.call(this.receiver, value, oldValue); + } + catch (error) { + if (error instanceof TypeError) { + error.message = `Stimulus Value "${this.context.identifier}.${descriptor.name}" - ${error.message}`; + } + throw error; + } + } + } + get valueDescriptors() { + const { valueDescriptorMap } = this; + return Object.keys(valueDescriptorMap).map((key) => valueDescriptorMap[key]); + } + get valueDescriptorNameMap() { + const descriptors = {}; + Object.keys(this.valueDescriptorMap).forEach((key) => { + const descriptor = this.valueDescriptorMap[key]; + descriptors[descriptor.name] = descriptor; + }); + return descriptors; + } + hasValue(attributeName) { + const descriptor = this.valueDescriptorNameMap[attributeName]; + const hasMethodName = `has${capitalize(descriptor.name)}`; + return this.receiver[hasMethodName]; + } + } + + class TargetObserver { + constructor(context, delegate) { + this.context = context; + this.delegate = delegate; + this.targetsByName = new Multimap(); + } + start() { + if (!this.tokenListObserver) { + this.tokenListObserver = new TokenListObserver(this.element, this.attributeName, this); + this.tokenListObserver.start(); + } + } + stop() { + if (this.tokenListObserver) { + this.disconnectAllTargets(); + this.tokenListObserver.stop(); + delete this.tokenListObserver; + } + } + tokenMatched({ element, content: name }) { + if (this.scope.containsElement(element)) { + this.connectTarget(element, name); + } + } + tokenUnmatched({ element, content: name }) { + this.disconnectTarget(element, name); + } + connectTarget(element, name) { + var _a; + if (!this.targetsByName.has(name, element)) { + this.targetsByName.add(name, element); + (_a = this.tokenListObserver) === null || _a === void 0 ? void 0 : _a.pause(() => this.delegate.targetConnected(element, name)); + } + } + disconnectTarget(element, name) { + var _a; + if (this.targetsByName.has(name, element)) { + this.targetsByName.delete(name, element); + (_a = this.tokenListObserver) === null || _a === void 0 ? void 0 : _a.pause(() => this.delegate.targetDisconnected(element, name)); + } + } + disconnectAllTargets() { + for (const name of this.targetsByName.keys) { + for (const element of this.targetsByName.getValuesForKey(name)) { + this.disconnectTarget(element, name); + } + } + } + get attributeName() { + return `data-${this.context.identifier}-target`; + } + get element() { + return this.context.element; + } + get scope() { + return this.context.scope; + } + } + + function readInheritableStaticArrayValues(constructor, propertyName) { + const ancestors = getAncestorsForConstructor(constructor); + return Array.from(ancestors.reduce((values, constructor) => { + getOwnStaticArrayValues(constructor, propertyName).forEach((name) => values.add(name)); + return values; + }, new Set())); + } + function readInheritableStaticObjectPairs(constructor, propertyName) { + const ancestors = getAncestorsForConstructor(constructor); + return ancestors.reduce((pairs, constructor) => { + pairs.push(...getOwnStaticObjectPairs(constructor, propertyName)); + return pairs; + }, []); + } + function getAncestorsForConstructor(constructor) { + const ancestors = []; + while (constructor) { + ancestors.push(constructor); + constructor = Object.getPrototypeOf(constructor); + } + return ancestors.reverse(); + } + function getOwnStaticArrayValues(constructor, propertyName) { + const definition = constructor[propertyName]; + return Array.isArray(definition) ? definition : []; + } + function getOwnStaticObjectPairs(constructor, propertyName) { + const definition = constructor[propertyName]; + return definition ? Object.keys(definition).map((key) => [key, definition[key]]) : []; + } + + class OutletObserver { + constructor(context, delegate) { + this.started = false; + this.context = context; + this.delegate = delegate; + this.outletsByName = new Multimap(); + this.outletElementsByName = new Multimap(); + this.selectorObserverMap = new Map(); + this.attributeObserverMap = new Map(); + } + start() { + if (!this.started) { + this.outletDefinitions.forEach((outletName) => { + this.setupSelectorObserverForOutlet(outletName); + this.setupAttributeObserverForOutlet(outletName); + }); + this.started = true; + this.dependentContexts.forEach((context) => context.refresh()); + } + } + refresh() { + this.selectorObserverMap.forEach((observer) => observer.refresh()); + this.attributeObserverMap.forEach((observer) => observer.refresh()); + } + stop() { + if (this.started) { + this.started = false; + this.disconnectAllOutlets(); + this.stopSelectorObservers(); + this.stopAttributeObservers(); + } + } + stopSelectorObservers() { + if (this.selectorObserverMap.size > 0) { + this.selectorObserverMap.forEach((observer) => observer.stop()); + this.selectorObserverMap.clear(); + } + } + stopAttributeObservers() { + if (this.attributeObserverMap.size > 0) { + this.attributeObserverMap.forEach((observer) => observer.stop()); + this.attributeObserverMap.clear(); + } + } + selectorMatched(element, _selector, { outletName }) { + const outlet = this.getOutlet(element, outletName); + if (outlet) { + this.connectOutlet(outlet, element, outletName); + } + } + selectorUnmatched(element, _selector, { outletName }) { + const outlet = this.getOutletFromMap(element, outletName); + if (outlet) { + this.disconnectOutlet(outlet, element, outletName); + } + } + selectorMatchElement(element, { outletName }) { + const selector = this.selector(outletName); + const hasOutlet = this.hasOutlet(element, outletName); + const hasOutletController = element.matches(`[${this.schema.controllerAttribute}~=${outletName}]`); + if (selector) { + return hasOutlet && hasOutletController && element.matches(selector); + } + else { + return false; + } + } + elementMatchedAttribute(_element, attributeName) { + const outletName = this.getOutletNameFromOutletAttributeName(attributeName); + if (outletName) { + this.updateSelectorObserverForOutlet(outletName); + } + } + elementAttributeValueChanged(_element, attributeName) { + const outletName = this.getOutletNameFromOutletAttributeName(attributeName); + if (outletName) { + this.updateSelectorObserverForOutlet(outletName); + } + } + elementUnmatchedAttribute(_element, attributeName) { + const outletName = this.getOutletNameFromOutletAttributeName(attributeName); + if (outletName) { + this.updateSelectorObserverForOutlet(outletName); + } + } + connectOutlet(outlet, element, outletName) { + var _a; + if (!this.outletElementsByName.has(outletName, element)) { + this.outletsByName.add(outletName, outlet); + this.outletElementsByName.add(outletName, element); + (_a = this.selectorObserverMap.get(outletName)) === null || _a === void 0 ? void 0 : _a.pause(() => this.delegate.outletConnected(outlet, element, outletName)); + } + } + disconnectOutlet(outlet, element, outletName) { + var _a; + if (this.outletElementsByName.has(outletName, element)) { + this.outletsByName.delete(outletName, outlet); + this.outletElementsByName.delete(outletName, element); + (_a = this.selectorObserverMap + .get(outletName)) === null || _a === void 0 ? void 0 : _a.pause(() => this.delegate.outletDisconnected(outlet, element, outletName)); + } + } + disconnectAllOutlets() { + for (const outletName of this.outletElementsByName.keys) { + for (const element of this.outletElementsByName.getValuesForKey(outletName)) { + for (const outlet of this.outletsByName.getValuesForKey(outletName)) { + this.disconnectOutlet(outlet, element, outletName); + } + } + } + } + updateSelectorObserverForOutlet(outletName) { + const observer = this.selectorObserverMap.get(outletName); + if (observer) { + observer.selector = this.selector(outletName); + } + } + setupSelectorObserverForOutlet(outletName) { + const selector = this.selector(outletName); + const selectorObserver = new SelectorObserver(document.body, selector, this, { outletName }); + this.selectorObserverMap.set(outletName, selectorObserver); + selectorObserver.start(); + } + setupAttributeObserverForOutlet(outletName) { + const attributeName = this.attributeNameForOutletName(outletName); + const attributeObserver = new AttributeObserver(this.scope.element, attributeName, this); + this.attributeObserverMap.set(outletName, attributeObserver); + attributeObserver.start(); + } + selector(outletName) { + return this.scope.outlets.getSelectorForOutletName(outletName); + } + attributeNameForOutletName(outletName) { + return this.scope.schema.outletAttributeForScope(this.identifier, outletName); + } + getOutletNameFromOutletAttributeName(attributeName) { + return this.outletDefinitions.find((outletName) => this.attributeNameForOutletName(outletName) === attributeName); + } + get outletDependencies() { + const dependencies = new Multimap(); + this.router.modules.forEach((module) => { + const constructor = module.definition.controllerConstructor; + const outlets = readInheritableStaticArrayValues(constructor, "outlets"); + outlets.forEach((outlet) => dependencies.add(outlet, module.identifier)); + }); + return dependencies; + } + get outletDefinitions() { + return this.outletDependencies.getKeysForValue(this.identifier); + } + get dependentControllerIdentifiers() { + return this.outletDependencies.getValuesForKey(this.identifier); + } + get dependentContexts() { + const identifiers = this.dependentControllerIdentifiers; + return this.router.contexts.filter((context) => identifiers.includes(context.identifier)); + } + hasOutlet(element, outletName) { + return !!this.getOutlet(element, outletName) || !!this.getOutletFromMap(element, outletName); + } + getOutlet(element, outletName) { + return this.application.getControllerForElementAndIdentifier(element, outletName); + } + getOutletFromMap(element, outletName) { + return this.outletsByName.getValuesForKey(outletName).find((outlet) => outlet.element === element); + } + get scope() { + return this.context.scope; + } + get schema() { + return this.context.schema; + } + get identifier() { + return this.context.identifier; + } + get application() { + return this.context.application; + } + get router() { + return this.application.router; + } + } + + class Context { + constructor(module, scope) { + this.logDebugActivity = (functionName, detail = {}) => { + const { identifier, controller, element } = this; + detail = Object.assign({ identifier, controller, element }, detail); + this.application.logDebugActivity(this.identifier, functionName, detail); + }; + this.module = module; + this.scope = scope; + this.controller = new module.controllerConstructor(this); + this.bindingObserver = new BindingObserver(this, this.dispatcher); + this.valueObserver = new ValueObserver(this, this.controller); + this.targetObserver = new TargetObserver(this, this); + this.outletObserver = new OutletObserver(this, this); + try { + this.controller.initialize(); + this.logDebugActivity("initialize"); + } + catch (error) { + this.handleError(error, "initializing controller"); + } + } + connect() { + this.bindingObserver.start(); + this.valueObserver.start(); + this.targetObserver.start(); + this.outletObserver.start(); + try { + this.controller.connect(); + this.logDebugActivity("connect"); + } + catch (error) { + this.handleError(error, "connecting controller"); + } + } + refresh() { + this.outletObserver.refresh(); + } + disconnect() { + try { + this.controller.disconnect(); + this.logDebugActivity("disconnect"); + } + catch (error) { + this.handleError(error, "disconnecting controller"); + } + this.outletObserver.stop(); + this.targetObserver.stop(); + this.valueObserver.stop(); + this.bindingObserver.stop(); + } + get application() { + return this.module.application; + } + get identifier() { + return this.module.identifier; + } + get schema() { + return this.application.schema; + } + get dispatcher() { + return this.application.dispatcher; + } + get element() { + return this.scope.element; + } + get parentElement() { + return this.element.parentElement; + } + handleError(error, message, detail = {}) { + const { identifier, controller, element } = this; + detail = Object.assign({ identifier, controller, element }, detail); + this.application.handleError(error, `Error ${message}`, detail); + } + targetConnected(element, name) { + this.invokeControllerMethod(`${name}TargetConnected`, element); + } + targetDisconnected(element, name) { + this.invokeControllerMethod(`${name}TargetDisconnected`, element); + } + outletConnected(outlet, element, name) { + this.invokeControllerMethod(`${namespaceCamelize(name)}OutletConnected`, outlet, element); + } + outletDisconnected(outlet, element, name) { + this.invokeControllerMethod(`${namespaceCamelize(name)}OutletDisconnected`, outlet, element); + } + invokeControllerMethod(methodName, ...args) { + const controller = this.controller; + if (typeof controller[methodName] == "function") { + controller[methodName](...args); + } + } + } + + function bless(constructor) { + return shadow(constructor, getBlessedProperties(constructor)); + } + function shadow(constructor, properties) { + const shadowConstructor = extend(constructor); + const shadowProperties = getShadowProperties(constructor.prototype, properties); + Object.defineProperties(shadowConstructor.prototype, shadowProperties); + return shadowConstructor; + } + function getBlessedProperties(constructor) { + const blessings = readInheritableStaticArrayValues(constructor, "blessings"); + return blessings.reduce((blessedProperties, blessing) => { + const properties = blessing(constructor); + for (const key in properties) { + const descriptor = blessedProperties[key] || {}; + blessedProperties[key] = Object.assign(descriptor, properties[key]); + } + return blessedProperties; + }, {}); + } + function getShadowProperties(prototype, properties) { + return getOwnKeys(properties).reduce((shadowProperties, key) => { + const descriptor = getShadowedDescriptor(prototype, properties, key); + if (descriptor) { + Object.assign(shadowProperties, { [key]: descriptor }); + } + return shadowProperties; + }, {}); + } + function getShadowedDescriptor(prototype, properties, key) { + const shadowingDescriptor = Object.getOwnPropertyDescriptor(prototype, key); + const shadowedByValue = shadowingDescriptor && "value" in shadowingDescriptor; + if (!shadowedByValue) { + const descriptor = Object.getOwnPropertyDescriptor(properties, key).value; + if (shadowingDescriptor) { + descriptor.get = shadowingDescriptor.get || descriptor.get; + descriptor.set = shadowingDescriptor.set || descriptor.set; + } + return descriptor; + } + } + const getOwnKeys = (() => { + if (typeof Object.getOwnPropertySymbols == "function") { + return (object) => [...Object.getOwnPropertyNames(object), ...Object.getOwnPropertySymbols(object)]; + } + else { + return Object.getOwnPropertyNames; + } + })(); + const extend = (() => { + function extendWithReflect(constructor) { + function extended() { + return Reflect.construct(constructor, arguments, new.target); + } + extended.prototype = Object.create(constructor.prototype, { + constructor: { value: extended }, + }); + Reflect.setPrototypeOf(extended, constructor); + return extended; + } + function testReflectExtension() { + const a = function () { + this.a.call(this); + }; + const b = extendWithReflect(a); + b.prototype.a = function () { }; + return new b(); + } + try { + testReflectExtension(); + return extendWithReflect; + } + catch (error) { + return (constructor) => class extended extends constructor { + }; + } + })(); + + function blessDefinition(definition) { + return { + identifier: definition.identifier, + controllerConstructor: bless(definition.controllerConstructor), + }; + } + + class Module { + constructor(application, definition) { + this.application = application; + this.definition = blessDefinition(definition); + this.contextsByScope = new WeakMap(); + this.connectedContexts = new Set(); + } + get identifier() { + return this.definition.identifier; + } + get controllerConstructor() { + return this.definition.controllerConstructor; + } + get contexts() { + return Array.from(this.connectedContexts); + } + connectContextForScope(scope) { + const context = this.fetchContextForScope(scope); + this.connectedContexts.add(context); + context.connect(); + } + disconnectContextForScope(scope) { + const context = this.contextsByScope.get(scope); + if (context) { + this.connectedContexts.delete(context); + context.disconnect(); + } + } + fetchContextForScope(scope) { + let context = this.contextsByScope.get(scope); + if (!context) { + context = new Context(this, scope); + this.contextsByScope.set(scope, context); + } + return context; + } + } + + class ClassMap { + constructor(scope) { + this.scope = scope; + } + has(name) { + return this.data.has(this.getDataKey(name)); + } + get(name) { + return this.getAll(name)[0]; + } + getAll(name) { + const tokenString = this.data.get(this.getDataKey(name)) || ""; + return tokenize(tokenString); + } + getAttributeName(name) { + return this.data.getAttributeNameForKey(this.getDataKey(name)); + } + getDataKey(name) { + return `${name}-class`; + } + get data() { + return this.scope.data; + } + } + + class DataMap { + constructor(scope) { + this.scope = scope; + } + get element() { + return this.scope.element; + } + get identifier() { + return this.scope.identifier; + } + get(key) { + const name = this.getAttributeNameForKey(key); + return this.element.getAttribute(name); + } + set(key, value) { + const name = this.getAttributeNameForKey(key); + this.element.setAttribute(name, value); + return this.get(key); + } + has(key) { + const name = this.getAttributeNameForKey(key); + return this.element.hasAttribute(name); + } + delete(key) { + if (this.has(key)) { + const name = this.getAttributeNameForKey(key); + this.element.removeAttribute(name); + return true; + } + else { + return false; + } + } + getAttributeNameForKey(key) { + return `data-${this.identifier}-${dasherize(key)}`; + } + } + + class Guide { + constructor(logger) { + this.warnedKeysByObject = new WeakMap(); + this.logger = logger; + } + warn(object, key, message) { + let warnedKeys = this.warnedKeysByObject.get(object); + if (!warnedKeys) { + warnedKeys = new Set(); + this.warnedKeysByObject.set(object, warnedKeys); + } + if (!warnedKeys.has(key)) { + warnedKeys.add(key); + this.logger.warn(message, object); + } + } + } + + function attributeValueContainsToken(attributeName, token) { + return `[${attributeName}~="${token}"]`; + } + + class TargetSet { + constructor(scope) { + this.scope = scope; + } + get element() { + return this.scope.element; + } + get identifier() { + return this.scope.identifier; + } + get schema() { + return this.scope.schema; + } + has(targetName) { + return this.find(targetName) != null; + } + find(...targetNames) { + return targetNames.reduce((target, targetName) => target || this.findTarget(targetName) || this.findLegacyTarget(targetName), undefined); + } + findAll(...targetNames) { + return targetNames.reduce((targets, targetName) => [ + ...targets, + ...this.findAllTargets(targetName), + ...this.findAllLegacyTargets(targetName), + ], []); + } + findTarget(targetName) { + const selector = this.getSelectorForTargetName(targetName); + return this.scope.findElement(selector); + } + findAllTargets(targetName) { + const selector = this.getSelectorForTargetName(targetName); + return this.scope.findAllElements(selector); + } + getSelectorForTargetName(targetName) { + const attributeName = this.schema.targetAttributeForScope(this.identifier); + return attributeValueContainsToken(attributeName, targetName); + } + findLegacyTarget(targetName) { + const selector = this.getLegacySelectorForTargetName(targetName); + return this.deprecate(this.scope.findElement(selector), targetName); + } + findAllLegacyTargets(targetName) { + const selector = this.getLegacySelectorForTargetName(targetName); + return this.scope.findAllElements(selector).map((element) => this.deprecate(element, targetName)); + } + getLegacySelectorForTargetName(targetName) { + const targetDescriptor = `${this.identifier}.${targetName}`; + return attributeValueContainsToken(this.schema.targetAttribute, targetDescriptor); + } + deprecate(element, targetName) { + if (element) { + const { identifier } = this; + const attributeName = this.schema.targetAttribute; + const revisedAttributeName = this.schema.targetAttributeForScope(identifier); + this.guide.warn(element, `target:${targetName}`, `Please replace ${attributeName}="${identifier}.${targetName}" with ${revisedAttributeName}="${targetName}". ` + + `The ${attributeName} attribute is deprecated and will be removed in a future version of Stimulus.`); + } + return element; + } + get guide() { + return this.scope.guide; + } + } + + class OutletSet { + constructor(scope, controllerElement) { + this.scope = scope; + this.controllerElement = controllerElement; + } + get element() { + return this.scope.element; + } + get identifier() { + return this.scope.identifier; + } + get schema() { + return this.scope.schema; + } + has(outletName) { + return this.find(outletName) != null; + } + find(...outletNames) { + return outletNames.reduce((outlet, outletName) => outlet || this.findOutlet(outletName), undefined); + } + findAll(...outletNames) { + return outletNames.reduce((outlets, outletName) => [...outlets, ...this.findAllOutlets(outletName)], []); + } + getSelectorForOutletName(outletName) { + const attributeName = this.schema.outletAttributeForScope(this.identifier, outletName); + return this.controllerElement.getAttribute(attributeName); + } + findOutlet(outletName) { + const selector = this.getSelectorForOutletName(outletName); + if (selector) + return this.findElement(selector, outletName); + } + findAllOutlets(outletName) { + const selector = this.getSelectorForOutletName(outletName); + return selector ? this.findAllElements(selector, outletName) : []; + } + findElement(selector, outletName) { + const elements = this.scope.queryElements(selector); + return elements.filter((element) => this.matchesElement(element, selector, outletName))[0]; + } + findAllElements(selector, outletName) { + const elements = this.scope.queryElements(selector); + return elements.filter((element) => this.matchesElement(element, selector, outletName)); + } + matchesElement(element, selector, outletName) { + const controllerAttribute = element.getAttribute(this.scope.schema.controllerAttribute) || ""; + return element.matches(selector) && controllerAttribute.split(" ").includes(outletName); + } + } + + class Scope { + constructor(schema, element, identifier, logger) { + this.targets = new TargetSet(this); + this.classes = new ClassMap(this); + this.data = new DataMap(this); + this.containsElement = (element) => { + return element.closest(this.controllerSelector) === this.element; + }; + this.schema = schema; + this.element = element; + this.identifier = identifier; + this.guide = new Guide(logger); + this.outlets = new OutletSet(this.documentScope, element); + } + findElement(selector) { + return this.element.matches(selector) ? this.element : this.queryElements(selector).find(this.containsElement); + } + findAllElements(selector) { + return [ + ...(this.element.matches(selector) ? [this.element] : []), + ...this.queryElements(selector).filter(this.containsElement), + ]; + } + queryElements(selector) { + return Array.from(this.element.querySelectorAll(selector)); + } + get controllerSelector() { + return attributeValueContainsToken(this.schema.controllerAttribute, this.identifier); + } + get isDocumentScope() { + return this.element === document.documentElement; + } + get documentScope() { + return this.isDocumentScope + ? this + : new Scope(this.schema, document.documentElement, this.identifier, this.guide.logger); + } + } + + class ScopeObserver { + constructor(element, schema, delegate) { + this.element = element; + this.schema = schema; + this.delegate = delegate; + this.valueListObserver = new ValueListObserver(this.element, this.controllerAttribute, this); + this.scopesByIdentifierByElement = new WeakMap(); + this.scopeReferenceCounts = new WeakMap(); + } + start() { + this.valueListObserver.start(); + } + stop() { + this.valueListObserver.stop(); + } + get controllerAttribute() { + return this.schema.controllerAttribute; + } + parseValueForToken(token) { + const { element, content: identifier } = token; + return this.parseValueForElementAndIdentifier(element, identifier); + } + parseValueForElementAndIdentifier(element, identifier) { + const scopesByIdentifier = this.fetchScopesByIdentifierForElement(element); + let scope = scopesByIdentifier.get(identifier); + if (!scope) { + scope = this.delegate.createScopeForElementAndIdentifier(element, identifier); + scopesByIdentifier.set(identifier, scope); + } + return scope; + } + elementMatchedValue(element, value) { + const referenceCount = (this.scopeReferenceCounts.get(value) || 0) + 1; + this.scopeReferenceCounts.set(value, referenceCount); + if (referenceCount == 1) { + this.delegate.scopeConnected(value); + } + } + elementUnmatchedValue(element, value) { + const referenceCount = this.scopeReferenceCounts.get(value); + if (referenceCount) { + this.scopeReferenceCounts.set(value, referenceCount - 1); + if (referenceCount == 1) { + this.delegate.scopeDisconnected(value); + } + } + } + fetchScopesByIdentifierForElement(element) { + let scopesByIdentifier = this.scopesByIdentifierByElement.get(element); + if (!scopesByIdentifier) { + scopesByIdentifier = new Map(); + this.scopesByIdentifierByElement.set(element, scopesByIdentifier); + } + return scopesByIdentifier; + } + } + + class Router { + constructor(application) { + this.application = application; + this.scopeObserver = new ScopeObserver(this.element, this.schema, this); + this.scopesByIdentifier = new Multimap(); + this.modulesByIdentifier = new Map(); + } + get element() { + return this.application.element; + } + get schema() { + return this.application.schema; + } + get logger() { + return this.application.logger; + } + get controllerAttribute() { + return this.schema.controllerAttribute; + } + get modules() { + return Array.from(this.modulesByIdentifier.values()); + } + get contexts() { + return this.modules.reduce((contexts, module) => contexts.concat(module.contexts), []); + } + start() { + this.scopeObserver.start(); + } + stop() { + this.scopeObserver.stop(); + } + loadDefinition(definition) { + this.unloadIdentifier(definition.identifier); + const module = new Module(this.application, definition); + this.connectModule(module); + const afterLoad = definition.controllerConstructor.afterLoad; + if (afterLoad) { + afterLoad.call(definition.controllerConstructor, definition.identifier, this.application); + } + } + unloadIdentifier(identifier) { + const module = this.modulesByIdentifier.get(identifier); + if (module) { + this.disconnectModule(module); + } + } + getContextForElementAndIdentifier(element, identifier) { + const module = this.modulesByIdentifier.get(identifier); + if (module) { + return module.contexts.find((context) => context.element == element); + } + } + proposeToConnectScopeForElementAndIdentifier(element, identifier) { + const scope = this.scopeObserver.parseValueForElementAndIdentifier(element, identifier); + if (scope) { + this.scopeObserver.elementMatchedValue(scope.element, scope); + } + else { + console.error(`Couldn't find or create scope for identifier: "${identifier}" and element:`, element); + } + } + handleError(error, message, detail) { + this.application.handleError(error, message, detail); + } + createScopeForElementAndIdentifier(element, identifier) { + return new Scope(this.schema, element, identifier, this.logger); + } + scopeConnected(scope) { + this.scopesByIdentifier.add(scope.identifier, scope); + const module = this.modulesByIdentifier.get(scope.identifier); + if (module) { + module.connectContextForScope(scope); + } + } + scopeDisconnected(scope) { + this.scopesByIdentifier.delete(scope.identifier, scope); + const module = this.modulesByIdentifier.get(scope.identifier); + if (module) { + module.disconnectContextForScope(scope); + } + } + connectModule(module) { + this.modulesByIdentifier.set(module.identifier, module); + const scopes = this.scopesByIdentifier.getValuesForKey(module.identifier); + scopes.forEach((scope) => module.connectContextForScope(scope)); + } + disconnectModule(module) { + this.modulesByIdentifier.delete(module.identifier); + const scopes = this.scopesByIdentifier.getValuesForKey(module.identifier); + scopes.forEach((scope) => module.disconnectContextForScope(scope)); + } + } + + const defaultSchema = { + controllerAttribute: "data-controller", + actionAttribute: "data-action", + targetAttribute: "data-target", + targetAttributeForScope: (identifier) => `data-${identifier}-target`, + outletAttributeForScope: (identifier, outlet) => `data-${identifier}-${outlet}-outlet`, + keyMappings: Object.assign(Object.assign({ enter: "Enter", tab: "Tab", esc: "Escape", space: " ", up: "ArrowUp", down: "ArrowDown", left: "ArrowLeft", right: "ArrowRight", home: "Home", end: "End", page_up: "PageUp", page_down: "PageDown" }, objectFromEntries("abcdefghijklmnopqrstuvwxyz".split("").map((c) => [c, c]))), objectFromEntries("0123456789".split("").map((n) => [n, n]))), + }; + function objectFromEntries(array) { + return array.reduce((memo, [k, v]) => (Object.assign(Object.assign({}, memo), { [k]: v })), {}); + } + + class Application { + constructor(element = document.documentElement, schema = defaultSchema) { + this.logger = console; + this.debug = false; + this.logDebugActivity = (identifier, functionName, detail = {}) => { + if (this.debug) { + this.logFormattedMessage(identifier, functionName, detail); + } + }; + this.element = element; + this.schema = schema; + this.dispatcher = new Dispatcher(this); + this.router = new Router(this); + this.actionDescriptorFilters = Object.assign({}, defaultActionDescriptorFilters); + } + static start(element, schema) { + const application = new this(element, schema); + application.start(); + return application; + } + async start() { + await domReady(); + this.logDebugActivity("application", "starting"); + this.dispatcher.start(); + this.router.start(); + this.logDebugActivity("application", "start"); + } + stop() { + this.logDebugActivity("application", "stopping"); + this.dispatcher.stop(); + this.router.stop(); + this.logDebugActivity("application", "stop"); + } + register(identifier, controllerConstructor) { + this.load({ identifier, controllerConstructor }); + } + registerActionOption(name, filter) { + this.actionDescriptorFilters[name] = filter; + } + load(head, ...rest) { + const definitions = Array.isArray(head) ? head : [head, ...rest]; + definitions.forEach((definition) => { + if (definition.controllerConstructor.shouldLoad) { + this.router.loadDefinition(definition); + } + }); + } + unload(head, ...rest) { + const identifiers = Array.isArray(head) ? head : [head, ...rest]; + identifiers.forEach((identifier) => this.router.unloadIdentifier(identifier)); + } + get controllers() { + return this.router.contexts.map((context) => context.controller); + } + getControllerForElementAndIdentifier(element, identifier) { + const context = this.router.getContextForElementAndIdentifier(element, identifier); + return context ? context.controller : null; + } + handleError(error, message, detail) { + var _a; + this.logger.error(`%s\n\n%o\n\n%o`, message, error, detail); + (_a = window.onerror) === null || _a === void 0 ? void 0 : _a.call(window, message, "", 0, 0, error); + } + logFormattedMessage(identifier, functionName, detail = {}) { + detail = Object.assign({ application: this }, detail); + this.logger.groupCollapsed(`${identifier} #${functionName}`); + this.logger.log("details:", Object.assign({}, detail)); + this.logger.groupEnd(); + } + } + function domReady() { + return new Promise((resolve) => { + if (document.readyState == "loading") { + document.addEventListener("DOMContentLoaded", () => resolve()); + } + else { + resolve(); + } + }); + } + + function ClassPropertiesBlessing(constructor) { + const classes = readInheritableStaticArrayValues(constructor, "classes"); + return classes.reduce((properties, classDefinition) => { + return Object.assign(properties, propertiesForClassDefinition(classDefinition)); + }, {}); + } + function propertiesForClassDefinition(key) { + return { + [`${key}Class`]: { + get() { + const { classes } = this; + if (classes.has(key)) { + return classes.get(key); + } + else { + const attribute = classes.getAttributeName(key); + throw new Error(`Missing attribute "${attribute}"`); + } + }, + }, + [`${key}Classes`]: { + get() { + return this.classes.getAll(key); + }, + }, + [`has${capitalize(key)}Class`]: { + get() { + return this.classes.has(key); + }, + }, + }; + } + + function OutletPropertiesBlessing(constructor) { + const outlets = readInheritableStaticArrayValues(constructor, "outlets"); + return outlets.reduce((properties, outletDefinition) => { + return Object.assign(properties, propertiesForOutletDefinition(outletDefinition)); + }, {}); + } + function getOutletController(controller, element, identifier) { + return controller.application.getControllerForElementAndIdentifier(element, identifier); + } + function getControllerAndEnsureConnectedScope(controller, element, outletName) { + let outletController = getOutletController(controller, element, outletName); + if (outletController) + return outletController; + controller.application.router.proposeToConnectScopeForElementAndIdentifier(element, outletName); + outletController = getOutletController(controller, element, outletName); + if (outletController) + return outletController; + } + function propertiesForOutletDefinition(name) { + const camelizedName = namespaceCamelize(name); + return { + [`${camelizedName}Outlet`]: { + get() { + const outletElement = this.outlets.find(name); + const selector = this.outlets.getSelectorForOutletName(name); + if (outletElement) { + const outletController = getControllerAndEnsureConnectedScope(this, outletElement, name); + if (outletController) + return outletController; + throw new Error(`The provided outlet element is missing an outlet controller "${name}" instance for host controller "${this.identifier}"`); + } + throw new Error(`Missing outlet element "${name}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${selector}".`); + }, + }, + [`${camelizedName}Outlets`]: { + get() { + const outlets = this.outlets.findAll(name); + if (outlets.length > 0) { + return outlets + .map((outletElement) => { + const outletController = getControllerAndEnsureConnectedScope(this, outletElement, name); + if (outletController) + return outletController; + console.warn(`The provided outlet element is missing an outlet controller "${name}" instance for host controller "${this.identifier}"`, outletElement); + }) + .filter((controller) => controller); + } + return []; + }, + }, + [`${camelizedName}OutletElement`]: { + get() { + const outletElement = this.outlets.find(name); + const selector = this.outlets.getSelectorForOutletName(name); + if (outletElement) { + return outletElement; + } + else { + throw new Error(`Missing outlet element "${name}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${selector}".`); + } + }, + }, + [`${camelizedName}OutletElements`]: { + get() { + return this.outlets.findAll(name); + }, + }, + [`has${capitalize(camelizedName)}Outlet`]: { + get() { + return this.outlets.has(name); + }, + }, + }; + } + + function TargetPropertiesBlessing(constructor) { + const targets = readInheritableStaticArrayValues(constructor, "targets"); + return targets.reduce((properties, targetDefinition) => { + return Object.assign(properties, propertiesForTargetDefinition(targetDefinition)); + }, {}); + } + function propertiesForTargetDefinition(name) { + return { + [`${name}Target`]: { + get() { + const target = this.targets.find(name); + if (target) { + return target; + } + else { + throw new Error(`Missing target element "${name}" for "${this.identifier}" controller`); + } + }, + }, + [`${name}Targets`]: { + get() { + return this.targets.findAll(name); + }, + }, + [`has${capitalize(name)}Target`]: { + get() { + return this.targets.has(name); + }, + }, + }; + } + + function ValuePropertiesBlessing(constructor) { + const valueDefinitionPairs = readInheritableStaticObjectPairs(constructor, "values"); + const propertyDescriptorMap = { + valueDescriptorMap: { + get() { + return valueDefinitionPairs.reduce((result, valueDefinitionPair) => { + const valueDescriptor = parseValueDefinitionPair(valueDefinitionPair, this.identifier); + const attributeName = this.data.getAttributeNameForKey(valueDescriptor.key); + return Object.assign(result, { [attributeName]: valueDescriptor }); + }, {}); + }, + }, + }; + return valueDefinitionPairs.reduce((properties, valueDefinitionPair) => { + return Object.assign(properties, propertiesForValueDefinitionPair(valueDefinitionPair)); + }, propertyDescriptorMap); + } + function propertiesForValueDefinitionPair(valueDefinitionPair, controller) { + const definition = parseValueDefinitionPair(valueDefinitionPair, controller); + const { key, name, reader: read, writer: write } = definition; + return { + [name]: { + get() { + const value = this.data.get(key); + if (value !== null) { + return read(value); + } + else { + return definition.defaultValue; + } + }, + set(value) { + if (value === undefined) { + this.data.delete(key); + } + else { + this.data.set(key, write(value)); + } + }, + }, + [`has${capitalize(name)}`]: { + get() { + return this.data.has(key) || definition.hasCustomDefaultValue; + }, + }, + }; + } + function parseValueDefinitionPair([token, typeDefinition], controller) { + return valueDescriptorForTokenAndTypeDefinition({ + controller, + token, + typeDefinition, + }); + } + function parseValueTypeConstant(constant) { + switch (constant) { + case Array: + return "array"; + case Boolean: + return "boolean"; + case Number: + return "number"; + case Object: + return "object"; + case String: + return "string"; + } + } + function parseValueTypeDefault(defaultValue) { + switch (typeof defaultValue) { + case "boolean": + return "boolean"; + case "number": + return "number"; + case "string": + return "string"; + } + if (Array.isArray(defaultValue)) + return "array"; + if (Object.prototype.toString.call(defaultValue) === "[object Object]") + return "object"; + } + function parseValueTypeObject(payload) { + const { controller, token, typeObject } = payload; + const hasType = isSomething(typeObject.type); + const hasDefault = isSomething(typeObject.default); + const fullObject = hasType && hasDefault; + const onlyType = hasType && !hasDefault; + const onlyDefault = !hasType && hasDefault; + const typeFromObject = parseValueTypeConstant(typeObject.type); + const typeFromDefaultValue = parseValueTypeDefault(payload.typeObject.default); + if (onlyType) + return typeFromObject; + if (onlyDefault) + return typeFromDefaultValue; + if (typeFromObject !== typeFromDefaultValue) { + const propertyPath = controller ? `${controller}.${token}` : token; + throw new Error(`The specified default value for the Stimulus Value "${propertyPath}" must match the defined type "${typeFromObject}". The provided default value of "${typeObject.default}" is of type "${typeFromDefaultValue}".`); + } + if (fullObject) + return typeFromObject; + } + function parseValueTypeDefinition(payload) { + const { controller, token, typeDefinition } = payload; + const typeObject = { controller, token, typeObject: typeDefinition }; + const typeFromObject = parseValueTypeObject(typeObject); + const typeFromDefaultValue = parseValueTypeDefault(typeDefinition); + const typeFromConstant = parseValueTypeConstant(typeDefinition); + const type = typeFromObject || typeFromDefaultValue || typeFromConstant; + if (type) + return type; + const propertyPath = controller ? `${controller}.${typeDefinition}` : token; + throw new Error(`Unknown value type "${propertyPath}" for "${token}" value`); + } + function defaultValueForDefinition(typeDefinition) { + const constant = parseValueTypeConstant(typeDefinition); + if (constant) + return defaultValuesByType[constant]; + const hasDefault = hasProperty(typeDefinition, "default"); + const hasType = hasProperty(typeDefinition, "type"); + const typeObject = typeDefinition; + if (hasDefault) + return typeObject.default; + if (hasType) { + const { type } = typeObject; + const constantFromType = parseValueTypeConstant(type); + if (constantFromType) + return defaultValuesByType[constantFromType]; + } + return typeDefinition; + } + function valueDescriptorForTokenAndTypeDefinition(payload) { + const { token, typeDefinition } = payload; + const key = `${dasherize(token)}-value`; + const type = parseValueTypeDefinition(payload); + return { + type, + key, + name: camelize(key), + get defaultValue() { + return defaultValueForDefinition(typeDefinition); + }, + get hasCustomDefaultValue() { + return parseValueTypeDefault(typeDefinition) !== undefined; + }, + reader: readers[type], + writer: writers[type] || writers.default, + }; + } + const defaultValuesByType = { + get array() { + return []; + }, + boolean: false, + number: 0, + get object() { + return {}; + }, + string: "", + }; + const readers = { + array(value) { + const array = JSON.parse(value); + if (!Array.isArray(array)) { + throw new TypeError(`expected value of type "array" but instead got value "${value}" of type "${parseValueTypeDefault(array)}"`); + } + return array; + }, + boolean(value) { + return !(value == "0" || String(value).toLowerCase() == "false"); + }, + number(value) { + return Number(value.replace(/_/g, "")); + }, + object(value) { + const object = JSON.parse(value); + if (object === null || typeof object != "object" || Array.isArray(object)) { + throw new TypeError(`expected value of type "object" but instead got value "${value}" of type "${parseValueTypeDefault(object)}"`); + } + return object; + }, + string(value) { + return value; + }, + }; + const writers = { + default: writeString, + array: writeJSON, + object: writeJSON, + }; + function writeJSON(value) { + return JSON.stringify(value); + } + function writeString(value) { + return `${value}`; + } + + class Controller { + constructor(context) { + this.context = context; + } + static get shouldLoad() { + return true; + } + static afterLoad(_identifier, _application) { + return; + } + get application() { + return this.context.application; + } + get scope() { + return this.context.scope; + } + get element() { + return this.scope.element; + } + get identifier() { + return this.scope.identifier; + } + get targets() { + return this.scope.targets; + } + get outlets() { + return this.scope.outlets; + } + get classes() { + return this.scope.classes; + } + get data() { + return this.scope.data; + } + initialize() { + } + connect() { + } + disconnect() { + } + dispatch(eventName, { target = this.element, detail = {}, prefix = this.identifier, bubbles = true, cancelable = true, } = {}) { + const type = prefix ? `${prefix}:${eventName}` : eventName; + const event = new CustomEvent(type, { detail, bubbles, cancelable }); + target.dispatchEvent(event); + return event; + } + } + Controller.blessings = [ + ClassPropertiesBlessing, + TargetPropertiesBlessing, + ValuePropertiesBlessing, + OutletPropertiesBlessing, + ]; + Controller.targets = []; + Controller.outlets = []; + Controller.values = {}; + + exports.Application = Application; + exports.AttributeObserver = AttributeObserver; + exports.Context = Context; + exports.Controller = Controller; + exports.ElementObserver = ElementObserver; + exports.IndexedMultimap = IndexedMultimap; + exports.Multimap = Multimap; + exports.SelectorObserver = SelectorObserver; + exports.StringMapObserver = StringMapObserver; + exports.TokenListObserver = TokenListObserver; + exports.ValueListObserver = ValueListObserver; + exports.add = add; + exports.defaultSchema = defaultSchema; + exports.del = del; + exports.fetch = fetch; + exports.prune = prune; + + Object.defineProperty(exports, '__esModule', { value: true }); + +}))); diff --git a/vendor/assets/javascripts/tom-select/tom-select.complete.js b/vendor/assets/javascripts/tom-select/tom-select.complete.js new file mode 100644 index 00000000..a1dc48f0 --- /dev/null +++ b/vendor/assets/javascripts/tom-select/tom-select.complete.js @@ -0,0 +1,5021 @@ +/** +* Tom Select v2.4.2 +* Licensed under the Apache License, Version 2.0 (the "License"); +*/ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.TomSelect = factory()); +})(this, (function () { 'use strict'; + + /** + * MicroEvent - to make any js object an event emitter + * + * - pure javascript - server compatible, browser compatible + * - dont rely on the browser doms + * - super simple - you get it immediatly, no mistery, no magic involved + * + * @author Jerome Etienne (https://github.com/jeromeetienne) + */ + + /** + * Execute callback for each event in space separated list of event names + * + */ + function forEvents(events, callback) { + events.split(/\s+/).forEach(event => { + callback(event); + }); + } + class MicroEvent { + constructor() { + this._events = {}; + } + on(events, fct) { + forEvents(events, event => { + const event_array = this._events[event] || []; + event_array.push(fct); + this._events[event] = event_array; + }); + } + off(events, fct) { + var n = arguments.length; + if (n === 0) { + this._events = {}; + return; + } + forEvents(events, event => { + if (n === 1) { + delete this._events[event]; + return; + } + const event_array = this._events[event]; + if (event_array === undefined) return; + event_array.splice(event_array.indexOf(fct), 1); + this._events[event] = event_array; + }); + } + trigger(events, ...args) { + var self = this; + forEvents(events, event => { + const event_array = self._events[event]; + if (event_array === undefined) return; + event_array.forEach(fct => { + fct.apply(self, args); + }); + }); + } + } + + /** + * microplugin.js + * Copyright (c) 2013 Brian Reavis & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * @author Brian Reavis + */ + + function MicroPlugin(Interface) { + Interface.plugins = {}; + return class extends Interface { + constructor(...args) { + super(...args); + this.plugins = { + names: [], + settings: {}, + requested: {}, + loaded: {} + }; + } + /** + * Registers a plugin. + * + * @param {function} fn + */ + static define(name, fn) { + Interface.plugins[name] = { + 'name': name, + 'fn': fn + }; + } + + /** + * Initializes the listed plugins (with options). + * Acceptable formats: + * + * List (without options): + * ['a', 'b', 'c'] + * + * List (with options): + * [{'name': 'a', options: {}}, {'name': 'b', options: {}}] + * + * Hash (with options): + * {'a': { ... }, 'b': { ... }, 'c': { ... }} + * + * @param {array|object} plugins + */ + initializePlugins(plugins) { + var key, name; + const self = this; + const queue = []; + if (Array.isArray(plugins)) { + plugins.forEach(plugin => { + if (typeof plugin === 'string') { + queue.push(plugin); + } else { + self.plugins.settings[plugin.name] = plugin.options; + queue.push(plugin.name); + } + }); + } else if (plugins) { + for (key in plugins) { + if (plugins.hasOwnProperty(key)) { + self.plugins.settings[key] = plugins[key]; + queue.push(key); + } + } + } + while (name = queue.shift()) { + self.require(name); + } + } + loadPlugin(name) { + var self = this; + var plugins = self.plugins; + var plugin = Interface.plugins[name]; + if (!Interface.plugins.hasOwnProperty(name)) { + throw new Error('Unable to find "' + name + '" plugin'); + } + plugins.requested[name] = true; + plugins.loaded[name] = plugin.fn.apply(self, [self.plugins.settings[name] || {}]); + plugins.names.push(name); + } + + /** + * Initializes a plugin. + * + */ + require(name) { + var self = this; + var plugins = self.plugins; + if (!self.plugins.loaded.hasOwnProperty(name)) { + if (plugins.requested[name]) { + throw new Error('Plugin has circular dependency ("' + name + '")'); + } + self.loadPlugin(name); + } + return plugins.loaded[name]; + } + }; + } + + /** + * Convert array of strings to a regular expression + * ex ['ab','a'] => (?:ab|a) + * ex ['a','b'] => [ab] + */ + const arrayToPattern = (chars) => { + chars = chars.filter(Boolean); + if (chars.length < 2) { + return chars[0] || ''; + } + return (maxValueLength(chars) == 1) ? '[' + chars.join('') + ']' : '(?:' + chars.join('|') + ')'; + }; + const sequencePattern = (array) => { + if (!hasDuplicates(array)) { + return array.join(''); + } + let pattern = ''; + let prev_char_count = 0; + const prev_pattern = () => { + if (prev_char_count > 1) { + pattern += '{' + prev_char_count + '}'; + } + }; + array.forEach((char, i) => { + if (char === array[i - 1]) { + prev_char_count++; + return; + } + prev_pattern(); + pattern += char; + prev_char_count = 1; + }); + prev_pattern(); + return pattern; + }; + /** + * Convert array of strings to a regular expression + * ex ['ab','a'] => (?:ab|a) + * ex ['a','b'] => [ab] + */ + const setToPattern = (chars) => { + let array = Array.from(chars); + return arrayToPattern(array); + }; + /** + * https://stackoverflow.com/questions/7376598/in-javascript-how-do-i-check-if-an-array-has-duplicate-values + */ + const hasDuplicates = (array) => { + return (new Set(array)).size !== array.length; + }; + /** + * https://stackoverflow.com/questions/63006601/why-does-u-throw-an-invalid-escape-error + */ + const escape_regex = (str) => { + return (str + '').replace(/([\$\(\)\*\+\.\?\[\]\^\{\|\}\\])/gu, '\\$1'); + }; + /** + * Return the max length of array values + */ + const maxValueLength = (array) => { + return array.reduce((longest, value) => Math.max(longest, unicodeLength(value)), 0); + }; + const unicodeLength = (str) => { + return Array.from(str).length; + }; + + /** + * Get all possible combinations of substrings that add up to the given string + * https://stackoverflow.com/questions/30169587/find-all-the-combination-of-substrings-that-add-up-to-the-given-string + */ + const allSubstrings = (input) => { + if (input.length === 1) + return [[input]]; + let result = []; + const start = input.substring(1); + const suba = allSubstrings(start); + suba.forEach(function (subresult) { + let tmp = subresult.slice(0); + tmp[0] = input.charAt(0) + tmp[0]; + result.push(tmp); + tmp = subresult.slice(0); + tmp.unshift(input.charAt(0)); + result.push(tmp); + }); + return result; + }; + + const code_points = [[0, 65535]]; + const accent_pat = '[\u0300-\u036F\u{b7}\u{2be}\u{2bc}]'; + let unicode_map; + let multi_char_reg; + const max_char_length = 3; + const latin_convert = {}; + const latin_condensed = { + '/': '⁄∕', + '0': '߀', + "a": "ⱥɐɑ", + "aa": "ꜳ", + "ae": "æǽǣ", + "ao": "ꜵ", + "au": "ꜷ", + "av": "ꜹꜻ", + "ay": "ꜽ", + "b": "ƀɓƃ", + "c": "ꜿƈȼↄ", + "d": "đɗɖᴅƌꮷԁɦ", + "e": "ɛǝᴇɇ", + "f": "ꝼƒ", + "g": "ǥɠꞡᵹꝿɢ", + "h": "ħⱨⱶɥ", + "i": "ɨı", + "j": "ɉȷ", + "k": "ƙⱪꝁꝃꝅꞣ", + "l": "łƚɫⱡꝉꝇꞁɭ", + "m": "ɱɯϻ", + "n": "ꞥƞɲꞑᴎлԉ", + "o": "øǿɔɵꝋꝍᴑ", + "oe": "œ", + "oi": "ƣ", + "oo": "ꝏ", + "ou": "ȣ", + "p": "ƥᵽꝑꝓꝕρ", + "q": "ꝗꝙɋ", + "r": "ɍɽꝛꞧꞃ", + "s": "ßȿꞩꞅʂ", + "t": "ŧƭʈⱦꞇ", + "th": "þ", + "tz": "ꜩ", + "u": "ʉ", + "v": "ʋꝟʌ", + "vy": "ꝡ", + "w": "ⱳ", + "y": "ƴɏỿ", + "z": "ƶȥɀⱬꝣ", + "hv": "ƕ" + }; + for (let latin in latin_condensed) { + let unicode = latin_condensed[latin] || ''; + for (let i = 0; i < unicode.length; i++) { + let char = unicode.substring(i, i + 1); + latin_convert[char] = latin; + } + } + const convert_pat = new RegExp(Object.keys(latin_convert).join('|') + '|' + accent_pat, 'gu'); + /** + * Initialize the unicode_map from the give code point ranges + */ + const initialize = (_code_points) => { + if (unicode_map !== undefined) + return; + unicode_map = generateMap(code_points); + }; + /** + * Helper method for normalize a string + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize + */ + const normalize = (str, form = 'NFKD') => str.normalize(form); + /** + * Remove accents without reordering string + * calling str.normalize('NFKD') on \u{594}\u{595}\u{596} becomes \u{596}\u{594}\u{595} + * via https://github.com/krisk/Fuse/issues/133#issuecomment-318692703 + */ + const asciifold = (str) => { + return Array.from(str).reduce( + /** + * @param {string} result + * @param {string} char + */ + (result, char) => { + return result + _asciifold(char); + }, ''); + }; + const _asciifold = (str) => { + str = normalize(str) + .toLowerCase() + .replace(convert_pat, (/** @type {string} */ char) => { + return latin_convert[char] || ''; + }); + //return str; + return normalize(str, 'NFC'); + }; + /** + * Generate a list of unicode variants from the list of code points + */ + function* generator(code_points) { + for (const [code_point_min, code_point_max] of code_points) { + for (let i = code_point_min; i <= code_point_max; i++) { + let composed = String.fromCharCode(i); + let folded = asciifold(composed); + if (folded == composed.toLowerCase()) { + continue; + } + // skip when folded is a string longer than 3 characters long + // bc the resulting regex patterns will be long + // eg: + // folded صلى الله عليه وسلم length 18 code point 65018 + // folded جل جلاله length 8 code point 65019 + if (folded.length > max_char_length) { + continue; + } + if (folded.length == 0) { + continue; + } + yield { folded: folded, composed: composed, code_point: i }; + } + } + } + /** + * Generate a unicode map from the list of code points + */ + const generateSets = (code_points) => { + const unicode_sets = {}; + const addMatching = (folded, to_add) => { + /** @type {Set} */ + const folded_set = unicode_sets[folded] || new Set(); + const patt = new RegExp('^' + setToPattern(folded_set) + '$', 'iu'); + if (to_add.match(patt)) { + return; + } + folded_set.add(escape_regex(to_add)); + unicode_sets[folded] = folded_set; + }; + for (let value of generator(code_points)) { + addMatching(value.folded, value.folded); + addMatching(value.folded, value.composed); + } + return unicode_sets; + }; + /** + * Generate a unicode map from the list of code points + * ae => (?:(?:ae|Æ|Ǽ|Ǣ)|(?:A|Ⓐ|A...)(?:E|ɛ|Ⓔ...)) + */ + const generateMap = (code_points) => { + const unicode_sets = generateSets(code_points); + const unicode_map = {}; + let multi_char = []; + for (let folded in unicode_sets) { + let set = unicode_sets[folded]; + if (set) { + unicode_map[folded] = setToPattern(set); + } + if (folded.length > 1) { + multi_char.push(escape_regex(folded)); + } + } + multi_char.sort((a, b) => b.length - a.length); + const multi_char_patt = arrayToPattern(multi_char); + multi_char_reg = new RegExp('^' + multi_char_patt, 'u'); + return unicode_map; + }; + /** + * Map each element of an array from its folded value to all possible unicode matches + */ + const mapSequence = (strings, min_replacement = 1) => { + let chars_replaced = 0; + strings = strings.map((str) => { + if (unicode_map[str]) { + chars_replaced += str.length; + } + return unicode_map[str] || str; + }); + if (chars_replaced >= min_replacement) { + return sequencePattern(strings); + } + return ''; + }; + /** + * Convert a short string and split it into all possible patterns + * Keep a pattern only if min_replacement is met + * + * 'abc' + * => [['abc'],['ab','c'],['a','bc'],['a','b','c']] + * => ['abc-pattern','ab-c-pattern'...] + */ + const substringsToPattern = (str, min_replacement = 1) => { + min_replacement = Math.max(min_replacement, str.length - 1); + return arrayToPattern(allSubstrings(str).map((sub_pat) => { + return mapSequence(sub_pat, min_replacement); + })); + }; + /** + * Convert an array of sequences into a pattern + * [{start:0,end:3,length:3,substr:'iii'}...] => (?:iii...) + */ + const sequencesToPattern = (sequences, all = true) => { + let min_replacement = sequences.length > 1 ? 1 : 0; + return arrayToPattern(sequences.map((sequence) => { + let seq = []; + const len = all ? sequence.length() : sequence.length() - 1; + for (let j = 0; j < len; j++) { + seq.push(substringsToPattern(sequence.substrs[j] || '', min_replacement)); + } + return sequencePattern(seq); + })); + }; + /** + * Return true if the sequence is already in the sequences + */ + const inSequences = (needle_seq, sequences) => { + for (const seq of sequences) { + if (seq.start != needle_seq.start || seq.end != needle_seq.end) { + continue; + } + if (seq.substrs.join('') !== needle_seq.substrs.join('')) { + continue; + } + let needle_parts = needle_seq.parts; + const filter = (part) => { + for (const needle_part of needle_parts) { + if (needle_part.start === part.start && needle_part.substr === part.substr) { + return false; + } + if (part.length == 1 || needle_part.length == 1) { + continue; + } + // check for overlapping parts + // a = ['::=','=='] + // b = ['::','==='] + // a = ['r','sm'] + // b = ['rs','m'] + if (part.start < needle_part.start && part.end > needle_part.start) { + return true; + } + if (needle_part.start < part.start && needle_part.end > part.start) { + return true; + } + } + return false; + }; + let filtered = seq.parts.filter(filter); + if (filtered.length > 0) { + continue; + } + return true; + } + return false; + }; + class Sequence { + parts; + substrs; + start; + end; + constructor() { + this.parts = []; + this.substrs = []; + this.start = 0; + this.end = 0; + } + add(part) { + if (part) { + this.parts.push(part); + this.substrs.push(part.substr); + this.start = Math.min(part.start, this.start); + this.end = Math.max(part.end, this.end); + } + } + last() { + return this.parts[this.parts.length - 1]; + } + length() { + return this.parts.length; + } + clone(position, last_piece) { + let clone = new Sequence(); + let parts = JSON.parse(JSON.stringify(this.parts)); + let last_part = parts.pop(); + for (const part of parts) { + clone.add(part); + } + let last_substr = last_piece.substr.substring(0, position - last_part.start); + let clone_last_len = last_substr.length; + clone.add({ start: last_part.start, end: last_part.start + clone_last_len, length: clone_last_len, substr: last_substr }); + return clone; + } + } + /** + * Expand a regular expression pattern to include unicode variants + * eg /a/ becomes /aⓐaẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐɑAⒶAÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄȺⱯ/ + * + * Issue: + * ﺊﺋ [ 'ﺊ = \\u{fe8a}', 'ﺋ = \\u{fe8b}' ] + * becomes: ئئ [ 'ي = \\u{64a}', 'ٔ = \\u{654}', 'ي = \\u{64a}', 'ٔ = \\u{654}' ] + * + * İIJ = IIJ = ⅡJ + * + * 1/2/4 + */ + const getPattern = (str) => { + initialize(); + str = asciifold(str); + let pattern = ''; + let sequences = [new Sequence()]; + for (let i = 0; i < str.length; i++) { + let substr = str.substring(i); + let match = substr.match(multi_char_reg); + const char = str.substring(i, i + 1); + const match_str = match ? match[0] : null; + // loop through sequences + // add either the char or multi_match + let overlapping = []; + let added_types = new Set(); + for (const sequence of sequences) { + const last_piece = sequence.last(); + if (!last_piece || last_piece.length == 1 || last_piece.end <= i) { + // if we have a multi match + if (match_str) { + const len = match_str.length; + sequence.add({ start: i, end: i + len, length: len, substr: match_str }); + added_types.add('1'); + } + else { + sequence.add({ start: i, end: i + 1, length: 1, substr: char }); + added_types.add('2'); + } + } + else if (match_str) { + let clone = sequence.clone(i, last_piece); + const len = match_str.length; + clone.add({ start: i, end: i + len, length: len, substr: match_str }); + overlapping.push(clone); + } + else { + // don't add char + // adding would create invalid patterns: 234 => [2,34,4] + added_types.add('3'); + } + } + // if we have overlapping + if (overlapping.length > 0) { + // ['ii','iii'] before ['i','i','iii'] + overlapping = overlapping.sort((a, b) => { + return a.length() - b.length(); + }); + for (let clone of overlapping) { + // don't add if we already have an equivalent sequence + if (inSequences(clone, sequences)) { + continue; + } + sequences.push(clone); + } + continue; + } + // if we haven't done anything unique + // clean up the patterns + // helps keep patterns smaller + // if str = 'r₨㎧aarss', pattern will be 446 instead of 655 + if (i > 0 && added_types.size == 1 && !added_types.has('3')) { + pattern += sequencesToPattern(sequences, false); + let new_seq = new Sequence(); + const old_seq = sequences[0]; + if (old_seq) { + new_seq.add(old_seq.last()); + } + sequences = [new_seq]; + } + } + pattern += sequencesToPattern(sequences, true); + return pattern; + }; + + /** + * A property getter resolving dot-notation + * @param {Object} obj The root object to fetch property on + * @param {String} name The optionally dotted property name to fetch + * @return {Object} The resolved property value + */ + const getAttr = (obj, name) => { + if (!obj) + return; + return obj[name]; + }; + /** + * A property getter resolving dot-notation + * @param {Object} obj The root object to fetch property on + * @param {String} name The optionally dotted property name to fetch + * @return {Object} The resolved property value + */ + const getAttrNesting = (obj, name) => { + if (!obj) + return; + var part, names = name.split("."); + while ((part = names.shift()) && (obj = obj[part])) + ; + return obj; + }; + /** + * Calculates how close of a match the + * given value is against a search token. + * + */ + const scoreValue = (value, token, weight) => { + var score, pos; + if (!value) + return 0; + value = value + ''; + if (token.regex == null) + return 0; + pos = value.search(token.regex); + if (pos === -1) + return 0; + score = token.string.length / value.length; + if (pos === 0) + score += 0.5; + return score * weight; + }; + /** + * Cast object property to an array if it exists and has a value + * + */ + const propToArray = (obj, key) => { + var value = obj[key]; + if (typeof value == 'function') + return value; + if (value && !Array.isArray(value)) { + obj[key] = [value]; + } + }; + /** + * Iterates over arrays and hashes. + * + * ``` + * iterate(this.items, function(item, id) { + * // invoked for each item + * }); + * ``` + * + */ + const iterate$1 = (object, callback) => { + if (Array.isArray(object)) { + object.forEach(callback); + } + else { + for (var key in object) { + if (object.hasOwnProperty(key)) { + callback(object[key], key); + } + } + } + }; + const cmp = (a, b) => { + if (typeof a === 'number' && typeof b === 'number') { + return a > b ? 1 : (a < b ? -1 : 0); + } + a = asciifold(a + '').toLowerCase(); + b = asciifold(b + '').toLowerCase(); + if (a > b) + return 1; + if (b > a) + return -1; + return 0; + }; + + /** + * sifter.js + * Copyright (c) 2013–2020 Brian Reavis & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * @author Brian Reavis + */ + class Sifter { + items; // []|{}; + settings; + /** + * Textually searches arrays and hashes of objects + * by property (or multiple properties). Designed + * specifically for autocomplete. + * + */ + constructor(items, settings) { + this.items = items; + this.settings = settings || { diacritics: true }; + } + ; + /** + * Splits a search string into an array of individual + * regexps to be used to match results. + * + */ + tokenize(query, respect_word_boundaries, weights) { + if (!query || !query.length) + return []; + const tokens = []; + const words = query.split(/\s+/); + var field_regex; + if (weights) { + field_regex = new RegExp('^(' + Object.keys(weights).map(escape_regex).join('|') + ')\:(.*)$'); + } + words.forEach((word) => { + let field_match; + let field = null; + let regex = null; + // look for "field:query" tokens + if (field_regex && (field_match = word.match(field_regex))) { + field = field_match[1]; + word = field_match[2]; + } + if (word.length > 0) { + if (this.settings.diacritics) { + regex = getPattern(word) || null; + } + else { + regex = escape_regex(word); + } + if (regex && respect_word_boundaries) + regex = "\\b" + regex; + } + tokens.push({ + string: word, + regex: regex ? new RegExp(regex, 'iu') : null, + field: field, + }); + }); + return tokens; + } + ; + /** + * Returns a function to be used to score individual results. + * + * Good matches will have a higher score than poor matches. + * If an item is not a match, 0 will be returned by the function. + * + * @returns {T.ScoreFn} + */ + getScoreFunction(query, options) { + var search = this.prepareSearch(query, options); + return this._getScoreFunction(search); + } + /** + * @returns {T.ScoreFn} + * + */ + _getScoreFunction(search) { + const tokens = search.tokens, token_count = tokens.length; + if (!token_count) { + return function () { return 0; }; + } + const fields = search.options.fields, weights = search.weights, field_count = fields.length, getAttrFn = search.getAttrFn; + if (!field_count) { + return function () { return 1; }; + } + /** + * Calculates the score of an object + * against the search query. + * + */ + const scoreObject = (function () { + if (field_count === 1) { + return function (token, data) { + const field = fields[0].field; + return scoreValue(getAttrFn(data, field), token, weights[field] || 1); + }; + } + return function (token, data) { + var sum = 0; + // is the token specific to a field? + if (token.field) { + const value = getAttrFn(data, token.field); + if (!token.regex && value) { + sum += (1 / field_count); + } + else { + sum += scoreValue(value, token, 1); + } + } + else { + iterate$1(weights, (weight, field) => { + sum += scoreValue(getAttrFn(data, field), token, weight); + }); + } + return sum / field_count; + }; + })(); + if (token_count === 1) { + return function (data) { + return scoreObject(tokens[0], data); + }; + } + if (search.options.conjunction === 'and') { + return function (data) { + var score, sum = 0; + for (let token of tokens) { + score = scoreObject(token, data); + if (score <= 0) + return 0; + sum += score; + } + return sum / token_count; + }; + } + else { + return function (data) { + var sum = 0; + iterate$1(tokens, (token) => { + sum += scoreObject(token, data); + }); + return sum / token_count; + }; + } + } + ; + /** + * Returns a function that can be used to compare two + * results, for sorting purposes. If no sorting should + * be performed, `null` will be returned. + * + * @return function(a,b) + */ + getSortFunction(query, options) { + var search = this.prepareSearch(query, options); + return this._getSortFunction(search); + } + _getSortFunction(search) { + var implicit_score, sort_flds = []; + const self = this, options = search.options, sort = (!search.query && options.sort_empty) ? options.sort_empty : options.sort; + if (typeof sort == 'function') { + return sort.bind(this); + } + /** + * Fetches the specified sort field value + * from a search result item. + * + */ + const get_field = function (name, result) { + if (name === '$score') + return result.score; + return search.getAttrFn(self.items[result.id], name); + }; + // parse options + if (sort) { + for (let s of sort) { + if (search.query || s.field !== '$score') { + sort_flds.push(s); + } + } + } + // the "$score" field is implied to be the primary + // sort field, unless it's manually specified + if (search.query) { + implicit_score = true; + for (let fld of sort_flds) { + if (fld.field === '$score') { + implicit_score = false; + break; + } + } + if (implicit_score) { + sort_flds.unshift({ field: '$score', direction: 'desc' }); + } + // without a search.query, all items will have the same score + } + else { + sort_flds = sort_flds.filter((fld) => fld.field !== '$score'); + } + // build function + const sort_flds_count = sort_flds.length; + if (!sort_flds_count) { + return null; + } + return function (a, b) { + var result, field; + for (let sort_fld of sort_flds) { + field = sort_fld.field; + let multiplier = sort_fld.direction === 'desc' ? -1 : 1; + result = multiplier * cmp(get_field(field, a), get_field(field, b)); + if (result) + return result; + } + return 0; + }; + } + ; + /** + * Parses a search query and returns an object + * with tokens and fields ready to be populated + * with results. + * + */ + prepareSearch(query, optsUser) { + const weights = {}; + var options = Object.assign({}, optsUser); + propToArray(options, 'sort'); + propToArray(options, 'sort_empty'); + // convert fields to new format + if (options.fields) { + propToArray(options, 'fields'); + const fields = []; + options.fields.forEach((field) => { + if (typeof field == 'string') { + field = { field: field, weight: 1 }; + } + fields.push(field); + weights[field.field] = ('weight' in field) ? field.weight : 1; + }); + options.fields = fields; + } + return { + options: options, + query: query.toLowerCase().trim(), + tokens: this.tokenize(query, options.respect_word_boundaries, weights), + total: 0, + items: [], + weights: weights, + getAttrFn: (options.nesting) ? getAttrNesting : getAttr, + }; + } + ; + /** + * Searches through all items and returns a sorted array of matches. + * + */ + search(query, options) { + var self = this, score, search; + search = this.prepareSearch(query, options); + options = search.options; + query = search.query; + // generate result scoring function + const fn_score = options.score || self._getScoreFunction(search); + // perform search and sort + if (query.length) { + iterate$1(self.items, (item, id) => { + score = fn_score(item); + if (options.filter === false || score > 0) { + search.items.push({ 'score': score, 'id': id }); + } + }); + } + else { + iterate$1(self.items, (_, id) => { + search.items.push({ 'score': 1, 'id': id }); + }); + } + const fn_sort = self._getSortFunction(search); + if (fn_sort) + search.items.sort(fn_sort); + // apply limits + search.total = search.items.length; + if (typeof options.limit === 'number') { + search.items = search.items.slice(0, options.limit); + } + return search; + } + ; + } + + /** + * Converts a scalar to its best string representation + * for hash keys and HTML attribute values. + * + * Transformations: + * 'str' -> 'str' + * null -> '' + * undefined -> '' + * true -> '1' + * false -> '0' + * 0 -> '0' + * 1 -> '1' + * + */ + const hash_key = value => { + if (typeof value === 'undefined' || value === null) return null; + return get_hash(value); + }; + const get_hash = value => { + if (typeof value === 'boolean') return value ? '1' : '0'; + return value + ''; + }; + + /** + * Escapes a string for use within HTML. + * + */ + const escape_html = str => { + return (str + '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + }; + + /** + * use setTimeout if timeout > 0 + */ + const timeout = (fn, timeout) => { + if (timeout > 0) { + return window.setTimeout(fn, timeout); + } + fn.call(null); + return null; + }; + + /** + * Debounce the user provided load function + * + */ + const loadDebounce = (fn, delay) => { + var timeout; + return function (value, callback) { + var self = this; + if (timeout) { + self.loading = Math.max(self.loading - 1, 0); + clearTimeout(timeout); + } + timeout = setTimeout(function () { + timeout = null; + self.loadedSearches[value] = true; + fn.call(self, value, callback); + }, delay); + }; + }; + + /** + * Debounce all fired events types listed in `types` + * while executing the provided `fn`. + * + */ + const debounce_events = (self, types, fn) => { + var type; + var trigger = self.trigger; + var event_args = {}; + + // override trigger method + self.trigger = function () { + var type = arguments[0]; + if (types.indexOf(type) !== -1) { + event_args[type] = arguments; + } else { + return trigger.apply(self, arguments); + } + }; + + // invoke provided function + fn.apply(self, []); + self.trigger = trigger; + + // trigger queued events + for (type of types) { + if (type in event_args) { + trigger.apply(self, event_args[type]); + } + } + }; + + /** + * Determines the current selection within a text input control. + * Returns an object containing: + * - start + * - length + * + * Note: "selectionStart, selectionEnd ... apply only to inputs of types text, search, URL, tel and password" + * - https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange + */ + const getSelection = input => { + return { + start: input.selectionStart || 0, + length: (input.selectionEnd || 0) - (input.selectionStart || 0) + }; + }; + + /** + * Prevent default + * + */ + const preventDefault = (evt, stop = false) => { + if (evt) { + evt.preventDefault(); + if (stop) { + evt.stopPropagation(); + } + } + }; + + /** + * Add event helper + * + */ + const addEvent = (target, type, callback, options) => { + target.addEventListener(type, callback, options); + }; + + /** + * Return true if the requested key is down + * Will return false if more than one control character is pressed ( when [ctrl+shift+a] != [ctrl+a] ) + * The current evt may not always set ( eg calling advanceSelection() ) + * + */ + const isKeyDown = (key_name, evt) => { + if (!evt) { + return false; + } + if (!evt[key_name]) { + return false; + } + var count = (evt.altKey ? 1 : 0) + (evt.ctrlKey ? 1 : 0) + (evt.shiftKey ? 1 : 0) + (evt.metaKey ? 1 : 0); + if (count === 1) { + return true; + } + return false; + }; + + /** + * Get the id of an element + * If the id attribute is not set, set the attribute with the given id + * + */ + const getId = (el, id) => { + const existing_id = el.getAttribute('id'); + if (existing_id) { + return existing_id; + } + el.setAttribute('id', id); + return id; + }; + + /** + * Returns a string with backslashes added before characters that need to be escaped. + */ + const addSlashes = str => { + return str.replace(/[\\"']/g, '\\$&'); + }; + + /** + * + */ + const append = (parent, node) => { + if (node) parent.append(node); + }; + + /** + * Iterates over arrays and hashes. + * + * ``` + * iterate(this.items, function(item, id) { + * // invoked for each item + * }); + * ``` + * + */ + const iterate = (object, callback) => { + if (Array.isArray(object)) { + object.forEach(callback); + } else { + for (var key in object) { + if (object.hasOwnProperty(key)) { + callback(object[key], key); + } + } + } + }; + + /** + * Return a dom element from either a dom query string, jQuery object, a dom element or html string + * https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518 + * + * param query should be {} + */ + const getDom = query => { + if (query.jquery) { + return query[0]; + } + if (query instanceof HTMLElement) { + return query; + } + if (isHtmlString(query)) { + var tpl = document.createElement('template'); + tpl.innerHTML = query.trim(); // Never return a text node of whitespace as the result + return tpl.content.firstChild; + } + return document.querySelector(query); + }; + const isHtmlString = arg => { + if (typeof arg === 'string' && arg.indexOf('<') > -1) { + return true; + } + return false; + }; + const escapeQuery = query => { + return query.replace(/['"\\]/g, '\\$&'); + }; + + /** + * Dispatch an event + * + */ + const triggerEvent = (dom_el, event_name) => { + var event = document.createEvent('HTMLEvents'); + event.initEvent(event_name, true, false); + dom_el.dispatchEvent(event); + }; + + /** + * Apply CSS rules to a dom element + * + */ + const applyCSS = (dom_el, css) => { + Object.assign(dom_el.style, css); + }; + + /** + * Add css classes + * + */ + const addClasses = (elmts, ...classes) => { + var norm_classes = classesArray(classes); + elmts = castAsArray(elmts); + elmts.map(el => { + norm_classes.map(cls => { + el.classList.add(cls); + }); + }); + }; + + /** + * Remove css classes + * + */ + const removeClasses = (elmts, ...classes) => { + var norm_classes = classesArray(classes); + elmts = castAsArray(elmts); + elmts.map(el => { + norm_classes.map(cls => { + el.classList.remove(cls); + }); + }); + }; + + /** + * Return arguments + * + */ + const classesArray = args => { + var classes = []; + iterate(args, _classes => { + if (typeof _classes === 'string') { + _classes = _classes.trim().split(/[\t\n\f\r\s]/); + } + if (Array.isArray(_classes)) { + classes = classes.concat(_classes); + } + }); + return classes.filter(Boolean); + }; + + /** + * Create an array from arg if it's not already an array + * + */ + const castAsArray = arg => { + if (!Array.isArray(arg)) { + arg = [arg]; + } + return arg; + }; + + /** + * Get the closest node to the evt.target matching the selector + * Stops at wrapper + * + */ + const parentMatch = (target, selector, wrapper) => { + if (wrapper && !wrapper.contains(target)) { + return; + } + while (target && target.matches) { + if (target.matches(selector)) { + return target; + } + target = target.parentNode; + } + }; + + /** + * Get the first or last item from an array + * + * > 0 - right (last) + * <= 0 - left (first) + * + */ + const getTail = (list, direction = 0) => { + if (direction > 0) { + return list[list.length - 1]; + } + return list[0]; + }; + + /** + * Return true if an object is empty + * + */ + const isEmptyObject = obj => { + return Object.keys(obj).length === 0; + }; + + /** + * Get the index of an element amongst sibling nodes of the same type + * + */ + const nodeIndex = (el, amongst) => { + if (!el) return -1; + amongst = amongst || el.nodeName; + var i = 0; + while (el = el.previousElementSibling) { + if (el.matches(amongst)) { + i++; + } + } + return i; + }; + + /** + * Set attributes of an element + * + */ + const setAttr = (el, attrs) => { + iterate(attrs, (val, attr) => { + if (val == null) { + el.removeAttribute(attr); + } else { + el.setAttribute(attr, '' + val); + } + }); + }; + + /** + * Replace a node + */ + const replaceNode = (existing, replacement) => { + if (existing.parentNode) existing.parentNode.replaceChild(replacement, existing); + }; + + /** + * highlight v3 | MIT license | Johann Burkard + * Highlights arbitrary terms in a node. + * + * - Modified by Marshal 2011-6-24 (added regex) + * - Modified by Brian Reavis 2012-8-27 (cleanup) + */ + + const highlight = (element, regex) => { + if (regex === null) return; + + // convet string to regex + if (typeof regex === 'string') { + if (!regex.length) return; + regex = new RegExp(regex, 'i'); + } + + // Wrap matching part of text node with highlighting , e.g. + // Soccer -> Soccer for regex = /soc/i + const highlightText = node => { + var match = node.data.match(regex); + if (match && node.data.length > 0) { + var spannode = document.createElement('span'); + spannode.className = 'highlight'; + var middlebit = node.splitText(match.index); + middlebit.splitText(match[0].length); + var middleclone = middlebit.cloneNode(true); + spannode.appendChild(middleclone); + replaceNode(middlebit, spannode); + return 1; + } + return 0; + }; + + // Recurse element node, looking for child text nodes to highlight, unless element + // is childless,