turbo_boost-elements 0.0.14 → 0.0.15

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.
@@ -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
- }