turbo_boost-elements 0.0.13 → 0.0.15

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,374 +0,0 @@
1
- // Icons courtesy of https://feathericons.com/
2
- import {
3
- appendHTML,
4
- addHighlight,
5
- attempt,
6
- coordinates,
7
- removeHighlight
8
- } from '../../../utils/dom'
9
- import supervisor from '../../../devtools/supervisor'
10
-
11
- let activeToggle
12
-
13
- document.addEventListener('turbo-boost:devtools-start', () =>
14
- supervisor.register('toggle', 'toggles')
15
- )
16
-
17
- function appendTooltip (title, subtitle, content, options = {}) {
18
- let { backgroundColor, color, position } = options
19
- color = color || 'white'
20
- position = position || 'top'
21
- return appendHTML(`
22
- <turbo-boost-devtool-tooltip position="${position}" background-color="${backgroundColor}" color="${color}">
23
- <div slot='title'>${title}</div>
24
- <div slot='subtitle'>${subtitle}</div>
25
- ${content}
26
- </turbo-boost-devtool-tooltip>
27
- `)
28
- }
29
-
30
- export default class Devtool {
31
- constructor (triggerElement) {
32
- this.name = 'toggle'
33
- this.command = triggerElement.dataset.turboCommand
34
- this.triggerElement = triggerElement // SEE: app/javascript/elements/toggle_trigger_element.js
35
- this.targetElement = triggerElement.targetElement // SEE: app/javascript/elements/toggle_target_element.js
36
- this.morphElement = triggerElement.morphElement
37
-
38
- let hideTimeout
39
- const debouncedHide = () => {
40
- clearTimeout(hideTimeout)
41
- hideTimeout = setTimeout(this.hide({ active: false }), 25)
42
- }
43
-
44
- this.eventListeners['turbo-boost:devtool-enable'] = event => {
45
- // LeaderLine.positionByWindowResize = false
46
- const { name } = event.detail
47
- if (name !== this.name) return
48
-
49
- addHighlight(this.triggerElement, {
50
- outline: '3px dashed blueviolet',
51
- outlineOffset: '2px'
52
- })
53
-
54
- this.hide({ active: false })
55
- if (this.active) this.show()
56
- }
57
-
58
- this.eventListeners['turbo-boost:devtool-disable'] = event => {
59
- const { name } = event.detail
60
- if (name === this.name) removeHighlight(this.triggerElement)
61
- }
62
-
63
- this.eventListeners['click'] = event => {
64
- if (event.target.closest('turbo-boost-devtool-tooltip')) return
65
- debouncedHide()
66
- }
67
-
68
- this.eventListeners['turbo:load'] = debouncedHide
69
- this.eventListeners['turbo-frame:load'] = debouncedHide
70
- this.eventListeners[TurboBoost.Commands.events.finish] = debouncedHide
71
-
72
- this.registerEventListeners()
73
- }
74
-
75
- registerEventListeners () {
76
- Object.entries(this.eventListeners).forEach(([type, listener]) => {
77
- addEventListener(type, listener)
78
- })
79
- }
80
-
81
- unregisterEventListeners () {
82
- Object.entries(this.eventListeners).forEach(([type, listener]) => {
83
- removeEventListener(type, listener)
84
- })
85
- }
86
-
87
- get eventListeners () {
88
- return this._eventListeners || (this._eventListeners = {})
89
- }
90
-
91
- get enabled () {
92
- return supervisor.enabled(this.name)
93
- }
94
-
95
- get active () {
96
- return activeToggle === this
97
- }
98
-
99
- set active (value) {
100
- if (value) activeToggle = this
101
- else activeToggle = null
102
- }
103
-
104
- show () {
105
- if (!this.enabled) return
106
-
107
- if (this.active) return
108
- this.active = true
109
-
110
- this.hide({ active: true })
111
-
112
- addHighlight(this.targetElement, {
113
- outline: '3px dashed darkcyan',
114
- outlineOffset: '-2px'
115
- })
116
-
117
- addHighlight(this.triggerElement.morphElement, {
118
- outline: '3px dashed chocolate',
119
- outlineOffset: '3px'
120
- })
121
-
122
- this.renderingTooltip = this.createRenderingTooltip()
123
- this.targetTooltip = this.createTargetTooltip()
124
- this.triggerTooltip = this.createTriggerTooltip(
125
- this.targetTooltip,
126
- this.renderingTooltip
127
- )
128
-
129
- document
130
- .querySelectorAll('.leader-line')
131
- .forEach(el => (el.style.zIndex = 100000))
132
-
133
- const data = {
134
- morph: {
135
- partial: this.triggerElement.renders,
136
- id: this.triggerElement.morphs,
137
- status: this.morphElement ? 'OK' : 'Not Found'
138
- },
139
- trigger: { partial: null, id: null, status: 'Not Found' },
140
- target: { partial: null, id: null, status: 'Not Found' }
141
- }
142
-
143
- if (this.triggerElement) {
144
- data.trigger = {
145
- partial: this.triggerElement.partial,
146
- id: this.triggerElement.id,
147
- status: 'OK'
148
- }
149
- data.target.id = this.triggerElement.controls
150
- }
151
-
152
- if (this.targetElement)
153
- data.target = {
154
- partial: this.targetElement.partial,
155
- dom_id: this.targetElement.id,
156
- status: 'OK'
157
- }
158
-
159
- console.table(data)
160
- }
161
-
162
- hide ({ active: active = false }) {
163
- document
164
- .querySelectorAll('turbo-boost-devtool-tooltip')
165
- .forEach(tooltip => {
166
- attempt(() => tooltip.line.remove())
167
- attempt(() => tooltip.drag.remove())
168
- attempt(() => tooltip.lineToRendering.remove())
169
- attempt(() => tooltip.lineToTarget.remove())
170
- attempt(() => tooltip.remove())
171
- })
172
-
173
- document.querySelectorAll('[data-turbo-boost-highlight]').forEach(el => {
174
- if (!el.tagName.match(/turbo-boost-toggle-trigger/i)) removeHighlight(el)
175
- })
176
-
177
- this.active = active
178
- }
179
-
180
- createRenderingTooltip () {
181
- if (!this.triggerElement.renders)
182
- return console.debug(
183
- `Unable to create the rendering tooltip! The trigger element must set the 'renders' attribute.`
184
- )
185
-
186
- if (!this.triggerElement.morphs)
187
- return console.debug(
188
- `Unable to create the rendering tooltip! The trigger element specified the 'morphs' attrbiute but no element matches the DOM id: '${this.triggerElement.morphs}'`
189
- )
190
-
191
- const title = `
192
- <svg xmlns="http://www.w3.org/2000/svg" style="display:inline-block;" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19l7-7 3 3-7 7-3-3z"></path><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"></path><path d="M2 2l7.586 7.586"></path><circle cx="11" cy="11" r="2"></circle></svg>
193
- RENDERING
194
- `
195
- const subtitle = `
196
- <b>partial</b>: ${this.triggerElement.renders || 'unknown'}<br>
197
- <b>morphs</b>: ${this.triggerElement.morphs || 'unknown'}<br>
198
- `
199
- const content = `
200
- <div slot="content-top" style="font-size:85%; font-style:italic; font-weight:100;">
201
- The <b>TRIGGER</b> toggles the <b>TARGET</b> then renders the partial &amp; morphs the element.<br>
202
- </div>
203
- `
204
- const tooltip = appendTooltip(title, subtitle, content, {
205
- backgroundColor: 'lightyellow',
206
- color: 'chocolate'
207
- })
208
-
209
- const coords = coordinates(this.morphElement)
210
- const top = Math.ceil(
211
- coords.top + coords.height / 2 - tooltip.offsetHeight / 2
212
- )
213
- const left = Math.ceil(coords.left + coords.width + 100)
214
- tooltip.style.top = `${top}px`
215
- tooltip.style.left = `${left}px`
216
-
217
- tooltip.line = new LeaderLine(tooltip, this.morphElement, {
218
- ...this.leaderLineOptions,
219
- color: 'chocolate'
220
- })
221
-
222
- tooltip.drag = new PlainDraggable(tooltip)
223
- return tooltip
224
- }
225
-
226
- createTargetTooltip () {
227
- if (!this.targetElement)
228
- return console.debug(
229
- `Unable to create the target tooltip! No element matches the DOM id: '${this.triggerElement.controls}'`
230
- )
231
-
232
- const title = `
233
- <svg xmlns="http://www.w3.org/2000/svg" style="display:inline-block;" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="6"></circle><circle cx="12" cy="12" r="2"></circle></svg>
234
- TARGET
235
- `
236
- const subtitle = `
237
- <b>id</b>: ${this.targetElement.id}<br>
238
- <b>aria-labeled-by</b>: ${this.targetElement.labeledBy}<br>
239
- `
240
- let content = this.targetElement.viewStack
241
- .reverse()
242
- .map((view, index) => {
243
- return this.triggerElement.sharedViews.includes(view)
244
- ? `<div slot="content">${index + 1}. ${view}</div>`
245
- : `<div slot="content-bottom">${index + 1}. ${view}</div>`
246
- }, this)
247
- .join('')
248
-
249
- content = `
250
- <div slot="content-top">
251
- <svg xmlns="http://www.w3.org/2000/svg" style="display:inline-block;" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 2 7 12 12 22 7 12 2"></polygon><polyline points="2 17 12 22 22 17"></polyline><polyline points="2 12 12 17 22 12"></polyline></svg>
252
- <b>View Stack</b>
253
- </div>
254
- ${content}
255
- `
256
-
257
- const tooltip = appendTooltip(title, subtitle, content, {
258
- backgroundColor: 'lightcyan',
259
- color: 'darkcyan',
260
- position: 'bottom'
261
- })
262
-
263
- const coords = coordinates(this.targetElement)
264
- const top = Math.ceil(coords.top + tooltip.offsetHeight)
265
- const left = Math.ceil(coords.left + coords.width + tooltip.offsetWidth / 3)
266
- tooltip.style.top = `${top}px`
267
- tooltip.style.left = `${left}px`
268
-
269
- tooltip.line = new LeaderLine(tooltip, this.targetElement, {
270
- ...this.leaderLineOptions,
271
- color: 'darkcyan'
272
- })
273
-
274
- tooltip.drag = new PlainDraggable(tooltip)
275
- return tooltip
276
- }
277
-
278
- createTriggerTooltip (targetTooltip, renderingTooltip) {
279
- if (!this.triggerElement) return
280
- const title = `
281
- <svg xmlns="http://www.w3.org/2000/svg" style="display:inline;" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>
282
- TRIGGER
283
- `
284
- const subtitle = `
285
- <b>id</b>: ${this.triggerElement.id}<br>
286
- <b>aria-controls</b>: ${this.triggerElement.controls}<br>
287
- <b>aria-expanded</b>: ${this.triggerElement.expanded}<br>
288
- <b>remember</b>: ${this.triggerElement.remember}<br>
289
- `
290
- let content = this.triggerElement.viewStack
291
- .reverse()
292
- .map((view, index) => {
293
- return this.triggerElement.sharedViews.includes(view)
294
- ? `<div slot="content">${index + 1}. ${view}</div>`
295
- : `<div slot="content-bottom">${index + 1}. ${view}</div>`
296
- }, this)
297
- .join('')
298
-
299
- content = `
300
- <div slot="content-top">
301
- <svg xmlns="http://www.w3.org/2000/svg" style="display:inline-block;" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 2 7 12 12 22 7 12 2"></polygon><polyline points="2 17 12 22 22 17"></polyline><polyline points="2 12 12 17 22 12"></polyline></svg>
302
- <b>View Stack</b>
303
- </div>
304
- ${content}
305
- `
306
-
307
- const tooltip = appendTooltip(title, subtitle, content, {
308
- backgroundColor: 'lavender',
309
- color: 'blueviolet'
310
- })
311
-
312
- const coords = coordinates(this.triggerElement)
313
- const top = Math.ceil(coords.top - tooltip.offsetHeight * 2)
314
- const left = Math.ceil(coords.left + coords.width + tooltip.offsetWidth / 3)
315
- tooltip.style.top = `${top}px`
316
- tooltip.style.left = `${left}px`
317
-
318
- tooltip.line = new LeaderLine(this.triggerElement, tooltip, {
319
- ...this.leaderLineOptions,
320
- color: 'blueviolet'
321
- })
322
-
323
- if (targetTooltip) {
324
- tooltip.lineToTarget = new LeaderLine(tooltip, targetTooltip, {
325
- ...this.leaderLineOptions,
326
- color: 'blueviolet',
327
- middleLabel: 'toggles',
328
- size: 2.1
329
- })
330
-
331
- targetTooltip.drag.onMove = () => {
332
- targetTooltip.line.position()
333
- tooltip.lineToTarget.position()
334
- tooltip.lineToRendering.position()
335
- }
336
- }
337
-
338
- if (renderingTooltip) {
339
- tooltip.lineToRendering = new LeaderLine(tooltip, renderingTooltip, {
340
- ...this.leaderLineOptions,
341
- color: 'blueviolet',
342
- middleLabel: 'renders & morphs',
343
- size: 2.1
344
- })
345
-
346
- renderingTooltip.drag.onMove = () => {
347
- renderingTooltip.line.position()
348
- if (tooltip.lineToTarget) tooltip.lineToTarget.position()
349
- tooltip.lineToRendering.position()
350
- }
351
- }
352
-
353
- tooltip.drag = new PlainDraggable(tooltip)
354
- tooltip.drag.onMove = () => {
355
- tooltip.line.position()
356
- if (tooltip.lineToTarget) tooltip.lineToTarget.position()
357
- if (tooltip.lineToRendering) tooltip.lineToRendering.position()
358
- }
359
-
360
- return tooltip
361
- }
362
-
363
- get leaderLineOptions () {
364
- return {
365
- dash: { animation: true },
366
- dropShadow: { opacity: 0.3 },
367
- endPlug: 'arrow3',
368
- endPlugSize: 1.7,
369
- size: 3,
370
- startPlug: 'disc',
371
- startPlugSize: 1
372
- }
373
- }
374
- }
@@ -1,70 +0,0 @@
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
- const clone = template(html).content.cloneNode(true)
10
- const child = clone.querySelector('*')
11
- return parent.appendChild(child)
12
- }
13
-
14
- export function addHighlight (element, options = {}) {
15
- if (!element) return
16
- removeHighlight(element)
17
- let { outline, outlineOffset } = options
18
-
19
- outline = outline || 'dashed 3px red'
20
- outlineOffset = outlineOffset || '0px'
21
-
22
- element.originalStyles = element.originalStyles || {
23
- display: element.style.display,
24
- minHeight: element.style.minHeight,
25
- minWidth: element.style.minWidth,
26
- outline: element.style.outline,
27
- outlineOffset: element.style.outlineOffset
28
- }
29
-
30
- if (
31
- getComputedStyle(element).display.match(/^inline$/i) &&
32
- element.offsetWidth === 0 &&
33
- element.offsetHeight === 0
34
- ) {
35
- element.style.display = 'inline-block'
36
- element.style.minHeight = '2px'
37
- element.style.minWidth = '2px'
38
- }
39
- element.style.outline = outline
40
- element.style.outlineOffset = outlineOffset
41
- element.dataset.turboBoostHighlight = true
42
- }
43
-
44
- export function removeHighlight (element) {
45
- if (!element) return
46
- if (element.originalStyles) {
47
- for (const [key, value] of Object.entries(element.originalStyles))
48
- value ? (element.style[key] = value) : (element.style[key] = '')
49
- delete element.originalStyles
50
- }
51
- delete element.dataset.turboBoostHighlight
52
- }
53
-
54
- export function coordinates (element) {
55
- if (!element) return {}
56
- const rect = element.getBoundingClientRect()
57
- const width = element.offsetWidth
58
- const height = element.offsetHeight
59
- const top = rect.top + window.scrollY
60
- const left = rect.left + window.scrollX
61
- const right = left + width
62
- const bottom = top + height
63
- return { top, left, right, bottom, width, height }
64
- }
65
-
66
- export function attempt (callback) {
67
- try {
68
- callback()
69
- } catch {}
70
- }