katalyst-tables 3.3.0 → 3.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/assets/builds/katalyst/tables.esm.js +332 -6
  4. data/app/assets/builds/katalyst/tables.js +332 -6
  5. data/app/assets/builds/katalyst/tables.min.js +1 -1
  6. data/app/assets/builds/katalyst/tables.min.js.map +1 -1
  7. data/app/assets/stylesheets/katalyst/tables/_index.scss +1 -1
  8. data/app/assets/stylesheets/katalyst/tables/_query.scss +109 -0
  9. data/app/assets/stylesheets/katalyst/tables/typed-columns/_date.scss +1 -1
  10. data/app/assets/stylesheets/katalyst/tables/typed-columns/_datetime.scss +1 -1
  11. data/app/components/katalyst/table_component.rb +13 -2
  12. data/app/components/katalyst/tables/cells/currency_component.rb +21 -2
  13. data/app/components/katalyst/tables/cells/number_component.rb +31 -1
  14. data/app/components/katalyst/tables/query/input_component.html.erb +12 -0
  15. data/app/components/katalyst/tables/query/input_component.rb +46 -0
  16. data/app/components/katalyst/tables/query/modal_component.html.erb +33 -0
  17. data/app/components/katalyst/tables/query/modal_component.rb +89 -0
  18. data/app/components/katalyst/tables/query_component.html.erb +8 -0
  19. data/app/components/katalyst/tables/{filter_component.rb → query_component.rb} +37 -30
  20. data/app/controllers/concerns/katalyst/tables/backend.rb +14 -0
  21. data/app/helpers/katalyst/tables/frontend.rb +14 -8
  22. data/app/javascript/tables/application.js +8 -3
  23. data/app/javascript/tables/query_controller.js +108 -0
  24. data/app/javascript/tables/query_input_controller.js +228 -0
  25. data/app/models/concerns/katalyst/tables/collection/core.rb +5 -3
  26. data/app/models/concerns/katalyst/tables/collection/query/array_value_parser.rb +13 -26
  27. data/app/models/concerns/katalyst/tables/collection/query/parser.rb +16 -13
  28. data/app/models/concerns/katalyst/tables/collection/query/single_value_parser.rb +11 -3
  29. data/app/models/concerns/katalyst/tables/collection/query/value_parser.rb +10 -5
  30. data/app/models/concerns/katalyst/tables/collection/query.rb +44 -6
  31. data/app/models/concerns/katalyst/tables/collection/sorting.rb +11 -1
  32. data/app/models/katalyst/tables/collection/base.rb +10 -0
  33. data/app/models/katalyst/tables/collection/filter.rb +10 -0
  34. data/app/models/katalyst/tables/collection/type/boolean.rb +11 -1
  35. data/app/models/katalyst/tables/collection/type/date.rb +19 -22
  36. data/app/models/katalyst/tables/collection/type/enum.rb +11 -0
  37. data/app/models/katalyst/tables/collection/type/float.rb +3 -39
  38. data/app/models/katalyst/tables/collection/type/helpers/delegate.rb +2 -22
  39. data/app/models/katalyst/tables/collection/type/helpers/extensions.rb +14 -0
  40. data/app/models/katalyst/tables/collection/type/helpers/multiple.rb +30 -0
  41. data/app/models/katalyst/tables/collection/type/helpers/range.rb +59 -0
  42. data/app/models/katalyst/tables/collection/type/integer.rb +3 -39
  43. data/app/models/katalyst/tables/collection/type/value.rb +22 -2
  44. metadata +12 -8
  45. data/app/assets/stylesheets/katalyst/tables/_filter.scss +0 -43
  46. data/app/components/katalyst/tables/filter/modal_component.html.erb +0 -25
  47. data/app/components/katalyst/tables/filter/modal_component.rb +0 -112
  48. data/app/components/katalyst/tables/filter_component.html.erb +0 -18
  49. data/app/javascript/tables/filter/modal_controller.js +0 -13
@@ -1,4 +1,4 @@
1
1
  // where psuedo selector has a specificity of 0 so it can be easily overwritten with simple selectors
2
2
  :where(th.type-date, td.type-date) {
3
- width: var(--width-medium);
3
+ width: var(--width-small);
4
4
  }
@@ -1,4 +1,4 @@
1
1
  // where psuedo selector has a specificity of 0 so it can be easily overwritten with simple selectors
