mypgs 1.1.2 → 1.3.2

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 (40) hide show
  1. package/README.md +124 -11
  2. package/assets/javascript/_imports.js +25 -0
  3. package/assets/javascript/_pgs.js +37 -1
  4. package/assets/javascript/components/_accordion.js +105 -66
  5. package/assets/javascript/components/_dropdown.js +51 -5
  6. package/assets/javascript/components/_menu.js +71 -6
  7. package/assets/javascript/components/_modals.js +45 -3
  8. package/assets/javascript/components/_notifications.js +163 -0
  9. package/assets/javascript/components/_slides.js +12 -1
  10. package/assets/javascript/components/_stepTabs.js +18 -5
  11. package/assets/javascript/components/_steps.js +33 -6
  12. package/assets/javascript/functions/_formValidate.js +4 -5
  13. package/assets/javascript/index.js +8 -4
  14. package/assets/javascript/patterns/_header.js +1 -1
  15. package/assets/javascript/pgs.d.ts +51 -16
  16. package/assets/scss/components/_dropdown.scss +19 -26
  17. package/assets/scss/components/_logo.scss +1 -1
  18. package/assets/scss/components/_tooltip.scss +9 -1
  19. package/assets/scss/layout/_pageShell.scss +15 -20
  20. package/assets/scss/mixin/_mx-button.scss +12 -12
  21. package/dist/css/index.css +94 -96
  22. package/dist/css/index.css.map +1 -1
  23. package/dist/css/index.min.css +1 -1
  24. package/dist/index.d.ts +50 -16
  25. package/dist/javascript/index.js +840 -271
  26. package/dist/javascript/index.js.map +1 -1
  27. package/dist/javascript/index.min.js +1 -1
  28. package/package.json +7 -2
  29. package/pgs-html-suggestions.vsix +0 -0
  30. package/templates/components/md-button.html +13 -7
  31. package/templates/components/md-card.html +11 -9
  32. package/templates/components/md-dropdown.html +1 -1
  33. package/templates/components/md-modal.html +4 -4
  34. package/templates/components/md-stepTabs.html +3 -3
  35. package/templates/components/md-tooltip.html +1 -0
  36. package/templates/demo.css +4 -0
  37. package/templates/demo.js +2 -2
  38. package/assets/javascript/components/_exeNotifications.js +0 -85
  39. package/assets/javascript/functions/_notifications.js +0 -74
  40. package/assets/javascript/functions/_sendForm.js +0 -100
package/README.md CHANGED
@@ -1,8 +1,12 @@
1
1
  # MyPGS
2
2
 
3
- Libreria frontend con asset JavaScript e CSS precompilati per i componenti e i pattern MyPGS.
3
+ Libreria frontend condivisa per componenti, layout e pattern basati sull'attributo `pgs`.
4
4
 
5
- ## Installazione
5
+ Contiene sorgenti SCSS, mixin, comportamenti JavaScript, template HTML di esempio e asset compilati pronti all'uso. Puo' essere usata sia importando direttamente CSS e JavaScript compilati, sia importando gli SCSS sorgenti dentro il build del progetto che la utilizza.
6
+
7
+ ## Installazione come dipendenza
8
+
9
+ Da registry npm, se pubblicata:
6
10
 
7
11
  ```bash
8
12
  npm install mypgs
@@ -10,24 +14,133 @@ npm install mypgs
10
14
 
11
15
  ## Utilizzo
12
16
 
13
- Importa gli asset compilati nel tuo progetto:
17
+ ### JavaScript
18
+
19
+ Importa la libreria nell'entrypoint JavaScript del progetto:
14
20
 
15
21
  ```js
