reflex_behaviors 0.0.2 → 0.0.3

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 @@
1
+ //= link reflex_behaviors.css
@@ -0,0 +1,51 @@
1
+ #reflex-behaviors-devtools {
2
+ background-color: ghostwhite;
3
+ border-radius: 10px;
4
+ border: solid 1px gainsboro;
5
+ bottom: 20px;
6
+ filter: drop-shadow(0 4px 3px rgba(0,0,0,.07));
7
+ left: 50%;
8
+ padding: 5px 10px;
9
+ position: fixed;
10
+ transform: translateX(-50%);
11
+ z-index: 10000;
12
+ }
13
+
14
+ #reflex-behaviors-devtools * {
15
+ -webkit-user-select: none;
16
+ user-select: none;
17
+ }
18
+
19
+ #reflex-behaviors-devtools strong {
20
+ color: silver;
21
+ font-weight: 600;
22
+ }
23
+
24
+ #reflex-behaviors-devtools button[data-action='close'] {
25
+ background-color: gainsboro;
26
+ border-radius: 50%;
27
+ color: white;
28
+ font-size: 12px;
29
+ height: 18px;
30
+ line-height: 18px;
31
+ margin: 0 -5px 0 10px;
32
+ padding: 0 3px;
33
+ width: 18px;
34
+ }
35
+
36
+ #reflex-behaviors-devtools .devtool {
37
+ cursor: pointer;
38
+ display: inline-block;
39
+ margin-left: 5px;
40
+ }
41
+
42
+ #reflex-behaviors-devtools .devtool input[type='checkbox'] {
43
+ margin-bottom: 2px;
44
+ }
45
+
46
+ #reflex-behaviors-devtools .devtool input,
47
+ #reflex-behaviors-devtools .devtool label {
48
+ display: inline-block;
49
+ margin: 0;
50
+ padding: 0;
51
+ }
@@ -0,0 +1,4 @@
1
+ /* TODO: precompile the stylesheet and host on a CDN
2
+ *= require_self
3
+ *= require_tree .
4
+ */
@@ -0,0 +1,56 @@
1
+ body[data-devtools~='toggle'] toggle-trigger {
2
+ box-sizing: border-box;
3
+ outline-offset: -2px;
4
+ outline: solid 1px salmon;
5
+ }
6
+
7
+ .reflex-behaviors-tooltip.trigger {
8
+ background-color: salmon;
9
+ border-bottom-left-radius: 0;
10
+ color: white;
11
+ }
12
+
13
+ .reflex-behaviors-tooltip.trigger .shared,
14
+ .reflex-behaviors-tooltip.trigger strong{
15
+ color: darkred;
16
+ }
17
+
18
+ .reflex-behaviors-tooltip.trigger::after {
19
+ border-color: salmon transparent transparent transparent;
20
+ border-style: solid;
21
+ border-width: 5px;
22
+ content: "";
23
+ left: 5px;
24
+ margin-left: -5px;
25
+ position: absolute;
26
+ top: 100%;
27
+ }
28
+
29
+ body[data-devtools~='toggle'] toggle-target.debug {
30
+ box-sizing: border-box;
31
+ /*display: inline-block;*/
32
+ outline-offset: -2px;
33
+ outline: solid 1px lightskyblue;
34
+ }
35
+
36
+ .reflex-behaviors-tooltip.target {
37
+ background-color: lightskyblue;
38
+ border-top-left-radius: 0;
39
+ color: white;
40
+ }
41
+
42
+ .reflex-behaviors-tooltip.target .shared,
43
+ .reflex-behaviors-tooltip.target strong {
44
+ color: blue;
45
+ }
46
+
47
+ .reflex-behaviors-tooltip.target::after {
48
+ border-color: transparent transparent lightskyblue transparent;
49
+ border-style: solid;
50
+ border-width: 5px;
51
+ bottom: 100%;
52
+ content: "";
53
+ left: 5px;
54
+ margin-left: -5px;
55
+ position: absolute;
56
+ }
@@ -0,0 +1,18 @@
1
+ .reflex-behaviors-tooltip {
2
+ border-radius: 5px;
3
+ filter: drop-shadow(0 4px 3px rgba(0,0,0,.07));
4
+ font-family: monospace;
5
+ opacity: 0.9;
6
+ padding: 8px;
7
+ position: absolute;
8
+ white-space: nowrap;
9
+ z-index: 10000;
10
+ }
11
+
12
+ .reflex-behaviors-tooltip hr {
13
+ border-top: solid 1px white;
14
+ height: 1px;
15
+ margin-bottom: 5px;
16
+ margin-top: 5px;
17
+ opacity: 0.2;
18
+ }
@@ -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 friendly_partial_path(partial_path = nil, start: 1)
7
- prefix = "app/views/"
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
- friendly_partial_path start: 2
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,117 @@
1
+ // Tasks
2
+ // - [ ] audit css class names and refine
3
+ // - [ ] extract stylesheet and host on cdn
4
+ // - [ ] ensure tooltips don't overlap or run off screen
5
+ // - [ ] add ability to remember start/stop in local storage
6
+ // - [ ] isolate individual tools and register them (i.e. plugin framework)
7
+ // will probably have behaviors register any devtools they support
8
+ //
9
+ // Tool markup example
10
+ //
11
+ // <div name="TOOL_NAME" class="devtool">
12
+ // <input name="TOOL_NAME-checkbox" value="TOOL_NAME" type="checkbox">
13
+ // <label for="TOOL_NAME-checkbox">TOOL_LABEL</label>
14
+ // </div>
15
+
16
+ let tray
17
+
18
+ addEventListener('click', () => {
19
+ setTimeout(() => {
20
+ document
21
+ .querySelectorAll('.reflex-behaviors-tooltip')
22
+ .forEach(tooltip => tooltip.remove())
23
+ }, 300)
24
+ })
25
+
26
+ function tooltip (reflexElement, title, body, cssClass, position = 'top') {
27
+ const el = document.createElement('div')
28
+ el.classList.add('reflex-behaviors-tooltip', cssClass)
29
+ el.innerHTML = `<strong>${title}</strong><hr>${body}`
30
+ document.body.appendChild(el)
31
+
32
+ const coords = reflexElement.coordinates
33
+
34
+ if (position === 'top') {
35
+ el.style.top = `${Math.ceil(coords.top - el.offsetHeight - 5)}px`
36
+ el.style.left = `${Math.ceil(coords.left + 4)}px`
37
+ }
38
+
39
+ if (position === 'bottom') {
40
+ el.style.top = `${Math.ceil(coords.top + coords.height + 5)}px`
41
+ el.style.left = `${Math.ceil(coords.left + 4)}px`
42
+ }
43
+ }
44
+
45
+ function enabled () {
46
+ const tools = document.body.dataset.devtools || ''
47
+ return tools.trim().split(' ')
48
+ }
49
+
50
+ function isEnabled (tool) {
51
+ return enabled().includes(tool)
52
+ }
53
+
54
+ function enable (tool) {
55
+ if (isEnabled(tool)) return
56
+ const list = enabled()
57
+ list.push(tool)
58
+ document.body.dataset.devtools = list.join(' ').trim()
59
+ }
60
+
61
+ function disable (tool) {
62
+ const list = enabled()
63
+ const index = list.indexOf(tool)
64
+ if (index < 0) return
65
+ list.splice(index, 1)
66
+ document.body.dataset.devtools = list.join(' ').trim()
67
+ }
68
+
69
+ function stop () {
70
+ if (!tray) return
71
+ tray
72
+ .querySelectorAll('.devtool')
73
+ .forEach(devtool => disable(devtool.getAttribute('name')))
74
+ tray.remove()
75
+ tray = null
76
+ }
77
+
78
+ function start () {
79
+ stop()
80
+ tray = document.createElement('div')
81
+ tray.id = 'reflex-behaviors-devtools'
82
+ tray.innerHTML = `
83
+ <strong>DEVTOOLS</strong>
84
+ <div name="toggle" class="devtool">
85
+ <input name="toggle-checkbox" value="toggle" type="checkbox">
86
+ <label for="toggle-checkbox">toggles<small>(trigger/target)</small></label>
87
+ </div>
88
+ <button data-action='close'>X</button>
89
+ `
90
+ document.body.appendChild(tray)
91
+ tray
92
+ .querySelector('button[data-action=close]')
93
+ .addEventListener('click', () => stop())
94
+ tray.querySelectorAll('.devtool').forEach(devtool => {
95
+ devtool.querySelector('input').addEventListener('change', event => {
96
+ event.target.checked
97
+ ? enable(event.target.value)
98
+ : disable(event.target.value)
99
+ })
100
+ devtool.addEventListener('click', event => {
101
+ if (event.target.tagName.match(/input/i)) return
102
+ event.target
103
+ .closest('.devtool')
104
+ .querySelector('input')
105
+ .click()
106
+ })
107
+ })
108
+ }
109
+
110
+ export default {
111
+ disable,
112
+ enable,
113
+ isEnabled,
114
+ start,
115
+ stop,
116
+ tooltip
117
+ }
@@ -17,4 +17,19 @@ export default class ReflexElement extends HTMLElement {
17
17
  ).toString(16)