2
- :where(th.type-datetime, td.type-date) {
2
+ :where(th.type-datetime, td.type-datetime) {
3
3
  width: var(--width-medium);
4
4
  }
@@ -239,9 +239,20 @@ module Katalyst
239
239
 
240
240
  # Generates a column from numeric values formatted appropriately.
241
241
  #
242
+ # Supports Rails' built in number formatters, i.e.
243
+ # * +phone+: ActiveSupport::NumberHelper#number_to_phone
244
+ # * +currency+: ActiveSupport::NumberHelper#number_to_currency
245
+ # * +percentage+: ActiveSupport::NumberHelper#number_to_percentage
246
+ # * +delimited+: ActiveSupport::NumberHelper#number_to_delimited
247
+ # * +rounded+: ActiveSupport::NumberHelper#number_to_rounded
248
+ # * +human_size+: ActiveSupport::NumberHelper#number_to_human_size
249
+ # * +human+: ActiveSupport::NumberHelper#number_to_human
250
+ #
242
251
  # @param column [Symbol] the column's name, called as a method on the record
243
252
  # @param label [String|nil] the label to use for the column header
244
253
  # @param heading [boolean] if true, data cells will use `th` tags
254
+ # @param format [String|Symbol] Rails number_to_X format option, defaults to +delimited+
255
+ # @param options [Hash] options to be passed to `number_to_<format>`
245
256
  # @param ** [Hash] HTML attributes to be added to column cells
246
257
  # @param & [Proc] optional block to alter the cell content
247
258
  #
@@ -252,9 +263,9 @@ module Katalyst
252
263
  #
253
264
  # @example Render the number of comments on a post
254
265
  # <% row.number :comment_count %> # => <td>0</td>
255
- def number(column, label: nil, heading: false, **, &)
266
+ def number(column, label: nil, heading: false, format: :delimited, options: {}, **, &)
256
267
  with_cell(Tables::Cells::NumberComponent.new(
257
- collection:, row:, column:, record:, label:, heading:, **,
268
+ collection:, row:, column:, record:, label:, heading:, format:, options:, **,
258
269
  ), &)
259
270
  end
260
271
 
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "bigdecimal/util"
4
+
3
5
  module Katalyst
4
6
  module Tables
5
7
  module Cells
6
8
  # Formats the value as a money value
7
9
  #
8
- # The value is expected to be in cents.
10
+ # The value is assumed to be cents if integer, or dollars if float or
11
+ # decimal. Also supports RubyMoney type if defined.
12
+ #
9
13
  # Adds a class to the cell to allow for custom styling
10
14
  class CurrencyComponent < CellComponent
11
15
  def initialize(options:, **)
@@ -15,7 +19,22 @@ module Katalyst
15
19
  end
16
20
 
17
21
  def rendered_value
18
- value.present? ? number_to_currency(value / 100.0, @options) : ""
22
+ format(value)
23
+ end
24
+
25
+ def format(value)
26
+ value.present? ? number_to_currency(value, @options) : ""
27
+ end
28
+
29
+ def value
30
+ case (v = super)
31
+ when nil
32
+ nil
33
+ when Integer
34
+ (super.to_d / BigDecimal("100"))
35
+ else
36
+ (v.to_d rescue nil) # rubocop:disable Style/RescueModifier
37
+ end
19
38
  end
20
39
 
21
40
  private
@@ -7,8 +7,38 @@ module Katalyst
7
7
  #
8
8
  # Adds a class to the cell to allow for custom styling
9
9
  class NumberComponent < CellComponent
10
+ include ActiveSupport::NumberHelper
11
+
12
+ def initialize(format:, options:, **)
13
+ super(**)
14
+
15
+ @format = format
16
+ @options = options
17
+ end
18
+
19
+ def format(value)
20
+ case @format
21
+ when :phone
22
+ number_to_phone(value, @options)
23
+ when :currency
24
+ number_to_currency(value, @options)
25
+ when :percentage
26
+ number_to_percentage(value, @options)
27
+ when :delimited
28
+ number_to_delimited(value, @options)
29
+ when :rounded
30
+ number_to_rounded(value, @options)
31
+ when :human_size
32
+ number_to_human_size(value, @options)
33
+ when :human
34
+ number_to_human(value, @options)
35
+ else
36
+ raise ArgumentError, "Unsupported format #{@format}"
37
+ end
38
+ end
39
+
10
40
  def rendered_value
11
- value.present? ? number_to_human(value) : ""
41
+ value.present? ? format(value) : ""
12
42
  end
13
43
 
14
44
  private
@@ -0,0 +1,12 @@
1
+ <div class="query-input" data-controller="tables--query-input" data-turbo-permanent>
2
+ <%= form.label name, "Search", hidden: "" %>
3
+ <%= form.text_field(
4
+ name,
5
+ type: :search,
6
+ autocomplete: "off",
7
+ **html_attributes,
8
+ ) %>
9
+ <div aria-hidden="true" class="highlight" data-tables--query-input-target="highlight"></div>
10
+ <%= form.hidden_field(:p) %>
11
+ <%= form.button("Apply", type: :submit, name: nil, tabindex: -1) %>
12
+ </div>
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Katalyst
4
+ module Tables
5
+ module Query
6
+ class InputComponent < ViewComponent::Base
7
+ include Katalyst::HtmlAttributes
8
+
9
+ attr_reader :form
10
+
11
+ def initialize(form:, **)
12
+ super(**)
13
+
14
+ @form = form
15
+ end
16
+
17
+ def name
18
+ query_attribute || raise(ArgumentError, "No query attribute. " \
19
+ "Does your collection include Katalyst::Tables::Collection::Query?")
20
+ end
21
+
22
+ def collection
23
+ form.object
24
+ end
25
+
26
+ def default_html_attributes
27
+ {
28
+ data: {
29
+ action: %w[
30
+ input->tables--query-input#update
31
+ keyup.enter->tables--query#closeModal
32
+ ],
33
+ tables__query_input_target: "input",
34
+ },
35
+ }
36
+ end
37
+
38
+ private
39
+
40
+ def query_attribute
41
+ collection.class.attribute_types.detect { |_, a| a.type == :query }&.first
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,33 @@
1
+ <%= tag.section(**html_attributes) do %>
2
+ <% if collection.errors.any? %>
3
+ <header>
4
+ <% collection.errors.each do |error| %>
5
+ <% next unless error.type == :unknown %>
6
+ <div class="error">Sorry, we don&rsquo;t support the <code><%= error.attribute %></code> filter.</div>
7
+ <% end %>
8
+ </header>
9
+ <% end %>
10
+ <div class="content">
11
+ <% if show_values? %>
12
+ <h4>Possible values for <code><%= current_key %>:</code></h4>
13
+ <ul>
14
+ <% values_for(current_key).each do |value| %>
15
+ <li><code><%= format_value(value) %></code></li>
16
+ <% end %>
17
+ </ul>
18
+ <% else %>
19
+ <h4>Available filters:</h4>
20
+ <dl>
21
+ <% available_filters.each do |key, description| %>
22
+ <dt><code><%= key %>:</code></dt>
23
+ <dd>Filter on values for <%= description.downcase %></dd>
24
+ <% end %>
25
+ </dl>
26
+ <% end %>
27
+ </div>
28
+ <% if footer? %>
29
+ <footer>
30
+ <%= footer %>
31
+ </footer>
32
+ <% end %>
33
+ <% end %>
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Katalyst
4
+ module Tables
5
+ module Query
6
+ class ModalComponent < ViewComponent::Base
7
+ include Katalyst::HtmlAttributes
8
+ include Katalyst::Tables::Frontend
9
+
10
+ renders_one :footer
11
+
12
+ attr_reader :collection, :url
13
+
14
+ def initialize(collection:, **)
15
+ super(**)
16
+
17
+ @collection = collection
18
+ end
19
+
20
+ private
21
+
22
+ def default_html_attributes
23
+ {
24
+ class: "query-modal",
25
+ data: {
26
+ tables__query_target: "modal",
27
+ action: ["turbo:before-morph-attribute->tables--query#beforeMorphAttribute"],
28
+ },
29
+ }
30
+ end
31
+
32
+ using Collection::Type::Helpers::Extensions
33
+
34
+ def show_values?
35
+ current_key && attributes[current_key]
36
+ end
37
+
38
+ def current_key
39
+ unless instance_variable_defined?(:@current_key)
40
+ attributes.each_key do |key|
41
+ @current_key = key if collection.query_active?(key)
42
+ end
43
+ end
44
+
45
+ @current_key ||= nil
46
+ end
47
+
48
+ def attributes
49
+ collection.class.attribute_types
50
+ .select { |_, a| a.filterable? && a.type != :search }
51
+ .to_h
52
+ end
53
+
54
+ def available_filters
55
+ keys = attributes.keys
56
+
57
+ if current_token.present?
58
+ keys = keys.select { |k| k.include?(current_token) }
59
+ end
60
+
61
+ keys.map do |key|
62
+ [key, collection.model.human_attribute_name(key)]
63
+ end
64
+ end
65
+
66
+ def values_for(key)
67
+ collection.examples_for(key).map(&:to_s).compact_blank
68
+ end
69
+
70
+ def format_value(value)
71
+ if /\A[\w.-]*\z/.match?(value)
72
+ value
73
+ else
74
+ %("#{value}")
75
+ end
76
+ end
77
+
78
+ def current_token
79
+ return nil unless collection.position&.in?(0..collection.query.length)
80
+
81
+ prefix = collection.query[...collection.position].match(/\w*\z/)
82
+ suffix = collection.query[collection.position..].match(/\A\w*/)
83
+
84
+ "#{prefix}#{suffix}"
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,8 @@
1
+ <% if content? %>
2
+ <%= content %>
3
+ <% else %>
4
+ <%= form do |form| %>
5
+ <%= render query_input(form:) %>
6
+ <%= modal %>
7
+ <% end %>
8
+ <% end %>
@@ -3,7 +3,7 @@
3
3
  module Katalyst
4
4
  module Tables
5
5
  # A component for rendering a data driven filter for a collection.
6
- # <%= Katalyst::Tables::FilterComponent.new(collection: @people, url: peoples_path) %>
6
+ # <%= Katalyst::Tables::QueryComponent.new(collection: @people, url: peoples_path) %>
7
7
  #
8
8
  # By default, the component will render a form containing a single text field. Interacting with the
9
9
  # text field will display a dropdown outlining all available keys and values to be filtered on.
@@ -13,30 +13,29 @@ module Katalyst
13
13
  # to ensure the correct attributes and default form fields are collected.
14
14
  # You can pass additional options to the `form` method to modify it.
15
15
  #
16
- # <%= Katalyst::Tables::FilterComponent.new(collection: @people, url: peoples_path) do |filter| %>
17
- # <%= filter.form(builder: GOVUKFormBuilder) do |form| %>
18
- # <%= form.govuk_text_field :query %>
19
- # <%= form.govuk_submit "Apply" %>
16
+ # <%= Katalyst::Tables::QueryComponent.new(collection: @people, url: peoples_path) do |query| %>
17
+ # <%= query.form(builder: GOVUKFormBuilder) do |form| %>
18
+ # <%= form.govuk_text_field :q %>
19
+ # <%= form.govuk_submit "Apply" %>
20
+ # <%= modal %>
20
21
  # <% end %>
21
22
  # <% end %>
22
23
  #
23
- #
24
24
  # Additionally the component allows for access to the dropdown that displays when interacting with the input.
25
25
  # The dropdown supports additional "footer" content to be added.
26
26
  #
27
- # <%= Katalyst::Tables::FilterComponent.new(collection: @people, url: peoples_path) do |filter| %>
28
- # <% filter.with_modal(collection:) do |modal| %>
29
- # <% modal.with_footer do %>
30
- # <%= link_to "Docs", docs_path %>
31
- # <% end %>
27
+ # <%= Katalyst::Tables::QueryComponent.new(collection: @people, url: peoples_path) do |query| %>
28
+ # <% query.with_modal(collection:) do |modal| %>
29
+ # <% modal.with_footer do %>
30
+ # <%= link_to "Docs", docs_path %>
31
+ # <% end %>
32
32
  # <% end %>
33
33
  # <% end %>
34
- #
35
- class FilterComponent < ViewComponent::Base
34
+ class QueryComponent < ViewComponent::Base
36
35
  include Katalyst::HtmlAttributes
37
36
  include Katalyst::Tables::Frontend
38
37
 
39
- renders_one :modal, Katalyst::Tables::Filter::ModalComponent
38
+ renders_one :modal, Katalyst::Tables::Query::ModalComponent
40
39
 
41
40
  define_html_attribute_methods :input_attributes
42
41
 
@@ -57,32 +56,40 @@ module Katalyst
57
56
  form_with(model: collection,
58
57
  url:,
59
58
  method: :get,
60
- **options) do |form|
61
- concat(form.hidden_field(:sort))
59
+ **options,
60
+ **html_attributes) do |form|
61
+ concat(sort_input(form:))
62
62
 
