@gudhub/ssg-web-components-library 1.0.117 → 1.0.118

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gudhub/ssg-web-components-library",
3
- "version": "1.0.117",
3
+ "version": "1.0.118",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -10,9 +10,6 @@
10
10
  <button type="submit" class="btn">${buttonText ? buttonText : 'Get in touch'}</button>
11
11
  <div class="loader"></div>
12
12
  </div>
13
- <div class="recaptcha-notice">
14
- This site is protected by reCAPTCHA.
15
- </div>
16
13
  </form>
17
14
  <div class="overflow success">
18
15
  <div class="icon_wrapper">
@@ -5,7 +5,6 @@ import defaultConfigs from './get-in-touch-form-data.json';
5
5
  import { checkInputsValidations } from './inputsValidation.js';
6
6
 
7
7
  let recaptchaPromise = null;
8
- let recaptchaBadgeObserver = null;
9
8
 
10
9
  function loadRecaptcha(siteKey) {
11
10
  if (!siteKey) {
@@ -36,41 +35,16 @@ function loadRecaptcha(siteKey) {
36
35
  return recaptchaPromise;
37
36
  }
38
37
 
39
- function hideRecaptchaBadge() {
40
- const badge = document.querySelector('.grecaptcha-badge');
41
-
42
- if (!badge) return;
43
-
44
- badge.style.setProperty('visibility', 'hidden', 'important');
45
- badge.style.setProperty('opacity', '0', 'important');
46
- badge.style.setProperty('pointer-events', 'none', 'important');
47
- }
48
-
49
- function observeRecaptchaBadge() {
50
- hideRecaptchaBadge();
51
-
52
- if (recaptchaBadgeObserver) return;
53
-
54
- recaptchaBadgeObserver = new MutationObserver(() => {
55
- hideRecaptchaBadge();
56
- });
57
-
58
- recaptchaBadgeObserver.observe(document.body, {
59
- childList: true,
60
- subtree: true
61
- });
62
- }
63
-
64
38
  class GetInTouchForm extends GHComponent {
65
39
  constructor() {
66
40
  super();
67
41
 
68
- this.formId = this.getAttribute('data-form-id');
42
+ this.formId = this.getAttribute("data-form-id");
69
43
  this.defaultConfigs = defaultConfigs;
70
44
 
71
45
  this.generateInput = this.generateInput;
72
46
  this.isFormSubmitted = false;
73
-
47
+
74
48
  this.placement = 'main';
75
49
  this.config = window.getConfig()?.componentsConfigs?.formConfig || window.getConfig()?.formConfig;
76
50
 
@@ -92,11 +66,7 @@ class GetInTouchForm extends GHComponent {
92
66
  this.attachEventListeners();
93
67
 
94
68
  if (this.recaptcha_site_key) {
95
- loadRecaptcha(this.recaptcha_site_key)
96
- .then(() => {
97
- observeRecaptchaBadge();
98
- })
99
- .catch(console.error);
69
+ loadRecaptcha(this.recaptcha_site_key).catch(console.error);
100
70
  }
101
71
  }
102
72
  }
@@ -106,26 +76,15 @@ class GetInTouchForm extends GHComponent {
106
76
  super.render(html);
107
77
  this.attachEventListeners();
108
78
 
109
- if (this.recaptcha_site_key) {
110
- loadRecaptcha(this.recaptcha_site_key)
111
- .then(() => {
112
- observeRecaptchaBadge();
113
- })
114
- .catch(console.error);
115
- }
79
+ loadRecaptcha(this.recaptcha_site_key).catch(console.error);
116
80
  }
117
81
 
118
82
  attachEventListeners() {
119
- const form = this.getElementsByTagName('form')[0];
120
- const restartButton = this.getElementsByClassName('restart_button')[0];
83
+ this.getElementsByTagName('form')[0]
84
+ .addEventListener('submit', (e) => this.handleSubmit(e));
121
85
 
122
- if (form) {
123
- form.addEventListener('submit', (e) => this.handleSubmit(e));
124
- }
125
-
126
- if (restartButton) {
127
- restartButton.addEventListener('click', () => this.hideFail());
128
- }
86
+ this.getElementsByClassName('restart_button')[0]
87
+ .addEventListener('click', () => this.hideFail());
129
88
  }
130
89
 
131
90
  onParentPopupClose() {
@@ -140,7 +99,7 @@ class GetInTouchForm extends GHComponent {
140
99
  initConfig(formConfigs) {
141
100
  try {
142
101
  this.config = formConfigs.find(({ id }) => id === this.formId);
143
- if (!this.config) throw new Error('Config not found');
102
+ if (!this.config) throw new Error("Config not found");
144
103
  } catch {
145
104
  const defaultId = this.isInPopup ? 'default popup' : 'default';
146
105
  this.config = defaultConfigs.find(({ id }) => id === defaultId);
@@ -148,7 +107,7 @@ class GetInTouchForm extends GHComponent {
148
107
 
149
108
  this.titleName = this.getAttribute('data-form-title') || this.config.title;
150
109
  this.subtitleName = this.getAttribute('data-form-subtitle') || this.config.subtitle;
151
- this.placement = this.getAttribute('data-form-placement') || 'main';
110
+ this.placement = this.getAttribute('data-form-placement') || "main";
152
111
  this.buttonText = this.getAttribute('data-form-button-text') || this.config.button_text;
153
112
  }
154
113
 
@@ -162,8 +121,6 @@ class GetInTouchForm extends GHComponent {
162
121
 
163
122
  return new Promise((resolve, reject) => {
164
123
  grecaptcha.ready(() => {
165
- hideRecaptchaBadge();
166
-
167
124
  grecaptcha.execute(this.recaptcha_site_key, { action })
168
125
  .then(resolve)
169
126
  .catch(reject);
@@ -189,6 +146,7 @@ class GetInTouchForm extends GHComponent {
189
146
  }
190
147
 
191
148
  this.handleSuccessFormValidation(form, token);
149
+
192
150
  } catch (error) {
193
151
  console.error(error);
194
152
  this.showFail();
@@ -221,7 +179,7 @@ class GetInTouchForm extends GHComponent {
221
179
  inputsValidation = async (form) => {
222
180
  const inputs = Array.from(form.querySelectorAll('input'));
223
181
  return checkInputsValidations(inputs);
224
- };
182
+ }
225
183
 
226
184
  verifyRecaptcha = async (recaptchaToken) => {
227
185
  const response = await fetch('/api/verify-recaptcha', {
@@ -242,7 +200,7 @@ class GetInTouchForm extends GHComponent {
242
200
  }
243
201
 
244
202
  return data;
245
- };
203
+ }
246
204
 
247
205
  createDataObject(form, placement, recaptchaToken) {
248
206
  const inputs = {};
@@ -272,24 +230,13 @@ class GetInTouchForm extends GHComponent {
272
230
 
273
231
  addLoader() {
274
232
  this.classList.add('loading');
275
-
276
- const btn = this.querySelector('button[type="submit"]');
277
-
278
- if (btn) {
279
- btn.disabled = true;
280
- }
233
+ this.querySelector('button[type="submit"]').disabled = true;
281
234
  }
282
235
 
283
236
  removeLoader() {
284
237
  this.classList.remove('loading');
285
-
286
238
  const btn = this.querySelector('button[type="submit"]');
287
-
288
- if (btn) {
289
- setTimeout(() => {
290
- btn.disabled = false;
291
- }, 500);
292
- }
239
+ setTimeout(() => btn.disabled = false, 500);
293
240
  }
294
241
 
295
242
  showSuccess({ email, phone }) {
@@ -331,9 +278,6 @@ class GetInTouchForm extends GHComponent {
331
278
 
332
279
  hideFail() {
333
280
  const el = this.querySelector('.overflow.fail');
334
-
335
- if (!el) return;
336
-
337
281
  el.style.opacity = 0;
338
282
 
339
283
  setTimeout(() => {
@@ -371,4 +315,4 @@ class GetInTouchForm extends GHComponent {
371
315
 
372
316
  if (!customElements.get('get-in-touch-form')) {
373
317
  customElements.define('get-in-touch-form', GetInTouchForm);
374
- }
318
+ }
@@ -271,12 +271,6 @@ get-in-touch-form {
271
271
  }
272
272
  }
273
273
  }
274
- .recaptcha-notice {
275
- margin-top: 10px;
276
- font-size: 12px;
277
- line-height: 1.4;
278
- opacity: 0.7;
279
- }
280
274
  &[data-in-popup] {
281
275
  .get-in-touch-form {
282
276
  padding-top: 0;
@@ -374,10 +368,4 @@ popup-container[data-position="bottom-right"] {
374
368
  }
375
369
  }
376
370
  }
377
- }
378
-
379
- body .grecaptcha-badge {
380
- visibility: hidden !important;
381
- opacity: 0 !important;
382
- pointer-events: none !important;
383
371
  }
@@ -0,0 +1,89 @@
1
+ import { drawImageCover } from '../utils/draw-image-cover.js';
2
+
3
+ export default function cascadeStrips({
4
+ ctx,
5
+ canvas,
6
+ images,
7
+ progress,
8
+ }) {
9
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
10
+
11
+ // BASE IMAGE
12
+ drawImageCover({
13
+ ctx,
14
+ canvas,
15
+ image: images[0],
16
+ });
17
+
18
+ const slidesCount =
19
+ images.length;
20
+
21
+ const sectorWidth =
22
+ canvas.width / slidesCount;
23
+
24
+ // GLOBAL WIPE POSITION
25
+ const totalTravel =
26
+ canvas.width
27
+ + (
28
+ sectorWidth
29
+ * (slidesCount - 1)
30
+ );
31
+
32
+ const wipeX =
33
+ totalTravel * progress;
34
+
35
+ for (let i = 1; i < slidesCount; i++) {
36
+
37
+ const image =
38
+ images[i];
39
+
40
+ // OFFSET BETWEEN WIPES
41
+ const offset =
42
+ sectorWidth * (i - 1);
43
+
44
+ // CURRENT WIPE POSITION
45
+ const currentX =
46
+ wipeX - offset;
47
+
48
+ // NOT YET VISIBLE
49
+ if (currentX <= 0) {
50
+ continue;
51
+ }
52
+
53
+ ctx.save();
54
+
55
+ ctx.beginPath();
56
+
57
+ // LAST SLIDE
58
+ if (i === slidesCount - 1) {
59
+
60
+ // CONTINUES TO FULLSCREEN
61
+ ctx.rect(
62
+ 0,
63
+ 0,
64
+ currentX,
65
+ canvas.height
66
+ );
67
+
68
+ } else {
69
+
70
+ // NORMAL STRIP
71
+ ctx.rect(
72
+ currentX - sectorWidth,
73
+ 0,
74
+ sectorWidth,
75
+ canvas.height
76
+ );
77
+ }
78
+
79
+ ctx.clip();
80
+
81
+ drawImageCover({
82
+ ctx,
83
+ canvas,
84
+ image,
85
+ });
86
+
87
+ ctx.restore();
88
+ }
89
+ }
@@ -0,0 +1,31 @@
1
+ import { drawImageCover } from '../utils/draw-image-cover.js';
2
+
3
+ export default function fade({
4
+ ctx,
5
+ canvas,
6
+ currentImage,
7
+ nextImage,
8
+ progress,
9
+ }) {
10
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
11
+
12
+ // CURRENT IMAGE
13
+ drawImageCover({
14
+ ctx,
15
+ canvas,
16
+ image: currentImage,
17
+ });
18
+
19
+ // NEXT IMAGE
20
+ ctx.save();
21
+
22
+ ctx.globalAlpha = progress;
23
+
24
+ drawImageCover({
25
+ ctx,
26
+ canvas,
27
+ image: nextImage,
28
+ });
29
+
30
+ ctx.restore();
31
+ }
@@ -0,0 +1,66 @@
1
+ import { drawImageCover } from '../utils/draw-image-cover.js';
2
+
3
+ export default function multiStrip({
4
+ ctx,
5
+ canvas,
6
+ currentImage,
7
+ nextImage,
8
+ progress,
9
+ isTransitionPhase,
10
+ }) {
11
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
12
+
13
+ // CURRENT IMAGE
14
+ drawImageCover({
15
+ ctx,
16
+ canvas,
17
+ image: currentImage,
18
+ });
19
+
20
+ if (!isTransitionPhase) {
21
+ return;
22
+ }
23
+
24
+ const strips = 8;
25
+
26
+ const stripWidth = canvas.width / strips;
27
+
28
+ const stagger = 0.08;
29
+
30
+ for (let i = 0; i < strips; i++) {
31
+
32
+ const delay = i * stagger;
33
+
34
+ let localProgress =
35
+ (progress - delay) / (1 - (stagger * strips));
36
+
37
+ localProgress = Math.max(0, Math.min(1, localProgress));
38
+
39
+ const revealWidth =
40
+ stripWidth * localProgress;
41
+
42
+ const x =
43
+ i * stripWidth;
44
+
45
+ ctx.save();
46
+
47
+ ctx.beginPath();
48
+
49
+ ctx.rect(
50
+ x,
51
+ 0,
52
+ revealWidth,
53
+ canvas.height
54
+ );
55
+
56
+ ctx.clip();
57
+
58
+ drawImageCover({
59
+ ctx,
60
+ canvas,
61
+ image: nextImage,
62
+ });
63
+
64
+ ctx.restore();
65
+ }
66
+ }
@@ -0,0 +1,104 @@
1
+ import { drawImageCover } from '../utils/draw-image-cover.js';
2
+
3
+ export default function sectors({
4
+ ctx,
5
+ canvas,
6
+ images,
7
+ progress,
8
+ }) {
9
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
10
+
11
+ const slidesCount =
12
+ images.length;
13
+
14
+ const sectorWidth =
15
+ canvas.width / slidesCount;
16
+
17
+ // BASE SLIDE
18
+ drawImageCover({
19
+ ctx,
20
+ canvas,
21
+ image: images[0],
22
+ });
23
+
24
+ for (let i = 1; i < slidesCount; i++) {
25
+
26
+ // START OF THIS SECTOR
27
+ const start =
28
+ i / slidesCount;
29
+
30
+ // END OF THIS SECTOR
31
+ const end =
32
+ (i + 1) / slidesCount;
33
+
34
+ // LOCAL PROGRESS
35
+ let localProgress =
36
+ (progress - start)
37
+ / (end - start);
38
+
39
+ localProgress =
40
+ Math.max(
41
+ 0,
42
+ Math.min(1, localProgress)
43
+ );
44
+
45
+ // NOT STARTED
46
+ if (localProgress <= 0) {
47
+ continue;
48
+ }
49
+
50
+ const x =
51
+ i * sectorWidth;
52
+
53
+ ctx.save();
54
+
55
+ ctx.beginPath();
56
+
57
+ // LAST SECTOR
58
+ if (i === slidesCount - 1) {
59
+
60
+ const expandProgress =
61
+ Math.max(
62
+ 0,
63
+ (progress - 0.875) / 0.125
64
+ );
65
+
66
+ const dynamicWidth =
67
+ sectorWidth
68
+ + (
69
+ (canvas.width - sectorWidth)
70
+ * expandProgress
71
+ );
72
+
73
+ ctx.rect(
74
+ x,
75
+ 0,
76
+ dynamicWidth,
77
+ canvas.height
78
+ );
79
+
80
+ } else {
81
+
82
+ ctx.rect(
83
+ x,
84
+ 0,
85
+ sectorWidth,
86
+ canvas.height
87
+ );
88
+ }
89
+
90
+ ctx.clip();
91
+
92
+ // FADE-IN
93
+ ctx.globalAlpha =
94
+ localProgress;
95
+
96
+ drawImageCover({
97
+ ctx,
98
+ canvas,
99
+ image: images[i],
100
+ });
101
+
102
+ ctx.restore();
103
+ }
104
+ }
@@ -0,0 +1,43 @@
1
+ import { drawImageCover } from '../utils/draw-image-cover.js';
2
+ import { easeInOutCubic } from '../utils/easing.js';
3
+
4
+ export default function wipeLeft({
5
+ ctx,
6
+ canvas,
7
+ currentImage,
8
+ nextImage,
9
+ progress,
10
+ }) {
11
+ const eased = easeInOutCubic(progress);
12
+
13
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
14
+
15
+ drawImageCover({
16
+ ctx,
17
+ canvas,
18
+ image: currentImage,
19
+ });
20
+
21
+ const revealWidth = canvas.width * eased;
22
+
23
+ ctx.save();
24
+
25
+ ctx.beginPath();
26
+
27
+ ctx.rect(
28
+ 0,
29
+ 0,
30
+ revealWidth,
31
+ canvas.height
32
+ );
33
+
34
+ ctx.clip();
35
+
36
+ drawImageCover({
37
+ ctx,
38
+ canvas,
39
+ image: nextImage,
40
+ });
41
+
42
+ ctx.restore();
43
+ }
@@ -0,0 +1,51 @@
1
+ import { drawImageCover } from '../utils/draw-image-cover.js';
2
+
3
+ export default function zoom({
4
+ ctx,
5
+ canvas,
6
+ currentImage,
7
+ nextImage,
8
+ progress,
9
+ isTransitionPhase,
10
+ }) {
11
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
12
+
13
+ if (!isTransitionPhase) {
14
+ drawImageCover({
15
+ ctx,
16
+ canvas,
17
+ image: currentImage,
18
+ scale: 1.05,
19
+ });
20
+
21
+ return;
22
+ }
23
+
24
+ // CURRENT IMAGE
25
+ ctx.save();
26
+
27
+ ctx.globalAlpha = 1 - progress;
28
+
29
+ drawImageCover({
30
+ ctx,
31
+ canvas,
32
+ image: currentImage,
33
+ scale: 1 + (progress * 0.1),
34
+ });
35
+
36
+ ctx.restore();
37
+
38
+ // NEXT IMAGE
39
+ ctx.save();
40
+
41
+ ctx.globalAlpha = progress;
42
+
43
+ drawImageCover({
44
+ ctx,
45
+ canvas,
46
+ image: nextImage,
47
+ scale: 1.1 - (progress * 0.1),
48
+ });
49
+
50
+ ctx.restore();
51
+ }
@@ -0,0 +1,6 @@
1
+ export const videoSlider = {
2
+ tag: 'video-slider',
3
+ category: 'Media',
4
+ src: '@gudhub/ssg-web-components-library/src/components/video-slider/video-slider.js',
5
+ serverOnly: false
6
+ }
@@ -0,0 +1,27 @@
1
+ export function drawImageCover({
2
+ ctx,
3
+ canvas,
4
+ image,
5
+ scale = 1,
6
+ offsetX = 0,
7
+ offsetY = 0,
8
+ }) {
9
+ const canvasRatio = canvas.width / canvas.height;
10
+ const imageRatio = image.width / image.height;
11
+
12
+ let drawWidth;
13
+ let drawHeight;
14
+
15
+ if (imageRatio > canvasRatio) {
16
+ drawHeight = canvas.height * scale;
17
+ drawWidth = drawHeight * imageRatio;
18
+ } else {
19
+ drawWidth = canvas.width * scale;
20
+ drawHeight = drawWidth / imageRatio;
21
+ }
22
+
23
+ const x = ((canvas.width - drawWidth) / 2) + offsetX;
24
+ const y = ((canvas.height - drawHeight) / 2) + offsetY;
25
+
26
+ ctx.drawImage(image, x, y, drawWidth, drawHeight);
27
+ }
@@ -0,0 +1,5 @@
1
+ export function easeInOutCubic(x) {
2
+ return x < 0.5
3
+ ? 4 * x * x * x
4
+ : 1 - Math.pow(-2 * x + 2, 3) / 2;
5
+ }
@@ -0,0 +1,5 @@
1
+ <section class="video-slider-section">
2
+ <div class="canvas-wrapper">
3
+ <canvas></canvas>
4
+ </div>
5
+ </section>
@@ -0,0 +1,177 @@
1
+ import html from './video-slider.html';
2
+ import './video-slider.scss';
3
+
4
+ import wipeLeft from './animations/wipe-left.js';
5
+ import fade from './animations/fade.js';
6
+ import zoom from './animations/zoom.js';
7
+ import multiStrip from './animations/multi-strip.js';
8
+ import cascadeStrips from './animations/cascade-strips.js';
9
+ import sectors from './animations/sectors.js';
10
+
11
+ class VideoSlider extends GHComponent {
12
+ constructor() {
13
+ super();
14
+
15
+ this.animations = {
16
+ wipeLeft,
17
+ fade,
18
+ zoom,
19
+ multiStrip,
20
+ cascadeStrips,
21
+ sectors
22
+ };
23
+ }
24
+
25
+ async onServerRender() {
26
+
27
+ this.ghId = this.getAttribute('data-gh-id') || null;
28
+ this.chapter = this.getAttribute('data-chapter') || 'pages';
29
+ this.json = this.ghId
30
+ ? await super.getGhData(this.ghId, this.chapter)
31
+ : console.error('data-gh-id attribute is required for video-slider component');
32
+
33
+ this.setAttribute(
34
+ 'data-animation',
35
+ this.json.animation
36
+ );
37
+
38
+ this.setAttribute(
39
+ 'data-slide-duration',
40
+ this.json.slideDuration
41
+ );
42
+
43
+ this.setAttribute(
44
+ 'data-transition-duration',
45
+ this.json.transitionDuration
46
+ );
47
+
48
+ this.setAttribute(
49
+ 'data-images',
50
+ JSON.stringify(this.json.images)
51
+ );
52
+
53
+ super.render(html);
54
+ }
55
+
56
+ async onClientReady() {
57
+
58
+ this.config = {
59
+ animation: this.getAttribute('data-animation'),
60
+
61
+ slideDuration:
62
+ Number(this.getAttribute('data-slide-duration')),
63
+
64
+ transitionDuration:
65
+ Number(this.getAttribute('data-transition-duration'))
66
+ };
67
+
68
+ this.images = JSON.parse(this.getAttribute('data-images'));
69
+
70
+ this.canvas = this.querySelector('canvas');
71
+
72
+ this.ctx = this.canvas.getContext('2d');
73
+
74
+ this.canvas.width = 1280;
75
+ this.canvas.height = 720;
76
+
77
+ this.loadedImages = await this.preloadImages();
78
+
79
+ this.startLoop();
80
+ }
81
+
82
+ preloadImages() {
83
+ return Promise.all(
84
+ this.images.map((url) => {
85
+ return new Promise((resolve, reject) => {
86
+
87
+ const image = new Image();
88
+
89
+ image.crossOrigin = 'anonymous';
90
+
91
+ image.onload = () => {
92
+ resolve(image);
93
+ };
94
+
95
+ image.onerror = () => reject(url);
96
+
97
+ image.src = url;
98
+ });
99
+ })
100
+ );
101
+ }
102
+
103
+ startLoop() {
104
+ this.startTime = performance.now();
105
+
106
+ const render = (time) => {
107
+ const elapsed = time - this.startTime;
108
+
109
+ const totalSlides = this.loadedImages.length;
110
+
111
+ const singleSlideTotal =
112
+ this.config.slideDuration + this.config.transitionDuration;
113
+
114
+ const totalDuration =
115
+ singleSlideTotal * totalSlides;
116
+
117
+ const timelineTime =
118
+ elapsed % totalDuration;
119
+
120
+ const currentSlide =
121
+ Math.floor(timelineTime / singleSlideTotal);
122
+
123
+ const nextSlide =
124
+ (currentSlide + 1) % totalSlides;
125
+
126
+ const localTime =
127
+ timelineTime % singleSlideTotal;
128
+
129
+ const isTransitionPhase =
130
+ localTime >= this.config.slideDuration;
131
+
132
+ const transitionTime =
133
+ localTime - this.config.slideDuration;
134
+
135
+ const progress = isTransitionPhase
136
+ ? transitionTime / this.config.transitionDuration
137
+ : 0;
138
+
139
+ this.renderFrame({
140
+ currentImage: this.loadedImages[currentSlide],
141
+ nextImage: this.loadedImages[nextSlide],
142
+ progress,
143
+ isTransitionPhase,
144
+ });
145
+
146
+ requestAnimationFrame(render);
147
+ };
148
+
149
+ requestAnimationFrame(render);
150
+ }
151
+
152
+ renderFrame({
153
+ currentImage,
154
+ nextImage,
155
+ progress,
156
+ isTransitionPhase,
157
+ }) {
158
+ if (!currentImage) return;
159
+
160
+ const animation =
161
+ this.animations[this.config.animation];
162
+
163
+ if (!animation) return;
164
+
165
+ animation({
166
+ ctx: this.ctx,
167
+ canvas: this.canvas,
168
+ currentImage,
169
+ nextImage,
170
+ progress,
171
+ isTransitionPhase,
172
+ images: this.loadedImages,
173
+ });
174
+ }
175
+ }
176
+
177
+ window.customElements.define('video-slider', VideoSlider);
@@ -0,0 +1,218 @@
1
+ # Video Slider Component
2
+
3
+ ## Description
4
+
5
+ `video-slider` is a canvas-based animated image slider that supports multiple transition effects between slides.
6
+
7
+ The component loads configuration and images from GudHub data and automatically starts the animation loop after initialization.
8
+
9
+ ---
10
+
11
+ # Data Structure
12
+
13
+ The component expects the following JSON structure:
14
+
15
+ ```json
16
+ {
17
+ "video-slider": {
18
+ "animation": "fade",
19
+ "slideDuration": "100",
20
+ "transitionDuration": "2000",
21
+ "images": [
22
+ "https://app.gudhub.com/userdata/12345/image1.jpg",
23
+ "https://app.gudhub.com/userdata/12354/image2.jpg",
24
+ "https://app.gudhub.com/userdata/11345/image3.jpg"
25
+ ]
26
+ }
27
+ }
28
+ ```
29
+
30
+ ## Configuration Parameters
31
+
32
+ | Property | Type | Required | Description |
33
+ | ------------------ | ------------- | -------- | ------------------------------------------------------ |
34
+ | animation | string | Yes | Animation type used between slides |
35
+ | slideDuration | string/number | Yes | Duration of static slide display in milliseconds |
36
+ | transitionDuration | string/number | Yes | Duration of slide transition animation in milliseconds |
37
+ | images | array | Yes | Ordered list of image URLs |
38
+
39
+ ---
40
+
41
+ ## Supported Animations
42
+
43
+ The following animation values are supported:
44
+
45
+ ```text
46
+ wipeLeft
47
+ fade
48
+ zoom
49
+ multiStrip
50
+ cascadeStrips
51
+ sectors
52
+ ```
53
+
54
+ Example:
55
+
56
+ ```json
57
+ {
58
+ "animation": "zoom"
59
+ }
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Slide Duration
65
+
66
+ Defines how long a slide remains visible before transition starts.
67
+
68
+ Example:
69
+
70
+ ```json
71
+ {
72
+ "slideDuration": "3000"
73
+ }
74
+ ```
75
+
76
+ Result:
77
+
78
+ ```text
79
+ 3 seconds display
80
+ +
81
+ transition animation
82
+ ```
83
+
84
+ ---
85
+
86
+ ## Transition Duration
87
+
88
+ Defines how long the transition animation lasts.
89
+
90
+ Example:
91
+
92
+ ```json
93
+ {
94
+ "transitionDuration": "1500"
95
+ }
96
+ ```
97
+
98
+ Result:
99
+
100
+ ```text
101
+ 1.5 second transition
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Images
107
+
108
+ Images must be provided as an ordered array of URLs.
109
+
110
+ The component displays images in the exact order they appear in the array.
111
+
112
+ Example:
113
+
114
+ ```json
115
+ {
116
+ "images": [
117
+ "image-1.jpg",
118
+ "image-2.jpg",
119
+ "image-3.jpg"
120
+ ]
121
+ }
122
+ ```
123
+
124
+ Display order:
125
+
126
+ ```text
127
+ image-1
128
+
129
+ image-2
130
+
131
+ image-3
132
+
133
+ image-1
134
+ (repeat)
135
+ ```
136
+
137
+ ---
138
+
139
+ # Usage
140
+
141
+ ## Static Pages
142
+
143
+ For static pages only `data-gh-id` is required.
144
+
145
+ ```html
146
+ <video-slider
147
+ data-gh-id="video-slider"
148
+ ></video-slider>
149
+ ```
150
+
151
+ ---
152
+
153
+ ## Dynamic Pages
154
+
155
+ For dynamic pages an additional `data-chapter` attribute must be specified.
156
+
157
+ ```html
158
+ <video-slider
159
+ data-gh-id="video-slider"
160
+ data-chapter="areas"
161
+ ></video-slider>
162
+ ```
163
+
164
+ ### data-chapter
165
+
166
+ Defines the chapter from which GudHub should load dynamic page data.
167
+
168
+ Example:
169
+
170
+ ```html
171
+ data-chapter="areas"
172
+ ```
173
+
174
+ ---
175
+
176
+ # Required Attributes
177
+
178
+ | Attribute | Required | Description |
179
+ | ------------ | ------------------ | ------------------------------------------ |
180
+ | data-gh-id | Yes | GudHub component identifier |
181
+ | data-chapter | Dynamic pages only | Chapter used to retrieve current item data |
182
+
183
+ ---
184
+
185
+ # Example
186
+
187
+ ```html
188
+ <video-slider
189
+ data-gh-id="video-slider"
190
+ data-chapter="areas"
191
+ ></video-slider>
192
+ ```
193
+
194
+ ```json
195
+ {
196
+ "video-slider": {
197
+ "animation": "fade",
198
+ "slideDuration": "3000",
199
+ "transitionDuration": "1500",
200
+ "images": [
201
+ "https://example.com/slide-1.jpg",
202
+ "https://example.com/slide-2.jpg",
203
+ "https://example.com/slide-3.jpg"
204
+ ]
205
+ }
206
+ }
207
+ ```
208
+
209
+ ---
210
+
211
+ # Notes
212
+
213
+ * All images should be publicly accessible URLs.
214
+ * Images are preloaded before animation starts.
215
+ * The slider automatically loops through all images.
216
+ * Images are displayed using a cover-fit algorithm similar to CSS `object-fit: cover`.
217
+ * Animation starts automatically after component initialization.
218
+ * If an invalid animation name is provided, no transition animation will be rendered.
@@ -0,0 +1,16 @@
1
+ .video-slider-section {
2
+ padding: 0px;
3
+ width: 100%;
4
+ height: 100%;
5
+
6
+ .canvas-wrapper {
7
+ width: 100%;
8
+ height: 100%;
9
+ }
10
+
11
+ canvas {
12
+ width: 100%;
13
+ height: 100%;
14
+ display: block;
15
+ }
16
+ }
package/src/config.js CHANGED
@@ -67,9 +67,8 @@ export { GoogleAnalytics } from './components/google-analytics/config.js';
67
67
  export { GoogleTag } from './components/google-tag/config.js';
68
68
  export { cookiesPopup } from './components/cookies-popup/config.js';
69
69
  export { infoTable } from './components/info-table/config.js';
70
- export { liqpayPaymentButton } from './components/liqpay-payment-button/config.js';
71
- export { liqpayComponent } from './components/liqpay-component/config.js';
72
70
  export { faqComponent } from './components/faq-component/config.js';
71
+ export { videoSlider } from './components/video-slider/config.js';
73
72
 
74
73
  // export { liqPayComponent } from './components/liqpay-component/config.js';
75
74
  // export { htmlTextFromEditorJs } from './components/html-text-from-editor-js/config.js';