mypgs 1.6.0 → 2.1.0

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.
Files changed (125) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +25 -25
  3. package/assets/javascript/components/_accordion.js +2 -2
  4. package/assets/javascript/components/_modals.js +159 -136
  5. package/assets/javascript/components/_notifications.js +19 -9
  6. package/assets/javascript/components/_search.js +2 -2
  7. package/assets/javascript/components/_slides.js +66 -32
  8. package/assets/javascript/components/_stepTabs.js +4 -4
  9. package/assets/javascript/components/_steps.js +2 -2
  10. package/assets/javascript/components/_summary.js +2 -2
  11. package/assets/javascript/functions/_formValidate.js +97 -53
  12. package/assets/javascript/functions/_scrollY.js +10 -10
  13. package/assets/javascript/layout/_header.js +3 -0
  14. package/assets/scss/base/_html.scss +2 -2
  15. package/assets/scss/base/_variables.scss +2 -2
  16. package/assets/scss/components/_accordion.scss +0 -2
  17. package/assets/scss/components/_button.scss +1 -1
  18. package/assets/scss/components/_form.scss +2 -18
  19. package/assets/scss/components/_formAddon.scss +18 -0
  20. package/assets/scss/components/_modals.scss +5 -3
  21. package/assets/scss/components/_notification.scss +23 -30
  22. package/assets/scss/components/_search.scss +29 -23
  23. package/assets/scss/components/_slides.scss +0 -1
  24. package/assets/scss/components/_tooltip.scss +0 -1
  25. package/assets/scss/index.scss +1 -0
  26. package/assets/scss/layout/_footer.scss +0 -2
  27. package/assets/scss/layout/_header.scss +9 -1
  28. package/assets/scss/layout/_pageShell.scss +24 -17
  29. package/assets/scss/mixin/_mx-button.scss +30 -76
  30. package/assets/scss/mixin/_mx-card.scss +11 -9
  31. package/assets/scss/mixin/_mx-form.scss +10 -12
  32. package/assets/scss/mixin/{_mx-form-addon.scss → _mx-formAddon.scss} +23 -26
  33. package/assets/scss/mixin/_mx-hover.scss +75 -0
  34. package/assets/scss/mixin/_settings.scss +2 -1
  35. package/assets/scss/mixin/mixin.scss +2 -1
  36. package/{templates/html → demo}/demo.html +1 -1
  37. package/demo/demo.js +241 -0
  38. package/dist/css/index.css +1317 -1674
  39. package/dist/css/index.css.map +1 -1
  40. package/dist/css/index.min.css +1 -1
  41. package/dist/javascript/index.js +356 -255
  42. package/dist/javascript/index.js.map +1 -1
  43. package/dist/javascript/index.min.js +1 -1
  44. package/docs/componenti-e-markup.md +32 -32
  45. package/docs/components/accordion.md +61 -0
  46. package/docs/components/badges.md +67 -0
  47. package/docs/components/breadcumbs.md +43 -0
  48. package/docs/components/button.md +59 -0
  49. package/docs/components/card.md +50 -0
  50. package/docs/components/dropdown.md +135 -0
  51. package/docs/components/form.md +136 -0
  52. package/docs/components/formAddon.md +79 -0
  53. package/docs/components/logo.md +36 -0
  54. package/docs/components/menu.md +90 -0
  55. package/docs/components/modal.md +131 -0
  56. package/docs/components/notification.md +81 -0
  57. package/docs/components/search.md +118 -0
  58. package/docs/components/slides.md +85 -0
  59. package/docs/components/stepTabs.md +93 -0
  60. package/docs/components/steps.md +56 -0
  61. package/docs/components/summary.md +49 -0
  62. package/docs/components/table.md +42 -0
  63. package/docs/components/tooltip.md +53 -0
  64. package/docs/convenzioni.md +11 -11
  65. package/docs/export-e-sviluppo.md +3 -3
  66. package/docs/helper-javascript.md +73 -8
  67. package/docs/layout/body.md +41 -0
  68. package/docs/layout/flex.md +116 -0
  69. package/docs/layout/footer.md +71 -0
  70. package/docs/layout/grid.md +116 -0
  71. package/docs/layout/header.md +116 -0
  72. package/docs/layout/pageShell.md +94 -0
  73. package/docs/layout/section.md +69 -0
  74. package/docs/patterns/cookieConsent.md +89 -0
  75. package/docs/utilizzo-css-scss.md +8 -8
  76. package/package.json +2 -1
  77. package/scripts/generate-component-docs.js +557 -0
  78. package/templates/html/components/accordion.html +36 -7
  79. package/templates/html/components/badges.html +28 -5
  80. package/templates/html/components/breadcumbs.html +15 -1
  81. package/templates/html/components/button.html +28 -6
  82. package/templates/html/components/card.html +28 -8
  83. package/templates/html/components/dropdown.html +42 -8
  84. package/templates/html/components/form.html +110 -9
  85. package/templates/html/components/formAddon.html +72 -0
  86. package/templates/html/components/logo.html +22 -1
  87. package/templates/html/components/menu.html +33 -6
  88. package/templates/html/components/modal.html +58 -14
  89. package/templates/html/components/notification.html +68 -32
  90. package/templates/html/components/search.html +85 -8
  91. package/templates/html/components/slides.html +46 -3
  92. package/templates/html/components/stepTabs.html +55 -8
  93. package/templates/html/components/steps.html +24 -0
  94. package/templates/html/components/summary.html +30 -3
  95. package/templates/html/components/table.html +14 -2
  96. package/templates/html/components/tooltip.html +35 -0
  97. package/templates/html/layout/body.html +20 -6
  98. package/templates/html/layout/flex.html +53 -33
  99. package/templates/html/layout/footer.html +32 -4
  100. package/templates/html/layout/grid.html +53 -33
  101. package/templates/html/layout/header.html +47 -6
  102. package/templates/html/layout/pageShell.html +19 -0
  103. package/templates/html/layout/section.html +41 -19
  104. package/templates/html/patterns/cookieConsent.html +44 -13
  105. package/templates/react/components/accordion.jsx +4 -4
  106. package/templates/react/components/breadcumbs.jsx +1 -1
  107. package/templates/react/components/button.jsx +5 -5
  108. package/templates/react/components/card.jsx +4 -4
  109. package/templates/react/components/dropdown.jsx +8 -8
  110. package/templates/react/components/form.jsx +5 -5
  111. package/templates/react/components/modal.jsx +14 -14
  112. package/templates/react/components/notification.jsx +7 -7
  113. package/templates/react/components/search.jsx +5 -5
  114. package/templates/react/components/slides.jsx +3 -3
  115. package/templates/react/components/stepTabs.jsx +8 -8
  116. package/templates/react/components/summary.jsx +3 -3
  117. package/templates/react/components/table.jsx +2 -2
  118. package/templates/react/layout/flex.jsx +8 -8
  119. package/templates/react/layout/grid.jsx +8 -8
  120. package/templates/react/layout/section.jsx +12 -12
  121. package/templates/react/patterns/cookieConsent.jsx +8 -8
  122. package/templates/react/patterns/footer.jsx +4 -4
  123. package/templates/react/patterns/header.jsx +3 -3
  124. package/templates/html/demo.js +0 -130
  125. /package/{templates/html → demo}/demo.css +0 -0
