reflex_behaviors 0.0.2 → 0.0.4
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 +13 -8
- data/README.md +1 -1
- data/app/assets/builds/reflex_behaviors.js +133 -3
- data/app/assets/builds/reflex_behaviors.js.map +4 -4
- data/app/helpers/reflex_behaviors/application_helper.rb +13 -6
- data/app/javascript/devtools/dom.js +34 -0
- data/app/javascript/devtools/elements/devtool_element.js +62 -0
- data/app/javascript/devtools/elements/supervisor_element.js +113 -0
- data/app/javascript/devtools/elements/tooltip_element.js +102 -0
- data/app/javascript/devtools/index.js +5 -0
- data/app/javascript/devtools/supervisor.js +83 -0
- data/app/javascript/devtools/toggle.js +150 -0
- data/app/javascript/elements/reflex_element.js +27 -0
- data/app/javascript/elements/toggle_trigger_element.js +50 -2
- data/app/javascript/index.js +3 -0
- data/lib/reflex_behaviors/tag_builders/base_tag_builder.rb +8 -0
- data/lib/reflex_behaviors/tag_builders/toggle_tags_builder.rb +24 -33
- data/lib/reflex_behaviors/version.rb +1 -1
- data/package.json +1 -1
- data/tags +1069 -853
- data/yarn.lock +141 -141
- metadata +9 -2
@@ -3,18 +3,25 @@
|
|
3
3
|
require ReflexBehaviors::Engine.root.join("lib/reflex_behaviors/tag_builders")
|
4
4
|
|
5
5
|
module ReflexBehaviors::ApplicationHelper
|
6
|
-
def
|
7
|
-
|
8
|
-
partial_path ||= caller_locations(start, 1).first.path
|
9
|
-
partial_path = partial_path.split(prefix).last if partial_path.include?(prefix)
|
10
|
-
partial_path[0, partial_path.index(".") || partial_path.length].gsub(/\/_/, "/")
|
6
|
+
def idomatic_partial_path(partial_path)
|
7
|
+
partial_path.to_s.gsub("/_", "/").split(".").first
|
11
8
|
end
|
12
9
|
|
13
10
|
def current_partial_path
|
14
|
-
|
11
|
+
path = nil
|
12
|
+
prefix = "app/views/"
|
13
|
+
start = 1
|
14
|
+
while path.nil? && start < 100
|
15
|
+
location = caller_locations(start, 1).first
|
16
|
+
path = location.path if location.path.include?(prefix)
|
17
|
+
start += 1
|
18
|
+
end
|
19
|
+
return "unknown" if path.nil?
|
20
|
+
path[(path.index(prefix) + prefix.length), path.rindex("/")]
|
15
21
|
end
|
16
22
|
|
17
23
|
def reflex_render(**kwargs)
|
24
|
+
kwargs[:partial] = idomatic_partial_path(kwargs[:partial])
|
18
25
|
kwargs[:assigns] ||= {}
|
19
26
|
kwargs[:assigns].each { |key, val| kwargs[:assigns][key] = transportable_value(val) }
|
20
27
|
kwargs[:locals] ||= {}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
export function template (html) {
|
2
|
+
let template = document.createElement('template')
|
3
|
+
template.innerHTML = html
|
4
|
+
return template
|
5
|
+
}
|
6
|
+
|
7
|
+
export function appendHTML (html, parent) {
|
8
|
+
parent = parent || document.body
|
9
|
+
return parent.appendChild(template(html).content.cloneNode(true))
|
10
|
+
}
|
11
|
+
|
12
|
+
export function addHighlight (element, options = {}) {
|
13
|
+
if (!element) return
|
14
|
+
let { color, offset, width } = options
|
15
|
+
color = color || 'red'
|
16
|
+
offset = offset || '0px'
|
17
|
+
width = width || '3px'
|
18
|
+
const { outline, outlineOffset } = element.style
|
19
|
+
element.originalStyles = element.originalStyles || {
|
20
|
+
outline,
|
21
|
+
outlineOffset
|
22
|
+
}
|
23
|
+
element.style.outline = `dotted ${width} ${color}`
|
24
|
+
element.style.outlineOffset = offset
|
25
|
+
}
|
26
|
+
|
27
|
+
export function removeHighlight (element) {
|
28
|
+
if (element && element.originalStyles) {
|
29
|
+
for (const [key, value] of Object.entries(element.originalStyles)) {
|
30
|
+
value ? (element.style[key] = value) : (element.style[key] = '')
|
31
|
+
}
|
32
|
+
delete element.originalStyles
|
33
|
+
}
|
34
|
+
}
|
@@ -0,0 +1,62 @@
|
|
1
|
+
export default class DevtoolElement extends HTMLElement {
|
2
|
+
constructor () {
|
3
|
+
super()
|
4
|
+
this.attachShadow({ mode: 'open' })
|
5
|
+
this.shadowRoot.innerHTML = this.html
|
6
|
+
this.labelElement.addEventListener('click', event => {
|
7
|
+
event.preventDefault()
|
8
|
+
this.toggle()
|
9
|
+
})
|
10
|
+
this.checkboxElement.addEventListener('change', event =>
|
11
|
+
this.dispatchEvent(new CustomEvent('change', { bubbles: true }))
|
12
|
+
)
|
13
|
+
}
|
14
|
+
|
15
|
+
toggle () {
|
16
|
+
this.checked ? this.uncheck() : this.check()
|
17
|
+
}
|
18
|
+
|
19
|
+
check () {
|
20
|
+
this.checkboxElement.checked = true
|
21
|
+
this.dispatchEvent(new CustomEvent('change', { bubbles: true }))
|
22
|
+
}
|
23
|
+
|
24
|
+
uncheck () {
|
25
|
+
this.checkboxElement.checked = false
|
26
|
+
this.dispatchEvent(new CustomEvent('change', { bubbles: true }))
|
27
|
+
}
|
28
|
+
|
29
|
+
get name () {
|
30
|
+
return this.getAttribute('name')
|
31
|
+
}
|
32
|
+
|
33
|
+
get checked () {
|
34
|
+
return this.checkboxElement.checked
|
35
|
+
}
|
36
|
+
|
37
|
+
get checkboxElement () {
|
38
|
+
return this.shadowRoot.querySelector('input')
|
39
|
+
}
|
40
|
+
|
41
|
+
get labelElement () {
|
42
|
+
return this.shadowRoot.querySelector('label')
|
43
|
+
}
|
44
|
+
|
45
|
+
get html () {
|
46
|
+
return `
|
47
|
+
<style>${this.stylesheet}</style>
|
48
|
+
<div>
|
49
|
+
<input name="checkbox" type="checkbox">
|
50
|
+
<label for="checkbox"><slot name="label"></slot></label>
|
51
|
+
</div>
|
52
|
+
`
|
53
|
+
}
|
54
|
+
|
55
|
+
get stylesheet () {
|
56
|
+
return `
|
57
|
+
div {
|
58
|
+
display: flex;
|
59
|
+
}
|
60
|
+
`
|
61
|
+
}
|
62
|
+
}
|
@@ -0,0 +1,113 @@
|
|
1
|
+
import { appendHTML } from '../dom'
|
2
|
+
|
3
|
+
export default class SupervisorElement extends HTMLElement {
|
4
|
+
constructor () {
|
5
|
+
super()
|
6
|
+
this.enabledDevtools = {}
|
7
|
+
this.attachShadow({ mode: 'open' })
|
8
|
+
this.shadowRoot.innerHTML = this.html
|
9
|
+
this.shadowRoot
|
10
|
+
.querySelector('button[data-role="closer"]')
|
11
|
+
.addEventListener('click', () => this.close())
|
12
|
+
|
13
|
+
this.addEventListener('change', event => {
|
14
|
+
const devtoolElement = event.target
|
15
|
+
const { checked, name } = devtoolElement
|
16
|
+
checked ? this.enableDevtool(name) : this.disableDevtool(name)
|
17
|
+
})
|
18
|
+
}
|
19
|
+
|
20
|
+
enableDevtool (name) {
|
21
|
+
if (this.enabledDevtools[name]) return
|
22
|
+
this.enabledDevtools[name] = true
|
23
|
+
this.dispatchEvent(
|
24
|
+
new CustomEvent('reflex-behaviors:devtool-enable', {
|
25
|
+
bubbles: true,
|
26
|
+
detail: { name: name }
|
27
|
+
})
|
28
|
+
)
|
29
|
+
}
|
30
|
+
|
31
|
+
disableDevtool (name) {
|
32
|
+
if (!this.enabledDevtools[name]) return
|
33
|
+
delete this.enabledDevtools[name]
|
34
|
+
this.dispatchEvent(
|
35
|
+
new CustomEvent('reflex-behaviors:devtool-disable', {
|
36
|
+
bubbles: true,
|
37
|
+
detail: { name: name }
|
38
|
+
})
|
39
|
+
)
|
40
|
+
}
|
41
|
+
|
42
|
+
close () {
|
43
|
+
this.devtoolElements.forEach(el => {
|
44
|
+
if (el.checked) el.uncheck()
|
45
|
+
})
|
46
|
+
this.remove()
|
47
|
+
}
|
48
|
+
|
49
|
+
get devtoolElements () {
|
50
|
+
return this.querySelectorAll('[slot="devtool"]')
|
51
|
+
}
|
52
|
+
|
53
|
+
get closeElement () {
|
54
|
+
return this.querySelector('button[data-role="closer"]')
|
55
|
+
}
|
56
|
+
|
57
|
+
get html () {
|
58
|
+
return `
|
59
|
+
<style>${this.stylesheet}</style>
|
60
|
+
<div data-role="container">
|
61
|
+
<strong>ReflexBehaviors</strong>
|
62
|
+
<slot name="devtool"></slot>
|
63
|
+
<button data-role='closer'>X</button>
|
64
|
+
</div>
|
65
|
+
`
|
66
|
+
}
|
67
|
+
|
68
|
+
get stylesheet () {
|
69
|
+
return `
|
70
|
+
:host {
|
71
|
+
background-color: ghostwhite;
|
72
|
+
border-radius: 10px;
|
73
|
+
outline: solid 1px gainsboro;
|
74
|
+
bottom: 20px;
|
75
|
+
display: block;
|
76
|
+
filter: drop-shadow(0 4px 3px rgba(0,0,0,.07));
|
77
|
+
left: 50%;
|
78
|
+
padding: 5px 10px;
|
79
|
+
position: fixed;
|
80
|
+
transform: translateX(-50%);
|
81
|
+
z-index: 10000;
|
82
|
+
}
|
83
|
+
|
84
|
+
:host, :host * {
|
85
|
+
-webkit-user-select: none;
|
86
|
+
user-select: none;
|
87
|
+
}
|
88
|
+
|
89
|
+
strong {
|
90
|
+
color: silver;
|
91
|
+
font-weight: 600;
|
92
|
+
}
|
93
|
+
|
94
|
+
div[data-role="container"] {
|
95
|
+
display: flex;
|
96
|
+
gap: 0 5px;
|
97
|
+
}
|
98
|
+
|
99
|
+
button[data-role="closer"] {
|
100
|
+
border: none;
|
101
|
+
background-color: gainsboro;
|
102
|
+
border-radius: 50%;
|
103
|
+
color: white;
|
104
|
+
font-size: 12px;
|
105
|
+
height: 18px;
|
106
|
+
line-height: 18px;
|
107
|
+
margin: 0 -5px 0 10px;
|
108
|
+
padding: 0 3px;
|
109
|
+
width: 18px;
|
110
|
+
}
|
111
|
+
`
|
112
|
+
}
|
113
|
+
}
|
@@ -0,0 +1,102 @@
|
|
1
|
+
export default class TooltipElement extends HTMLElement {
|
2
|
+
constructor () {
|
3
|
+
super()
|
4
|
+
this.attachShadow({ mode: 'open' })
|
5
|
+
this.shadowRoot.innerHTML = this.html
|
6
|
+
}
|
7
|
+
|
8
|
+
get color () {
|
9
|
+
return this.getAttribute('color') || 'darkslategray'
|
10
|
+
}
|
11
|
+
|
12
|
+
get emphasisColor () {
|
13
|
+
return this.getAttribute('emphasis-color') || 'black'
|
14
|
+
}
|
15
|
+
|
16
|
+
get backgroundColor () {
|
17
|
+
return this.getAttribute('background-color') || 'gainsboro'
|
18
|
+
}
|
19
|
+
|
20
|
+
get position () {
|
21
|
+
return this.getAttribute('position') || 'top'
|
22
|
+
}
|
23
|
+
|
24
|
+
get cssArrow () {
|
25
|
+
switch (this.position) {
|
26
|
+
case 'bottom':
|
27
|
+
return `transparent transparent ${this.emphasisColor} transparent`
|
28
|
+
default:
|
29
|
+
return `${this.emphasisColor} transparent transparent transparent;`
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
get html () {
|
34
|
+
return `
|
35
|
+
<style>${this.stylesheet}</style>
|
36
|
+
<div role="tooltip">
|
37
|
+
<slot name="title"></slot>
|
38
|
+
<hr>
|
39
|
+
<slot name="normal"></slot>
|
40
|
+
<slot name="emphasis"></slot>
|
41
|
+
</div>
|
42
|
+
`
|
43
|
+
}
|
44
|
+
|
45
|
+
get stylesheet () {
|
46
|
+
return `
|
47
|
+
:host {
|
48
|
+
display: block;
|
49
|
+
position: absolute;
|
50
|
+
z-index: 10000;
|
51
|
+
}
|
52
|
+
|
53
|
+
* {
|
54
|
+
color: ${this.color}
|
55
|
+
}
|
56
|
+
|
57
|
+
[role="tooltip"] {
|
58
|
+
background-color: ${this.backgroundColor};
|
59
|
+
border-radius: 5px;
|
60
|
+
filter: drop-shadow(3px 3px 3px rgba(0,0,0,0.3));
|
61
|
+
font-family: monospace;
|
62
|
+
left: 50px;
|
63
|
+
min-height: 30px;
|
64
|
+
min-width: 100px;
|
65
|
+
opacity: 0.9;
|
66
|
+
outline: solid 2px ${this.emphasisColor};
|
67
|
+
padding: 8px 12px 8px 12px;
|
68
|
+
white-space: nowrap;
|
69
|
+
}
|
70
|
+
|
71
|
+
[role="tooltip"]::after {
|
72
|
+
border-color: ${this.cssArrow};
|
73
|
+
border-style: solid;
|
74
|
+
border-width: 5px;
|
75
|
+
content: "";
|
76
|
+
margin-left: -5px;
|
77
|
+
position: absolute;
|
78
|
+
top: ${this.position === 'bottom' ? '-10px' : '100%'};
|
79
|
+
}
|
80
|
+
|
81
|
+
slot[name="title"] {
|
82
|
+
color: ${this.emphasisColor};
|
83
|
+
font-weight: bold;
|
84
|
+
}
|
85
|
+
|
86
|
+
slot[name="emphasis"] {
|
87
|
+
color: ${this.emphasisColor};
|
88
|
+
font-weight: normal;
|
89
|
+
opacity: 0.7;
|
90
|
+
}
|
91
|
+
|
92
|
+
hr {
|
93
|
+
background-color: ${this.emphasisColor};
|
94
|
+
border: none;
|
95
|
+
height: 1px;
|
96
|
+
margin-bottom: 4px;
|
97
|
+
margin-top: 4px;
|
98
|
+
opacity: 0.3;
|
99
|
+
}
|
100
|
+
`
|
101
|
+
}
|
102
|
+
}
|
@@ -0,0 +1,83 @@
|
|
1
|
+
import { appendHTML } from './dom'
|
2
|
+
import DevtoolElement from './elements/devtool_element'
|
3
|
+
import SupervisorElement from './elements/supervisor_element'
|
4
|
+
import TooltipElement from './elements/tooltip_element'
|
5
|
+
|
6
|
+
customElements.define('reflex-behaviors-devtool', DevtoolElement)
|
7
|
+
customElements.define('reflex-behaviors-devtool-supervisor', SupervisorElement)
|
8
|
+
customElements.define('reflex-behaviors-devools-tooltip', TooltipElement)
|
9
|
+
|
10
|
+
let supervisorElement
|
11
|
+
|
12
|
+
function stop () {
|
13
|
+
if (!supervisorElement) return
|
14
|
+
supervisorElement.close()
|
15
|
+
supervisorElement.dispatchEvent(
|
16
|
+
new CustomEvent('reflex-behaviors:devtools-stop', {
|
17
|
+
bubbles: true
|
18
|
+
})
|
19
|
+
)
|
20
|
+
supervisorElement = null
|
21
|
+
}
|
22
|
+
|
23
|
+
function start () {
|
24
|
+
appendHTML(
|
25
|
+
'<reflex-behaviors-devtool-supervisor></reflex-behaviors-devtool-supervisor>'
|
26
|
+
)
|
27
|
+
supervisorElement = document.body.querySelector(
|
28
|
+
'reflex-behaviors-devtool-supervisor'
|
29
|
+
)
|
30
|
+
supervisorElement.dispatchEvent(
|
31
|
+
new CustomEvent('reflex-behaviors:devtools-start', {
|
32
|
+
bubbles: true
|
33
|
+
})
|
34
|
+
)
|
35
|
+
}
|
36
|
+
|
37
|
+
function restart () {
|
38
|
+
const enabledList = supervisorElement
|
39
|
+
? Object.keys(supervisorElement.enabledDevtools)
|
40
|
+
: []
|
41
|
+
|
42
|
+
stop()
|
43
|
+
start()
|
44
|
+
|
45
|
+
supervisorElement.devtoolElements.forEach(el => {
|
46
|
+
if (enabledList.includes(el.name)) el.check()
|
47
|
+
})
|
48
|
+
}
|
49
|
+
|
50
|
+
let restartTimeout
|
51
|
+
function debouncedRestart () {
|
52
|
+
clearTimeout(restartTimeout)
|
53
|
+
restartTimeout = setTimeout(restart, 25)
|
54
|
+
}
|
55
|
+
addEventListener('turbo:load', debouncedRestart)
|
56
|
+
addEventListener('turbo-frame:load', debouncedRestart)
|
57
|
+
addEventListener('turbo-reflex:success', debouncedRestart)
|
58
|
+
addEventListener('turbo-reflex:finish', debouncedRestart)
|
59
|
+
|
60
|
+
function register (name, label) {
|
61
|
+
if (!supervisorElement) return
|
62
|
+
return appendHTML(
|
63
|
+
`
|
64
|
+
<reflex-behaviors-devtool name="${name}" slot="devtool">
|
65
|
+
<span slot="label">${label}</span>
|
66
|
+
</reflex-behaviors-devtool>
|
67
|
+
`,
|
68
|
+
supervisorElement
|
69
|
+
)
|
70
|
+
}
|
71
|
+
|
72
|
+
function enabled (name) {
|
73
|
+
if (!supervisorElement) return false
|
74
|
+
return supervisorElement.enabledDevtools[name]
|
75
|
+
}
|
76
|
+
|
77
|
+
export default {
|
78
|
+
enabled,
|
79
|
+
register,
|
80
|
+
restart: debouncedRestart,
|
81
|
+
start,
|
82
|
+
stop
|
83
|
+
}
|
@@ -0,0 +1,150 @@
|
|
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
|
+
addEventListener('click', () => setTimeout(removeTooltips))
|
12
|
+
|
13
|
+
function removeTooltips () {
|
14
|
+
const ids = [triggerTooltipId, targetTooltipId]
|
15
|
+
ids.forEach(id => {
|
16
|
+
const el = document.getElementById(id)
|
17
|
+
if (el) el.remove()
|
18
|
+
})
|
19
|
+
}
|
20
|
+
|
21
|
+
function appendTooltip (id, title, content, options = {}) {
|
22
|
+
let { backgroundColor, color, emphaisColor, position } = options
|
23
|
+
color = color || 'white'
|
24
|
+
position = position || 'top'
|
25
|
+
|
26
|
+
appendHTML(`
|
27
|
+
<reflex-behaviors-devools-tooltip id="${id}" position="${position}" background-color="${backgroundColor}" color="${color}" emphasis-color="${emphaisColor}">
|
28
|
+
<div slot='title'>${title}</div>
|
29
|
+
${content}
|
30
|
+
</reflex-behaviors-devools-tooltip>
|
31
|
+
`)
|
32
|
+
return document.getElementById(id)
|
33
|
+
}
|
34
|
+
|
35
|
+
export default class ToggleDevtool {
|
36
|
+
constructor (trigger) {
|
37
|
+
this.name = 'toggle'
|
38
|
+
this.reflex = trigger.dataset.turboReflex
|
39
|
+
this.trigger = trigger
|
40
|
+
this.target = trigger.target
|
41
|
+
this.renderingPartial = trigger.renderingPartial
|
42
|
+
this.renderingElement = trigger.renderingElement
|
43
|
+
this.renderingElementId = this.renderingElement
|
44
|
+
? this.renderingElement.id
|
45
|
+
: null
|
46
|
+
|
47
|
+
document.addEventListener('reflex-behaviors:devtool-enable', event => {
|
48
|
+
const { name } = event.detail
|
49
|
+
if (name === this.name)
|
50
|
+
addHighlight(this.trigger, { color: 'red', offset: '2px' })
|
51
|
+
})
|
52
|
+
|
53
|
+
document.addEventListener('reflex-behaviors:devtool-disable', event => {
|
54
|
+
const { name } = event.detail
|
55
|
+
if (name === this.name) removeHighlight(this.trigger)
|
56
|
+
})
|
57
|
+
}
|
58
|
+
|
59
|
+
get enabled () {
|
60
|
+
return supervisor.enabled(this.name)
|
61
|
+
}
|
62
|
+
|
63
|
+
show () {
|
64
|
+
if (!this.enabled) return
|
65
|
+
this.hide()
|
66
|
+
this.createTriggerTooltip()
|
67
|
+
this.createTargetTooltip()
|
68
|
+
addHighlight(this.target, { color: 'blue', offset: '-2px' })
|
69
|
+
addHighlight(this.renderingElement, {
|
70
|
+
color: 'turquoise',
|
71
|
+
offset: '4px',
|
72
|
+
width: '4px'
|
73
|
+
})
|
74
|
+
|
75
|
+
console.table({
|
76
|
+
trigger: { id: this.trigger.id, partial: this.trigger.partial },
|
77
|
+
target: { id: this.target.id, partial: this.target.partial },
|
78
|
+
[this.reflex]: {
|
79
|
+
id: this.renderingElementId,
|
80
|
+
partial: this.renderingPartial
|
81
|
+
}
|
82
|
+
})
|
83
|
+
}
|
84
|
+
|
85
|
+
hide () {
|
86
|
+
this.destroyTriggerTooltip()
|
87
|
+
this.destroyTargetTooltip()
|
88
|
+
removeHighlight(this.target)
|
89
|
+
removeHighlight(this.renderingElement)
|
90
|
+
}
|
91
|
+
|
92
|
+
createTriggerTooltip () {
|
93
|
+
const title = `TRIGGER (targets: ${this.trigger.controls})`
|
94
|
+
const content = this.trigger.viewStack
|
95
|
+
.map(view => {
|
96
|
+
return this.trigger.sharedViews.includes(view)
|
97
|
+
? `<div slot="emphasis">${view}</div>`
|
98
|
+
: `<div slot="normal">${view}</div>`
|
99
|
+
}, this)
|
100
|
+
.join('')
|
101
|
+
|
102
|
+
this.triggerTooltip = appendTooltip(triggerTooltipId, title, content, {
|
103
|
+
backgroundColor: 'pink',
|
104
|
+
emphaisColor: 'darkred'
|
105
|
+
})
|
106
|
+
|
107
|
+
const coords = this.trigger.coordinates
|
108
|
+
const top = Math.ceil(coords.top - this.triggerTooltip.offsetHeight - 5)
|
109
|
+
const left = Math.ceil(coords.left)
|
110
|
+
this.triggerTooltip.style.top = `${top}px`
|
111
|
+
this.triggerTooltip.style.left = `${left}px`
|
112
|
+
}
|
113
|
+
|
114
|
+
destroyTriggerTooltip () {
|
115
|
+
if (!this.triggerTooltip) return
|
116
|
+
this.triggerTooltip.remove()
|
117
|
+
delete this.triggerTooltip
|
118
|
+
}
|
119
|
+
|
120
|
+
createTargetTooltip () {
|
121
|
+
if (!this.target) return
|
122
|
+
|
123
|
+
const title = `TARGET (id: ${this.target.id})`
|
124
|
+
const content = this.target.viewStack
|
125
|
+
.map(view => {
|
126
|
+
return this.trigger.sharedViews.includes(view)
|
127
|
+
? `<div slot="emphasis">${view}</div>`
|
128
|
+
: `<div slot="normal">${view}</div>`
|
129
|
+
}, this)
|
130
|
+
.join('')
|
131
|
+
|
132
|
+
this.targetTooltip = appendTooltip(targetTooltipId, title, content, {
|
133
|
+
backgroundColor: 'lightskyblue',
|
134
|
+
emphaisColor: 'blue',
|
135
|
+
position: 'bottom'
|
136
|
+
})
|
137
|
+
|
138
|
+
const coords = this.target.coordinates
|
139
|
+
const top = Math.ceil(coords.top + coords.height + 4)
|
140
|
+
const left = Math.ceil(coords.left)
|
141
|
+
this.targetTooltip.style.top = `${top}px`
|
142
|
+
this.targetTooltip.style.left = `${left}px`
|
143
|
+
}
|
144
|
+
|
145
|
+
destroyTargetTooltip () {
|
146
|
+
if (!this.targetTooltip) return
|
147
|
+
this.targetTooltip.remove()
|
148
|
+
delete this.targetTooltip
|
149
|
+
}
|
150
|
+
}
|
@@ -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 () {
|
@@ -17,4 +25,23 @@ export default class ReflexElement extends HTMLElement {
|
|
17
25
|
).toString(16)
|
18
26
|
)
|
19
27
|
}
|
28
|
+
|
29
|
+
get viewStack () {
|
30
|
+
if (!this.dataset.viewStack) return []
|
31
|
+
return JSON.parse(this.dataset.viewStack)
|
32
|
+
}
|
33
|
+
|
34
|
+
get partial () {
|
35
|
+
return this.viewStack[0]
|
36
|
+
}
|
37
|
+
|
38
|
+
get coordinates () {
|
39
|
+
const rect = this.getBoundingClientRect()
|
40
|
+
return {
|
41
|
+
left: rect.left + window.scrollX,
|
42
|
+
top: rect.top + window.scrollY,
|
43
|
+
width: this.offsetWidth,
|
44
|
+
height: this.offsetHeight
|
45
|
+
}
|
46
|
+
}
|
20
47
|
}
|
@@ -1,6 +1,29 @@
|
|
1
1
|
import ReflexElement from './reflex_element'
|
2
|
+
import DevtoolSupervisor from '../devtools/supervisor'
|
3
|
+
import ToggleDevtool from '../devtools/toggle'
|
2
4
|
|
3
5
|
export default class ToggleTriggerElement extends ReflexElement {
|
6
|
+
connectedCallback () {
|
7
|
+
super.connectedCallback()
|
8
|
+
|
9
|
+
const mouseenter = () => this.devtool.show()
|
10
|
+
const mouseleave = () => this.devtool.hide()
|
11
|
+
|
12
|
+
document.addEventListener('reflex-behaviors:devtools-start', () => {
|
13
|
+
this.devtool = new ToggleDevtool(this)
|
14
|
+
this.addEventListener('mouseenter', mouseenter)
|
15
|
+
this.addEventListener('mouseleave', mouseleave)
|
16
|
+
})
|
17
|
+
|
18
|
+
document.addEventListener('reflex-behaviors:devtools-stop', () => {
|
19
|
+
this.removeEventListener('mouseenter', mouseenter)
|
20
|
+
this.removeEventListener('mouseleave', mouseleave)
|
21
|
+
delete this.devtool
|
22
|
+
})
|
23
|
+
|
24
|
+
DevtoolSupervisor.restart()
|
25
|
+
}
|
26
|
+
|
4
27
|
collapse () {
|
5
28
|
try {
|
6
29
|
this.target.remove()
|
@@ -10,14 +33,38 @@ export default class ToggleTriggerElement extends ReflexElement {
|
|
10
33
|
}
|
11
34
|
}
|
12
35
|
|
13
|
-
get
|
14
|
-
|
36
|
+
get sharedViews () {
|
37
|
+
if (!this.target) return []
|
38
|
+
const reducer = (memo, view) => {
|
39
|
+
if (this.target.viewStack.includes(view)) memo.push(view)
|
40
|
+
return memo
|
41
|
+
}
|
42
|
+
return this.viewStack.reduce(reducer.bind(this), [])
|
43
|
+
}
|
44
|
+
|
45
|
+
get renderingInfo () {
|
46
|
+
if (!this.dataset.render) return {}
|
47
|
+
return JSON.parse(this.dataset.render)
|
48
|
+
}
|
49
|
+
|
50
|
+
get renderingPartial () {
|
51
|
+
return this.renderingInfo.partial
|
52
|
+
}
|
53
|
+
|
54
|
+
get renderingElement () {
|
55
|
+
const { id } = this.renderingInfo
|
56
|
+
if (!id) return null
|
57
|
+
return document.getElementById(id)
|
15
58
|
}
|
16
59
|
|
17
60
|
get expanded () {
|
18
61
|
return this.getAttribute('aria-expanded') === 'true'
|
19
62
|
}
|
20
63
|
|
64
|
+
get controls () {
|
65
|
+
return this.getAttribute('aria-controls')
|
66
|
+
}
|
67
|
+
|
21
68
|
get target () {
|
22
69
|
return document.getElementById(this.controls)
|
23
70
|
}
|
@@ -45,6 +92,7 @@ addEventListener(
|
|
45
92
|
)
|
46
93
|
|
47
94
|
addEventListener('click', event => {
|
95
|
+
if (event.target.tagName.match(/reflex-behaviors-devtool/i)) return
|
48
96
|
setTimeout(() => {
|
49
97
|
const selector =
|
50
98
|
'toggle-trigger[aria-controls][aria-expanded="true"][data-auto-collapse="true"]'
|