63
63
  yield form if block_given?
64
64
  end
65
65
  end
66
66
 
67
- private
67
+ def query_input(form:)
68
+ Query::InputComponent.new(form:, **input_attributes)
69
+ end
68
70
 
69
- def default_html_attributes
70
- {
71
- data: {
72
- controller: "tables--filter--modal",
73
- action: <<~ACTIONS.gsub(/\s+/, " "),
74
- click@window->tables--filter--modal#close
75
- click->tables--filter--modal#open:stop
76
- keydown.esc->tables--filter--modal#close
77
- ACTIONS
78
- },
79
- }
71
+ def sort_input(form:)
72
+ return if collection.default_sort?
73
+
74
+ form.hidden_field(:sort)
80
75
  end
81
76
 
82
- def default_input_attributes
77
+ private
78
+
79
+ def default_html_attributes
83
80
  {
84
81
  data: {
85
- action: "focus->tables--filter--modal#open",
82
+ controller: "tables--query",
83
+ turbo_action: :replace,
84
+ action: %w[
85
+ click@window->tables--query#closeModal
86
+ click->tables--query#openModal:stop
87
+ focusin@window->tables--query#closeModal
88
+ focusin->tables--query#openModal:stop
89
+ keydown.esc->tables--query#clear:stop
90
+ submit->tables--query#submit
91
+ input->tables--query#update
92
+ ],
86
93
  },
87
94
  }
