coupdoeil 1.0.0.pre.beta.2 → 1.0.0.pre.beta.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +7 -3
- data/app/assets/javascripts/coupdoeil.js +2 -2
- data/app/assets/javascripts/coupdoeil.min.js +1 -1
- data/app/assets/javascripts/coupdoeil.min.js.map +1 -1
- data/app/helpers/coupdoeil/application_helper.rb +2 -9
- data/app/javascript/coupdoeil/popover/attributes.js +1 -1
- data/app/javascript/coupdoeil/popover/opening.js +1 -1
- data/app/javascript/coupdoeil/popover/{optionsParser.js → options_parser.js} +17 -5
- data/app/models/coupdoeil/popover/option/offset.rb +18 -5
- data/app/models/coupdoeil/popover/options_set.rb +13 -8
- data/app/models/coupdoeil/popover/setup.rb +4 -12
- data/app/models/coupdoeil/popover.rb +16 -4
- data/app/models/coupdoeil/tag.rb +18 -9
- data/lib/coupdoeil/version.rb +1 -1
- metadata +4 -4
@@ -2,15 +2,8 @@
|
|
2
2
|
|
3
3
|
module Coupdoeil
|
4
4
|
module ApplicationHelper
|
5
|
-
def coupdoeil_popover_tag(popover,
|
6
|
-
|
7
|
-
attributes = attributes_or_options
|
8
|
-
else
|
9
|
-
options = attributes_or_options.extract!(*Popover::OptionsSet::OPTION_NAMES)
|
10
|
-
attributes = attributes_or_options
|
11
|
-
end
|
12
|
-
popover_options = options
|
13
|
-
render(Coupdoeil::Tag.new(popover:, popover_options:, attributes:), &)
|
5
|
+
def coupdoeil_popover_tag(popover, popover_options = nil, tag_attributes = nil, &)
|
6
|
+
render(Coupdoeil::Tag.new(popover:, popover_options:, attributes: tag_attributes), &)
|
14
7
|
end
|
15
8
|
end
|
16
9
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import {FETCH_DELAY_MS, POPOVER_CLASS_NAME, OPENING_DELAY_MS} from "./config"
|
2
2
|
import {getParams, getType, preloadedContentElement, triggeredOnClick} from "./attributes"
|
3
3
|
import {getPopoverContentHTML, setPopoverContentHTML} from "./cache"
|
4
|
-
import {extractOptionsFromElement} from "./
|
4
|
+
import {extractOptionsFromElement} from "./options_parser"
|
5
5
|
import {positionPopover} from "./positioning"
|
6
6
|
import {enter} from "el-transition"
|
7
7
|
import {addToCurrents} from "./current"
|
@@ -15,7 +15,7 @@ const ORDERED_OPTIONS = [
|
|
15
15
|
"openingDelay", // bit size: 1 shift: 4
|
16
16
|
"animation", // bit size: 3 shift: 5
|
17
17
|
"placement", // bit size: 16 shift: 8
|
18
|
-
"offset" // bit size:
|
18
|
+
"offset" // bit size: 20 shift: 24
|
19
19
|
]
|
20
20
|
|
21
21
|
const TRIGGERS = ["hover", "click"]
|
@@ -40,6 +40,13 @@ function parseCSSSize(value) {
|
|
40
40
|
return 0
|
41
41
|
}
|
42
42
|
|
43
|
+
// Offset option is a three part number.
|
44
|
+
// integer (8 bits) + decimals (10 bits) + config (2 bits)
|
45
|
+
// config bits are for sign (positive (0) or negative (1)) and offset unit (px (0) or rem (1))
|
46
|
+
// Examples:
|
47
|
+
// - "1.25rem" is: 00000001 . 0000011001 . 10
|
48
|
+
// - "-16px" is: 00010000 . 00000000 . 01
|
49
|
+
// 10 bits are reserved for decimals so values like 0.875rem are possible.
|
43
50
|
function getOffset(optionsInt) {
|
44
51
|
// shift is BigInt(16 + 3 + 1 + 1 + 2 + 1)
|
45
52
|
const offsetBits = Number(BigInt(optionsInt) >> BigInt(24))
|
@@ -48,16 +55,21 @@ function getOffset(optionsInt) {
|
|
48
55
|
|
49
56
|
const isNegative = (offsetBits & 1) === 1
|
50
57
|
const isREM = (offsetBits & 2) === 2
|
51
|
-
|
52
|
-
const
|
53
|
-
const integer = (offsetBits >> (2 + 11))
|
58
|
+
const decimals = (offsetBits >> 2 /* config bits */) & 1023 // (2 ** 10) - 1
|
59
|
+
const integer = (offsetBits >> (12 /* config (2) + decimals (10) bits */))
|
54
60
|
|
55
61
|
const CSSSize = `${isNegative ? '-' : ''}${integer}.${decimals}${isREM ? 'rem' : 'px'}`
|
56
62
|
return parseCSSSize(CSSSize)
|
57
63
|
}
|
58
64
|
|
65
|
+
// Placement option can have up to 4 placement values: a main one and 3 fallbacks.
|
66
|
+
// There are 13 possible values for one placement, stored as an array in the PLACEMENTS const.
|
67
|
+
// The max index of this array is 12, so a placement value can be stored as an index of this array,
|
68
|
+
// and this index value fits in 4 bits.
|
69
|
+
// Option for placements then consists of 4 times (main and fallbacks) this index value:
|
70
|
+
// So from 0.0.0.0 up to 15.15.15.15, or from 0 up to 65535 (0b1111111111111111)
|
59
71
|
function getPlacement(optionsInt) {
|
60
|
-
// shift is 3 + 1 + 1 + 2 + 1, mask is 2 ** 16 - 1
|
72
|
+
// shift is 3 + 1 + 1 + 2 + 1, mask is 2 ** 16 - 1 or 0b1111111111111111
|
61
73
|
const placementBits = (optionsInt >> 8) & 65535
|
62
74
|
let shift = 0
|
63
75
|
let lastPlacement = null
|
@@ -4,7 +4,10 @@ module Coupdoeil
|
|
4
4
|
class Popover
|
5
5
|
class Option
|
6
6
|
class Offset < Coupdoeil::Popover::Option
|
7
|
-
|
7
|
+
INTEGER_PART_BITS = 8
|
8
|
+
FLOAT_PART_BITS = 10
|
9
|
+
CONFIG_PART_BITS = 2
|
10
|
+
self.bit_size = INTEGER_PART_BITS + FLOAT_PART_BITS + CONFIG_PART_BITS
|
8
11
|
|
9
12
|
class << self
|
10
13
|
def parse(value)
|
@@ -16,19 +19,29 @@ module Coupdoeil
|
|
16
19
|
base |= 2 if value.is_a?(String) && value.end_with?("rem")
|
17
20
|
|
18
21
|
integer, decimals = float_value.abs.to_s.split(".")
|
19
|
-
base |= (decimals.to_i <<
|
20
|
-
base |= (integer.to_i <<
|
22
|
+
base |= (decimals.to_i << CONFIG_PART_BITS)
|
23
|
+
base |= (integer.to_i << CONFIG_PART_BITS + FLOAT_PART_BITS)
|
21
24
|
|
22
25
|
base
|
23
26
|
end
|
24
27
|
end
|
25
28
|
|
26
29
|
def validate!
|
27
|
-
return if value in Float | Integer
|
28
|
-
return if value.to_s.match?(/^-?\d+(\.\d{1,3})?(px|rem)?$/)
|
30
|
+
return ensure_no_overflow if (value in Float | Integer) || value.to_s.match?(/^-?\d+(\.\d{1,3})?(px|rem)?$/)
|
29
31
|
|
30
32
|
raise_invalid_option "Value should be a signed float or integer, followed or not by 'rem' or 'px'."
|
31
33
|
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def ensure_no_overflow
|
38
|
+
float_value = value.to_f
|
39
|
+
integer, decimals = float_value.abs.to_s.split(".").map(&:to_i)
|
40
|
+
return if integer.in?(0..255) && decimals.in?(0..999)
|
41
|
+
|
42
|
+
raise_invalid_option "Number should be comprised between -255.999 and 255.999, \
|
43
|
+
with a maximum of 3 decimal digits."
|
44
|
+
end
|
32
45
|
end
|
33
46
|
end
|
34
47
|
end
|
@@ -23,28 +23,33 @@ module Coupdoeil
|
|
23
23
|
def to_h = options
|
24
24
|
|
25
25
|
def initialize(options = {})
|
26
|
+
options.assert_valid_keys(OPTION_NAMES)
|
27
|
+
|
26
28
|
@options = options
|
27
29
|
end
|
28
30
|
|
29
31
|
def merge(options_set)
|
30
|
-
OptionsSet.new(
|
32
|
+
OptionsSet.new(options.merge(options_set.options))
|
31
33
|
end
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
-
|
35
|
+
if Rails.env.local?
|
36
|
+
def validate!
|
37
|
+
ORDERED_OPTIONS.map do |option|
|
38
|
+
next unless options.key?(option.key)
|
36
39
|
|
37
|
-
|
38
|
-
|
40
|
+
value = options[option.key]
|
41
|
+
option.new(value).validate!
|
42
|
+
end
|
39
43
|
end
|
40
|
-
|
44
|
+
else
|
45
|
+
def validate! = nil # no-op
|
41
46
|
end
|
42
47
|
|
43
48
|
def to_base36
|
44
49
|
@to_base36 ||= begin
|
45
50
|
shift = 0
|
46
51
|
ORDERED_OPTIONS.reverse.sum do |option|
|
47
|
-
bits = option.into_bits(
|
52
|
+
bits = option.into_bits(options[option.key])
|
48
53
|
result = bits << shift
|
49
54
|
shift += option.bit_size
|
50
55
|
result
|
@@ -13,9 +13,9 @@ module Coupdoeil
|
|
13
13
|
@params = EMPTY_PARAMS
|
14
14
|
end
|
15
15
|
|
16
|
-
def default_options = klass.default_options_for(type)
|
17
16
|
def identifier = "#{type}@#{klass.popover_resource_name}"
|
18
17
|
def render_in(view_context) = klass.new(params, view_context).process(type)
|
18
|
+
def options = @options ||= klass.default_options_for(type)
|
19
19
|
|
20
20
|
def with_type(type)
|
21
21
|
@type = type
|
@@ -27,17 +27,9 @@ module Coupdoeil
|
|
27
27
|
self
|
28
28
|
end
|
29
29
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
with_type(method_name)
|
34
|
-
else
|
35
|
-
super
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def respond_to_missing?(method, include_all = false)
|
40
|
-
klass.action_methods.include?(method.name) || super
|
30
|
+
def with_options(new_options)
|
31
|
+
@options = options.merge(Popover::OptionsSet.new(new_options))
|
32
|
+
self
|
41
33
|
end
|
42
34
|
end
|
43
35
|
end
|
@@ -46,7 +46,7 @@ module Coupdoeil
|
|
46
46
|
attr_reader :registry
|
47
47
|
|
48
48
|
def popover_resource_name = @popover_resource_name ||= name.delete_suffix("Popover").underscore
|
49
|
-
def with(...) =
|
49
|
+
def with(...) = setup_class.new(self).with_params(...)
|
50
50
|
|
51
51
|
def inherited(subclass)
|
52
52
|
super
|
@@ -69,14 +69,26 @@ module Coupdoeil
|
|
69
69
|
|
70
70
|
def method_missing(method_name, *args, &)
|
71
71
|
return super unless action_methods.include?(method_name.name)
|
72
|
-
raise ArgumentError, "expected no arguments" if args.any?
|
73
72
|
|
74
|
-
|
75
|
-
|
73
|
+
action_methods.each do |action_name|
|
74
|
+
define_singleton_method(action_name) { setup_class.new(self).with_type(action_name) }
|
75
|
+
end
|
76
|
+
public_send(method_name)
|
77
|
+
end
|
76
78
|
|
77
79
|
def respond_to_missing?(method, include_all = false)
|
78
80
|
action_methods.include?(method.name) || super
|
79
81
|
end
|
82
|
+
|
83
|
+
def setup_class
|
84
|
+
@setup_class ||= begin
|
85
|
+
setup_klass = Class.new(Setup)
|
86
|
+
action_methods.each do |action_name|
|
87
|
+
setup_klass.define_method(action_name) { with_type(action_name) }
|
88
|
+
end
|
89
|
+
setup_klass
|
90
|
+
end
|
91
|
+
end
|
80
92
|
end
|
81
93
|
|
82
94
|
attr_reader :params
|
data/app/models/coupdoeil/tag.rb
CHANGED
@@ -2,19 +2,20 @@
|
|
2
2
|
|
3
3
|
module Coupdoeil
|
4
4
|
class Tag
|
5
|
+
delegate :options, to: :popover_setup, prefix: :popover
|
6
|
+
|
5
7
|
def initialize(popover:, popover_options:, attributes:)
|
6
8
|
@popover_setup = popover
|
9
|
+
@popover_setup.with_options(popover_options) if popover_options
|
7
10
|
@attributes = attributes
|
8
|
-
|
9
|
-
@popover_options_set = popover_setup.default_options.merge(popover_options)
|
10
|
-
@popover_options_set.validate!
|
11
|
+
@popover_setup.options.validate!
|
11
12
|
end
|
12
13
|
|
13
14
|
def render_in(view_context, &block)
|
14
15
|
ActiveSupport::Notifications.instrument("render_tag.coupdoeil") do
|
15
16
|
content = view_context.capture(&block) if block_given?
|
16
|
-
view_context.content_tag("coup-doeil",
|
17
|
-
if
|
17
|
+
view_context.content_tag("coup-doeil", **tag_attributes) do
|
18
|
+
if popover_options.preload?
|
18
19
|
view_context.tag.template(view_context.render(popover_setup), class: "popover-content") + content
|
19
20
|
else
|
20
21
|
content
|
@@ -25,21 +26,29 @@ module Coupdoeil
|
|
25
26
|
|
26
27
|
private
|
27
28
|
|
28
|
-
attr_reader :
|
29
|
+
attr_reader :popover_setup
|
29
30
|
|
30
31
|
def popover_attributes
|
31
|
-
attributes = { "popover-options":
|
32
|
+
attributes = { "popover-options": popover_options.to_base36 }
|
32
33
|
|
33
|
-
unless
|
34
|
+
unless popover_options.preload?
|
34
35
|
params = Params.serialize(popover_setup.params).sole.presence&.to_json
|
35
36
|
attributes.merge!("popover-type" => popover_setup.identifier, "popover-params" => params)
|
36
37
|
end
|
37
38
|
|
38
39
|
if Rails.env.local?
|
39
|
-
attributes.merge!(
|
40
|
+
attributes.merge!(popover_options.to_h.transform_keys { "popover-#{_1}" })
|
40
41
|
end
|
41
42
|
|
42
43
|
attributes
|
43
44
|
end
|
45
|
+
|
46
|
+
def tag_attributes
|
47
|
+
if @attributes
|
48
|
+
@attributes.merge(popover_attributes)
|
49
|
+
else
|
50
|
+
popover_attributes
|
51
|
+
end
|
52
|
+
end
|
44
53
|
end
|
45
54
|
end
|
data/lib/coupdoeil/version.rb
CHANGED
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.0.0.pre.beta.
|
4
|
+
version: 1.0.0.pre.beta.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- PageHey
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-06-
|
10
|
+
date: 2025-06-23 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: actionpack
|
@@ -162,7 +162,7 @@ files:
|
|
162
162
|
- app/javascript/coupdoeil/popover/controller.js
|
163
163
|
- app/javascript/coupdoeil/popover/current.js
|
164
164
|
- app/javascript/coupdoeil/popover/opening.js
|
165
|
-
- app/javascript/coupdoeil/popover/
|
165
|
+
- app/javascript/coupdoeil/popover/options_parser.js
|
166
166
|
- app/javascript/coupdoeil/popover/positioning.js
|
167
167
|
- app/javascript/coupdoeil/popover/state_check.js
|
168
168
|
- app/models/coupdoeil/params.rb
|
@@ -200,7 +200,7 @@ licenses:
|
|
200
200
|
metadata:
|
201
201
|
homepage_uri: https://coupdoeil.org
|
202
202
|
source_code_uri: https://gitlab.com/Pagehey/coupdoeil
|
203
|
-
changelog_uri: https://gitlab.com/Pagehey/coupdoeil/CHANGELOG.md
|
203
|
+
changelog_uri: https://gitlab.com/Pagehey/coupdoeil/-/blob/main/CHANGELOG.md
|
204
204
|
rdoc_options: []
|
205
205
|
require_paths:
|
206
206
|
- lib
|