coupdoeil 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/README.md +1 -1
  4. data/app/assets/javascripts/coupdoeil.js +154 -53
  5. data/app/assets/javascripts/coupdoeil.min.js +1 -1
  6. data/app/assets/javascripts/coupdoeil.min.js.map +1 -1
  7. data/app/controllers/coupdoeil/popovers_controller.rb +4 -15
  8. data/app/helpers/coupdoeil/application_helper.rb +13 -0
  9. data/app/javascript/coupdoeil/elements/coupdoeil_element.js +41 -1
  10. data/app/javascript/coupdoeil/events/onclick.js +4 -2
  11. data/app/javascript/coupdoeil/events/onmouseover.js +27 -14
  12. data/app/javascript/coupdoeil/popover/attributes.js +6 -2
  13. data/app/javascript/coupdoeil/popover/closing.js +33 -12
  14. data/app/javascript/coupdoeil/popover/config.js +1 -1
  15. data/app/javascript/coupdoeil/popover/lazy_loading.js +36 -0
  16. data/app/javascript/coupdoeil/popover/opening.js +32 -48
  17. data/app/javascript/coupdoeil/popover/options_parser.js +9 -5
  18. data/app/javascript/coupdoeil/popover/utils.js +46 -0
  19. data/app/javascript/coupdoeil/popover.js +11 -0
  20. data/app/models/coupdoeil/popover/lazy_loading.rb +57 -0
  21. data/app/models/coupdoeil/popover/option/loading.rb +1 -1
  22. data/app/models/coupdoeil/popover/option/placement.rb +25 -4
  23. data/app/models/coupdoeil/popover/setup.rb +1 -1
  24. data/app/models/coupdoeil/popover.rb +27 -12
  25. data/app/models/coupdoeil/tag.rb +11 -10
  26. data/lib/coupdoeil/config.rb +7 -0
  27. data/lib/coupdoeil/engine.rb +2 -1
  28. data/lib/coupdoeil/version.rb +1 -1
  29. metadata +6 -8
  30. data/app/javascript/coupdoeil/popover/actions.js +0 -0
  31. data/app/javascript/coupdoeil/popover/state_check.js +0 -12
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coupdoeil
4
+ class Popover
5
+ module LazyLoading
6
+ extend ActiveSupport::Concern
7
+
8
+ prepended do
9
+ # Returns true if template is currently rendered for the first phase of lazy loading,
10
+ # which is rendering a simple template as fast as possible before fetch the actual content in second phase.
11
+ # @return [true, false]
12
+ def lazy_loading?
13
+ @lazy_loading
14
+ end
15
+ helper_method :lazy_loading?
16
+
17
+ def loader_variant_for_template?
18
+ loader_template.present?
19
+ end
20
+ helper_method :loader_variant_for_template?
21
+
22
+ def no_loader_variant_for_template?
23
+ loader_template.blank?
24
+ end
25
+ helper_method :no_loader_variant_for_template?
26
+ end
27
+
28
+ # @return [true, false]
29
+ def lazy_loading? = @lazy_loading || false
30
+ def lazy_loading! = @lazy_loading = true
31
+
32
+ # Returns the loader variant for the current action if it exists.
33
+ # @return [ActionView::Template, nil]
34
+ def loader_template
35
+ return @loader_template if defined?(@loader_template)
36
+
37
+ paths = ["#{self.class.popover_resource_name}_popover"]
38
+ found_template = lookup_context.find(action_name, paths, false, [], { variants: :loader })
39
+ @loader_template = found_template.variant == "loader" ? found_template : nil
40
+ end
41
+
42
+ def process(method_name, ...)
43
+ return super unless lazy_loading?
44
+
45
+ self.action_name = method_name
46
+
47
+ instrument_render(method_name) do
48
+ if loader_template
49
+ render template: loader_template
50
+ else
51
+ render html: "", layout: true
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -6,7 +6,7 @@ module Coupdoeil
6
6
  class Loading < Coupdoeil::Popover::Option
