rails_onboarding 0.8.11 → 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: 8e42f9771d8f9d357ebbf763900651576721b726c548e6f837853d6590ff2da5
4
- data.tar.gz: 5892846e24f4c86ab43217016e5713c8a80958e72aa861cdeed555d659e83179
3
+ metadata.gz: 532f89d681d2a2bf5cdf575a99b26742497707b65c1109525f51307d09bc50c6
4
+ data.tar.gz: 6f5e6c0c374b350ac09ac25c36846ad93eee2c2e10d31a531629f65cb19062f7
5
5
  SHA512:
6
- metadata.gz: ed6b1a9c9a64766bca536370562c962e04dfafb0478283834e335e7a80e0e1e11e5bd7619e468597af0c2fe24d1fa84a2c2bfd2eaf52b57a546a209053bca307
7
- data.tar.gz: 7dd60ecfec9f08115c6b25a40ed91c7c54790ca79e539085929891cb900895767bc4e17e2b64cfa50117132464dd655c709ae7bdc83fd05f1319142ca826acc7
6
+ metadata.gz: 68277199c94e5109304653b498934f8971779b6fe58696133b992b365753527545b06982f013e75faa2fd34e4cb7f71f4730937c2c599da30efdbd0a218d7f7e
7
+ data.tar.gz: f2b95e0a399d1981f5f01783231179ea62d7398e8145f0aed809e0621f158c8949e1152c8305f9c5f05daf328350eddf4a293885f954dfa99400c5bf8dac491f
@@ -216,8 +216,7 @@ export default class extends Controller {
216
216
  this.executeCallback(step.beforeShow, step)
217
217
  }
218
218
 
219
- // Find target element
220
- const targetElement = step.selector ? document.querySelector(step.selector) : null
219
+ const targetElement = this.resolveTarget(step)
221
220
 
222
221
  this.currentTargetElement = targetElement
223
222
  this.applyScrim(step, targetElement)
@@ -244,6 +243,37 @@ export default class extends Controller {
244
243
  this.dispatch('step-shown', { detail: { step, index } })
245
244
  }
246
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
+
247
277
  /**
248
278
  * Hide current step
249
279
  */
@@ -592,26 +622,26 @@ export default class extends Controller {
592
622
 
593
623
  const popupRect = this.popup.getBoundingClientRect()
594
624
  const margin = 20
595
- let top, left
596
-
597
- if (targetElement) {
598
- const targetRect = targetElement.getBoundingClientRect()
599
- const position = this.calculateBestPosition(step, targetRect, popupRect, margin)
625
+ const targetRect = targetElement ? targetElement.getBoundingClientRect() : null
600
626
 
601
- top = position.top
602
- left = position.left
603
- } else {
604
- // Center on screen if no target
605
- top = (window.innerHeight - popupRect.height) / 2
606
- left = (window.innerWidth - popupRect.width) / 2
607
- }
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
+ }
608
633
 
609
- // Ensure popup stays in viewport
610
- top = Math.max(margin, Math.min(top, window.innerHeight - popupRect.height - margin))
611
- left = Math.max(margin, Math.min(left, window.innerWidth - popupRect.width - margin))
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
+ }
612
642
 
613
- this.popup.style.top = `${top}px`
614
- this.popup.style.left = `${left}px`
643
+ clamp(value, min, max) {
644
+ return Math.max(min, Math.min(value, max))
615
645
  }
616
646
 
617
647
  /**
@@ -641,22 +671,40 @@ export default class extends Controller {
641
671
  }
642
672
  }
643
673
 
644
- // If position is auto, find best fit
645
- if (step.position === 'auto') {
646
- const preferences = ['bottom', 'top', 'right', 'left']
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
647
681
 
648
- for (const pos of preferences) {
649
- const coords = positions[pos]
650
- if (this.isPositionValid(coords, popupRect, margin)) {
651
- return coords
652
- }
653
- }
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
654
686
 
655
- // Fallback to center
656
- return positions.center
687
+ for (const pos of order) {
688
+ if (this.isPositionValid(positions[pos], popupRect, margin)) return positions[pos]
657
689
  }
658
690
 
659
- return positions[step.position] || positions.bottom
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 }
660
708
  }
661
709
 
662
710
  /**
@@ -1,3 +1,3 @@
1
1
  module RailsOnboarding
2
- VERSION = "0.8.11"
2
+ VERSION = "0.8.12"
3
3
  end
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.11
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.11
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: