reflex_behaviors 0.0.3 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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",
|