7
7
  self.bit_size = 2
8
8
 
9
- VALUES = ["async", "preload"].freeze
9
+ VALUES = ["async", "preload", "lazy"].freeze
10
10
 
11
11
  class << self
12
12
  def parse(value) = VALUES.index(value.to_s)
@@ -17,22 +17,43 @@ module Coupdoeil
17
17
 
18
18
  class << self
19
19
  def parse(value)
20
- values = value.to_s.split(",")
20
+ values = extract_values(value)
21
21
  4.times.sum do |index|
22
22
  next 0 unless (placement = values[index])
23
23
 
24
- placement.strip!
25
24
  placement_index = INDEX_BY_VALUES[placement]
26
25
  placement_index << (index * 4)
27
26
  end
28
27
  end
28
+
29
+ def extract_values(value, validate: false)
30
+ case value
31
+ when Array
32
+ raise_invalid_option("You can provide maximum 4 placement options.") if validate && value.length > 4
33
+ raise_invalid_option("You must provide at least one option.") if validate && value.empty?
34
+ value
35
+ when Symbol
36
+ [value.name]
37
+ when String
38
+ value.split(",").each(&:strip!).tap do |values|
39
+ if values.many?
40
+ locations = caller_locations
41
+ start_index = (locations.find_index { |s| s.path.include?(Rails.root.to_s) } || 0) + 1
42
+ Coupdoeil.deprecator.warn(
43
+ "Use array of string instead to provide several placement options",
44
+ caller_locations(start_index),
45
+ )
46
+ end
47
+ end
48
+ end
49
+ end
29
50
  end
30
51
 
31
52
  def validate!
32
- values = value.to_s.split(",")
53
+ values = Placement.extract_values(value, validate: true)
33
54
 
34
55
  values.each do |placement_value|
35
- next if placement_value.strip.in?(VALUES)
56
+ next if placement_value.in?(VALUES)
36
57
 
37
58
  values_sentence = VALUES.to_sentence(last_word_connector: " or ")
38
59
  raise_invalid_option("Value must be one of: #{values_sentence}")
@@ -14,7 +14,7 @@ module Coupdoeil
14
14
  end
15
15
 
16
16
  def identifier = "#{type}@#{klass.popover_resource_name}"
17
- def render_in(view_context) = klass.new(params, view_context).process(type)
17
+ def render_in(view_context) = klass.new(params, view_context.controller).process(type)
18
18
  def options = @options ||= klass.default_options_for(type)
19
19
 
20
20
  def with_type(type)
@@ -17,6 +17,8 @@ module Coupdoeil
17
17
  include ActionController::Cookies
18
18
  include ActionController::Helpers
19
19
 
20
+ prepend LazyLoading
21
+
20
22
  # For forgery protection
