rails_onboarding 0.8.10 → 0.8.12
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.
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 532f89d681d2a2bf5cdf575a99b26742497707b65c1109525f51307d09bc50c6
|
|
4
|
+
data.tar.gz: 6f5e6c0c374b350ac09ac25c36846ad93eee2c2e10d31a531629f65cb19062f7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 68277199c94e5109304653b498934f8971779b6fe58696133b992b365753527545b06982f013e75faa2fd34e4cb7f71f4730937c2c599da30efdbd0a218d7f7e
|
|
7
|
+
data.tar.gz: f2b95e0a399d1981f5f01783231179ea62d7398e8145f0aed809e0621f158c8949e1152c8305f9c5f05daf328350eddf4a293885f954dfa99400c5bf8dac491f
|
|
@@ -49,10 +49,33 @@ export default class extends Controller {
|
|
|
49
49
|
|
|
50
50
|
// Auto-start if configured and not completed
|
|
51
51
|
if (this.autoStartValue && !this.isTourCompleted()) {
|
|
52
|
-
setTimeout(() => this.
|
|
52
|
+
this.autoStartTimer = setTimeout(() => this.autoStart(), 1000)
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Begin an auto-started tour, if the page it was configured on is still the
|
|
58
|
+
* one on screen.
|
|
59
|
+
*
|
|
60
|
+
* Turbo renders a cached snapshot as a preview while the fresh response is
|
|
61
|
+
* still in flight, and every controller on the page connects to that preview
|
|
62
|
+
* as well as to the render that replaces it a few milliseconds later.
|
|
63
|
+
* Starting on the preview builds the whole tour, tears it down again when the
|
|
64
|
+
* preview is discarded, and rebuilds it - which a member sees as the popup
|
|
65
|
+
* appearing, vanishing and appearing again. The real render starts it
|
|
66
|
+
* properly, so the preview should simply stand aside.
|
|
67
|
+
*
|
|
68
|
+
* The element check covers the same ground from the other side: a controller
|
|
69
|
+
* whose element has left the document must not build an overlay and a popup,
|
|
70
|
+
* because it no longer has the handlers that would take them down again.
|
|
71
|
+
*/
|
|
72
|
+
autoStart() {
|
|
73
|
+
if (!this.element.isConnected) return
|
|
74
|
+
if (document.documentElement.hasAttribute('data-turbo-preview')) return
|
|
75
|
+
|
|
76
|
+
this.start()
|
|
77
|
+
}
|
|
78
|
+
|
|
56
79
|
/**
|
|
57
80
|
* Parse and validate tour steps from configuration
|
|
58
81
|
*/
|
|
@@ -193,8 +216,7 @@ export default class extends Controller {
|
|
|
193
216
|
this.executeCallback(step.beforeShow, step)
|
|
194
217
|
}
|
|
195
218
|
|
|
196
|
-
|
|
197
|
-
const targetElement = step.selector ? document.querySelector(step.selector) : null
|
|
219
|
+
const targetElement = this.resolveTarget(step)
|
|
198
220
|
|
|
199
221
|
this.currentTargetElement = targetElement
|
|
200
222
|
this.applyScrim(step, targetElement)
|
|
@@ -221,6 +243,37 @@ export default class extends Controller {
|
|
|
221
243
|
this.dispatch('step-shown', { detail: { step, index } })
|
|
222
244
|
}
|
|
223
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Find the element a step should point at.
|
|
248
|
+
*
|
|
249
|
+
* `selector` may be a list, which is how a step survives a responsive layout:
|
|
250
|
+
* the same idea is often two elements, one of them display:none at the current
|
|
251
|
+
* breakpoint - a row of tabs on a wide screen and a select on a narrow one. The
|
|
252
|
+
* first candidate that is actually rendered wins.
|
|
253
|
+
*
|
|
254
|
+
* An element with no layout box is treated as absent rather than used anyway.
|
|
255
|
+
* getBoundingClientRect() on a display:none element is all zeroes, so the
|
|
256
|
+
* spotlight became a small square in the top-left corner and the popup was
|
|
257
|
+
* placed against the origin - pointing confidently at nothing, on top of
|
|
258
|
+
* whatever happened to be there.
|
|
259
|
+
*/
|
|
260
|
+
resolveTarget(step) {
|
|
261
|
+
const selectors = Array.isArray(step.selector) ? step.selector : [step.selector]
|
|
262
|
+
|
|
263
|
+
for (const selector of selectors) {
|
|
264
|
+
if (!selector) continue
|
|
265
|
+
|
|
266
|
+
const element = document.querySelector(selector)
|
|
267
|
+
if (element && this.hasLayoutBox(element)) return element
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return null
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
hasLayoutBox(element) {
|
|
274
|
+
return element.getClientRects().length > 0
|
|
275
|
+
}
|
|
276
|
+
|
|
224
277
|
/**
|
|
225
278
|
* Hide current step
|
|
226
279
|
*/
|
|
@@ -569,26 +622,26 @@ export default class extends Controller {
|
|
|
569
622
|
|
|
570
623
|
const popupRect = this.popup.getBoundingClientRect()
|
|
571
624
|
const margin = 20
|
|
572
|
-
|
|
625
|
+
const targetRect = targetElement ? targetElement.getBoundingClientRect() : null
|
|
573
626
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
} else {
|
|
581
|
-
// Center on screen if no target
|
|
582
|
-
top = (window.innerHeight - popupRect.height) / 2
|
|
583
|
-
left = (window.innerWidth - popupRect.width) / 2
|
|
584
|
-
}
|
|
627
|
+
const coords = targetRect
|
|
628
|
+
? this.calculateBestPosition(step, targetRect, popupRect, margin)
|
|
629
|
+
: {
|
|
630
|
+
top: (window.innerHeight - popupRect.height) / 2,
|
|
631
|
+
left: (window.innerWidth - popupRect.width) / 2
|
|
632
|
+
}
|
|
585
633
|
|
|
586
|
-
//
|
|
587
|
-
|
|
588
|
-
|
|
634
|
+
// The clamp keeps the popup reachable, which matters more than anything else -
|
|
635
|
+
// its own buttons are the only way forward. calculateBestPosition is what keeps
|
|
636
|
+
// it off the highlight; this is the last word on staying on screen.
|
|
637
|
+
this.popup.style.top =
|
|
638
|
+
`${this.clamp(coords.top, margin, window.innerHeight - popupRect.height - margin)}px`
|
|
639
|
+
this.popup.style.left =
|
|
640
|
+
`${this.clamp(coords.left, margin, window.innerWidth - popupRect.width - margin)}px`
|
|
641
|
+
}
|
|
589
642
|
|
|
590
|
-
|
|
591
|
-
|
|
643
|
+
clamp(value, min, max) {
|
|
644
|
+
return Math.max(min, Math.min(value, max))
|
|
592
645
|
}
|
|
593
646
|
|
|
594
647
|
/**
|
|
@@ -618,22 +671,40 @@ export default class extends Controller {
|
|
|
618
671
|
}
|
|
619
672
|
}
|
|
620
673
|
|
|
621
|
-
//
|
|
622
|
-
|
|
623
|
-
|
|
674
|
+
// 'center' is a host saying "do not point at anything", so it is taken at its
|
|
675
|
+
// word. Every other position is a *preference*: it used to be handed back
|
|
676
|
+
// without being checked, and positionPopup then clamped it into the viewport -
|
|
677
|
+
// which on a phone slides the popup straight over the element it is describing.
|
|
678
|
+
// A tour that hides what it is explaining is worse than one placed on the wrong
|
|
679
|
+
// side, so the asked-for side is tried first and the others are tried after it.
|
|
680
|
+
if (step.position === 'center') return positions.center
|
|
624
681
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
}
|
|
630
|
-
}
|
|
682
|
+
const fallbacks = ['bottom', 'top', 'right', 'left']
|
|
683
|
+
const order = positions[step.position] && step.position !== 'auto'
|
|
684
|
+
? [step.position, ...fallbacks.filter((pos) => pos !== step.position)]
|
|
685
|
+
: fallbacks
|
|
631
686
|
|
|
632
|
-
|
|
633
|
-
return positions
|
|
687
|
+
for (const pos of order) {
|
|
688
|
+
if (this.isPositionValid(positions[pos], popupRect, margin)) return positions[pos]
|
|
634
689
|
}
|
|
635
690
|
|
|
636
|
-
return
|
|
691
|
+
return this.positionBesideTarget(targetRect, popupRect, margin)
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Nothing fits cleanly on any side, which on a small viewport is the ordinary
|
|
696
|
+
* case rather than the exceptional one. Put the popup in whichever band - above
|
|
697
|
+
* the target or below it - has more room. Centring instead, which is what this
|
|
698
|
+
* used to do, lands on the target more often than not.
|
|
699
|
+
*/
|
|
700
|
+
positionBesideTarget(targetRect, popupRect, margin) {
|
|
701
|
+
const roomAbove = targetRect.top - margin
|
|
702
|
+
const roomBelow = window.innerHeight - targetRect.bottom - margin
|
|
703
|
+
const left = targetRect.left + (targetRect.width - popupRect.width) / 2
|
|
704
|
+
|
|
705
|
+
return roomBelow >= roomAbove
|
|
706
|
+
? { top: targetRect.bottom + margin, left }
|
|
707
|
+
: { top: targetRect.top - popupRect.height - margin, left }
|
|
637
708
|
}
|
|
638
709
|
|
|
639
710
|
/**
|
|
@@ -871,6 +942,8 @@ export default class extends Controller {
|
|
|
871
942
|
disconnect() {
|
|
872
943
|
this.stop()
|
|
873
944
|
|
|
945
|
+
if (this.autoStartTimer) clearTimeout(this.autoStartTimer)
|
|
946
|
+
|
|
874
947
|
if (this.keyboardHandler) {
|
|
875
948
|
document.removeEventListener('keydown', this.keyboardHandler)
|
|
876
949
|
}
|
|
@@ -71,6 +71,13 @@
|
|
|
71
71
|
--onboarding-border: #e5e7eb;
|
|
72
72
|
--onboarding-border-focus: var(--onboarding-primary);
|
|
73
73
|
|
|
74
|
+
/* The edge of a tour spotlight's cutout. Transparent here because a light page
|
|
75
|
+
needs none: the scrim darkens the surround and the cutout keeps the page's
|
|
76
|
+
own white, which is a step anyone can see. The dark block at the foot of
|
|
77
|
+
this file turns them on, where that step does not exist. */
|
|
78
|
+
--onboarding-tour-spotlight-ring: transparent;
|
|
79
|
+
--onboarding-tour-spotlight-glow: transparent;
|
|
80
|
+
|
|
74
81
|
/* Spacing */
|
|
75
82
|
--onboarding-space-xs: 0.25rem;
|
|
76
83
|
--onboarding-space-sm: 0.5rem;
|
|
@@ -1143,6 +1150,15 @@
|
|
|
1143
1150
|
--onboarding-text-muted: #d1d5db;
|
|
1144
1151
|
--onboarding-text-light: #9ca3af;
|
|
1145
1152
|
--onboarding-border: #374151;
|
|
1153
|
+
|
|
1154
|
+
/* Draw the spotlight's edge instead of relying on one. A black scrim over a
|
|
1155
|
+
page that is already near-black moves the surround by a few points of
|
|
1156
|
+
luminance, so the cutout has no visible boundary and the highlight reads
|
|
1157
|
+
as nothing in particular - the tour points at the page and the page looks
|
|
1158
|
+
the same. The brand colour is the foreground role here, hence
|
|
1159
|
+
--onboarding-primary rather than the fill. */
|
|
1160
|
+
--onboarding-tour-spotlight-ring: var(--onboarding-primary);
|
|
1161
|
+
--onboarding-tour-spotlight-glow: color-mix(in srgb, var(--onboarding-primary) 35%, transparent);
|
|
1146
1162
|
}
|
|
1147
1163
|
|
|
1148
1164
|
.step-debug {
|
|
@@ -28,9 +28,21 @@
|
|
|
28
28
|
together composited to ~0.92 and buried the surrounding page.
|
|
29
29
|
The alpha comes from --onboarding-tour-scrim, which the controller sets on :root from
|
|
30
30
|
its overlayOpacity value. It is deliberately not animated: an animation on this
|
|
31
|
-
property overrides the inline style, which is what made overlayOpacity a dead knob.
|
|
31
|
+
property overrides the inline style, which is what made overlayOpacity a dead knob.
|
|
32
|
+
|
|
33
|
+
The ring and glow are listed first because a box-shadow list paints in reverse: the
|
|
34
|
+
first shadow lands on top, so both sit over the scrim rather than under it. Both
|
|
35
|
+
default to transparent, which is a light page's answer - there the scrim alone
|
|
36
|
+
separates the cutout, since the surround visibly darkens while the cutout keeps the
|
|
37
|
+
page's own white. A dark page has no such headroom - a black scrim over a near-black
|
|
38
|
+
background lands on a slightly nearer black, a difference nobody can see - so the
|
|
39
|
+
cutout has to be given an edge rather than a luminance step to be seen by.
|
|
40
|
+
application.css turns these on in its dark block. */
|
|
32
41
|
.tour-spotlight-spotlight {
|
|
33
|
-
box-shadow:
|
|
42
|
+
box-shadow:
|
|
43
|
+
0 0 0 3px var(--onboarding-tour-spotlight-ring, transparent),
|
|
44
|
+
0 0 24px 8px var(--onboarding-tour-spotlight-glow, transparent),
|
|
45
|
+
0 0 0 9999px rgba(0, 0, 0, var(--onboarding-tour-scrim, 0.7));
|
|
34
46
|
}
|
|
35
47
|
|
|
36
48
|
/* Border highlight style */
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_onboarding
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.12
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Lewis
|
|
@@ -282,7 +282,7 @@ licenses:
|
|
|
282
282
|
metadata:
|
|
283
283
|
allowed_push_host: https://rubygems.org
|
|
284
284
|
homepage_uri: https://github.com/bunnahabhain/rails_onboarding
|
|
285
|
-
source_code_uri: https://github.com/bunnahabhain/rails_onboarding/tree/v0.8.
|
|
285
|
+
source_code_uri: https://github.com/bunnahabhain/rails_onboarding/tree/v0.8.12
|
|
286
286
|
changelog_uri: https://github.com/bunnahabhain/rails_onboarding/blob/master/docs/CHANGELOG.md
|
|
287
287
|
rdoc_options: []
|
|
288
288
|
require_paths:
|