16
- import "mypgs";
17
- import "mypgs/style.css";
22
+ import * as mypgs from "mypgs";
23
+ ```
24
+
25
+ L'entrypoint di MyPGS inizializza:
26
+
27
+ - helper globale `pgs`
28
+ - dark mode
29
+ - gestione oggetti PGS
30
+ - accordion
31
+ - dropdown
32
+ - menu
33
+ - modali
34
+ - slides
35
+ - steps
36
+ - step tabs
37
+ - notifiche
38
+ - header
39
+ - cookie consent
40
+
41
+ Il bundle finale viene generato dal progetto che importa `mypgs`, in base alla propria configurazione Webpack o al proprio bundler.
42
+
43
+ ### SCSS
44
+
45
+ Per integrare MyPGS nel build SCSS del progetto, importa mixin e stili sorgenti da `node_modules`:
46
+
47
+ ```scss
48
+ @import "../../node_modules/mypgs/assets/scss/mixin/mixin.scss";
49
+ @import "../../node_modules/mypgs/assets/scss/index.scss";
18
50
  ```
19
51
 
20
- Lo script inizializza le funzionalita' globali della libreria ed espone `pgs` su `globalThis`.
52
+ Se servono solo i mixin, importa solo il file dedicato:
53
+
54
+ ```scss
55
+ @import "../../node_modules/mypgs/assets/scss/mixin/mixin.scss";
56
+ ```
57
+
58
+ Molti layout, componenti e pattern sono inclusi sotto `[pgs~=initP]`, quindi il markup principale deve abilitare la libreria così:
59
+
60
+ ```html
61
+ <html pgs="htmlBase initP">
62
+ <body pgs="bodyBase bodyImg bodyText bodyHeading">
63
+ ```
64
+
65
+ ## Markup PGS
66
+
67
+ I componenti vengono attivati usando token nell'attributo `pgs`, per esempio:
68
+
69
+ ```html
70
+ <nav pgs="menuHorizontal"></nav>
71
+ <button pgs="button modal-button"></button>
72
+ <div pgs="accordion">
73
+ <button pgs="accordion-button"></button>
74
+ <div pgs="accordion-content"></div>
75
+ </div>
76
+ ```
77
+
78
+ Gli stessi token devono restare coerenti tra markup, SCSS e JavaScript.
21
79
 
22
80
  ## Contenuto del pacchetto
23
81
 
24
- - `dist/javascript/index.js`
25
- - `dist/javascript/index.min.js`
26
- - `dist/css/index.css`
27
- - `dist/css/index.min.css`
82
+ - `assets/scss/`: sorgenti SCSS, mixin, base, layout, componenti e pattern
83
+ - `assets/javascript/`: sorgenti JavaScript dei comportamenti PGS
84
+ - `templates/`: esempi HTML dei componenti e dei layout
85
+ - `dist/javascript/index.js`: bundle JavaScript non minificato
86
+ - `dist/javascript/index.min.js`: bundle JavaScript minificato
87
+ - `dist/css/index.css`: CSS compilato non minificato
88
+ - `dist/css/index.min.css`: CSS compilato minificato
89
+ - `dist/index.d.ts`: dichiarazioni TypeScript esportate dal pacchetto
90
+
91
+ ## Export npm
92
+
93
+ Il pacchetto espone:
94
+
95
+ ```json
96
+ {
97
+ ".": "./dist/javascript/index.js",
98
+ "./style.css": "./dist/css/index.css",
99
+ "./style.min.css": "./dist/css/index.min.css"
100
+ }
101
+ ```
102
+
103
+ Uso diretto degli asset compilati:
104
+
105
+ ```js
106
+ import "mypgs";
107
+ import "mypgs/style.css";
108
+ ```
109
+
110
+ In alternativa, puoi importare gli SCSS sorgenti direttamente nel build del progetto quando vuoi ottenere un unico CSS finale.
28
111
 
29
112
  ## Sviluppo
30
113
 
114
+ Build una tantum:
115
+
31
116
  ```bash
