reflex_behaviors 0.0.9 → 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +4 -4
- data/README.md +23 -19
- data/app/assets/builds/reflex_behaviors.js +35 -13
- data/app/assets/builds/reflex_behaviors.js.map +3 -3
- data/app/helpers/reflex_behaviors/application_helper.rb +1 -19
- data/app/javascript/devtools/elements/tooltip_element.js +16 -4
- data/app/javascript/devtools/toggle.js +67 -51
- data/app/javascript/elements/reflex_element.js +3 -3
- data/app/javascript/elements/toggle_target_element.js +81 -1
- data/app/javascript/elements/toggle_trigger_element.js +72 -51
- data/app/reflexes/reflex_behaviors/application_reflex.rb +16 -9
- data/app/reflexes/reflex_behaviors/toggle_reflex.rb +3 -3
- data/lib/reflex_behaviors/engine.rb +1 -0
- data/lib/reflex_behaviors/tag_builders/base_tag_builder.rb +15 -0
- data/lib/reflex_behaviors/tag_builders/toggle_tags_builder.rb +57 -20
- data/lib/reflex_behaviors/version.rb +1 -1
- data/package.json +1 -1
- data/tags +854 -809
- data/yarn.lock +140 -140
- metadata +2 -2
@@ -1,12 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative "../../../lib/reflex_behaviors/tag_builders"
|
4
4
|
|
5
5
|
module ReflexBehaviors::ApplicationHelper
|
6
|
-
def idomatic_partial_path(partial_path)
|
7
|
-
partial_path.to_s.gsub("/_", "/").split(".").first
|
8
|
-
end
|
9
|
-
|
10
6
|
def current_partial_path
|
11
7
|
path = nil
|
12
8
|
prefix = "app/views/"
|
@@ -20,15 +16,6 @@ module ReflexBehaviors::ApplicationHelper
|
|
20
16
|
path[(path.index(prefix) + prefix.length), path.rindex("/")]
|
21
17
|
end
|
22
18
|
|
23
|
-
def reflex_render(**kwargs)
|
24
|
-
kwargs[:partial] = idomatic_partial_path(kwargs[:partial])
|
25
|
-
kwargs[:assigns] ||= {}
|
26
|
-
kwargs[:assigns].each { |key, val| kwargs[:assigns][key] = transportable_value(val) }
|
27
|
-
kwargs[:locals] ||= {}
|
28
|
-
kwargs[:locals].each { |key, val| kwargs[:locals][key] = transportable_value(val) }
|
29
|
-
kwargs.to_json
|
30
|
-
end
|
31
|
-
|
32
19
|
def method_missing(name, ...)
|
33
20
|
prefixes = %w[toggle_]
|
34
21
|
prefixes.each do |prefix|
|
@@ -52,9 +39,4 @@ module ReflexBehaviors::ApplicationHelper
|
|
52
39
|
def toggle_tag_builder
|
53
40
|
@toggle_tag_builder ||= ReflexBehaviors::TagBuilders::ToggleTagsBuilder.new(self)
|
54
41
|
end
|
55
|
-
|
56
|
-
def transportable_value(value)
|
57
|
-
return value.to_s unless value.respond_to?(:to_sgid_param)
|
58
|
-
value.try(:persisted?) ? value.to_sgid_param : nil
|
59
|
-
end
|
60
42
|
end
|
@@ -22,6 +22,7 @@ export default class TooltipElement extends HTMLElement {
|
|
22
22
|
<style>${this.stylesheet}</style>
|
23
23
|
<div>
|
24
24
|
<slot name="title"></slot>
|
25
|
+
<slot name="subtitle"></slot>
|
25
26
|
<slot name="content-top"></slot>
|
26
27
|
<slot name="content"></slot>
|
27
28
|
<slot name="content-bottom"></slot>
|
@@ -52,18 +53,29 @@ export default class TooltipElement extends HTMLElement {
|
|
52
53
|
opacity: 0.9;
|
53
54
|
outline-offset: 1px;
|
54
55
|
outline: dashed 3px ${this.color};
|
55
|
-
padding:
|
56
|
+
padding: 12px;
|
56
57
|
position: relative;
|
57
58
|
white-space: nowrap;
|
58
59
|
}
|
59
60
|
|
60
61
|
slot[name="title"] {
|
61
|
-
border-bottom: dotted 1px ${this.color};
|
62
62
|
color: ${this.color};
|
63
|
-
display:
|
63
|
+
display: block;
|
64
64
|
font-weight: bold;
|
65
|
+
width: 100%;
|
66
|
+
}
|
67
|
+
|
68
|
+
slot[name="subtitle"] {
|
69
|
+
border-bottom: dotted 1px ${this.color};
|
70
|
+
border-top: dotted 1px ${this.color};
|
71
|
+
color: ${this.color};
|
72
|
+
display: block;
|
73
|
+
font-size: 0.8rem;
|
74
|
+
font-weight: lighter;
|
65
75
|
margin-bottom: 8px;
|
66
|
-
|
76
|
+
margin-top: 4px;
|
77
|
+
padding-bottom: 4px;
|
78
|
+
padding-top: 4px;
|
67
79
|
width: 100%;
|
68
80
|
}
|
69
81
|
|
@@ -12,29 +12,31 @@ document.addEventListener('reflex-behaviors:devtools-start', () =>
|
|
12
12
|
supervisor.register('toggle', 'toggles<small>(trigger/target)</small>')
|
13
13
|
)
|
14
14
|
|
15
|
-
function appendTooltip (title, content, options = {}) {
|
15
|
+
function appendTooltip (title, subtitle, content, options = {}) {
|
16
16
|
let { backgroundColor, color, position } = options
|
17
17
|
color = color || 'white'
|
18
18
|
position = position || 'top'
|
19
19
|
return appendHTML(`
|
20
20
|
<reflex-behaviors-devools-tooltip position="${position}" background-color="${backgroundColor}" color="${color}">
|
21
21
|
<div slot='title'>${title}</div>
|
22
|
+
<div slot='subtitle'>${subtitle}</div>
|
22
23
|
${content}
|
23
24
|
</reflex-behaviors-devools-tooltip>
|
24
25
|
`)
|
25
26
|
}
|
26
27
|
|
27
28
|
export default class ToggleDevtool {
|
28
|
-
constructor (
|
29
|
+
constructor (triggerElement) {
|
29
30
|
this.name = 'toggle'
|
30
|
-
this.reflex =
|
31
|
-
this.
|
32
|
-
this.
|
31
|
+
this.reflex = triggerElement.dataset.turboReflex
|
32
|
+
this.triggerElement = triggerElement // SEE: app/javascript/elements/toggle_trigger_element.js
|
33
|
+
this.targetElement = triggerElement.targetElement // SEE: app/javascript/elements/toggle_target_element.js
|
34
|
+
this.morphElement = triggerElement.morphElement
|
33
35
|
|
34
36
|
document.addEventListener('reflex-behaviors:devtool-enable', event => {
|
35
37
|
const { name } = event.detail
|
36
38
|
if (name === this.name) {
|
37
|
-
addHighlight(this.
|
39
|
+
addHighlight(this.triggerElement, {
|
38
40
|
outline: '3px dashed blueviolet',
|
39
41
|
outlineOffset: '2px'
|
40
42
|
})
|
@@ -43,7 +45,7 @@ export default class ToggleDevtool {
|
|
43
45
|
|
44
46
|
document.addEventListener('reflex-behaviors:devtool-disable', event => {
|
45
47
|
const { name } = event.detail
|
46
|
-
if (name === this.name) removeHighlight(this.
|
48
|
+
if (name === this.name) removeHighlight(this.triggerElement)
|
47
49
|
})
|
48
50
|
|
49
51
|
let hideTimeout
|
@@ -73,45 +75,47 @@ export default class ToggleDevtool {
|
|
73
75
|
activeToggle = this
|
74
76
|
this.hide()
|
75
77
|
|
76
|
-
addHighlight(this.
|
78
|
+
addHighlight(this.targetElement, {
|
77
79
|
outline: '3px dashed darkcyan',
|
78
80
|
outlineOffset: '-2px'
|
79
81
|
})
|
80
82
|
|
81
|
-
addHighlight(this.
|
83
|
+
addHighlight(this.triggerElement.morphElement, {
|
82
84
|
outline: '3px dashed chocolate',
|
83
85
|
outlineOffset: '3px'
|
84
86
|
})
|
85
87
|
|
86
|
-
const
|
88
|
+
const morphTooltip = this.createMorphTooltip()
|
87
89
|
const targetTooltip = this.createTargetTooltip()
|
88
|
-
this.createTriggerTooltip(targetTooltip,
|
90
|
+
this.createTriggerTooltip(targetTooltip, morphTooltip)
|
89
91
|
|
90
92
|
document
|
91
93
|
.querySelectorAll('.leader-line')
|
92
94
|
.forEach(el => (el.style.zIndex = 100000))
|
93
95
|
|
94
96
|
const data = {
|
95
|
-
|
96
|
-
partial: this.
|
97
|
-
id: this.
|
98
|
-
status: this.
|
97
|
+
morph: {
|
98
|
+
partial: this.triggerElement.renders,
|
99
|
+
id: this.triggerElement.morphs,
|
100
|
+
status: this.morphElement ? 'OK' : 'Not Found'
|
99
101
|
},
|
100
102
|
trigger: { partial: null, id: null, status: 'Not Found' },
|
101
103
|
target: { partial: null, id: null, status: 'Not Found' }
|
102
104
|
}
|
103
105
|
|
104
|
-
if (this.
|
106
|
+
if (this.triggerElement) {
|
105
107
|
data.trigger = {
|
106
|
-
partial: this.
|
107
|
-
id: this.
|
108
|
+
partial: this.triggerElement.partial,
|
109
|
+
id: this.triggerElement.id,
|
108
110
|
status: 'OK'
|
109
111
|
}
|
112
|
+
data.target.id = this.triggerElement.controls
|
113
|
+
}
|
110
114
|
|
111
|
-
if (this.
|
115
|
+
if (this.targetElement)
|
112
116
|
data.target = {
|
113
|
-
partial: this.
|
114
|
-
id: this.
|
117
|
+
partial: this.targetElement.partial,
|
118
|
+
id: this.targetElement.id,
|
115
119
|
status: 'OK'
|
116
120
|
}
|
117
121
|
|
@@ -133,21 +137,24 @@ export default class ToggleDevtool {
|
|
133
137
|
if (clearActiveToggle) activeToggle = null
|
134
138
|
}
|
135
139
|
|
136
|
-
|
137
|
-
if (!this.
|
140
|
+
createMorphTooltip () {
|
141
|
+
if (!this.triggerElement.morphs)
|
138
142
|
return console.debug(
|
139
|
-
`Unable to create the
|
143
|
+
`Unable to create the morph tooltip! No element matches the DOM id: '${this.triggerElement.morphs}'`
|
140
144
|
)
|
141
145
|
|
142
|
-
const title =
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
+
const title = 'PARTIAL'
|
147
|
+
const subtitle = `
|
148
|
+
id: ${this.triggerElement.morphs || 'unknown'}<br>
|
149
|
+
partial: ${this.triggerElement.renders || 'unknown'}
|
150
|
+
`
|
151
|
+
const content = '<div slot="content"></div>'
|
152
|
+
const tooltip = appendTooltip(title, subtitle, content, {
|
146
153
|
backgroundColor: 'lightyellow',
|
147
154
|
color: 'chocolate'
|
148
155
|
})
|
149
156
|
|
150
|
-
const coords = coordinates(this.
|
157
|
+
const coords = coordinates(this.morphElement)
|
151
158
|
const top = Math.ceil(
|
152
159
|
coords.top + coords.height / 2 - tooltip.offsetHeight / 2
|
153
160
|
)
|
@@ -155,7 +162,7 @@ export default class ToggleDevtool {
|
|
155
162
|
tooltip.style.top = `${top}px`
|
156
163
|
tooltip.style.left = `${left}px`
|
157
164
|
|
158
|
-
tooltip.line = new LeaderLine(tooltip, this.
|
165
|
+
tooltip.line = new LeaderLine(tooltip, this.morphElement, {
|
159
166
|
...this.leaderLineOptions,
|
160
167
|
color: 'chocolate'
|
161
168
|
})
|
@@ -165,34 +172,38 @@ export default class ToggleDevtool {
|
|
165
172
|
}
|
166
173
|
|
167
174
|
createTargetTooltip () {
|
168
|
-
if (!this.
|
175
|
+
if (!this.targetElement)
|
169
176
|
return console.debug(
|
170
|
-
`Unable to create the target tooltip! No element matches the DOM id: '${this.
|
177
|
+
`Unable to create the target tooltip! No element matches the DOM id: '${this.triggerElement.controls}'`
|
171
178
|
)
|
172
179
|
|
173
|
-
const title =
|
174
|
-
const
|
180
|
+
const title = 'TARGET'
|
181
|
+
const subtitle = `
|
182
|
+
id: ${this.targetElement.id}<br>
|
183
|
+
labeled by: ${this.targetElement.labeledBy}
|
184
|
+
`
|
185
|
+
const content = this.targetElement.viewStack
|
175
186
|
.reverse()
|
176
187
|
.map((view, index) => {
|
177
|
-
return this.
|
188
|
+
return this.triggerElement.sharedViews.includes(view)
|
178
189
|
? `<div slot="content-top">${index + 1}. ${view}</div>`
|
179
190
|
: `<div slot="content-bottom">${index + 1}. ${view}</div>`
|
180
191
|
}, this)
|
181
192
|
.join('')
|
182
193
|
|
183
|
-
const tooltip = appendTooltip(title, content, {
|
194
|
+
const tooltip = appendTooltip(title, subtitle, content, {
|
184
195
|
backgroundColor: 'lightcyan',
|
185
196
|
color: 'darkcyan',
|
186
197
|
position: 'bottom'
|
187
198
|
})
|
188
199
|
|
189
|
-
const coords = coordinates(this.
|
200
|
+
const coords = coordinates(this.targetElement)
|
190
201
|
const top = Math.ceil(coords.top + tooltip.offsetHeight)
|
191
202
|
const left = Math.ceil(coords.left + coords.width + tooltip.offsetWidth / 3)
|
192
203
|
tooltip.style.top = `${top}px`
|
193
204
|
tooltip.style.left = `${left}px`
|
194
205
|
|
195
|
-
tooltip.line = new LeaderLine(tooltip, this.
|
206
|
+
tooltip.line = new LeaderLine(tooltip, this.targetElement, {
|
196
207
|
...this.leaderLineOptions,
|
197
208
|
color: 'darkcyan'
|
198
209
|
})
|
@@ -201,30 +212,34 @@ export default class ToggleDevtool {
|
|
201
212
|
return tooltip
|
202
213
|
}
|
203
214
|
|
204
|
-
createTriggerTooltip (targetTooltip,
|
205
|
-
if (!this.
|
206
|
-
const title =
|
207
|
-
const
|
215
|
+
createTriggerTooltip (targetTooltip, morphTooltip) {
|
216
|
+
if (!this.triggerElement) return
|
217
|
+
const title = 'TRIGGER'
|
218
|
+
const subtitle = `
|
219
|
+
id: ${this.triggerElement.id}<br>
|
220
|
+
controls: ${this.triggerElement.controls}
|
221
|
+
`
|
222
|
+
const content = this.triggerElement.viewStack
|
208
223
|
.reverse()
|
209
224
|
.map((view, index) => {
|
210
|
-
return this.
|
225
|
+
return this.triggerElement.sharedViews.includes(view)
|
211
226
|
? `<div slot="content-top">${index + 1}. ${view}</div>`
|
212
227
|
: `<div slot="content-bottom">${index + 1}. ${view}</div>`
|
213
228
|
}, this)
|
214
229
|
.join('')
|
215
230
|
|
216
|
-
const tooltip = appendTooltip(title, content, {
|
231
|
+
const tooltip = appendTooltip(title, subtitle, content, {
|
217
232
|
backgroundColor: 'lavender',
|
218
233
|
color: 'blueviolet'
|
219
234
|
})
|
220
235
|
|
221
|
-
const coords = coordinates(this.
|
236
|
+
const coords = coordinates(this.triggerElement)
|
222
237
|
const top = Math.ceil(coords.top - tooltip.offsetHeight * 2)
|
223
238
|
const left = Math.ceil(coords.left + coords.width + tooltip.offsetWidth / 3)
|
224
239
|
tooltip.style.top = `${top}px`
|
225
240
|
tooltip.style.left = `${left}px`
|
226
241
|
|
227
|
-
tooltip.line = new LeaderLine(this.
|
242
|
+
tooltip.line = new LeaderLine(this.triggerElement, tooltip, {
|
228
243
|
...this.leaderLineOptions,
|
229
244
|
color: 'blueviolet'
|
230
245
|
})
|
@@ -244,16 +259,16 @@ export default class ToggleDevtool {
|
|
244
259
|
}
|
245
260
|
}
|
246
261
|
|
247
|
-
if (
|
248
|
-
tooltip.lineToRendering = new LeaderLine(tooltip,
|
262
|
+
if (morphTooltip) {
|
263
|
+
tooltip.lineToRendering = new LeaderLine(tooltip, morphTooltip, {
|
249
264
|
...this.leaderLineOptions,
|
250
265
|
color: 'blueviolet',
|
251
|
-
middleLabel: 'renders',
|
266
|
+
middleLabel: 'renders and morphs',
|
252
267
|
size: 2.1
|
253
268
|
})
|
254
269
|
|
255
|
-
|
256
|
-
|
270
|
+
morphTooltip.drag.onMove = () => {
|
271
|
+
morphTooltip.line.position()
|
257
272
|
if (tooltip.lineToTarget) tooltip.lineToTarget.position()
|
258
273
|
tooltip.lineToRendering.position()
|
259
274
|
}
|
@@ -261,6 +276,7 @@ export default class ToggleDevtool {
|
|
261
276
|
|
262
277
|
tooltip.drag = new PlainDraggable(tooltip)
|
263
278
|
tooltip.drag.onMove = () => {
|
279
|
+
console.log('nate', tooltip.line)
|
264
280
|
tooltip.line.position()
|
265
281
|
if (tooltip.lineToTarget) tooltip.lineToTarget.position()
|
266
282
|
if (tooltip.lineToRendering) tooltip.lineToRendering.position()
|
@@ -8,7 +8,6 @@ export default class ReflexElement extends HTMLElement {
|
|
8
8
|
|
9
9
|
connectedCallback () {
|
10
10
|
this.ensureId()
|
11
|
-
this.dataset.elementOrigin = 'hopsoft/reflex_behaviors'
|
12
11
|
}
|
13
12
|
|
14
13
|
ensureId () {
|
@@ -27,8 +26,9 @@ export default class ReflexElement extends HTMLElement {
|
|
27
26
|
}
|
28
27
|
|
29
28
|
get viewStack () {
|
30
|
-
|
31
|
-
|
29
|
+
const value = this.getAttribute('view-stack')
|
30
|
+
if (!value) return []
|
31
|
+
return JSON.parse(value)
|
32
32
|
}
|
33
33
|
|
34
34
|
get partial () {
|
@@ -1,3 +1,83 @@
|
|
1
1
|
import ReflexElement from './reflex_element'
|
2
2
|
|
3
|
-
export default class ToggleTargetElement extends ReflexElement {
|
3
|
+
export default class ToggleTargetElement extends ReflexElement {
|
4
|
+
connectedCallback () {
|
5
|
+
super.connectedCallback()
|
6
|
+
|
7
|
+
this.addEventListener('mouseenter', () =>
|
8
|
+
clearTimeout(this.collapseTimeout)
|
9
|
+
)
|
10
|
+
|
11
|
+
this.collapseOn.forEach(name =>
|
12
|
+
this.addEventListener(name, () => this.collapse())
|
13
|
+
)
|
14
|
+
}
|
15
|
+
|
16
|
+
// TODO: get cached content working properly
|
17
|
+
// perhaps use a mechanic other than morph
|
18
|
+
|
19
|
+
cacheHTML () {
|
20
|
+
// this.cachedHTML = this.innerHTML
|
21
|
+
}
|
22
|
+
|
23
|
+
renderCachedHTML () {
|
24
|
+
// if (!this.cachedHTML) return
|
25
|
+
// this.innerHTML = this.cachedHTML
|
26
|
+
}
|
27
|
+
|
28
|
+
collapse () {
|
29
|
+
clearTimeout(this.collapseTimeout)
|
30
|
+
this.collapseTimeout = setTimeout(() => {
|
31
|
+
this.innerHTML = ''
|
32
|
+
try {
|
33
|
+
this.currentTriggerElement.expanded = false
|
34
|
+
this.currentTriggerElement.hideDevtool()
|
35
|
+
} catch {}
|
36
|
+
}, 250)
|
37
|
+
}
|
38
|
+
|
39
|
+
collapseMatches () {
|
40
|
+
document.querySelectorAll(this.collapseSelector).forEach(el => {
|
41
|
+
if (el === this) return
|
42
|
+
if (el.collapse) el.collapse()
|
43
|
+
})
|
44
|
+
}
|
45
|
+
|
46
|
+
get collapseSelector () {
|
47
|
+
if (
|
48
|
+
this.currentTriggerElement &&
|
49
|
+
this.currentTriggerElement.collapseSelector
|
50
|
+
)
|
51
|
+
return this.currentTriggerElement.collapseSelector
|
52
|
+
return this.getAttribute('collapse-selector')
|
53
|
+
}
|
54
|
+
|
55
|
+
focus () {
|
56
|
+
clearTimeout(this.focusTimeout)
|
57
|
+
this.focusTimeout = setTimeout(() => {
|
58
|
+
if (!this.focusElement) return
|
59
|
+
this.focusElement.focus()
|
60
|
+
this.focusElement.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
61
|
+
}, 50)
|
62
|
+
}
|
63
|
+
|
64
|
+
get focusSelector () {
|
65
|
+
if (this.currentTriggerElement && this.currentTriggerElement.focusSelector)
|
66
|
+
return this.currentTriggerElement.focusSelector
|
67
|
+
return this.getAttribute('focus-selector')
|
68
|
+
}
|
69
|
+
|
70
|
+
get focusElement () {
|
71
|
+
return this.querySelector(this.focusSelector)
|
72
|
+
}
|
73
|
+
|
74
|
+
get labeledBy () {
|
75
|
+
return this.getAttribute('aria-labeledby')
|
76
|
+
}
|
77
|
+
|
78
|
+
get collapseOn () {
|
79
|
+
const value = this.getAttribute('collapse-on')
|
80
|
+
if (!value) return []
|
81
|
+
return JSON.parse(value)
|
82
|
+
}
|
83
|
+
}
|
@@ -5,14 +5,38 @@ import ToggleDevtool from '../devtools/toggle'
|
|
5
5
|
export default class ToggleTriggerElement extends ReflexElement {
|
6
6
|
connectedCallback () {
|
7
7
|
super.connectedCallback()
|
8
|
+
|
9
|
+
if (this.targetElement) {
|
10
|
+
this.targetElement.setAttribute('aria-labeledby', this.id)
|
11
|
+
}
|
12
|
+
|
13
|
+
this.addEventListener(TurboReflex.events.start, () => {
|
14
|
+
this.busy = true
|
15
|
+
this.targetElement.currentTriggerElement = this
|
16
|
+
this.targetElement.renderCachedHTML()
|
17
|
+
})
|
18
|
+
|
19
|
+
this.addEventListener(TurboReflex.events.success, () => {
|
20
|
+
this.busy = false
|
21
|
+
this.targetElement.focus()
|
22
|
+
this.targetElement.collapseMatches()
|
23
|
+
this.targetElement.cacheHTML()
|
24
|
+
})
|
25
|
+
|
26
|
+
this.addEventListener(TurboReflex.events.finish, () => (this.busy = false))
|
27
|
+
|
28
|
+
this.initializeDevtool()
|
29
|
+
}
|
30
|
+
|
31
|
+
initializeDevtool () {
|
8
32
|
const mouseenter = () => this.devtool.show()
|
9
33
|
|
10
|
-
|
34
|
+
addEventListener('reflex-behaviors:devtools-start', () => {
|
11
35
|
this.devtool = new ToggleDevtool(this)
|
12
36
|
this.addEventListener('mouseenter', mouseenter)
|
13
37
|
})
|
14
38
|
|
15
|
-
|
39
|
+
addEventListener('reflex-behaviors:devtools-stop', () => {
|
16
40
|
this.removeEventListener('mouseenter', mouseenter)
|
17
41
|
delete this.devtool
|
18
42
|
})
|
@@ -20,79 +44,76 @@ export default class ToggleTriggerElement extends ReflexElement {
|
|
20
44
|
if (DevtoolSupervisor.started) DevtoolSupervisor.restart()
|
21
45
|
}
|
22
46
|
|
23
|
-
|
24
|
-
|
25
|
-
this.target.remove()
|
26
|
-
this.setAttribute('aria-expanded', false)
|
27
|
-
} catch (error) {
|
28
|
-
console.error('Failed to collapse toggle-trigger target!', error)
|
29
|
-
}
|
47
|
+
hideDevtool () {
|
48
|
+
if (this.devtool) this.devtool.hide(true)
|
30
49
|
}
|
31
50
|
|
51
|
+
// a list of views shared between the trigger and target
|
32
52
|
get sharedViews () {
|
33
|
-
if (!this.
|
34
|
-
if (!this.
|
53
|
+
if (!this.targetElement) return []
|
54
|
+
if (!this.targetElement.viewStack) return []
|
35
55
|
const reducer = (memo, view) => {
|
36
|
-
if (this.
|
56
|
+
if (this.targetElement.viewStack.includes(view)) memo.push(view)
|
37
57
|
return memo
|
38
58
|
}
|
39
59
|
return this.viewStack.reduce(reducer.bind(this), [])
|
40
60
|
}
|
41
61
|
|
42
|
-
|
43
|
-
|
44
|
-
return
|
62
|
+
// the partial to render
|
63
|
+
get renders () {
|
64
|
+
return this.getAttribute('renders')
|
65
|
+
}
|
66
|
+
|
67
|
+
// the renderered partial's top wrapping dom_id
|
68
|
+
get morphs () {
|
69
|
+
return this.getAttribute('morphs')
|
45
70
|
}
|
46
71
|
|
47
|
-
|
48
|
-
|
72
|
+
// the morph element
|
73
|
+
get morphElement () {
|
74
|
+
if (!this.morphs) return null
|
75
|
+
return document.getElementById(this.morphs)
|
49
76
|
}
|
50
77
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
78
|
+
// the target's dom_id
|
79
|
+
get controls () {
|
80
|
+
return this.getAttribute('aria-controls')
|
81
|
+
}
|
82
|
+
|
83
|
+
// the target element
|
84
|
+
get targetElement () {
|
85
|
+
if (!this.controls) return null
|
86
|
+
return document.getElementById(this.controls)
|
87
|
+
}
|
88
|
+
|
89
|
+
get collapseSelector () {
|
90
|
+
return this.getAttribute('collapse-selector')
|
55
91
|
}
|
56
92
|
|
93
|
+
get focusSelector () {
|
94
|
+
return this.getAttribute('focus-selector')
|
95
|
+
}
|
96
|
+
|
97
|
+
// indicates if the target is expanded
|
57
98
|
get expanded () {
|
58
99
|
return this.getAttribute('aria-expanded') === 'true'
|
59
100
|
}
|
60
101
|
|
61
|
-
|
62
|
-
|
102
|
+
set expanded (value) {
|
103
|
+
this.setAttribute('aria-expanded', !!value)
|
63
104
|
}
|
64
105
|
|
65
|
-
|
66
|
-
|
106
|
+
// indicates if the target is expanded
|
107
|
+
get collapsed () {
|
108
|
+
return !this.expanded
|
67
109
|
}
|
68
110
|
|
69
|
-
|
70
|
-
|
111
|
+
// indicates if an rpc call is active/busy
|
112
|
+
get busy () {
|
113
|
+
return this.getAttribute('busy') === 'true'
|
71
114
|
}
|
72
115
|
|
73
|
-
set
|
74
|
-
this.setAttribute('
|
116
|
+
set busy (value) {
|
117
|
+
this.setAttribute('busy', !!value)
|
75
118
|
}
|
76
119
|
}
|
77
|
-
|
78
|
-
addEventListener(
|
79
|
-
TurboReflex.events.start,
|
80
|
-
event => (event.target.active = true)
|
81
|
-
)
|
82
|
-
addEventListener(
|
83
|
-
TurboReflex.events.success,
|
84
|
-
event => (event.target.active = false)
|
85
|
-
)
|
86
|
-
addEventListener(
|
87
|
-
TurboReflex.events.finish,
|
88
|
-
event => (event.target.active = false)
|
89
|
-
)
|
90
|
-
|
91
|
-
addEventListener('click', event => {
|
92
|
-
if (event.target.tagName.match(/reflex-behaviors-devtool/i)) return
|
93
|
-
setTimeout(() => {
|
94
|
-
const selector =
|
95
|
-
'toggle-trigger[aria-controls][aria-expanded="true"][data-auto-collapse="true"]'
|
96
|
-
document.querySelectorAll(selector).forEach(trigger => trigger.collapse())
|
97
|
-
})
|
98
|
-
})
|
@@ -21,19 +21,22 @@ class ReflexBehaviors::ApplicationReflex < TurboReflex::Base
|
|
21
21
|
protected
|
22
22
|
|
23
23
|
def render_payload
|
24
|
-
return {} if element.
|
25
|
-
@render_payload ||=
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
24
|
+
return {} if element.renders.blank?
|
25
|
+
@render_payload ||= {partial: idomatic_partial_path(element.renders)}.tap do |payload|
|
26
|
+
if element.assigns.present?
|
27
|
+
payload[:assigns] = JSON.parse(element.assigns)
|
28
|
+
payload[:assigns].each { |key, value| payload[:assigns][key] = hydrate_value(value) }
|
29
|
+
end
|
30
|
+
if element.locals.present?
|
31
|
+
payload[:locals] = JSON.parse(element.locals)
|
32
|
+
payload[:locals].each { |key, value| payload[:locals][key] = hydrate_value(value) }
|
33
|
+
end
|
34
|
+
end.deep_symbolize_keys
|
32
35
|
end
|
33
36
|
|
34
37
|
private
|
35
38
|
|
36
|
-
def
|
39
|
+
def hydrate_value(value)
|
37
40
|
hydrated = begin
|
38
41
|
GlobalID::Locator.locate_signed(value)
|
39
42
|
rescue
|
@@ -41,4 +44,8 @@ class ReflexBehaviors::ApplicationReflex < TurboReflex::Base
|
|
41
44
|
end
|
42
45
|
hydrated.blank? ? nil : hydrated
|
43
46
|
end
|
47
|
+
|
48
|
+
def idomatic_partial_path(partial_path)
|
49
|
+
partial_path.to_s.gsub("/_", "/").split(".").first
|
50
|
+
end
|
44
51
|
end
|