21
23
  forgery_protection_methods = [
22
24
  :form_authenticity_token,
@@ -104,17 +106,15 @@ module Coupdoeil
104
106
  end
105
107
  end
106
108
 
107
- attr_reader :params, :context_controller
108
-
109
- helper_method :params
109
+ attr_reader :context_controller
110
110
 
111
111
  delegate :request, :session, to: :context_controller
112
112
 
113
- # @param [HashWithIndifferentAccess] params the deserialized popover params that were given to `.with`
113
+ # @raw_param [HashWithIndifferentAccess] raw_params the popover params to deserialize
114
114
  # @param [ActionController::Base] context_controller an instance of Coupdoeil::PopoversController or the current controller if the popover is rendered inline because of `loading: :preload` option.
115
- def initialize(params, context_controller)
115
+ def initialize(raw_params, context_controller)
116
116
  super()
117
- @params = params
117
+ @raw_params = raw_params
118
118
  @context_controller = context_controller
119
119
  end
120
120
 
@@ -141,13 +141,28 @@ See documentation on helpers at https://coupdoeil.org/guides/controller-api.html
141
141
  Also note that render does not terminate execution of the action."
142
142
  end
143
143
 
144
- def process(method_name, *)
145
- benchmark("processed popover #{self.class.popover_resource_name}/#{method_name}", silence: true) do
146
- ActiveSupport::Notifications.instrument("render_popover.coupdoeil") do
147
- super
148
- response_body || render(action_name)
149
- end
144
+ def instrument_render(method_name, &block)
145
+ ActiveSupport::Notifications.instrument("render_popover.coupdoeil", &block)
146
+ end
147
+
148
+ def process(method_name, ...)
149
+ instrument_render(method_name) do
150
+ super
151
+ response_body || render(action_name)
150
152
  end
151
153
  end
154
+
155
+ # @return [HashWithIndifferentAccess] the deserialized popover params that were given to `.with`
156
+ def params
157
+ @params ||=
158
+ if @raw_params&.is_a?(String)
159
+ parsed_params = JSON.parse(@raw_params)
160
+ parsed_params = Coupdoeil::Params.deserialize(parsed_params).sole
161
+ parsed_params.with_indifferent_access
162
+ else
163
+ {}.with_indifferent_access
164
+ end
165
+ end
166
+ helper_method :params
152
167
  end
153
168
  end
@@ -24,30 +24,31 @@ module Coupdoeil
24
24
  end
25
25
  end
26
26
 
27
- private
28
-
29
- attr_reader :popover_setup
30
-
31
- def popover_attributes
32
- attributes = { "popover-options": popover_options.to_base36 }
27
+ def popover_attributes(prefixed: false)
28
+ attributes = { popover_options: popover_options.to_base36 }
33
29
 
34
30
  unless popover_options.preload?
35
31
  params = Params.serialize(popover_setup.params).sole.presence&.to_json
36
- attributes.merge!("popover-type" => popover_setup.identifier, "popover-params" => params)
32
+ attributes.merge!(popover_type: popover_setup.identifier, popover_params: params)
37
33
  end
38
34
 
39
35
  if Coupdoeil.config.options_html_attributes
40
- attributes.merge!(popover_options.to_h.transform_keys { "popover-#{_1}" })
36
+ attributes.merge!(popover_options.to_h.transform_keys { "popover_#{_1}".to_sym })
41
37
  end
42
38
 
39
+ attributes.transform_keys! { "data-#{_1.name.tr("_", "-")}" } if prefixed
43
40
  attributes
44
41
  end
45
42
 
43
+ private
44
+
45
+ attr_reader :popover_setup
46
+
46
47
  def tag_attributes
47
48
  if @attributes
48
- @attributes.merge(popover_attributes)
49
+ @attributes.merge(popover_attributes(prefixed: true))
49
50
  else
50
- popover_attributes
51
+ popover_attributes(prefixed: true)
51
52
  end
52
53
  end
53
54
  end
@@ -9,6 +9,7 @@ module Coupdoeil
9
9
  options_html_attributes: Rails.env.local?,
10
10
  include_all_helpers: true,
11
11
  delegate_helper_methods: true,
12
+ default_dataset_format: :html,
12
13
  })
13
14
  end
14
15
 
@@ -41,6 +42,12 @@ module Coupdoeil
41
42
  # therefore be delegated to the context controller.
42
43
  # If this behavior causes unexpected behavior you can disable it with this configuration option, and/or [submit an issue](https://gitlab.com/Pagehey/coupdoeil/-/issues) so it can be investigated.
43
44
  # However, in absence of problem, it is still recommended to let it `true` by default.
45
+
46
+ # @!attribute default_dataset_format
47
+ # @return [Symbol]
48
+ # Default returned format of the coupdoeil_popover_dataset view helper.
49
+ # See the helper documentation for available formats and their behavior.
50
+ # Defaults to `:html`.
44
51
  end
45
52
  # @!attribute current