88
95
  end
@@ -8,6 +8,7 @@ module Katalyst
8
8
 
9
9
  included do
10
10
  class_attribute :_default_table_component, instance_accessor: false
11
+ class_attribute :_default_table_query_component, instance_accessor: false
11
12
  class_attribute :_default_summary_table_component, instance_accessor: false
12
13
  end
13
14
 
@@ -20,6 +21,14 @@ module Katalyst
20
21
  self._default_table_component = component
21
22
  end
22
23
 
24
+ # Set the table query component to be used as the default for all tables
25
+ # in the views rendered by this controller and its subclasses.
26
+ #
27
+ # @param component [Class] the table query component class to use
28
+ def default_table_query_component(component)
29
+ self._default_table_query_component = component
30
+ end
31
+
23
32
  # Set the summary table component to be used as the default for all
24
33
  # summary tables in the views rendered by this controller and its
25
34
  # subclasses.
@@ -35,6 +44,11 @@ module Katalyst
35
44
  self.class._default_table_component
36
45
  end
37
46
 
47
+ # Default table query component for this controller
48
+ def default_table_query_component
49
+ self.class._default_table_query_component
50
+ end
51
+
38
52
  # Default summary table component for this controller
39
53
  def default_summary_table_component
40
54
  self.class._default_summary_table_component
