@vanduo-oss/framework 1.3.5 → 1.3.7
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.
- package/README.md +5 -4
- package/css/components/cards.css +11 -1
- package/css/components/datepicker.css +10 -1
- package/css/components/expanding-cards.css +215 -0
- package/css/components/spotlight.css +8 -3
- package/css/components/timeline.css +47 -0
- package/css/effects/morph.css +0 -12
- package/css/vanduo.css +1 -0
- package/dist/build-info.json +3 -3
- package/dist/vanduo.cjs.js +647 -63
- package/dist/vanduo.cjs.js.map +3 -3
- package/dist/vanduo.cjs.min.js +4 -4
- package/dist/vanduo.cjs.min.js.map +4 -4
- package/dist/vanduo.css +255 -24
- package/dist/vanduo.css.map +1 -1
- package/dist/vanduo.esm.js +647 -63
- package/dist/vanduo.esm.js.map +3 -3
- package/dist/vanduo.esm.min.js +4 -4
- package/dist/vanduo.esm.min.js.map +4 -4
- package/dist/vanduo.js +647 -63
- package/dist/vanduo.js.map +3 -3
- package/dist/vanduo.min.css +2 -2
- package/dist/vanduo.min.css.map +1 -1
- package/dist/vanduo.min.js +4 -4
- package/dist/vanduo.min.js.map +4 -4
- package/js/components/datepicker.js +392 -70
- package/js/components/expanding-cards.js +136 -0
- package/js/components/morph.js +0 -3
- package/js/components/timeline.js +226 -0
- package/js/index.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vanduo Framework — Expanding flex cards
|
|
3
|
+
* Click / Enter / Space / Arrow keys to change the active card.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* <div class="vd-expanding-cards" data-vd-expanding-cards>
|
|
7
|
+
* Use data-vd-expanding-cards="manual" to skip auto-init.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
(function () {
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const ExpandingCards = {
|
|
14
|
+
instances: new Map(),
|
|
15
|
+
|
|
16
|
+
init: function () {
|
|
17
|
+
document.querySelectorAll('.vd-expanding-cards').forEach(function (el) {
|
|
18
|
+
if (el.getAttribute('data-vd-expanding-cards') === 'manual') return;
|
|
19
|
+
if (ExpandingCards.instances.has(el)) return;
|
|
20
|
+
ExpandingCards.initContainer(el);
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
initContainer: function (container) {
|
|
25
|
+
const cleanup = [];
|
|
26
|
+
|
|
27
|
+
const getCards = function () {
|
|
28
|
+
return Array.prototype.slice.call(container.querySelectorAll('.vd-expanding-card'));
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const setActive = function (card) {
|
|
32
|
+
const cards = getCards();
|
|
33
|
+
if (!card || cards.indexOf(card) === -1) return;
|
|
34
|
+
cards.forEach(function (c) {
|
|
35
|
+
c.classList.toggle('is-active', c === card);
|
|
36
|
+
});
|
|
37
|
+
card.focus({ preventScroll: true });
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const onClick = function (e) {
|
|
41
|
+
const t = e.target;
|
|
42
|
+
const card = t.closest ? t.closest('.vd-expanding-card') : null;
|
|
43
|
+
if (!card || !container.contains(card)) return;
|
|
44
|
+
setActive(card);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const onKeydown = function (e) {
|
|
48
|
+
if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight' && e.key !== 'Home' && e.key !== 'End') {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const cards = getCards().filter(function (c) {
|
|
52
|
+
return c.offsetParent !== null || c.getClientRects().length > 0;
|
|
53
|
+
});
|
|
54
|
+
if (!cards.length) return;
|
|
55
|
+
const activeEl = document.activeElement;
|
|
56
|
+
let idx = cards.indexOf(activeEl);
|
|
57
|
+
if (idx < 0) {
|
|
58
|
+
idx = cards.findIndex(function (c) {
|
|
59
|
+
return c.classList.contains('is-active');
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (idx < 0) idx = 0;
|
|
63
|
+
|
|
64
|
+
if (e.key === 'ArrowLeft') {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
setActive(cards[Math.max(0, idx - 1)]);
|
|
67
|
+
} else if (e.key === 'ArrowRight') {
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
setActive(cards[Math.min(cards.length - 1, idx + 1)]);
|
|
70
|
+
} else if (e.key === 'Home') {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
setActive(cards[0]);
|
|
73
|
+
} else if (e.key === 'End') {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
setActive(cards[cards.length - 1]);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
container.addEventListener('click', onClick);
|
|
80
|
+
cleanup.push(function () {
|
|
81
|
+
container.removeEventListener('click', onClick);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
container.addEventListener('keydown', onKeydown);
|
|
85
|
+
cleanup.push(function () {
|
|
86
|
+
container.removeEventListener('keydown', onKeydown);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
getCards().forEach(function (card) {
|
|
90
|
+
if (!card.hasAttribute('tabindex')) {
|
|
91
|
+
card.setAttribute('tabindex', '0');
|
|
92
|
+
}
|
|
93
|
+
card.setAttribute('role', 'button');
|
|
94
|
+
if (!card.hasAttribute('aria-pressed')) {
|
|
95
|
+
card.setAttribute('aria-pressed', card.classList.contains('is-active') ? 'true' : 'false');
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const syncAria = function () {
|
|
100
|
+
getCards().forEach(function (card) {
|
|
101
|
+
card.setAttribute('aria-pressed', card.classList.contains('is-active') ? 'true' : 'false');
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const mo = new MutationObserver(syncAria);
|
|
106
|
+
mo.observe(container, { attributes: true, subtree: true, attributeFilter: ['class'] });
|
|
107
|
+
cleanup.push(function () {
|
|
108
|
+
mo.disconnect();
|
|
109
|
+
});
|
|
110
|
+
syncAria();
|
|
111
|
+
|
|
112
|
+
ExpandingCards.instances.set(container, { cleanup: cleanup });
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
destroy: function (container) {
|
|
116
|
+
const inst = this.instances.get(container);
|
|
117
|
+
if (!inst) return;
|
|
118
|
+
inst.cleanup.forEach(function (fn) {
|
|
119
|
+
fn();
|
|
120
|
+
});
|
|
121
|
+
this.instances.delete(container);
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
destroyAll: function () {
|
|
125
|
+
this.instances.forEach(function (_, el) {
|
|
126
|
+
ExpandingCards.destroy(el);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
if (typeof window.Vanduo !== 'undefined') {
|
|
132
|
+
window.Vanduo.register('expandingCards', ExpandingCards);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
window.VanduoExpandingCards = ExpandingCards;
|
|
136
|
+
})();
|
package/js/components/morph.js
CHANGED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vanduo Framework — Timeline animated reveal
|
|
3
|
+
* Opt-in: add `.vd-timeline-animated` to a `.vd-timeline` container.
|
|
4
|
+
* Uses IntersectionObserver to add `.is-revealed` per item with staggered delays.
|
|
5
|
+
* Optional: `.vd-timeline-playback` with `[data-vd-timeline-prev|next|play|pause]` for stepped control.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
(function () {
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const STAGGER_MS = 140;
|
|
12
|
+
const MAX_STAGGER_INDEX = 7;
|
|
13
|
+
const PLAY_INTERVAL_MS = 800;
|
|
14
|
+
|
|
15
|
+
function countRevealedPrefix(items) {
|
|
16
|
+
let count = 0;
|
|
17
|
+
for (let i = 0; i < items.length; i++) {
|
|
18
|
+
if (!items[i].classList.contains('is-revealed')) break;
|
|
19
|
+
count++;
|
|
20
|
+
}
|
|
21
|
+
return count;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function findPlaybackControls(container) {
|
|
25
|
+
return container.parentElement || document.body;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function initPlayback(container, items, cleanup) {
|
|
29
|
+
items.forEach(function (item) {
|
|
30
|
+
item.classList.remove('is-revealed');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const scope = findPlaybackControls(container);
|
|
34
|
+
const prevBtn = scope.querySelector('[data-vd-timeline-prev]');
|
|
35
|
+
const nextBtn = scope.querySelector('[data-vd-timeline-next]');
|
|
36
|
+
const playBtn = scope.querySelector('[data-vd-timeline-play]');
|
|
37
|
+
const pauseBtn = scope.querySelector('[data-vd-timeline-pause]');
|
|
38
|
+
|
|
39
|
+
let playTimer = null;
|
|
40
|
+
|
|
41
|
+
function updateNavButtons() {
|
|
42
|
+
const k = countRevealedPrefix(items);
|
|
43
|
+
const n = items.length;
|
|
44
|
+
if (prevBtn) {
|
|
45
|
+
const atStart = k === 0;
|
|
46
|
+
prevBtn.disabled = atStart;
|
|
47
|
+
prevBtn.setAttribute('aria-disabled', atStart ? 'true' : 'false');
|
|
48
|
+
}
|
|
49
|
+
if (nextBtn) {
|
|
50
|
+
const atEnd = k >= n;
|
|
51
|
+
nextBtn.disabled = atEnd;
|
|
52
|
+
nextBtn.setAttribute('aria-disabled', atEnd ? 'true' : 'false');
|
|
53
|
+
}
|
|
54
|
+
if (playBtn) {
|
|
55
|
+
playBtn.setAttribute('aria-pressed', playTimer ? 'true' : 'false');
|
|
56
|
+
}
|
|
57
|
+
if (pauseBtn) {
|
|
58
|
+
pauseBtn.disabled = !playTimer;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function stepNext() {
|
|
63
|
+
const k = countRevealedPrefix(items);
|
|
64
|
+
if (k < items.length) {
|
|
65
|
+
items[k].classList.add('is-revealed');
|
|
66
|
+
}
|
|
67
|
+
updateNavButtons();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function stepPrev() {
|
|
71
|
+
const k = countRevealedPrefix(items);
|
|
72
|
+
if (k > 0) {
|
|
73
|
+
items[k - 1].classList.remove('is-revealed');
|
|
74
|
+
}
|
|
75
|
+
updateNavButtons();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function play() {
|
|
79
|
+
if (playTimer) return;
|
|
80
|
+
playTimer = setInterval(function () {
|
|
81
|
+
if (countRevealedPrefix(items) >= items.length) {
|
|
82
|
+
pause();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
stepNext();
|
|
86
|
+
}, PLAY_INTERVAL_MS);
|
|
87
|
+
updateNavButtons();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function pause() {
|
|
91
|
+
if (playTimer) {
|
|
92
|
+
clearInterval(playTimer);
|
|
93
|
+
playTimer = null;
|
|
94
|
+
}
|
|
95
|
+
updateNavButtons();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function addClick(el, fn) {
|
|
99
|
+
if (!el) return;
|
|
100
|
+
const handler = function (e) {
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
fn();
|
|
103
|
+
};
|
|
104
|
+
el.addEventListener('click', handler);
|
|
105
|
+
cleanup.push(function () {
|
|
106
|
+
el.removeEventListener('click', handler);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
addClick(prevBtn, stepPrev);
|
|
111
|
+
addClick(nextBtn, stepNext);
|
|
112
|
+
addClick(playBtn, play);
|
|
113
|
+
addClick(pauseBtn, pause);
|
|
114
|
+
|
|
115
|
+
cleanup.push(function () {
|
|
116
|
+
pause();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
updateNavButtons();
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
stepNext: stepNext,
|
|
123
|
+
stepPrev: stepPrev,
|
|
124
|
+
play: play,
|
|
125
|
+
pause: pause
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const Timeline = {
|
|
130
|
+
instances: new Map(),
|
|
131
|
+
|
|
132
|
+
init: function () {
|
|
133
|
+
document.querySelectorAll('.vd-timeline.vd-timeline-animated').forEach(function (el) {
|
|
134
|
+
if (Timeline.instances.has(el)) return;
|
|
135
|
+
Timeline.initInstance(el);
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
reinit: function () {
|
|
140
|
+
Timeline.destroyAll();
|
|
141
|
+
Timeline.init();
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
initInstance: function (container) {
|
|
145
|
+
const cleanup = [];
|
|
146
|
+
const items = Array.prototype.filter.call(container.children, function (child) {
|
|
147
|
+
return child.classList && child.classList.contains('vd-timeline-item');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
items.forEach(function (item, i) {
|
|
151
|
+
const idx = Math.min(i, MAX_STAGGER_INDEX);
|
|
152
|
+
item.style.setProperty('--vd-timeline-reveal-delay', (idx * STAGGER_MS) + 'ms');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const reducedMotion = typeof window.matchMedia === 'function'
|
|
156
|
+
&& window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
157
|
+
|
|
158
|
+
if (reducedMotion) {
|
|
159
|
+
items.forEach(function (item) {
|
|
160
|
+
item.classList.add('is-revealed');
|
|
161
|
+
});
|
|
162
|
+
Timeline.instances.set(container, { cleanup: cleanup });
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const playback = container.classList && container.classList.contains('vd-timeline-playback');
|
|
167
|
+
|
|
168
|
+
if (playback) {
|
|
169
|
+
const playbackApi = initPlayback(container, items, cleanup);
|
|
170
|
+
Timeline.instances.set(container, { cleanup: cleanup, playback: playbackApi });
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (typeof IntersectionObserver === 'undefined') {
|
|
175
|
+
items.forEach(function (item) {
|
|
176
|
+
item.classList.add('is-revealed');
|
|
177
|
+
});
|
|
178
|
+
Timeline.instances.set(container, { cleanup: cleanup });
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const observer = new IntersectionObserver(function (entries) {
|
|
183
|
+
entries.forEach(function (entry) {
|
|
184
|
+
if (!entry.isIntersecting) return;
|
|
185
|
+
entry.target.classList.add('is-revealed');
|
|
186
|
+
observer.unobserve(entry.target);
|
|
187
|
+
});
|
|
188
|
+
}, {
|
|
189
|
+
root: null,
|
|
190
|
+
rootMargin: '0px 0px -10% 0px',
|
|
191
|
+
threshold: 0.15
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
items.forEach(function (item) {
|
|
195
|
+
observer.observe(item);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
cleanup.push(function () {
|
|
199
|
+
observer.disconnect();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
Timeline.instances.set(container, { cleanup: cleanup });
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
destroy: function (container) {
|
|
206
|
+
const inst = this.instances.get(container);
|
|
207
|
+
if (!inst) return;
|
|
208
|
+
inst.cleanup.forEach(function (fn) {
|
|
209
|
+
fn();
|
|
210
|
+
});
|
|
211
|
+
this.instances.delete(container);
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
destroyAll: function () {
|
|
215
|
+
this.instances.forEach(function (_, el) {
|
|
216
|
+
Timeline.destroy(el);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
if (typeof window.Vanduo !== 'undefined') {
|
|
222
|
+
window.Vanduo.register('timeline', Timeline);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
window.VanduoTimeline = Timeline;
|
|
226
|
+
})();
|
package/js/index.js
CHANGED
|
@@ -50,6 +50,8 @@ import './components/lazy-load.js';
|
|
|
50
50
|
// Effects (glass scroll activation, water morph)
|
|
51
51
|
import './components/glass.js';
|
|
52
52
|
import './components/morph.js';
|
|
53
|
+
import './components/expanding-cards.js';
|
|
54
|
+
import './components/timeline.js';
|
|
53
55
|
|
|
54
56
|
// Phase 10 (v1.2.7) components
|
|
55
57
|
import './components/flow.js';
|