46
53
  # @return [Coupdoeil::Config]
@@ -63,9 +63,10 @@ module Coupdoeil
63
63
 
64
64
  initializer "coupdoeil.view_paths" do
65
65
  config.to_prepare do
66
- views = Rails.application.config.paths["app/views"].existent
66
+ views = []
67
67
  views += Rails.application.config.paths.add("app/popovers").existent
68
68
  views += Rails.application.config.paths.add("app/popovers/layouts").existent
69
+ views += Rails.application.config.paths["app/views"].existent
69
70
  Coupdoeil::Popover.prepend_view_path(views)
70
71
  end
71
72
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Coupdoeil
4
- VERSION = "1.1.0"
4
+ VERSION = "1.2.0"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coupdoeil
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - PageHey
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-08-11 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: actionpack
@@ -154,19 +154,20 @@ files:
154
154
  - app/javascript/coupdoeil/events/onmouseover.js
155
155
  - app/javascript/coupdoeil/index.js
156
156
  - app/javascript/coupdoeil/popover.js
157
- - app/javascript/coupdoeil/popover/actions.js
158
157
  - app/javascript/coupdoeil/popover/attributes.js
159
158
  - app/javascript/coupdoeil/popover/cache.js
160
159
  - app/javascript/coupdoeil/popover/closing.js
161
160
  - app/javascript/coupdoeil/popover/config.js
162
161
  - app/javascript/coupdoeil/popover/controller.js
163
162
  - app/javascript/coupdoeil/popover/current.js
163
+ - app/javascript/coupdoeil/popover/lazy_loading.js
164
164
  - app/javascript/coupdoeil/popover/opening.js
165
165
  - app/javascript/coupdoeil/popover/options_parser.js
166
166
  - app/javascript/coupdoeil/popover/positioning.js
167
- - app/javascript/coupdoeil/popover/state_check.js
167
+ - app/javascript/coupdoeil/popover/utils.js
168
168
  - app/models/coupdoeil/params.rb
169
169
  - app/models/coupdoeil/popover.rb
170
+ - app/models/coupdoeil/popover/lazy_loading.rb
170
171
  - app/models/coupdoeil/popover/option.rb
171
172
  - app/models/coupdoeil/popover/option/animation.rb
172
173
  - app/models/coupdoeil/popover/option/cache.rb
@@ -201,9 +202,6 @@ metadata:
201
202
  homepage_uri: https://coupdoeil.org
202
203
  source_code_uri: https://gitlab.com/Pagehey/coupdoeil
203
204
  changelog_uri: https://gitlab.com/Pagehey/coupdoeil/-/blob/main/CHANGELOG.md
204
- post_install_message: |
205
- Coupdoeil v.1.1.0 fixed the helper usage in popovers and should allow some refactoring.
206
- See CHANGELOG for details: https://gitlab.com/Pagehey/coupdoeil/-/blob/main/CHANGELOG.md?ref_type=heads#v110
207
205
  rdoc_options: []
208
206
  require_paths:
209
207
  - lib
@@ -218,7 +216,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
218
216
  - !ruby/object:Gem::Version
219
217
  version: '0'
220
218
  requirements: []
221
- rubygems_version: 3.6.2
219
+ rubygems_version: 3.7.2
222
220
  specification_version: 4
223
221
  summary: A framework to easily create popovers.
224
222
  test_files: []
File without changes
@@ -1,12 +0,0 @@
1
- import {POPOVER_CLOSE_BTN_SELECTOR} from "./config";
2
-
3
- import {currentPopoversById} from "./current";
4
-
5
- export function isElementClosePopoverButton(element) {
6
- return element.closest(POPOVER_CLOSE_BTN_SELECTOR) ||
7
- element.dataset.hasOwnProperty("popoverClose")
8
- }
9
-
10
- export function isAnyPopoverOpened() {
11
- return currentPopoversById().size > 0
12
- }