reflex_behaviors 0.0.3 → 0.0.5

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