@@ -1,10 +1,16 @@
1
- import { PGS_scrollHorizontal } from "../functions/_scrollY"
1
+ import { PGS_scrollHorizontal } from "../functions/_scrollY.js";
2
2
  const API = new WeakMap();
3
3
 
4
+ function getSlides(root) {
5
+ const slides = root instanceof Element && pgs(root).contains("slides") ? [root] : [];
6
+ slides.push(...pgs(root).querySelectorAll("slides"));
7
+ return slides;
8
+ }
9
+
4
10
  class PGS_Slides {
5
11
  //- CONSTRUCTOR
6
- constructor({ selector, viewRatio = 0.97, optionIntersectionObserver = {}, scrollOptions = {} } = {}) {
7
- this.selector = selector;
12
+ constructor({ element, viewRatio = 0.97, optionIntersectionObserver = {}, scrollOptions = {} } = {}) {
13
+ this.element = element;
8
14
  this.viewRatio = viewRatio;
9
15
 
10
16
  this.optionIntersectionObserver = {
@@ -21,28 +27,36 @@ class PGS_Slides {
21
27
  ...scrollOptions,
22
28
  };
23
29
 
24
- this.container = this.selector ? pgs(this.selector).querySelector("slides-container") : null;
30
+ this.container = this.element ? pgs(this.element).querySelector("slides-container") : null;
25
31
  }
26
32
 
27
33
  //+ CREATE BUTTON
28
34
  #createButtonsAndDots() {
29
- const EL = this.selector
35
+ const EL = this.element;
30
36
 
31
37
  //== PULSANTI
32
- if (!pgs(EL).querySelector('slides-prec') && !pgs(EL).querySelector('slides-next')) {
38
+ if (!pgs(EL).querySelector('slides-prec')) {
33
39
  EL.insertAdjacentHTML("afterbegin", `<button pgs="slides-prec buttonIcon" type="button" class="precButton" aria-label="slide precedente"> <span> <i class="fa-solid fa-arrow-left"></i></span></button>`);
40
+ }
41
+ if (!pgs(EL).querySelector('slides-next')) {
34
42
  EL.insertAdjacentHTML("beforeend", `<button pgs="slides-next buttonIcon" type="button" class="nextButton" aria-label="prossima slide"> <span> <i class="fa-solid fa-arrow-right"></i></span></button>`);
35
43
  }
36
44
 
37
45
  //== DOTS
38
46
  if (!pgs(EL).querySelector('slides-dots')) {
39
47
  EL.insertAdjacentHTML("beforeend", `<div pgs="slides-dots" class="slides-dots"></div>`);
48
+ }
40
49
 
41
- Array.from(this.container.children).forEach(() => {
42
- pgs(EL).querySelector('slides-dots')
43
- .insertAdjacentHTML("beforeend", `<button type="button" class="slide-dot" aria-label="vai alla prossima slide"></button>`);
44
- });
50
+ const dotsContainer = pgs(EL).querySelector('slides-dots');
51
+ while (dotsContainer.children.length < this.container.children.length) {
52
+ dotsContainer.insertAdjacentHTML("beforeend", `<button type="button" class="slide-dot"></button>`);
53
+ }
54
+ while (dotsContainer.children.length > this.container.children.length) {
55
+ dotsContainer.lastElementChild.remove();
45
56
  }
57
+ Array.from(dotsContainer.children).forEach((dot, index) => {
58
+ dot.setAttribute("aria-label", `vai alla slide ${index + 1}`);
59
+ });
46
60
  }
47
61
 
48
62
  //+ PREV
@@ -51,7 +65,7 @@ class PGS_Slides {
51
65
  let current;
52
66
 
53
67
 
54
- if (pgs(this.selector).option.contains('singleScroll')) current = currents[currents.length - 1];
68
+ if (pgs(this.element).option.contains('singleScroll')) current = currents[currents.length - 1];
55
69
  else current = currents[0];
56
70
 
57
71
  const prev = current?.previousElementSibling;
@@ -67,19 +81,17 @@ class PGS_Slides {
67
81
 
68
82
 
69
83
 
70
- if (pgs(this.selector).option.contains('singleScroll')) current = currents[0];
84
+ if (pgs(this.element).option.contains('singleScroll')) current = currents[0];
71
85
  else current = currents[currents.length - 1];
72
86
 
73
87
  const next = current?.nextElementSibling;
74
- console.log(current, next);
75
-
76
88
  next?.scrollIntoView(this.scrollOptions);
77
89
  next?.focus({ preventScroll: true });
78
90
  }
79
91
 
80
92
  //+ GO TO NUMBER SLIDE
81
93
  #goToNumberSlide(index) {
82
- this.container.children[index].scrollIntoView(this.scrollOptions)
94
+ this.container.children[index]?.scrollIntoView(this.scrollOptions);
83
95
  }
84
96
 
85
97
  //+ CALLBACK
@@ -118,8 +130,10 @@ class PGS_Slides {
118
130
 
119
131
  //= EXECUTE
120
132
  execute() {
121
- const slides = this.selector;
122
- if (!this.container) return
133
+ const slides = this.element;
134
+ if (!this.container) return;
135
+ const eventController = new AbortController();
136
+ const { signal } = eventController;
123
137
 
124
138
  //== elements
125
139
  this.#createButtonsAndDots();
@@ -131,12 +145,14 @@ class PGS_Slides {
131
145
  const notScrollWithMouse = pgs(slides).option.contains('notScrollWithMouse');
132
146
 
133
147
  //== scroll
134
- if (!notScrollWithMouse) PGS_scrollHorizontal(this.container, 5);
148
+ const removeHorizontalScroll = notScrollWithMouse
149
+ ? null
150
+ : PGS_scrollHorizontal(this.container, 5);
135
151
 
136
152
  //==Listener: DOT, PREC, NEXT
137
- dots.forEach((dot, index) => dot.addEventListener("click", e => this.#goToNumberSlide(index)));
138
- precButton.addEventListener("click", e => this.#previousSlide(), { passive: true });
139
- nextButton.addEventListener("click", e => this.#nextSlide(), { passive: true });
153
+ dots.forEach((dot, index) => dot.addEventListener("click", () => this.#goToNumberSlide(index), { signal }));
154
+ precButton.addEventListener("click", () => this.#previousSlide(), { passive: true, signal });
155
+ nextButton.addEventListener("click", () => this.#nextSlide(), { passive: true, signal });
140
156
 
141
157
  //== observer
142
158
  const observer = new IntersectionObserver(
@@ -146,9 +162,18 @@ class PGS_Slides {
146
162
  Array.from(this.container.children).forEach(allLi => observer.observe(allLi));
147
163
 
148
164
 
165
+ let api;
166
+ const destroy = () => {
167
+ if (API.get(this.element) !== api) return;
168
+ eventController.abort();
169
+ observer.disconnect();
170
+ removeHorizontalScroll?.();
171
+ API.delete(this.element);
172
+ };
173
+
149
174
  //- API
150
- API.set(this.selector, {
151
- element: this.selector,
175
+ api = {
176
+ element: this.element,
152
177
  container: this.container,
153
178
  previous: () => this.#previousSlide(),
154
179
  next: () => this.#nextSlide(),
@@ -163,19 +188,28 @@ class PGS_Slides {
163
188
  return last?.classList.contains("view") || false;
164
189
  },
165
190
  refresh: () => {
166
- PGS_slides_init(this.selector.parentNode || document);
167
- return API.get(this.selector);
191
+ if (API.get(this.element) !== api) return API.get(this.element);
192
+ destroy();
193
+ const instance = new PGS_Slides({
194
+ element: this.element,
195
+ viewRatio: this.viewRatio,
196
+ optionIntersectionObserver: this.optionIntersectionObserver,
197
+ scrollOptions: this.scrollOptions,
198
+ });
199
+ instance.execute();
200
+ return API.get(this.element);
168
201
  },
169
- });
202
+ };
203
+ API.set(this.element, api);
170
204
  }
171
205
  }
172
206
 
173
207
  //# INIT
174
- export function PGS_slides_init(root = document) {
175
- pgs(root).querySelectorAll("slides").forEach(el => {
176
- if (API.has(el)) return;
208
+ function PGS_slides_init(root = document) {
209
+ getSlides(root).forEach(element => {
210
+ if (API.has(element)) return;
177
211
 
178
- const instance = new PGS_Slides({ selector: el });
212
+ const instance = new PGS_Slides({ element });
179
213
  instance.execute();
180
214
  });
181
215
  }
@@ -183,8 +217,8 @@ export function PGS_slides_init(root = document) {
183
217
  PGS_slides_init();
184
218
 
185
219
  //# API
186
- export function PGS_slides_api(selector) {
187
- return API.get(selector);
220
+ function PGS_slides_api(element) {
221
+ return API.get(element);
188
222
  }
189
223
 
190
224
  export const PGS_slides = {
@@ -1,6 +1,6 @@
1
1
  const API = new WeakMap();
2
2
 
3
- export function PGS_stepTabs_init(root = document) {
3
+ function PGS_stepTabs_init(root = document) {
4
4
  pgs(root).querySelectorAll("stepTabs").forEach(tabsWizard => {
5
5
  if (tabsWizard.dataset.stepTabsInitialized === "true") return;
6
6
  tabsWizard.dataset.stepTabsInitialized = "true";
@@ -32,7 +32,7 @@ export function PGS_stepTabs_init(root = document) {
32
32
  const dot = document.createElement("button");
33
33
  dot.type = "button";
34
34
  pgs(dot).add("stepTabs-dots-dot");
35
- dot.setAttribute("data-step", index);
35
+ pgs(dot).option.setValueBrackets("step", index);
36
36
  dot.innerHTML = `<i class="fa-solid ${iconClass}"></i>`;
37
37
 
38
38
  dot.addEventListener("click", () => {
@@ -97,7 +97,7 @@ export function PGS_stepTabs_init(root = document) {
97
97
  //= INIT
98
98
  goTo(0, false);
99
99
 
100
- //= data-tab-locked
100
+ //= tab-locked
101
101
  const observer = new MutationObserver(() => {
102
102
  if (isRendering) return;
103
103
  updateControls();
@@ -135,7 +135,7 @@ export function PGS_stepTabs_init(root = document) {
135
135
 
136
136
  PGS_stepTabs_init()
137
137
 
138
- export function PGS_stepTabs_api(selector) {
138
+ function PGS_stepTabs_api(selector) {
139
139
  return API.get(selector);
140
140
  }
141
141
 
@@ -1,6 +1,6 @@
1
1
  const API = new WeakMap();
2
2
 
3
- export function PGS_steps_init(root = document) {
3
+ function PGS_steps_init(root = document) {
4
4
  pgs(root).querySelectorAll("steps").forEach(steps => {
5
5
  if (API.has(steps)) return;
6
6
 
@@ -43,7 +43,7 @@ export function PGS_steps_init(root = document) {
43
43
  PGS_steps_init()
44
44
 
45
45
  //# API
46
- export function PGS_steps_api(selector) {
46
+ function PGS_steps_api(selector) {
47
47
  return API.get(selector);
48
48
  }
49
49
 
@@ -20,7 +20,7 @@ function directPgsChild(element, token) {
20
20
  return Array.from(element.children).find(child => pgs(child).contains(token));
21
21
  }
22
22
 
23
- export function PGS_summary_init(root = document) {
23
+ function PGS_summary_init(root = document) {
24
24
  pgs(root).querySelectorAll("summary").forEach((summary) => {
25
25
  if (API.has(summary)) return;
26
26
 
@@ -95,7 +95,7 @@ export function PGS_summary_init(root = document) {
95
95
  PGS_summary_init();
96
96
 
97
97
  //# API
98
- export function PGS_summary_api(selector) {
98
+ function PGS_summary_api(selector) {
99
99
  return API.get(selector);
100
100
  }
101
101
 
@@ -1,40 +1,43 @@
1
+ import { pgs } from "../_pgs.js";
1
2
  import { PGS_notification } from "../components/_notifications.js";
2
3
 
3
4
 
4
5
  export class PGS_formValidate {
5
- constructor({ form } = {}) {
6
+ #message = {};
7
+ #insideValidatedCallback = false;
8
+
9
+ constructor(form, options = {}) {
10
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
11
+ throw new TypeError("options must be an object");
12
+ }
13
+
6
14
  this.container = form;
7
15
  this._rules = [];
8
- // pgs(this.container).add("formError");
16
+ this.message = {
17
+ fieldError: "Please complete this field.",
18
+ fieldsError: "Please complete all required fields.",
19
+ success: "Submitted successfully."
20
+ };
21
+
22
+ if (options.message !== undefined) this.message = options.message;
23
+ this.container?.setAttribute("novalidate", "");
9
24
  }
10
25
 
11
- //+ ADD
12
- addError(field, i) {
13
- field.setAttribute("data-form-field-status", "error");
14
- if (i == 0) field.scrollIntoView();
15
-
16
- let message = field.getAttribute("data-form-field-message");
17
-
18
- if (i == 0 && message) PGS_notification.toast.error(message);
19
- else if (i == 0) PGS_notification.toast.error("Compila tutti i campi!");
26
+ get message() {
27
+ return { ...this.#message };
20
28
  }
21
29
 
22
- //+ REMOVE
23
- removeError(field) {
24
- field.setAttribute("data-form-field-status", "");
25
- }
30
+ set message(value) {
31
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
32
+ throw new TypeError("message must be an object");
33
+ }
26
34
 
27
- #removeErrorOnClick(allFields) {
28
- allFields.forEach(element => {
29
- element.addEventListener("click", e => this.removeError(element))
30
- });
35
+ this.#message = { ...this.#message, ...value };
31
36
  }
32
37
 
33
- // + --------------------------
34
- // + Helpers
35
- // + --------------------------
36
- help = {
37
- // supporta sia required nativo, sia data-required="true"
38
+ // - Helpers
39
+ #help = {
40
+ // supporta sia required nativo
38
41
  isRequired(field) {
39
42
  if (!field) return false;
40
43
 
@@ -77,26 +80,26 @@ export class PGS_formValidate {
77
80
  if (input.type === "hidden") return false;
78
81
  if (input.type === "checkbox" || input.type === "radio" || input.type === "file") return false;
79
82
 
80
- // valida solo se required (o data-required="true")
81
- if (!this.help.isRequired(input)) return false;
83
+ // valida solo se required
84
+ if (!this.#help.isRequired(input)) return false;
82
85
 
83
- return this.help.isEmptyTextLike(input);
86
+ return this.#help.isEmptyTextLike(input);
84
87
  });
85
88
 
86
89
  //== TEXTAREA
87
90
  // required vuote
88
91
  const textareas = Array.from(container.querySelectorAll("textarea")).filter((ta) => {
89
92
  if (ta.disabled) return false;
90
- if (!this.help.isRequired(ta)) return false;
91
- return this.help.isEmptyTextLike(ta);
93
+ if (!this.#help.isRequired(ta)) return false;
94
+ return this.#help.isEmptyTextLike(ta);
92
95
  });
93
96
 
94
97
  //== SELECT
95
98
  // required vuoti
96
99
  const selects = Array.from(container.querySelectorAll("select")).filter((sel) => {
97
100
  if (sel.disabled) return false;
98
- if (!this.help.isRequired(sel)) return false;
99
- return this.help.isEmptySelect(sel);
101
+ if (!this.#help.isRequired(sel)) return false;
102
+ return this.#help.isEmptySelect(sel);
100
103
  });
101
104
 
102
105
  //== RADIO
@@ -104,8 +107,8 @@ export class PGS_formValidate {
104
107
  const radios = Array.from(container.querySelectorAll('input[type="radio"]')).filter((r) => !r.disabled);
105
108
  const requiredRadioGroups = new Map(); // name -> [elements]
106
109
  for (const r of radios) {
107
- if (!this.help.isRequired(r)) continue;
108
- const name = this.help.getGroupName(r);
110
+ if (!this.#help.isRequired(r)) continue;
111
+ const name = this.#help.getGroupName(r);
109
112
  if (!name) continue;
110
113
  if (!requiredRadioGroups.has(name)) requiredRadioGroups.set(name, []);
111
114
  requiredRadioGroups.get(name).push(r);
@@ -126,9 +129,9 @@ export class PGS_formValidate {
126
129
  const requiredCheckboxSingles = [];
127
130
  const requiredCheckboxGroups = new Map(); // name -> [elements]
128
131
  for (const c of checkboxes) {
129
- if (!this.help.isRequired(c)) continue;
132
+ if (!this.#help.isRequired(c)) continue;
130
133
 
131
- const name = this.help.getGroupName(c);
134
+ const name = this.#help.getGroupName(c);
132
135
  if (!name) {
133
136
  // checkbox senza name: trattala come singola required
134
137
  if (!c.checked) requiredCheckboxSingles.push(c);
@@ -151,7 +154,7 @@ export class PGS_formValidate {
151
154
  // required: se vuoi includerlo
152
155
  const fileInputs = Array.from(container.querySelectorAll('input[type="file"]')).filter((f) => {
153
156
  if (f.disabled) return false;
154
- if (!this.help.isRequired(f)) return false;
157
+ if (!this.#help.isRequired(f)) return false;
155
158
  return !(f.files && f.files.length > 0);
156
159
  });
157
160
 
@@ -167,12 +170,37 @@ export class PGS_formValidate {
167
170
  ruleInvalidFields
168
171
  ];
169
172
 
170
- return invalidFields.flat();
173
+ return [...new Set(invalidFields.flat())];
174
+ }
175
+
176
+ //+ ADD
177
+ addFieldError(field, i = 0, total = 1) {
178
+ pgs(field).option.add("error");
179
+ if (i === 0) field.scrollIntoView();
180
+
181
+ if (i !== 0) return;
182
+
183
+ if (total > 1) {
184
+ PGS_notification.toast.error(this.message.fieldsError);
185
+ return;
186
+ }
187
+
188
+ const message = pgs(field).option.getValueBrackets("message");
189
+ PGS_notification.toast.error(message || this.message.fieldError);
190
+ }
191
+
192
+ //+ REMOVE
193
+ removeFieldError(field) {
194
+ pgs(field).option.remove("error");
195
+ }
196
+
197
+ // + SUCCESS
198
+ success(text = this.message.success) {
199
+ if (this.#insideValidatedCallback || this.validate() === true) PGS_notification.toast.success(text)
171
200
  }
172
201
 
173
- // + -------------------------
174
- // + VALIDATE
175
- // + -------------------------
202
+
203
+ // + VALIDATE
176
204
  validate() {
177
205
  const invalid = this.#inputValue(this.container);
178
206
  const allFields = this.container.querySelectorAll("input, textarea, select")
@@ -183,34 +211,50 @@ export class PGS_formValidate {
183
211
 
184
212
  //== per radio/checkbox in gruppo:
185
213
  // rimuovi l'errore solo sull'elemento che lo ospita (qui: se presente)
186
- for (const el of allFields) { if (!invalid.includes(el)) this.removeError(el); }
214
+ for (const el of allFields) { if (!invalid.includes(el)) this.removeFieldError(el); }
187
215
 
188
216
  //== aggiungo errori dove serve
189
- invalid.forEach((el, i) => this.addError(el, i))
217
+ invalid.forEach((el, i) => this.addFieldError(el, i, invalid.length))
190
218
 
191
219
  //== rimuove l'errore al click
192
- this.#removeErrorOnClick(allFields)
220
+ allFields.forEach(element => element.addEventListener("click", e => this.removeFieldError(element)));
193
221
 
194
222
  //== status form
195
223
  if (invalid.length) {
196
- this.container.setAttribute("data-form-status", "error");
224
+ pgs(this.container).option.remove("success").add("error");
197
225
  return false;
198
226
  } else {
199
- this.container.setAttribute("data-form-status", "success");
227
+ pgs(this.container).option.remove("error").add("success");
200
228
  return true;
201
229
  }
202
230
  }
203
231
 
204
- ifSuccess(text = "Inviato con successo") {
205
- if (this.validate() == true) PGS_notification.toast.success(text)
232
+ // + EVENT VALIDATOR
233
+ validator(callback, eventName = "submit") {
234
+ if (typeof callback !== "function") throw new TypeError("callback must be a function");
235
+ if (typeof eventName !== "string" || !eventName.trim()) throw new TypeError("eventName must be a non-empty string");
236
+
237
+ this.container.addEventListener(eventName, event => {
238
+ event.preventDefault();
239
+ if (!this.validate()) return;
240
+
241
+ this.#insideValidatedCallback = true;
242
+
243
+ try {
244
+ this.success();
245
+ callback(event);
246
+ } finally {
247
+ this.#insideValidatedCallback = false;
248
+ }
249
+ });
250
+
251
+ return this;
206
252
  }
207
253
 
208
- // + -------------------------
209
- // + ADD RULE
210
- // + -------------------------
211
- addNewRule(container) {
212
- if (typeof container !== "function") throw new Error("Rule must be a function");
213
- this._rules.push(container);
254
+ // + ADD RULE
255
+ addNewRule(rule) {
256
+ if (typeof rule !== "function") throw new Error("Rule must be a function");
257
+ this._rules.push(rule);
214
258
  return this;
215
259
  }
216
260
  }
@@ -1,4 +1,4 @@
1
- export function PGS_scrollHorizontal(querySelector, dataSpeed) {
1
+ export function PGS_scrollHorizontal(element, speed) {
2
2
  // Se hai più contenitori, selezionali tutti:
3
3
  // Semplice "singleton" per stimare se la sorgente è trackpad
4
4
  const TrackpadDetector = (() => {
@@ -40,10 +40,7 @@ export function PGS_scrollHorizontal(querySelector, dataSpeed) {
40
40
 
41
41
  //= Scorrimento orizzontale con rotella (evita il trackpad)
42
42
 
43
- let el = querySelector
44
- el.addEventListener('wheel', (e) => {
45
- const speed = dataSpeed;
46
-
43
+ const onWheel = (e) => {
47
44
  //== lascia lo scroll naturale del trackpad
48
45
  if (TrackpadDetector.update(e)) return;
49
46
 
@@ -54,11 +51,11 @@ export function PGS_scrollHorizontal(querySelector, dataSpeed) {
54
51
  //== Converti delta in px per lo shift orizzontale
55
52
  let delta = e.deltaY;
56
53
  if (e.deltaMode === 1) delta *= 16;
57
- else if (e.deltaMode === 2) delta *= el.clientHeight;
54
+ else if (e.deltaMode === 2) delta *= element.clientHeight;
58
55
 
59
56
  //== Verifica se il contenitore può ancora scrollare orizzontalmente
60
- const atStart = el.scrollLeft <= 0;
61
- const atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 1;
57
+ const atStart = element.scrollLeft <= 0;
58
+ const atEnd = element.scrollLeft + element.clientWidth >= element.scrollWidth - 1;
62
59
  const scrollingRight = delta > 0;
63
60
  const scrollingLeft = delta < 0;
64
61
  const canScrollHoriz =
@@ -72,6 +69,9 @@ export function PGS_scrollHorizontal(querySelector, dataSpeed) {
72
69
  e.preventDefault();
73
70
 
74
71
  //== rotella giù => destra
75
- el.scrollLeft += delta * speed;
76
- }, { passive: false });
72
+ element.scrollLeft += delta * speed;
73
+ };
74
+
75
+ element.addEventListener('wheel', onWheel, { passive: false });
76
+ return () => element.removeEventListener('wheel', onWheel);
77
77
  }
@@ -8,7 +8,10 @@ function PGS_header(selectHeader = document) {
8
8
 
9
9
  const headerElements = pgs(header).querySelectorAll("header-element");
10
10
 
11
+ if (!headerElements.length) console.log('For the header to work correctly, insert "header-element" under "header"');
11
12
  if (!headerElements.length) return;
13
+
14
+
12
15
 
13
16
  headerElements.forEach(selectHeader => {
14
17
 
@@ -5,14 +5,14 @@
5
5
  margin: 0;
6
6
  padding: 0;
7
7
  box-sizing: border-box;
8
- scroll-margin-top: calc(var(--heightOfHeader) + 5vh);
8
+ scroll-margin-top: var(--page-topScroll);
9
9
 
10
10
  // Reset properties
11
11
  * {
12
12
  margin: 0;
13
13
  padding: 0;
14
14
  box-sizing: border-box;
15
- scroll-margin-top: calc(var(--heightOfHeader) + 5vh);
15
+ scroll-margin-top: var(--page-topScroll);
16
16
  }
17
17
 
18
18
  &:root,
@@ -19,8 +19,8 @@
19
19
  --text-shadow: 0px 0px 50px #000000;
20
20
  --backdrop: #0000003e;
21
21
  --blur: blur(20px);
22
- --page-top: calc(var(--heightOfHeader) + (var(--gap-elements)*2));
23
- --page-topScroll: calc(var(--heightOfHeaderScroll) + (var(--gap-elements)*2));
22
+ --page-top: calc(var(--heightOfHeader) + var(--heightOfHeader));
23
+ --page-topScroll: calc(var(--heightOfHeaderScroll) + var(--heightOfHeader));
24
24
  --page-width: 1280px;
25
25
  --page-space: clamp(var(--padding-page), calc((100% - var(--section-width)) / 2 + var(--padding-page)), 100%);
26
26
  --gap-texts: 1.0rem;
@@ -11,7 +11,6 @@
11
11
 
12
12
  &:not([pgs-state~=open]) {
13
13
  @include button(true);
14
- --button-background: var(--color-box);
15
14
  --button-size: var(--padding-2);
16
15
  }
17
16
 
@@ -59,7 +58,6 @@
59
58
  //= OPEN
60
59
  &[pgs-state~=open] {
61
60
  @include button(false);
62
- --button-background: var(--color-box);
63
61
  --button-size: var(--padding-2);
64
62
  margin-top: var(--padding);
65
63
 
@@ -27,4 +27,4 @@
27
27
 
28
28
  [pgs~=buttonClose] {
29
29
  @include buttonClose();
30
- }
30
+ }
@@ -25,22 +25,6 @@
25
25
  @include select();
26
26
  }
27
27
 
28
- [pgs~=checkboxBackground] {
29
- @include checkboxBackground();
30
- }
31
-
32
- [pgs~=twoState] {
33
- @include twoState();
34
- }
35
-
36
- [pgs~=toggle] {
37
- @include toggle();
38
- }
39
-
40
- [pgs~=chips] {
41
- @include chips();
42
- }
43
-
44
28
  [pgs~=radio],
45
29
  [pgs~=checkbox] {
46
30
  @include checkbox();
@@ -50,6 +34,6 @@
50
34
  @include form();
51
35
  }
52
36
 
53
- [pgs~=formError] {
54
- @include formError();
37
+ [pgs~=formValidate] {
38
+ @include formValidate();
55
39
  }
@@ -0,0 +1,18 @@
1
+ @use "../mixin/mixin" as *;
2
+
3
+ //# FORM ADDON
4
+ [pgs~=checkboxBackground] {
5
+ @include checkboxBackground();
6
+ }
7
+
8
+ [pgs~=twoState] {
9
+ @include twoState();
10
+ }
11
+
12
+ [pgs~=toggle] {
13
+ @include toggle();
14
+ }
15
+
16
+ [pgs~=chips] {
17
+ @include chips();
18
+ }