18
18
  )
19
19
  }
20
+
21
+ get viewStack () {
22
+ if (!this.dataset.viewStack) return []
23
+ return JSON.parse(this.dataset.viewStack)
24
+ }
25
+
26
+ get coordinates () {
27
+ const rect = this.getBoundingClientRect()
28
+ return {
29
+ left: rect.left + window.scrollX,
30
+ top: rect.top + window.scrollY,
31
+ width: this.offsetWidth,
32
+ height: this.offsetHeight
33
+ }
34
+ }
20
35
  }
@@ -1,6 +1,50 @@
1
1
  import ReflexElement from './reflex_element'
2
+ import devtools from '../devtools'
2
3
 
3
4
  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
+ })
15
+
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
+ }
26
+ })
27
+ }
28
+
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>`
36
+ })
37
+ devtools.tooltip(this, title, body.join(''), 'trigger', 'top')
38
+
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')
46
+ }
47
+
4
48
  collapse () {
5
49
  try {
6
50
  this.target.remove()
@@ -10,6 +54,11 @@ export default class ToggleTriggerElement extends ReflexElement {
10
54
  }
11
55
  }
12
56
 
57
+ get sharedViewPath () {
58
+ const targetViewStack = this.target.viewStack
59
+ return this.viewStack.find(path => targetViewStack.includes(path))
60
+ }
61
+
13
62
  get controls () {
14
63
  return this.getAttribute('aria-controls')
15
64
  }
@@ -3,3 +3,10 @@ TurboReady.initialize(Turbo.StreamActions)
3
3
 
4
4
  import 'turbo_reflex'
5
5
  import './elements'
6
+ import devtools from './devtools'
7
+
8
+ const { enable, disable, start, stop } = devtools
9
+
10
+ self.ReflexBehavors = {
11
+ devtools: { enable, disable, start, stop }
12
+ }
@@ -8,6 +8,7 @@ module ReflexBehaviors
8
8
  end
9
9
 
10
10
  class Engine < ::Rails::Engine
11
+ config.assets.precompile << "reflex_behaviors_manifest.js"
11
12
  config.reflex_behaviors = ActiveSupport::OrderedOptions.new
12
13
 
13
14
  ActiveSupport.on_load(:action_controller) do
@@ -9,6 +9,14 @@ module ReflexBehaviors
9
9
  def initialize(view_context)
10
10
  @view_context = view_context
11
11
  end
12
+
13
+ def view_stack
14
+ prefix = "app/views/"
15
+ locations = caller_locations.select { |location| location.path.include?(prefix) }
16
+ locations.each_with_object(Set.new) do |location, memo|
17
+ memo << location.path[(location.path.index(prefix) + prefix.length)..]
18
+ end
19
+ end
12
20
  end
13
21
  end
14
22
  end
@@ -5,22 +5,39 @@ require_relative "base_tag_builder"
5
5
  module ReflexBehaviors::TagBuilders
6
6
  class ToggleTagsBuilder < BaseTagBuilder
7
7
  def target_tag(id, expanded: false, **kwargs, &block)
8
- return unless expanded || target_expanded?(id)
9
-
10
8
  kwargs = kwargs.with_indifferent_access
11
9
  kwargs[:id] = id
12
10
  kwargs[:role] = "region"
11
+
13
12
  kwargs[:aria] ||= {}
14
- kwargs[:aria] = target_aria(**kwargs[:aria])
13
+ kwargs[:aria][:label] ||= "Dynamic Content Region"
14
+ kwargs[:aria][:live] ||= "polite"
15
15
 
16
- content_tag("toggle-target", nil, kwargs, &block)
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
17
24
  end
18
25
 
19
- def trigger_tag(target:, render:, disabled: false, aria: {}, data: {remember: false, auto_collapse: false}, **kwargs, &block)
26
+ def trigger_tag(target:, render:, action: :toggle, disabled: false, **kwargs, &block)
20
27
  kwargs = kwargs.with_indifferent_access
21
28
  kwargs[:id] ||= "#{target}-toggle-trigger"
22
- kwargs[:aria] = trigger_aria(controls: target, expanded: target_expanded?(target), **aria)
23
- kwargs[:data] = trigger_data(render: render, disabled: disabled, **data)
29
+
30
+ kwargs[:aria] ||= {}
31
+ kwargs[:aria][:atomic] ||= true
32
+ kwargs[:aria][:relevant] ||= "all"
33
+ kwargs[:aria].merge!(controls: target, expanded: target_expanded?(target))
34
+
35
+ kwargs[:data] ||= {}
36
+ kwargs[:data][:auto_collapse] = !!kwargs[:data][:auto_collapse]
37
+ kwargs[:data][:view_stack] = view_stack.to_json if Rails.env.development?
38
+ kwargs[:data][:remember] = !!kwargs[:data][:remember]
39
+ kwargs[:data][:render] = render
40
+ kwargs[:data][:turbo_reflex] = "ReflexBehaviors::ToggleReflex##{action}" unless disabled
24
41
 
25
42
  content_tag("toggle-trigger", nil, kwargs, &block)
26
43
  end
@@ -32,31 +49,5 @@ module ReflexBehaviors::TagBuilders
32
49
  def target_collapsed?(target)
33
50
  !target_expanded?(target)
34
51
  end
35
-
36
- private
37
-
38
- def trigger_aria(controls:, expanded:, **kwargs)
39
- kwargs = kwargs.with_indifferent_access
40
- kwargs[:atomic] ||= true
41
- kwargs[:relevant] ||= "all"
42
- kwargs.merge(controls: controls, expanded: !!expanded)
43
- end
44
-
45
- def trigger_data(render:, disabled: false, remember: false, auto_collapse: false, **kwargs)
46
- kwargs = kwargs.with_indifferent_access
47
- kwargs.merge(
48
- turbo_reflex: disabled ? nil : "ReflexBehaviors::ToggleReflex#toggle",
49
- render: render,
50
- remember: !!remember,
51
- auto_collapse: !!auto_collapse
52
- )
53
- end
54
-
55
- def target_aria(**kwargs)
56
- kwargs = kwargs.with_indifferent_access
57
- kwargs[:label] ||= "Dynamic Content Region"
58
- kwargs[:live] ||= "polite"
59
- kwargs
60
- end
61
52
  end
62
53
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ReflexBehaviors
4
- VERSION = "0.0.2"
4
+ VERSION = "0.0.3"
5
5
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reflex_behaviors",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
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",
data/tags CHANGED
@@ -213689,7 +213689,7 @@ VDash node_modules/parse-srcset/tests/he.js /^ var decodeMap = {'Aacute':'\\xC1'
213689
213689
  VDash node_modules/simple-html-tokenizer/dist/es6/index.js /^ Aacute: "Á", aacute: "á", Abreve: "Ă", abreve: "ă", ac: "∾", acd: "∿", acE: "∾̳",/;" p variable:namedCharRefs
213690
213690
  VDash node_modules/simple-html-tokenizer/dist/simple-html-tokenizer.js /^ Aacute: "Á", aacute: "á", Abreve: "Ă", abreve: "ă", ac: "∾", acd: "∿", acE: "∾/;" p variable:anonymousFunction3825d8d70300.namedCharRefs
213691
213691
  VDash node_modules/simple-html-tokenizer/dist/types/generated/html5-named-char-refs.d.ts /^ VDash: string;$/;" C
213692
- VERSION lib/reflex_behaviors/version.rb /^ VERSION = "0.0.2"$/;" C module:ReflexBehaviors
213692
+ VERSION lib/reflex_behaviors/version.rb /^ VERSION = "0.0.3"$/;" C module:ReflexBehaviors
213693
213693
  VERSION node_modules/@angular/compiler/esm2015/src/output/source_map.js /^const VERSION = 3;$/;" C
213694
213694
  VERSION node_modules/@angular/compiler/esm2015/src/version.js /^export const VERSION = new Version('8.2.14');$/;" C
213695
213695
  VERSION node_modules/@angular/compiler/esm5/src/output/source_map.js /^var VERSION = 3;$/;" v
@@ -473563,7 +473563,7 @@ version node_modules/yaml/dist/index.js /^ version: '1.2'$/;" p variable:defaul
473563
473563
  version node_modules/yaml/dist/test-events.js /^ version: '1.2'$/;" p variable:testEvents.anonymousObjecta2b129440205
473564
473564
  version node_modules/yaml/package.json /^ "version": "1.8.3",$/;" s
473565
473565
  version node_modules/yocto-queue/package.json /^ "version": "0.1.0",$/;" s
473566
- version package.json /^ "version": "0.0.1",$/;" s
473566
+ version package.json /^ "version": "0.0.2",$/;" s
473567
473567
  versionIncluded node_modules/prettier-standard/src/vendor/node_modules/resolve/lib/core.js /^function versionIncluded(specifierValue) {$/;" f
473568
473568
  versionIncluded node_modules/resolve/lib/core.js /^function versionIncluded(specifierValue) {$/;" f
473569
473569
  versionInfo node_modules/graphql/version.d.ts /^export const versionInfo: {$/;" C
data/yarn.lock CHANGED
@@ -165,9 +165,9 @@
165
165
  integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==
166
166
 
167
167
  "@types/node@*":
168
- version "18.11.9"
169
- resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4"
170
- integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==
168
+ version "18.11.10"
169
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.10.tgz#4c64759f3c2343b7e6c4b9caf761c7a3a05cee34"
170
+ integrity sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ==
171
171
 
172
172
  "@types/unist@^2.0.0", "@types/unist@^2.0.2":
173
173
  version "2.0.6"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reflex_behaviors
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nate Hopkins (hopsoft)
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-30 00:00:00.000000000 Z
11
+ date: 2022-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -152,7 +152,13 @@ files:
152
152
  - Rakefile
153
153
  - app/assets/builds/reflex_behaviors.js
154
154
  - app/assets/builds/reflex_behaviors.js.map
155
+ - app/assets/config/reflex_behaviors_manifest.js
156
+ - app/assets/stylesheets/devtools.css
157
+ - app/assets/stylesheets/reflex_behaviors.css
158
+ - app/assets/stylesheets/toggle.css
159
+ - app/assets/stylesheets/tooltip.css
155
160
  - app/helpers/reflex_behaviors/application_helper.rb
161
+ - app/javascript/devtools/index.js
156
162
  - app/javascript/elements/index.js
157
163
  - app/javascript/elements/reflex_element.js
158
164
  - app/javascript/elements/toggle_target_element.js