reflex_behaviors 0.0.3 → 0.0.5
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/Gemfile.lock +12 -7
- data/README.md +1 -1
- data/app/assets/builds/reflex_behaviors.js +164 -10
- data/app/assets/builds/reflex_behaviors.js.map +4 -4
- data/app/javascript/devtools/dom.js +36 -0
- data/app/javascript/devtools/elements/devtool_element.js +76 -0
- data/app/javascript/devtools/elements/supervisor_element.js +129 -0
- data/app/javascript/devtools/elements/tooltip_element.js +103 -0
- data/app/javascript/devtools/index.js +3 -115
- data/app/javascript/devtools/supervisor.js +103 -0
- data/app/javascript/devtools/toggle.js +178 -0
- data/app/javascript/elements/reflex_element.js +12 -0
- data/app/javascript/elements/toggle_trigger_element.js +38 -42
- data/app/javascript/index.js +1 -5
- data/app/reflexes/reflex_behaviors/application_reflex.rb +6 -3
- data/lib/reflex_behaviors/engine.rb +0 -1
- data/lib/reflex_behaviors/tag_builders/toggle_tags_builder.rb +19 -19
- data/lib/reflex_behaviors/version.rb +1 -1
- data/package.json +1 -1
- data/tags +1069 -853
- data/yarn.lock +141 -141
- metadata +8 -7
- data/app/assets/config/reflex_behaviors_manifest.js +0 -1
- data/app/assets/stylesheets/devtools.css +0 -51
- data/app/assets/stylesheets/reflex_behaviors.css +0 -4
- data/app/assets/stylesheets/toggle.css +0 -56
- data/app/assets/stylesheets/tooltip.css +0 -18
@@ -0,0 +1,178 @@
|
|
1
|
+
import { appendHTML, addHighlight, removeHighlight } from './dom'
|
2
|
+
import supervisor from './supervisor'
|
3
|
+
|
4
|
+
document.addEventListener('reflex-behaviors:devtools-start', () =>
|
5
|
+
supervisor.register('toggle', 'toggles<small>(trigger/target)</small>')
|
6
|
+
)
|
7
|
+
|
8
|
+
const triggerTooltipId = 'toggle-trigger-tooltip'
|
9
|
+
const targetTooltipId = 'toggle-target-tooltip'
|
10
|
+
|
11
|
+
function appendTooltip (id, title, content, options = {}) {
|
12
|
+
let { backgroundColor, color, emphaisColor, position } = options
|
13
|
+
color = color || 'white'
|
14
|
+
position = position || 'top'
|
15
|
+
|
16
|
+
appendHTML(`
|
17
|
+
<reflex-behaviors-devools-tooltip id="${id}" position="${position}" background-color="${backgroundColor}" color="${color}" emphasis-color="${emphaisColor}">
|
18
|
+
<div slot='title'>${title}</div>
|
19
|
+
${content}
|
20
|
+
</reflex-behaviors-devools-tooltip>
|
21
|
+
`)
|
22
|
+
return document.getElementById(id)
|
23
|
+
}
|
24
|
+
|
25
|
+
export default class ToggleDevtool {
|
26
|
+
constructor (trigger) {
|
27
|
+
this.name = 'toggle'
|
28
|
+
this.reflex = trigger.dataset.turboReflex
|
29
|
+
this.trigger = trigger
|
30
|
+
this.target = trigger.target
|
31
|
+
|
32
|
+
document.addEventListener('reflex-behaviors:devtool-enable', event => {
|
33
|
+
const { name } = event.detail
|
34
|
+
if (name === this.name)
|
35
|
+
addHighlight(this.trigger, { color: 'red', offset: '2px' })
|
36
|
+
})
|
37
|
+
|
38
|
+
document.addEventListener('reflex-behaviors:devtool-disable', event => {
|
39
|
+
const { name } = event.detail
|
40
|
+
if (name === this.name) removeHighlight(this.trigger)
|
41
|
+
})
|
42
|
+
|
43
|
+
document.addEventListener('click', event => {
|
44
|
+
if (event.target.closest('reflex-behaviors-devools-tooltip')) return
|
45
|
+
this.hide()
|
46
|
+
})
|
47
|
+
}
|
48
|
+
|
49
|
+
get enabled () {
|
50
|
+
return supervisor.enabled(this.name)
|
51
|
+
}
|
52
|
+
|
53
|
+
show () {
|
54
|
+
if (!this.enabled) return
|
55
|
+
this.hide()
|
56
|
+
this.createTriggerTooltip()
|
57
|
+
this.createTargetTooltip()
|
58
|
+
addHighlight(this.target, { color: 'blue', offset: '-2px' })
|
59
|
+
|
60
|
+
let renderingPartial = this.trigger ? this.trigger.renderingPartial : null
|
61
|
+
renderingPartial =
|
62
|
+
renderingPartial || (this.target ? this.target.renderingPartial : null)
|
63
|
+
|
64
|
+
let renderingElement = this.trigger ? this.trigger.renderingElement : null
|
65
|
+
renderingElement =
|
66
|
+
renderingElement || (this.target ? this.target.renderingElement : null)
|
67
|
+
|
68
|
+
addHighlight(renderingElement, {
|
69
|
+
color: 'turquoise',
|
70
|
+
offset: '4px',
|
71
|
+
width: '4px'
|
72
|
+
})
|
73
|
+
|
74
|
+
const data = {
|
75
|
+
rendering: { partial: null, id: null },
|
76
|
+
trigger: { partial: null, id: null },
|
77
|
+
target: { partial: null, id: null }
|
78
|
+
}
|
79
|
+
|
80
|
+
if (renderingPartial) data.rendering.partial = renderingPartial
|
81
|
+
if (renderingElement) data.rendering.id = renderingElement.id
|
82
|
+
|
83
|
+
if (this.trigger)
|
84
|
+
data.trigger = { partial: this.trigger.partial, id: this.trigger.id }
|
85
|
+
|
86
|
+
if (this.target)
|
87
|
+
data.target = { partial: this.target.partial, id: this.target.id }
|
88
|
+
else if (this.trigger)
|
89
|
+
data.target.id = `No element matches the targeted DOM id: ${this.trigger.controls}`
|
90
|
+
|
91
|
+
console.table(data)
|
92
|
+
}
|
93
|
+
|
94
|
+
hide () {
|
95
|
+
let renderingElement = this.trigger ? this.trigger.renderingElement : null
|
96
|
+
renderingElement =
|
97
|
+
renderingElement || (this.target ? this.target.renderingElement : null)
|
98
|
+
|
99
|
+
this.destroyTriggerTooltip()
|
100
|
+
this.destroyTargetTooltip()
|
101
|
+
removeHighlight(this.target)
|
102
|
+
removeHighlight(renderingElement)
|
103
|
+
this.cleanup()
|
104
|
+
}
|
105
|
+
|
106
|
+
cleanup () {
|
107
|
+
document
|
108
|
+
.querySelectorAll('reflex-behaviors-devools-tooltip')
|
109
|
+
.forEach(el => el.remove())
|
110
|
+
|
111
|
+
document
|
112
|
+
.querySelectorAll('[data-reflex-behaviors-highlight]')
|
113
|
+
.forEach(el => {
|
114
|
+
if (!el.tagName.match(/toggle-trigger/i)) removeHighlight(el)
|
115
|
+
})
|
116
|
+
}
|
117
|
+
|
118
|
+
createTriggerTooltip () {
|
119
|
+
if (!this.trigger) return
|
120
|
+
const title = `TRIGGER (targets: ${this.trigger.controls})`
|
121
|
+
const content = this.trigger.viewStack
|
122
|
+
.map(view => {
|
123
|
+
return this.trigger.sharedViews.includes(view)
|
124
|
+
? `<div slot="emphasis">${view}</div>`
|
125
|
+
: `<div slot="normal">${view}</div>`
|
126
|
+
}, this)
|
127
|
+
.join('')
|
128
|
+
|
129
|
+
this.triggerTooltip = appendTooltip(triggerTooltipId, title, content, {
|
130
|
+
backgroundColor: 'pink',
|
131
|
+
emphaisColor: 'darkred'
|
132
|
+
})
|
133
|
+
|
134
|
+
const coords = this.trigger.coordinates
|
135
|
+
const top = Math.ceil(coords.top - this.triggerTooltip.offsetHeight - 14)
|
136
|
+
const left = Math.ceil(coords.left - 15)
|
137
|
+
this.triggerTooltip.style.top = `${top}px`
|
138
|
+
this.triggerTooltip.style.left = `${left}px`
|
139
|
+
}
|
140
|
+
|
141
|
+
destroyTriggerTooltip () {
|
142
|
+
if (!this.triggerTooltip) return
|
143
|
+
this.triggerTooltip.remove()
|
144
|
+
delete this.triggerTooltip
|
145
|
+
}
|
146
|
+
|
147
|
+
createTargetTooltip () {
|
148
|
+
if (!this.target) return
|
149
|
+
if (!this.target.viewStack) return
|
150
|
+
|
151
|
+
const title = `TARGET (id: ${this.target.id})`
|
152
|
+
const content = this.target.viewStack
|
153
|
+
.map(view => {
|
154
|
+
return this.trigger.sharedViews.includes(view)
|
155
|
+
? `<div slot="emphasis">${view}</div>`
|
156
|
+
: `<div slot="normal">${view}</div>`
|
157
|
+
}, this)
|
158
|
+
.join('')
|
159
|
+
|
160
|
+
this.targetTooltip = appendTooltip(targetTooltipId, title, content, {
|
161
|
+
backgroundColor: 'lightskyblue',
|
162
|
+
emphaisColor: 'blue',
|
163
|
+
position: 'bottom'
|
164
|
+
})
|
165
|
+
|
166
|
+
const coords = this.target.coordinates
|
167
|
+
const top = Math.ceil(coords.top + coords.height + 12)
|
168
|
+
const left = Math.ceil(coords.left - 15)
|
169
|
+
this.targetTooltip.style.top = `${top}px`
|
170
|
+
this.targetTooltip.style.left = `${left}px`
|
171
|
+
}
|
172
|
+
|
173
|
+
destroyTargetTooltip () {
|
174
|
+
if (!this.targetTooltip) return
|
175
|
+
this.targetTooltip.remove()
|
176
|
+
delete this.targetTooltip
|
177
|
+
}
|
178
|
+
}
|
@@ -1,6 +1,14 @@
|
|
1
1
|
export default class ReflexElement extends HTMLElement {
|
2
|
+
constructor () {
|
3
|
+
super()
|
4
|
+
this.devtool = 'unknown'
|
5
|
+
this.attachShadow({ mode: 'open' })
|
6
|
+
this.shadowRoot.innerHTML = '<slot></slot>'
|
7
|
+
}
|
8
|
+
|
2
9
|
connectedCallback () {
|
3
10
|
this.ensureId()
|
11
|
+
this.dataset.elementOrigin = 'hopsoft/reflex_behaviors'
|
4
12
|
}
|
5
13
|
|
6
14
|
ensureId () {
|
@@ -23,6 +31,10 @@ export default class ReflexElement extends HTMLElement {
|
|
23
31
|
return JSON.parse(this.dataset.viewStack)
|
24
32
|
}
|
25
33
|
|
34
|
+
get partial () {
|
35
|
+
return this.viewStack[0]
|
36
|
+
}
|
37
|
+
|
26
38
|
get coordinates () {
|
27
39
|
const rect = this.getBoundingClientRect()
|
28
40
|
return {
|
@@ -1,48 +1,23 @@
|
|
1
1
|
import ReflexElement from './reflex_element'
|
2
|
-
import
|
2
|
+
import DevtoolSupervisor from '../devtools/supervisor'
|
3
|
+
import ToggleDevtool from '../devtools/toggle'
|
3
4
|
|
4
5
|
export default class ToggleTriggerElement extends ReflexElement {
|
5
|
-
|
6
|
-
super()
|
7
|
-
|
8
|
-
this.addEventListener('mouseenter', event => {
|
9
|
-
if (devtools.isEnabled('toggle')) {
|
10
|
-
clearTimeout(this.mouseLeaveTimeout)
|
11
|
-
event.target.target.classList.add('debug')
|
12
|
-
event.target.showDebugTooltips()
|
13
|
-
}
|
14
|
-
})
|
6
|
+
connectedCallback () {
|
7
|
+
super.connectedCallback()
|
8
|
+
const mouseenter = () => this.devtool.show()
|
15
9
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
this.mouseLeaveTimeout = setTimeout(() => {
|
20
|
-
event.target.target.classList.remove('debug')
|
21
|
-
document
|
22
|
-
.querySelectorAll('.reflex-behaviors-tooltip')
|
23
|
-
.forEach(tooltip => tooltip.remove())
|
24
|
-
}, 250)
|
25
|
-
}
|
10
|
+
document.addEventListener('reflex-behaviors:devtools-start', () => {
|
11
|
+
this.devtool = new ToggleDevtool(this)
|
12
|
+
this.addEventListener('mouseenter', mouseenter)
|
26
13
|
})
|
27
|
-
}
|
28
14
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
let title = `controls: ${this.controls}`
|
33
|
-
let body = this.viewStack.map(path => {
|
34
|
-
shared = shared || path === sharedViewPath
|
35
|
-
return `<div class='${shared ? 'shared' : null}'>${path}<div>`
|
15
|
+
document.addEventListener('reflex-behaviors:devtools-stop', () => {
|
16
|
+
this.removeEventListener('mouseenter', mouseenter)
|
17
|
+
delete this.devtool
|
36
18
|
})
|
37
|
-
devtools.tooltip(this, title, body.join(''), 'trigger', 'top')
|
38
19
|
|
39
|
-
|
40
|
-
title = `id: ${this.target.id}`
|
41
|
-
body = this.target.viewStack.map(path => {
|
42
|
-
shared = shared || path === sharedViewPath
|
43
|
-
return `<div class='${shared ? 'shared' : null}'>${path}<div>`
|
44
|
-
})
|
45
|
-
devtools.tooltip(this.target, title, body.join(''), 'target', 'bottom')
|
20
|
+
if (DevtoolSupervisor.started) DevtoolSupervisor.restart()
|
46
21
|
}
|
47
22
|
|
48
23
|
collapse () {
|
@@ -54,19 +29,39 @@ export default class ToggleTriggerElement extends ReflexElement {
|
|
54
29
|
}
|
55
30
|
}
|
56
31
|
|
57
|
-
get
|
58
|
-
|
59
|
-
|
32
|
+
get sharedViews () {
|
33
|
+
if (!this.target) return []
|
34
|
+
if (!this.target.viewStack) return []
|
35
|
+
const reducer = (memo, view) => {
|
36
|
+
if (this.target.viewStack.includes(view)) memo.push(view)
|
37
|
+
return memo
|
38
|
+
}
|
39
|
+
return this.viewStack.reduce(reducer.bind(this), [])
|
60
40
|
}
|
61
41
|
|
62
|
-
get
|
63
|
-
|
42
|
+
get renderingInfo () {
|
43
|
+
if (!this.dataset.render) return {}
|
44
|
+
return JSON.parse(this.dataset.render)
|
45
|
+
}
|
46
|
+
|
47
|
+
get renderingPartial () {
|
48
|
+
return this.renderingInfo.partial
|
49
|
+
}
|
50
|
+
|
51
|
+
get renderingElement () {
|
52
|
+
const { id } = this.renderingInfo
|
53
|
+
if (!id) return null
|
54
|
+
return document.getElementById(id)
|
64
55
|
}
|
65
56
|
|
66
57
|
get expanded () {
|
67
58
|
return this.getAttribute('aria-expanded') === 'true'
|
68
59
|
}
|
69
60
|
|
61
|
+
get controls () {
|
62
|
+
return this.getAttribute('aria-controls')
|
63
|
+
}
|
64
|
+
|
70
65
|
get target () {
|
71
66
|
return document.getElementById(this.controls)
|
72
67
|
}
|
@@ -94,6 +89,7 @@ addEventListener(
|
|
94
89
|
)
|
95
90
|
|
96
91
|
addEventListener('click', event => {
|
92
|
+
if (event.target.tagName.match(/reflex-behaviors-devtool/i)) return
|
97
93
|
setTimeout(() => {
|
98
94
|
const selector =
|
99
95
|
'toggle-trigger[aria-controls][aria-expanded="true"][data-auto-collapse="true"]'
|
data/app/javascript/index.js
CHANGED
@@ -34,8 +34,11 @@ class ReflexBehaviors::ApplicationReflex < TurboReflex::Base
|
|
34
34
|
private
|
35
35
|
|
36
36
|
def hydrated_value(value)
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
hydrated = begin
|
38
|
+
GlobalID::Locator.locate_signed(value)
|
39
|
+
rescue
|
40
|
+
value
|
41
|
+
end
|
42
|
+
hydrated.blank? ? nil : hydrated
|
40
43
|
end
|
41
44
|
end
|
@@ -4,25 +4,6 @@ require_relative "base_tag_builder"
|
|
4
4
|
|
5
5
|
module ReflexBehaviors::TagBuilders
|
6
6
|
class ToggleTagsBuilder < BaseTagBuilder
|
7
|
-
def target_tag(id, expanded: false, **kwargs, &block)
|
8
|
-
kwargs = kwargs.with_indifferent_access
|
9
|
-
kwargs[:id] = id
|
10
|
-
kwargs[:role] = "region"
|
11
|
-
|
12
|
-
kwargs[:aria] ||= {}
|
13
|
-
kwargs[:aria][:label] ||= "Dynamic Content Region"
|
14
|
-
kwargs[:aria][:live] ||= "polite"
|
15
|
-
|
16
|
-
kwargs[:data] ||= {}
|
17
|
-
kwargs[:data][:view_stack] = view_stack.to_json if Rails.env.development?
|
18
|
-
|
19
|
-
if expanded || target_expanded?(id)
|
20
|
-
content_tag("toggle-target", nil, kwargs, &block)
|
21
|
-
else
|
22
|
-
content_tag("toggle-target", nil, kwargs)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
7
|
def trigger_tag(target:, render:, action: :toggle, disabled: false, **kwargs, &block)
|
27
8
|
kwargs = kwargs.with_indifferent_access
|
28
9
|
kwargs[:id] ||= "#{target}-toggle-trigger"
|
@@ -42,6 +23,25 @@ module ReflexBehaviors::TagBuilders
|
|
42
23
|
content_tag("toggle-trigger", nil, kwargs, &block)
|
43
24
|
end
|
44
25
|
|
26
|
+
def target_tag(id, expanded: false, **kwargs, &block)
|
27
|
+
kwargs = kwargs.with_indifferent_access
|
28
|
+
kwargs[:id] = id
|
29
|
+
kwargs[:role] = "region"
|
30
|
+
|
31
|
+
kwargs[:aria] ||= {}
|
32
|
+
kwargs[:aria][:label] ||= "Dynamic Content Region"
|
33
|
+
kwargs[:aria][:live] ||= "polite"
|
34
|
+
|
35
|
+
kwargs[:data] ||= {}
|
36
|
+
kwargs[:data][:view_stack] = view_stack.to_json if Rails.env.development?
|
37
|
+
|
38
|
+
if expanded || target_expanded?(id)
|
39
|
+
content_tag("toggle-target", nil, kwargs, &block)
|
40
|
+
else
|
41
|
+
content_tag("toggle-target", nil, kwargs)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
45
|
def target_expanded?(target)
|
46
46
|
!!turbo_reflex.state[target]
|
47
47
|
end
|
data/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "reflex_behaviors",
|
3
|
-
"version": "0.0.
|
3
|
+
"version": "0.0.4",
|
4
4
|
"description": "Pre-built easy to use reactive TurboReflex behaviors for Rails/Hotwire apps.",
|
5
5
|
"main": "app/javascript/index.js",
|
6
6
|
"repository": "https://github.com/hopsoft/reflex_behaviors",
|