@vanduo-oss/framework 1.4.6 → 1.5.1

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.
@@ -45,7 +45,14 @@
45
45
  };
46
46
 
47
47
  const onKeydown = function (e) {
48
- if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight' && e.key !== 'Home' && e.key !== 'End') {
48
+ if (
49
+ e.key !== 'ArrowLeft' &&
50
+ e.key !== 'ArrowRight' &&
51
+ e.key !== 'ArrowUp' &&
52
+ e.key !== 'ArrowDown' &&
53
+ e.key !== 'Home' &&
54
+ e.key !== 'End'
55
+ ) {
49
56
  return;
50
57
  }
51
58
  const cards = getCards().filter(function (c) {
@@ -61,10 +68,15 @@
61
68
  }
62
69
  if (idx < 0) idx = 0;
63
70
 
64
- if (e.key === 'ArrowLeft') {
71
+ const isVertical = window.getComputedStyle(container).flexDirection === 'column';
72
+ if (!isVertical && (e.key === 'ArrowUp' || e.key === 'ArrowDown')) {
73
+ return;
74
+ }
75
+
76
+ if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
65
77
  e.preventDefault();
66
78
  setActive(cards[Math.max(0, idx - 1)]);
67
- } else if (e.key === 'ArrowRight') {
79
+ } else if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
68
80
  e.preventDefault();
69
81
  setActive(cards[Math.min(cards.length - 1, idx + 1)]);
70
82
  } else if (e.key === 'Home') {
@@ -14,6 +14,7 @@
14
14
  _cleanup: [],
15
15
  _boundTriggers: new WeakMap(),
16
16
  _triggerElement: null,
17
+ _currentTarget: null,
17
18
 
18
19
  init: function (root) {
19
20
  const triggers = window.Vanduo.queryAll(root, '[data-vd-spotlight]');
@@ -113,9 +114,39 @@
113
114
  // Overlay click to close
114
115
  overlay.addEventListener('click', () => this.stop());
115
116
 
117
+ // Keep the tooltip glued to its target while active — through the smooth
118
+ // scrollIntoView and any late layout (e.g. content-visibility sections that
119
+ // only render their real geometry once scrolled into view).
120
+ const reposition = () => {
121
+ if (this._active && this._currentTarget) this._positionTooltip(this._currentTarget);
122
+ };
123
+ window.addEventListener('scroll', reposition, { passive: true });
124
+ window.addEventListener('resize', reposition);
125
+ this._cleanup.push(() => window.removeEventListener('scroll', reposition));
126
+ this._cleanup.push(() => window.removeEventListener('resize', reposition));
127
+
116
128
  this._showStep(this._currentStep);
117
129
  },
118
130
 
131
+ _positionTooltip: function (target) {
132
+ const tooltip = this._elements.tooltip;
133
+ if (!tooltip || !target || !target.isConnected) return;
134
+
135
+ const rect = target.getBoundingClientRect();
136
+ const tRect = tooltip.getBoundingClientRect();
137
+ let top = rect.bottom + 12 + window.scrollY;
138
+ let left = rect.left + (rect.width - tRect.width) / 2 + window.scrollX;
139
+
140
+ // Keep in viewport
141
+ left = Math.max(8, Math.min(left, window.innerWidth - tRect.width - 8));
142
+ if (top + tRect.height > window.innerHeight + window.scrollY) {
143
+ top = rect.top - tRect.height - 12 + window.scrollY;
144
+ }
145
+
146
+ tooltip.style.top = top + 'px';
147
+ tooltip.style.left = left + 'px';
148
+ },
149
+
119
150
  _showStep: function (index) {
120
151
  const step = this._steps[index];
121
152
  if (!step) return;
@@ -206,23 +237,21 @@
206
237
  footer.appendChild(actions);
207
238
  tooltip.appendChild(footer);
208
239
 
209
- // Position tooltip near target
240
+ // Position the tooltip near the target. A single measurement right after a
241
+ // smooth scrollIntoView is unreliable: the scroll is still animating and the
242
+ // target's final geometry may not exist yet (content-visibility sections only
243
+ // render once scrolled into view). Re-position across the settle window so the
244
+ // tooltip converges on the correct spot; the scroll/resize listeners keep it
245
+ // there afterward.
246
+ this._currentTarget = target || null;
210
247
  if (target) {
211
- requestAnimationFrame(() => {
212
- const rect = target.getBoundingClientRect();
213
- const tRect = tooltip.getBoundingClientRect();
214
- let top = rect.bottom + 12 + window.scrollY;
215
- let left = rect.left + (rect.width - tRect.width) / 2 + window.scrollX;
216
-
217
- // Keep in viewport
218
- left = Math.max(8, Math.min(left, window.innerWidth - tRect.width - 8));
219
- if (top + tRect.height > window.innerHeight + window.scrollY) {
220
- top = rect.top - tRect.height - 12 + window.scrollY;
221
- }
222
-
223
- tooltip.style.top = top + 'px';
224
- tooltip.style.left = left + 'px';
225
- });
248
+ let frames = 0;
249
+ const settle = () => {
250
+ if (!this._active || this._currentTarget !== target) return;
251
+ this._positionTooltip(target);
252
+ if (frames++ < 30) requestAnimationFrame(settle);
253
+ };
254
+ requestAnimationFrame(settle);
226
255
  }
227
256
 
228
257
  document.dispatchEvent(new CustomEvent('spotlight:step', {
@@ -272,6 +301,7 @@
272
301
  this._elements = {};
273
302
  this._steps = [];
274
303
  this._currentStep = 0;
304
+ this._currentTarget = null;
275
305
 
276
306
  if (this._triggerElement && this._triggerElement.isConnected && typeof this._triggerElement.focus === 'function') {
277
307
  this._triggerElement.focus();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vanduo-oss/framework",
3
- "version": "1.4.6",
3
+ "version": "1.5.1",
4
4
  "description": "Zero-dependency CSS/JS framework built on Fibonacci/Golden Ratio design system with Open Color integration",
5
5
  "keywords": [
6
6
  "css",