@@ -55,6 +55,15 @@ module Katalyst
55
55
  render(Selectable::FormComponent.new(collection:, id:, primary_key:), &)
56
56
  end
57
57
 
58
+ # Construct a new query interface for filtering the current page.
59
+ #
60
+ # @param collection [Katalyst::Tables::Collection::Core] the collection to render
61
+ # @param url [String] the url to submit the form to (e.g. <resources>_path)
62
+ def table_query_with(collection:, url: url_for(action: :index), component: nil, &)
63
+ component ||= default_table_query_component_class
64
+ render(component.new(collection:, url:), &)
65
+ end
66
+
58
67
  # Construct a new summary table component.
59
68
  #
60
69
  # @param model [ActiveRecord::Base] subject for the table
@@ -69,14 +78,6 @@ module Katalyst
69
78
  render(component, &)
70
79
  end
71
80
 
72
- # Construct a new filter.
73
- #
74
- # @param collection [Katalyst::Tables::Collection::Core] the collection to render
75
- # @param url [String] the url to submit the form to (e.g. <resources>_path)
76
- def filter_with(collection:, url: url_for(action: :index), &)
77
- render(FilterComponent.new(collection:, url:), &)
78
- end
79
-
80
81
  private
81
82
 
82
83
  def default_table_component_class
@@ -84,6 +85,11 @@ module Katalyst
84
85
  component.respond_to?(:constantize) ? component.constantize : component
85
86
  end
86
87
 
88
+ def default_table_query_component_class
89
+ component = controller.try(:default_table_query_component) || QueryComponent
90
+ component.respond_to?(:constantize) ? component.constantize : component
91
+ end
92
+
87
93
  def default_summary_table_component_class
88
94
  component = controller.try(:default_summary_table_component) || SummaryTableComponent
89
95
  component.respond_to?(:constantize) ? component.constantize : component
@@ -3,7 +3,8 @@ import OrderableListController from "./orderable/list_controller";
3
3
  import OrderableFormController from "./orderable/form_controller";
4
4
  import SelectionFormController from "./selection/form_controller";
5
5
  import SelectionItemController from "./selection/item_controller";
6
- import FilterModalController from "./filter/modal_controller";
6
+ import QueryController from "./query_controller";
7
+ import QueryInputController from "./query_input_controller";
7
8
 
8
9
  const Definitions = [
9
10
  {
@@ -27,8 +28,12 @@ const Definitions = [
27
28
  controllerConstructor: SelectionItemController,
28
29
  },
29
30
  {
30
- identifier: "tables--filter--modal",
31
- controllerConstructor: FilterModalController,
31
+ identifier: "tables--query",
32
+ controllerConstructor: QueryController,
33
+ },
34
+ {
35
+ identifier: "tables--query-input",
36
+ controllerConstructor: QueryInputController,
32
37
  },
33
38
  ];
34
39
 
@@ -0,0 +1,108 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class QueryController extends Controller {
4
+ static targets = ["modal"];
5
+
6
+ disconnect() {
7
+ delete this.pending;
8
+
9
+ document.removeEventListener("selectionchange", this.selection);
10
+ }
11
+
12
+ focus() {
13
+ if (document.activeElement === this.query) return;
14
+
15
+ this.query.addEventListener(
16
+ "focusin",
17
+ (e) => {
18
+ e.target.setSelectionRange(-1, -1);
19
+ },
20
+ { once: true },
21
+ );
22
+
23
+ this.query.focus();
24
+ }
25
+
26
+ closeModal() {
27
+ delete this.modalTarget.dataset.open;
28
+
29
+ if (document.activeElement === this.query) document.activeElement.blur();
30
+
31
+ document.removeEventListener("selectionchange", this.selection);
32
+ }
33
+
34
+ openModal() {
35
+ this.modalTarget.dataset.open = true;
36
+
37
+ document.addEventListener("selectionchange", this.selection);
38
+ }
39
+
40
+ clear() {
41
+ if (this.query.value === "") {
42
+ // if the user presses escape once, browser clears the input
43
+ // if the user presses escape again, get them out of here
44
+ this.closeModal();
45
+ }
46
+ }
47
+
48
+ submit() {
49
+ const hasFocus = this.isFocused;
50
+ const position = hasFocus && this.query.selectionStart;
51
+
52
+ if (this.pending) {
53
+ clearTimeout(this.pending);
54
+ delete this.pending;
55
+ }
56
+
57
+ // prevent an unnecessary `?q=` parameter from appearing in the URL
58
+ if (this.query.value === "") {
59
+ this.query.disabled = true;
60
+
61
+ // restore input and focus after form submission
62
+ setTimeout(() => {
63
+ this.query.disabled = false;
64
+ if (hasFocus) this.query.focus();
65
+ }, 0);
66
+ }
67
+
68
+ // add/remove current cursor position
69
+ if (hasFocus && position) {
70
+ this.position.value = position;
71
+ this.position.disabled = false;
72
+ } else {
73
+ this.position.value = "";
74
+ this.position.disabled = true;
75
+ }
76
+ }
77
+
78
+ update = () => {
79
+ if (this.pending) clearTimeout(this.pending);
80
+ this.pending = setTimeout(() => {
81
+ this.element.requestSubmit();
82
+ }, 300);
83
+ };
84
+
85
+ selection = () => {
86
+ if (this.isFocused) this.update();
87
+ };
88
+
89
+ beforeMorphAttribute(e) {
90
+ switch (e.detail.attributeName) {
91
+ case "data-open":
92
+ e.preventDefault();
93
+ break;
94
+ }
95
+ }
96
+
97
+ get query() {
98
+ return this.element.querySelector("input[type=search]");
99
+ }
100
+
101
+ get position() {
102
+ return this.element.querySelector("input[name=p]");
103
+ }
104
+
105
+ get isFocused() {
106
+ return this.query === document.activeElement;
107
+ }
108
+ }