reflex_behaviors 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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