32
- npm run build
117
+ npm run start
33
118
  ```
119
+
120
+ Build in watch mode:
121
+
122
+ ```bash
123
+ npm run "start watch"
124
+ ```
125
+
126
+ Per preparare un archivio locale installabile in un altro progetto:
127
+
128
+ ```bash
129
+ npm pack
130
+ ```
131
+
132
+ Quando modifichi MyPGS e vuoi provarlo in un progetto che la usa:
133
+
134
+ 1. ricompila MyPGS con `npm run start`
135
+ 2. crea un nuovo archivio con `npm pack`
136
+ 3. copia o aggiorna il `.tgz` usato dal progetto
137
+ 4. esegui `npm install` nel progetto
138
+ 5. ricompila il progetto
139
+
140
+ ## Convenzioni
141
+
142
+ - Usa l'attributo `pgs` per collegare markup, CSS e JavaScript.
143
+ - Mantieni i token PGS coerenti tra template HTML, componenti, moduli e script.
144
+ - Metti in MyPGS solo stili e comportamenti riutilizzabili.
145
+ - Mantieni nel progetto finale gli stili e le personalizzazioni specifiche.
146
+ - Esporta in `dist/` prima di creare il pacchetto locale `.tgz`.
@@ -0,0 +1,25 @@
1
+ import { pgs } from "./_pgs.js";
2
+
3
+ import { PGS_accordion } from "./components/_accordion.js";
4
+ import { PGS_dropdown } from "./components/_dropdown.js";
5
+ import { PGS_menu } from "./components/_menu.js";
6
+ import { PGS_modal } from "./components/_modals.js";
7
+ import { PGS_notification } from "./components/_notifications.js";
8
+ import { PGS_slides } from "./components/_slides.js";
9
+ import { PGS_stepTabs } from "./components/_stepTabs.js";
10
+ import { PGS_steps } from "./components/_steps.js";
11
+ import { PGS_formValidate } from "./functions/_formValidate.js";
12
+ import { PGS_scrollHorizontal } from "./functions/_scrollY.js";
13
+
14
+ pgs.registerImport({
15
+ PGS_accordion,
16
+ PGS_dropdown,
17
+ PGS_menu,
18
+ PGS_modal,
19
+ PGS_notification,
20
+ PGS_slides,
21
+ PGS_stepTabs,
22
+ PGS_steps,
23
+ PGS_formValidate,
24
+ PGS_scrollHorizontal,
25
+ });
@@ -224,6 +224,42 @@ export function pgs(root) {
224
224
  return api;
225
225
  }
226
226
 
227
- globalThis.pgs ??= pgs;
227
+ const PGS_IMPORTS = {};
228
+
229
+ function registerImportModule(name, module) {
230
+ const key = String(name || "").trim().replace(/^pgs[_-\s]*/i, "").toLowerCase();
228
231
 
232
+ if (!key) throw new TypeError("pgs.registerImport(...modules): ogni modulo deve avere name o PGS_name");
233
+
234
+ PGS_IMPORTS[key] = {
235
+ name,
236
+ module
237
+ };
238
+ }
239
+
240
+ pgs.registerImport = function (...modules) {
241
+ modules.flat().forEach(item => {
242
+ if (item && typeof item === "object" && !item.PGS_name && !item.name) {
243
+ Object.entries(item).forEach(([name, module]) => registerImportModule(name, module));
244
+ return;
245
+ }
229
246
 
247
+ registerImportModule(item?.PGS_name || item?.name, item);
248
+ });
249
+
250
+ return pgs;
251
+ };
252
+
253
+ pgs.import = function (...names) {
254
+ return names.flat().reduce((imports, name) => {
255
+ const key = String(name || "").trim().replace(/^pgs[_-\s]*/i, "").toLowerCase();
256
+ const item = PGS_IMPORTS[key];
257
+
258
+ if (!item) throw new Error(`pgs.import(): modulo "${name}" non registrato`);
259
+
260
+ imports[item.name] = item.module;
261
+ return imports;
262
+ }, {});
263
+ };
264
+
265
+ globalThis.pgs ??= pgs;
@@ -1,77 +1,116 @@
1
- //= ACCORDION
2
- const allAccordion = pgs(document).querySelectorAll("accordion")
3
-
4
- allAccordion.forEach((accordion, index) => {
5
-
6
- const BUTTON = pgs(accordion).querySelector("accordion-button");
7
- const CONTENT = pgs(accordion).querySelector("accordion-content");
8
-
9
- //== ID univoci per aria-controls / aria-labelledby
10
- const ID = index + 1;
11
- const btnId = `acc-btn-${ID}`;
12
- const panelId = `acc-panel-${ID}`;
13
-
14
- //== Stato iniziale
15
- const isOpenInit = pgs(accordion).state.contains("open");
16
-
17
- //== Accessibilità (setup una volta)
18
- BUTTON.setAttribute("role", "button");
19
- BUTTON.setAttribute("tabindex", "0");
20
- BUTTON.setAttribute("id", btnId);
21
- BUTTON.setAttribute("aria-controls", panelId);
22
-
23
- CONTENT.setAttribute("id", panelId);
24
- CONTENT.setAttribute("role", "region");
25
- CONTENT.setAttribute("aria-labelledby", btnId);
26
-
27
- //+ Accessibility (applica stato aperto/chiuso)
28
- function accordionAccessibility(isOpen, button, content) {
29
- const text = (button?.textContent || "").trim().replace(/\s+/g, " ");
30
- button.setAttribute("aria-label", `${isOpen ? "Chiudi" : "Apri"} ${text || "sezione"}`);
31
- button.setAttribute("aria-expanded", String(isOpen));
32
- content.hidden = !isOpen;
33
- }
34
-
35
- //+ Chiudi tutti gli altri
36
- function closeOltherAccordion() {
37
- for (const otherLi of allAccordion) {
38
- if (otherLi === accordion) continue;
39
-
40
- const otherBtn = pgs(otherLi).querySelector("accordion-button");
41
- const otherContent = pgs(otherLi).querySelector("accordion-content");
42
- if (!otherBtn || !otherContent) continue;
43
-
44
- pgs(otherLi).state().remove("open");
45
- accordionAccessibility(false, otherBtn, otherContent);
1
+ //= ACCORDION
2
+ const API = new WeakMap();
3
+
4
+ export function PGS_accordion_init(root = document) {
5
+ pgs(root).querySelectorAll("accordion").forEach((accordion, index) => {
6
+ if (API.has(accordion)) return;
7
+
8
+ const BUTTON = pgs(accordion).querySelector("accordion-button");
9
+ const CONTENT = pgs(accordion).querySelector("accordion-content");
10
+ if (!BUTTON || !CONTENT) return;
11
+
12
+ //== ID univoci per aria-controls / aria-labelledby
13
+ const ID = index + 1;
14
+ const btnId = `acc-btn-${ID}`;
15
+ const panelId = `acc-panel-${ID}`;
16
+
17
+ //== Stato iniziale
18
+ const isOpenInit = pgs(accordion).state.contains("open");
19
+
20
+ //== Accessibilità (setup una volta)
21
+ BUTTON.setAttribute("role", "button");
22
+ BUTTON.setAttribute("tabindex", "0");
23
+ BUTTON.setAttribute("id", btnId);
24
+ BUTTON.setAttribute("aria-controls", panelId);
25
+
26
+ CONTENT.setAttribute("id", panelId);
27
+ CONTENT.setAttribute("role", "region");
28
+ CONTENT.setAttribute("aria-labelledby", btnId);
29
+
30
+ //+ Accessibility (applica stato aperto/chiuso)
31
+ function accordionAccessibility(isOpen, button, content) {
32
+ const text = (button?.textContent || "").trim().replace(/\s+/g, " ");
33
+ button.setAttribute("aria-label", `${isOpen ? "Chiudi" : "Apri"} ${text || "sezione"}`);
34
+ button.setAttribute("aria-expanded", String(isOpen));
35
+ content.hidden = !isOpen;
46
36
  }
47
- }
48
37
 
49
- //+ FN ACCORDION
50
- function accordionFunction() {
51
- const isOpen = pgs(accordion).state.contains("open");
52
- const nowOpen = !isOpen;
38
+ //+ Chiudi tutti gli altri
39
+ function closeOltherAccordion() {
40
+ for (const otherLi of pgs(document).querySelectorAll("accordion")) {
41
+ if (otherLi === accordion) continue;
53
42
 
54
- pgs(accordion).state.toggle("open", nowOpen);
55
- accordionAccessibility(nowOpen, BUTTON, CONTENT);
43
+ const otherBtn = pgs(otherLi).querySelector("accordion-button");
44
+ const otherContent = pgs(otherLi).querySelector("accordion-content");
45
+ if (!otherBtn || !otherContent) continue;
56
46
 
57
- closeOltherAccordion();
47
+ pgs(otherLi).state().remove("open");
48
+ accordionAccessibility(false, otherBtn, otherContent);
49
+ }
50
+ }
51
+
52
+ //+ FN ACCORDION
53
+ function accordionFunction() {
54
+ const isOpen = pgs(accordion).state.contains("open");
55
+ const nowOpen = !isOpen;
58
56
 
59
- //== scroll to view
60
- if (nowOpen) setTimeout(() => accordion.scrollIntoView({ block: "nearest", inline: "nearest" }), 100);
61
- }
57
+ pgs(accordion).state.toggle("open", nowOpen);
58
+ accordionAccessibility(nowOpen, BUTTON, CONTENT);
62
59
 
63
- // applica stato iniziale
64
- accordionAccessibility(isOpenInit, BUTTON, CONTENT);
60
+ closeOltherAccordion();
61
+
62
+ //== scroll to view
63
+ if (nowOpen) setTimeout(() => accordion.scrollIntoView({ block: "nearest", inline: "nearest" }), 100);
64
+ }
65
65
 
66
- //- Eventi
67
- BUTTON.addEventListener("click", accordionFunction);
66
+ function open() {
67
+ if (!pgs(accordion).state.contains("open")) accordionFunction();
68
+ }
68
69
 
69
- //- Tastiera: Enter / Space
70
- BUTTON.addEventListener("keydown", (e) => {
71
- if (e.key === "Enter" || e.key === " ") {
72
- e.preventDefault();
73
- accordionFunction();
70
+ function close() {
71
+ if (pgs(accordion).state.contains("open")) accordionFunction();
74
72
  }
73
+
74
+ // applica stato iniziale
75
+ accordionAccessibility(isOpenInit, BUTTON, CONTENT);
76
+
77
+ //- Eventi
78
+ BUTTON.addEventListener("click", accordionFunction);
79
+
80
+ //- Tastiera: Enter / Space
81
+ BUTTON.addEventListener("keydown", (e) => {
82
+ if (e.key === "Enter" || e.key === " ") {
83
+ e.preventDefault();
84
+ accordionFunction();
85
+ }
86
+ });
87
+
88
+ API.set(accordion, {
89
+ element: accordion,
90
+ button: BUTTON,
91
+ content: CONTENT,
92
+ open,
93
+ close,
94
+ toggle: accordionFunction,
95
+ refresh: () => {
96
+ PGS_accordion_init(accordion.parentNode || document);
97
+ return API.get(accordion);
98
+ },
99
+ isOpen: () => pgs(accordion).state.contains("open"),
100
+ });
75
101
  });
102
+ }
103
+
104
+ //# INIT
105
+ PGS_accordion_init();
106
+
107
+ //# API
108
+ export function PGS_accordion_api(selector) {
109
+ return API.get(selector);
110
+ }
76
111
 
77
- });
112
+ export const PGS_accordion = {
113
+ PGS_name: "PGS_accordion",
114
+ init: PGS_accordion_init,
115
+ api: PGS_accordion_api
116
+ };
@@ -1,6 +1,10 @@
1
1
  // + dropdown (Popover API)
2
- export function pgs_dropdown() {
3
- pgs(document).querySelectorAll("dropdown").forEach((DROPDOWN, index) => {
2
+ const API = new WeakMap();
3
+
4
+ export function PGS_dropdown_init(root = document) {
5
+ pgs(root).querySelectorAll("dropdown").forEach((DROPDOWN, index) => {
6
+ if (API.has(DROPDOWN)) return;
7
+
4
8
  const BUTTON = pgs(DROPDOWN).querySelector("dropdown-button");
5
9
  const CONTENT = pgs(DROPDOWN).querySelector("dropdown-content");
6
10
 
@@ -34,9 +38,10 @@ export function pgs_dropdown() {
34
38
  CSS.supports("position-anchor: --dropdown-anchor") &&
35
39
  CSS.supports("position-area: bottom") &&
36
40
  CSS.supports("top: anchor(bottom)");
41
+ const USE_FALLBACK_POSITIONING = pgs(DROPDOWN).contains("tooltip") || !HAS_ANCHOR_POSITIONING;
37
42
 
38
43
  const updatePopoverPosition = () => {
39
- if (HAS_ANCHOR_POSITIONING) return;
44
+ if (!USE_FALLBACK_POSITIONING) return;
40
45
 
41
46
  const buttonRect = BUTTON.getBoundingClientRect();
42
47
  const style = getComputedStyle(DROPDOWN);
@@ -71,7 +76,7 @@ export function pgs_dropdown() {
71
76
  };
72
77
 
73
78
  BUTTON.addEventListener("click", e => {
74
- if (HAS_ANCHOR_POSITIONING) return;
79
+ if (!USE_FALLBACK_POSITIONING) return;
75
80
 
76
81
  e.preventDefault();
77
82
  if (CONTENT.matches(":popover-open")) {
@@ -100,8 +105,49 @@ export function pgs_dropdown() {
100
105
  window.addEventListener("scroll", () => {
101
106
  if (CONTENT.matches(":popover-open")) updatePopoverPosition();
102
107
  }, true);
108
+
109
+ function open() {
110
+ if (CONTENT.matches(":popover-open")) return;
111
+ updatePopoverPosition();
112
+ CONTENT.showPopover();
113
+ }
114
+
115
+ function close() {
116
+ if (!CONTENT.matches(":popover-open")) return;
117
+ CONTENT.hidePopover();
118
+ }
119
+
120
+ function toggle() {
121
+ CONTENT.matches(":popover-open") ? close() : open();
122
+ }
123
+
124
+ API.set(DROPDOWN, {
125
+ element: DROPDOWN,
126
+ button: BUTTON,
127
+ content: CONTENT,
128
+ open,
129
+ close,
130
+ toggle,
131
+ updatePosition: updatePopoverPosition,
132
+ refresh: () => {
133
+ PGS_dropdown_init(DROPDOWN.parentNode || document);
134
+ return API.get(DROPDOWN);
135
+ },
136
+ isOpen: () => CONTENT.matches(":popover-open"),
137
+ });
103
138
  });
104
139
  }
105
140
 
106
141
  // # INIT
107
- pgs_dropdown();
142
+ PGS_dropdown_init();
143
+
144
+ // # API
145
+ export function PGS_dropdown_api(selector) {
146
+ return API.get(selector);
147
+ }
148
+
149
+ export const PGS_dropdown = {
150
+ PGS_name: "PGS_dropdown",
151
+ init: PGS_dropdown_init,
152
+ api: PGS_dropdown_api
153
+ };
@@ -1,13 +1,17 @@
1
- import { pgs_dropdown } from "./_dropdown";
1
+ import { PGS_dropdown_init, PGS_dropdown_api } from "./_dropdown";
2
+
3
+ const API = new WeakMap();
2
4
 
3
5
  //= DROP DOWN MENU
4
- export function PGS_menu() {
6
+ export function PGS_menu_init(root = document) {
5
7
 
6
- pgs(document).querySelectorAll('menu-horizontal').forEach(MENU => {
8
+ pgs(root).querySelectorAll('menu-horizontal').forEach(MENU => {
9
+ if (API.has(MENU)) return;
7
10
 
8
11
  MENU.querySelectorAll('nav > ul > li.menu-item-has-children').forEach(li => {
9
12
  if (li.querySelector("ul")) {
10
13
  const ul = li.querySelector("ul");
14
+ if (pgs(li).querySelector("dropdown-button")) return;
11
15
 
12
16
  const button = document.createElement("button");
13
17
  button.className = "icon-down";
@@ -22,14 +26,28 @@ export function PGS_menu() {
22
26
  pgs(ul).add("menu-vertical")
23
27
  }
24
28
  });
29
+
30
+ API.set(MENU, {
31
+ element: MENU,
32
+ type: "horizontal",
33
+ items: () => Array.from(MENU.querySelectorAll('nav > ul > li')),
34
+ submenus: () => Array.from(MENU.querySelectorAll('.menu-item-has-children > ul')),
35
+ dropdowns: () => Array.from(MENU.querySelectorAll('.menu-item-has-children')).map(PGS_dropdown_api).filter(Boolean),
36
+ refresh: () => {
37
+ PGS_menu_init(MENU.parentNode || document);
38
+ return API.get(MENU);
39
+ },
40
+ });
25
41
  });
26
42
 
27
- pgs(document).querySelectorAll('menu-vertical').forEach(MENU => {
43
+ pgs(root).querySelectorAll('menu-vertical').forEach(MENU => {
44
+ if (API.has(MENU)) return;
28
45
 
29
46
  MENU.querySelectorAll('.menu-item-has-children').forEach((li, index) => {
30
47
  const ul = li.querySelector("ul");
31
48
 
32
49
  if (!ul) return
50
+ if (li.querySelector(":scope > button")) return;
33
51
 
34
52
  const button = document.createElement("button");
35
53
  button.className = "icon-down buttonIcon";
@@ -64,6 +82,42 @@ export function PGS_menu() {
64
82
  });
65
83
  });
66
84
 
85
+ function submenu(index) {
86
+ return MENU.querySelectorAll('.menu-item-has-children > ul')[index];
87
+ }
88
+
89
+ function setSubmenu(index, open) {
90
+ const ul = submenu(index);
91
+ const button = ul?.parentElement?.querySelector(":scope > button");
92
+ if (!ul || !button) return;
93
+
94
+ pgs(ul).state.toggle("open", open);
95
+ button.setAttribute("aria-expanded", String(open));
96
+ button.setAttribute("aria-label", open ? "Chiudi sottomenu" : "Apri sottomenu");
97
+ }
98
+
99
+ API.set(MENU, {
100
+ element: MENU,
101
+ type: "vertical",
102
+ items: () => Array.from(MENU.querySelectorAll(':scope > li')),
103
+ submenus: () => Array.from(MENU.querySelectorAll('.menu-item-has-children > ul')),
104
+ openSubmenu: (index) => setSubmenu(index, true),
105
+ closeSubmenu: (index) => setSubmenu(index, false),
106
+ toggleSubmenu: (index) => {
107
+ const ul = submenu(index);
108
+ if (!ul) return;
109
+ setSubmenu(index, !pgs(ul).state.contains("open"));
110
+ },
111
+ isSubmenuOpen: (index) => {
112
+ const ul = submenu(index);
113
+ return ul ? pgs(ul).state.contains("open") : false;
114
+ },
115
+ refresh: () => {
116
+ PGS_menu_init(MENU.parentNode || document);
117
+ return API.get(MENU);
118
+ },
119
+ });
120
+
67
121
  // pgs(document).querySelectorAll('menu-vertical').forEach(MENU => {
68
122
 
69
123
  // MENU.querySelectorAll('.menu-item-has-children').forEach(li => {
@@ -85,8 +139,19 @@ export function PGS_menu() {
85
139
  // });
86
140
  // });
87
141
  });
88
- pgs_dropdown()
142
+ PGS_dropdown_init()
89
143
  }
90
144
 
91
145
  //# INIT PGS_menu
92
- PGS_menu()
146
+ PGS_menu_init()
147
+
148
+ //# API
149
+ export function PGS_menu_api(selector) {
150
+ return API.get(selector);
151
+ }
152
+
153
+ export const PGS_menu = {
154
+ PGS_name: "PGS_menu",
155
+ init: PGS_menu_init,
156
+ api: PGS_menu_api
157
+ };