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.
@@ -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 devtools from '../devtools'
2
+ import DevtoolSupervisor from '../devtools/supervisor'
3
+ import ToggleDevtool from '../devtools/toggle'
3
4
 
4
5
  export default class ToggleTriggerElement extends ReflexElement {
5
- constructor () {
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
- this.addEventListener('mouseleave', event => {
17
- if (devtools.isEnabled('toggle')) {
18
- clearTimeout(this.mouseLeaveTimeout)
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
- showDebugTooltips () {
30
- const sharedViewPath = this.sharedViewPath
31
- let shared = false
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
- shared = false
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 sharedViewPath () {
58
- const targetViewStack = this.target.viewStack
59
- return this.viewStack.find(path => targetViewStack.includes(path))
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 controls () {
63
- return this.getAttribute('aria-controls')
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"]'
@@ -5,8 +5,4 @@ import 'turbo_reflex'
5
5
  import './elements'
6
6
  import devtools from './devtools'
7
7
 
8
- const { enable, disable, start, stop } = devtools
9
-
10
- self.ReflexBehavors = {
11
- devtools: { enable, disable, start, stop }
12
- }
8
+ self.ReflexBehaviors = { devtools }
@@ -34,8 +34,11 @@ class ReflexBehaviors::ApplicationReflex < TurboReflex::Base
34
34
  private
35
35
 
36
36
  def hydrated_value(value)
37
- GlobalID::Locator.locate_signed(value)
38
- rescue
39
- value
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
@@ -8,7 +8,6 @@ module ReflexBehaviors
8
8
  end
9
9
 
10
10
  class Engine < ::Rails::Engine
11
- config.assets.precompile << "reflex_behaviors_manifest.js"
12
11
  config.reflex_behaviors = ActiveSupport::OrderedOptions.new
13
12
 
14
13
  ActiveSupport.on_load(:action_controller) do
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ReflexBehaviors
4
- VERSION = "0.0.3"
4
+ VERSION = "0.0.5"
5
5
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reflex_behaviors",
3
- "version": "0.0.2",
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",