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,36 @@
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
+ element.dataset.reflexBehaviorsHighlight = true
26
+ }
27
+
28
+ export function removeHighlight (element) {
29
+ if (!element) return
30
+ if (element.originalStyles) {
31
+ for (const [key, value] of Object.entries(element.originalStyles))
32
+ value ? (element.style[key] = value) : (element.style[key] = '')
33
+ delete element.originalStyles
34
+ }
35
+ delete element.dataset.reflexBehaviorsHighlight
36
+ }
@@ -0,0 +1,76 @@
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
+ :host, :host * {
58
+ cursor: pointer;
59
+ }
60
+
61
+ div {
62
+ display: flex;
63
+ position: relative;
64
+ top: -1px;
65
+ }
66
+
67
+ input:checked + label{
68
+ font-weight: bold;
69
+ }
70
+
71
+ label {
72
+ color: indigo;
73
+ }
74
+ `
75
+ }
76
+ }
@@ -0,0 +1,129 @@
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')
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')
55
+ }
56
+
57
+ get html () {
58
+ return `
59
+ <style>${this.stylesheet}</style>
60
+ <div>
61
+ <label>ReflexBehaviors</label>
62
+ <slot name="devtool"></slot>
63
+ <button>X</button>
64
+ </div>
65
+ `
66
+ }
67
+
68
+ get stylesheet () {
69
+ return `
70
+ :host {
71
+ background-color: lavender;
72
+ border-radius: 15px;
73
+ bottom: 20px;
74
+ display: block;
75
+ filter: drop-shadow(3px 3px 3px rgba(0,0,0,0.3));
76
+ left: 50%;
77
+ outline-offset: 1px;
78
+ outline: solid 3px indigo;
79
+ padding: 5px 10px;
80
+ position: fixed;
81
+ transform: translateX(-50%);
82
+ z-index: 100000;
83
+ }
84
+
85
+ :host, :host * {
86
+ -webkit-user-select: none;
87
+ font-family: helvetica, sans-serif;
88
+ user-select: none;
89
+ }
90
+
91
+ :host:has( input) {
92
+ outline-color: red;
93
+ outline-width: 2px;
94
+ }
95
+
96
+ label {
97
+ color: indigo;
98
+ opacity: 0.5;
99
+ }
100
+
101
+ div {
102
+ display: flex;
103
+ gap: 0 5px;
104
+ position: relative;
105
+ }
106
+
107
+ button {
108
+ background-color: thistle;
109
+ border-radius: 50%;
110
+ border: none;
111
+ color: indigo;
112
+ cursor: pointer;
113
+ font-size: 10px;
114
+ height: 18px;
115
+ line-height: 18px;
116
+ margin: 0 -5px 0 10px;
117
+ outline: solid 1px indigo;
118
+ padding: 0 2px;
119
+ position: relative;
120
+ top: 1px;
121
+ width: 18px;
122
+ }
123
+
124
+ button:hover {
125
+ outline-width: 2px;
126
+ }
127
+ `
128
+ }
129
+ }
@@ -0,0 +1,103 @@
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: 15px;
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-offset: 1px;
67
+ outline: solid 3px ${this.emphasisColor};
68
+ padding: 8px 12px 8px 12px;
69
+ white-space: nowrap;
70
+ }
71
+
72
+ [role="tooltip"]::after {
73
+ border-color: ${this.cssArrow};
74
+ border-style: solid;
75
+ border-width: 10px;
76
+ content: "";
77
+ margin-left: -7px;
78
+ position: absolute;
79
+ top: ${this.position === 'bottom' ? '-21px' : 'calc(100% + 1px)'};
80
+ }
81
+
82
+ slot[name="title"] {
83
+ color: ${this.emphasisColor};
84
+ font-weight: bold;
85
+ }
86
+
87
+ slot[name="emphasis"] {
88
+ color: ${this.emphasisColor};
89
+ font-weight: normal;
90
+ opacity: 0.7;
91
+ }
92
+
93
+ hr {
94
+ background-color: ${this.emphasisColor};
95
+ border: none;
96
+ height: 1px;
97
+ margin-bottom: 4px;
98
+ margin-top: 4px;
99
+ opacity: 0.3;
100
+ }
101
+ `
102
+ }
103
+ }
@@ -1,117 +1,5 @@
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>
1
+ import supervisor from './supervisor'
15
2
 
16
- let tray
3
+ const { restart, start, stop } = supervisor
17
4
 
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
- }
5
+ export default { restart, start, stop }
@@ -0,0 +1,103 @@
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 (stopped()) 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
+ if (started()) return
25
+ appendHTML(
26
+ '<reflex-behaviors-devtool-supervisor></reflex-behaviors-devtool-supervisor>'
27
+ )
28
+ supervisorElement = document.body.querySelector(
29
+ 'reflex-behaviors-devtool-supervisor'
30
+ )
31
+ supervisorElement.dispatchEvent(
32
+ new CustomEvent('reflex-behaviors:devtools-start', {
33
+ bubbles: true
34
+ })
35
+ )
36
+ }
37
+
38
+ function restart () {
39
+ const enabledList = supervisorElement
40
+ ? Object.keys(supervisorElement.enabledDevtools)
41
+ : []
42
+
43
+ stop()
44
+ start()
45
+
46
+ supervisorElement.devtoolElements.forEach(el => {
47
+ if (enabledList.includes(el.name)) el.check()
48
+ })
49
+ }
50
+
51
+ function started () {
52
+ return !!supervisorElement
53
+ }
54
+
55
+ function stopped () {
56
+ return !started()
57
+ }
58
+
59
+ let restartTimeout
60
+ function debouncedRestart () {
61
+ clearTimeout(restartTimeout)
62
+ restartTimeout = setTimeout(restart, 25)
63
+ }
64
+
65
+ function autoRestart () {
66
+ if (started()) debouncedRestart()
67
+ }
68
+
69
+ addEventListener('turbo:load', autoRestart)
70
+ addEventListener('turbo-frame:load', autoRestart)
71
+ addEventListener('turbo-reflex:success', autoRestart)
72
+ addEventListener('turbo-reflex:finish', autoRestart)
73
+
74
+ function register (name, label) {
75
+ if (!supervisorElement) return
76
+ return appendHTML(
77
+ `
78
+ <reflex-behaviors-devtool name="${name}" slot="devtool">
79
+ <span slot="label">${label}</span>
80
+ </reflex-behaviors-devtool>
81
+ `,
82
+ supervisorElement
83
+ )
84
+ }
85
+
86
+ function enabled (name) {
87
+ if (!supervisorElement) return false
88
+ return supervisorElement.enabledDevtools[name]
89
+ }
90
+
91
+ export default {
92
+ enabled,
93
+ register,
94
+ start,
95
+ stop,
96
+ restart: debouncedRestart,
97
+ get started () {
98
+ return started()
99
+ },
100
+ get stopped () {
101
+ return stopped()
102
+ }
103
+ }