mypgs 1.1.5 → 1.3.3
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/AI_GUIDELINES.md +353 -0
- package/README.md +483 -53
- package/assets/javascript/_imports.js +26 -0
- package/assets/javascript/_pgs.js +53 -2
- package/assets/javascript/components/_accordion.js +114 -66
- package/assets/javascript/components/_dropdown.js +199 -98
- package/assets/javascript/components/_menu.js +33 -69
- package/assets/javascript/components/_modals.js +44 -3
- package/assets/javascript/components/_notifications.js +162 -0
- package/assets/javascript/components/_slides.js +11 -1
- package/assets/javascript/components/_stepTabs.js +16 -4
- package/assets/javascript/components/_steps.js +32 -6
- package/assets/javascript/functions/_formValidate.js +4 -5
- package/assets/javascript/index.js +8 -4
- package/assets/javascript/patterns/_cookieConsent.js +8 -2
- package/assets/javascript/patterns/_header.js +1 -1
- package/assets/javascript/pgs.d.ts +17 -2
- package/assets/scss/base/_html.scss +23 -1
- package/assets/scss/components/_dropdown.scss +24 -88
- package/assets/scss/components/_logo.scss +1 -1
- package/assets/scss/components/_menu.scss +129 -15
- package/assets/scss/components/_modals.scss +15 -3
- package/assets/scss/components/_notification.scss +11 -14
- package/assets/scss/components/_tooltip.scss +2 -9
- package/assets/scss/index.scss +0 -1
- package/assets/scss/layout/_gap.scss +7 -1
- package/assets/scss/layout/_layout.scss +0 -12
- package/assets/scss/layout/_pageShell.scss +102 -108
- package/assets/scss/mixin/_mx-form.scss +1 -3
- package/assets/scss/mixin/_mx-responsive.scss +2 -2
- package/assets/scss/mixin/_mx-semantic.scss +0 -60
- package/assets/scss/mixin/mixin.scss +0 -1
- package/assets/scss/patterns/_footer.scss +1 -0
- package/assets/scss/patterns/_header.scss +20 -18
- package/dist/css/index.css +325 -1161
- package/dist/css/index.css.map +1 -1
- package/dist/css/index.min.css +1 -1
- package/dist/index.d.ts +17 -2
- package/dist/javascript/index.js +985 -412
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/index.min.js +1 -1
- package/package.json +7 -2
- package/templates/components/{md-accordion.html → accordion.html} +2 -2
- package/templates/components/dropdown.html +91 -0
- package/templates/components/menu.html +41 -0
- package/templates/components/modal.html +78 -0
- package/templates/components/notification.html +35 -0
- package/templates/components/{md-slides.html → slides.html} +5 -5
- package/templates/components/{md-steps.html → steps.html} +7 -7
- package/templates/components/{md-tooltip.html → tooltip.html} +4 -2
- package/templates/demo.css +18 -0
- package/templates/demo.html +4 -5
- package/templates/demo.js +53 -52
- package/templates/layout/flex.html +89 -0
- package/templates/layout/grid.html +89 -0
- package/templates/layout/pageShell.html +59 -0
- package/templates/layout/section.html +40 -0
- package/templates/{layout/md-footer.html → patterns/footer.html} +1 -0
- package/templates/{layout/md-header.html → patterns/header.html} +6 -9
- package/assets/javascript/components/_exeNotifications.js +0 -85
- package/assets/javascript/functions/_notifications.js +0 -74
- package/assets/javascript/functions/_sendForm.js +0 -100
- package/assets/scss/base/_reset.scss +0 -15
- package/assets/scss/mixin/_mx-menu.scss +0 -154
- package/templates/components/md-dropdown.html +0 -16
- package/templates/components/md-menu.html +0 -40
- package/templates/components/md-modal.html +0 -19
- package/templates/components/md-notification.html +0 -13
- package/templates/layout/md-pageShell.html +0 -22
- package/templates/layout/md-section.html +0 -25
- /package/templates/components/{md-breadcumbs.html → breadcumbs.html} +0 -0
- /package/templates/components/{md-button.html → button.html} +0 -0
- /package/templates/components/{md-card.html → card.html} +0 -0
- /package/templates/components/{md-form.html → form.html} +0 -0
- /package/templates/components/{md-logo.html → logo.html} +0 -0
- /package/templates/components/{md-searchbar.html → searchbar.html} +0 -0
- /package/templates/components/{md-stepTabs.html → stepTabs.html} +0 -0
- /package/templates/components/{md-table.html → table.html} +0 -0
- /package/templates/layout/{md-body.html → body.html} +0 -0
- /package/templates/{layout/md-cookieConsent.html → patterns/cookieConsent.html} +0 -0
|
@@ -220,10 +220,61 @@ export function pgs(root) {
|
|
|
220
220
|
const api = createBasePgs();
|
|
221
221
|
api.state = createState("pgs-state");
|
|
222
222
|
api.option = createOption("pgs-option");
|
|
223
|
-
api.modules = createOption("pgs-modules");
|
|
224
223
|
return api;
|
|
225
224
|
}
|
|
226
225
|
|
|
227
|
-
|
|
226
|
+
const PGS_IMPORTS = {};
|
|
227
|
+
|
|
228
|
+
function registerImportModule(name, module) {
|
|
229
|
+
const key = String(name || "").trim().replace(/^pgs[_-\s]*/i, "").toLowerCase();
|
|
230
|
+
|
|
231
|
+
if (!key) throw new TypeError("pgs.registerImport(...modules): ogni modulo deve avere name o PGS_name");
|
|
232
|
+
|
|
233
|
+
PGS_IMPORTS[key] = {
|
|
234
|
+
name,
|
|
235
|
+
module
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
pgs.registerImport = function (...modules) {
|
|
240
|
+
modules.flat().forEach(item => {
|
|
241
|
+
if (item && typeof item === "object" && !item.PGS_name && !item.name) {
|
|
242
|
+
Object.entries(item).forEach(([name, module]) => registerImportModule(name, module));
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
registerImportModule(item?.PGS_name || item?.name, item);
|
|
247
|
+
});
|
|
228
248
|
|
|
249
|
+
return pgs;
|
|
250
|
+
};
|
|
229
251
|
|
|
252
|
+
pgs.registerModules = function (modules = {}) {
|
|
253
|
+
Object.entries(modules).forEach(([name, module]) => {
|
|
254
|
+
const key = String(name || "").trim();
|
|
255
|
+
if (!key) return;
|
|
256
|
+
|
|
257
|
+
const hasOwn = Object.prototype.hasOwnProperty.call(pgs, key);
|
|
258
|
+
if (hasOwn && pgs[key] !== module) {
|
|
259
|
+
throw new Error(`pgs.registerModules(): "${key}" e' gia' definito su pgs`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
pgs[key] = module;
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
return pgs;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
pgs.import = function (...names) {
|
|
269
|
+
return names.flat().reduce((imports, name) => {
|
|
270
|
+
const key = String(name || "").trim().replace(/^pgs[_-\s]*/i, "").toLowerCase();
|
|
271
|
+
const item = PGS_IMPORTS[key];
|
|
272
|
+
|
|
273
|
+
if (!item) throw new Error(`pgs.import(): modulo "${name}" non registrato`);
|
|
274
|
+
|
|
275
|
+
imports[item.name] = item.module;
|
|
276
|
+
return imports;
|
|
277
|
+
}, {});
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
globalThis.pgs ??= pgs;
|
|
@@ -1,77 +1,125 @@
|
|
|
1
|
-
//= ACCORDION
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
1
|
+
//= ACCORDION
|
|
2
|
+
const API = new WeakMap();
|
|
3
|
+
let accordionId = 0;
|
|
4
|
+
|
|
5
|
+
function nextAccordionId() {
|
|
6
|
+
accordionId += 1;
|
|
7
|
+
return accordionId;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function directPgsChild(element, token) {
|
|
11
|
+
return Array.from(element.children).find(child => pgs(child).contains(token));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function PGS_accordion_init(root = document) {
|
|
15
|
+
pgs(root).querySelectorAll("accordion").forEach((accordion) => {
|
|
16
|
+
if (API.has(accordion)) return;
|
|
17
|
+
|
|
18
|
+
const BUTTON = directPgsChild(accordion, "accordion-button");
|
|
19
|
+
const CONTENT = directPgsChild(accordion, "accordion-content");
|
|
20
|
+
if (!BUTTON || !CONTENT) return;
|
|
21
|
+
|
|
22
|
+
//== ID univoci per aria-controls / aria-labelledby
|
|
23
|
+
const ID = nextAccordionId();
|
|
24
|
+
const btnId = `acc-btn-${ID}`;
|
|
25
|
+
const panelId = `acc-panel-${ID}`;
|
|
26
|
+
|
|
27
|
+
//== Stato iniziale
|
|
28
|
+
const isOpenInit = pgs(accordion).state.contains("open");
|
|
29
|
+
|
|
30
|
+
//== Accessibilità (setup una volta)
|
|
31
|
+
BUTTON.setAttribute("role", "button");
|
|
32
|
+
BUTTON.setAttribute("tabindex", "0");
|
|
33
|
+
if (!BUTTON.id) BUTTON.setAttribute("id", btnId);
|
|
34
|
+
|
|
35
|
+
if (!CONTENT.id) CONTENT.setAttribute("id", panelId);
|
|
36
|
+
BUTTON.setAttribute("aria-controls", CONTENT.id);
|
|
37
|
+
CONTENT.setAttribute("role", "region");
|
|
38
|
+
CONTENT.setAttribute("aria-labelledby", BUTTON.id);
|
|
39
|
+
|
|
40
|
+
//+ Accessibility (applica stato aperto/chiuso)
|
|
41
|
+
function accordionAccessibility(isOpen, button, content) {
|
|
42
|
+
const text = (button?.textContent || "").trim().replace(/\s+/g, " ");
|
|
43
|
+
button.setAttribute("aria-label", `${isOpen ? "Chiudi" : "Apri"} ${text || "sezione"}`);
|
|
44
|
+
button.setAttribute("aria-expanded", String(isOpen));
|
|
45
|
+
content.hidden = !isOpen;
|
|
46
46
|
}
|
|
47
|
-
}
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
//+ Chiudi tutti gli altri
|
|
49
|
+
function closeOltherAccordion() {
|
|
50
|
+
for (const otherLi of pgs(document).querySelectorAll("accordion")) {
|
|
51
|
+
if (otherLi === accordion) continue;
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
const otherBtn = pgs(otherLi).querySelector("accordion-button");
|
|
54
|
+
const otherContent = pgs(otherLi).querySelector("accordion-content");
|
|
55
|
+
if (!otherBtn || !otherContent) continue;
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
pgs(otherLi).state().remove("open");
|
|
58
|
+
accordionAccessibility(false, otherBtn, otherContent);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
//+ FN ACCORDION
|
|
63
|
+
function accordionFunction() {
|
|
64
|
+
const isOpen = pgs(accordion).state.contains("open");
|
|
65
|
+
const nowOpen = !isOpen;
|
|
58
66
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
67
|
+
pgs(accordion).state.toggle("open", nowOpen);
|
|
68
|
+
accordionAccessibility(nowOpen, BUTTON, CONTENT);
|
|
62
69
|
|
|
63
|
-
|
|
64
|
-
|
|
70
|
+
closeOltherAccordion();
|
|
71
|
+
|
|
72
|
+
//== scroll to view
|
|
73
|
+
if (nowOpen) setTimeout(() => accordion.scrollIntoView({ block: "nearest", inline: "nearest" }), 100);
|
|
74
|
+
}
|
|
65
75
|
|
|
66
|
-
|
|
67
|
-
|
|
76
|
+
function open() {
|
|
77
|
+
if (!pgs(accordion).state.contains("open")) accordionFunction();
|
|
78
|
+
}
|
|
68
79
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
72
|
-
e.preventDefault();
|
|
73
|
-
accordionFunction();
|
|
80
|
+
function close() {
|
|
81
|
+
if (pgs(accordion).state.contains("open")) accordionFunction();
|
|
74
82
|
}
|
|
83
|
+
|
|
84
|
+
// applica stato iniziale
|
|
85
|
+
accordionAccessibility(isOpenInit, BUTTON, CONTENT);
|
|
86
|
+
|
|
87
|
+
//- Eventi
|
|
88
|
+
BUTTON.addEventListener("click", accordionFunction);
|
|
89
|
+
|
|
90
|
+
//- Tastiera: Enter / Space
|
|
91
|
+
BUTTON.addEventListener("keydown", (e) => {
|
|
92
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
accordionFunction();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
API.set(accordion, {
|
|
99
|
+
element: accordion,
|
|
100
|
+
button: BUTTON,
|
|
101
|
+
content: CONTENT,
|
|
102
|
+
open,
|
|
103
|
+
close,
|
|
104
|
+
toggle: accordionFunction,
|
|
105
|
+
refresh: () => {
|
|
106
|
+
PGS_accordion_init(accordion.parentNode || document);
|
|
107
|
+
return API.get(accordion);
|
|
108
|
+
},
|
|
109
|
+
isOpen: () => pgs(accordion).state.contains("open"),
|
|
110
|
+
});
|
|
75
111
|
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
//# INIT
|
|
115
|
+
PGS_accordion_init();
|
|
116
|
+
|
|
117
|
+
//# API
|
|
118
|
+
export function PGS_accordion_api(selector) {
|
|
119
|
+
return API.get(selector);
|
|
120
|
+
}
|
|
76
121
|
|
|
77
|
-
|
|
122
|
+
export const PGS_accordion = {
|
|
123
|
+
init: PGS_accordion_init,
|
|
124
|
+
api: PGS_accordion_api
|
|
125
|
+
};
|
|
@@ -1,108 +1,209 @@
|
|
|
1
|
-
// + dropdown
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if (!BUTTON || !CONTENT) return;
|
|
8
|
-
|
|
9
|
-
// = INIT
|
|
10
|
-
if (DROPDOWN.getAttribute("data-initialize") === "true") return;
|
|
11
|
-
DROPDOWN.setAttribute("data-initialize", "true");
|
|
12
|
-
|
|
13
|
-
// == CSS dropdown-anchor
|
|
14
|
-
const ANCHOR_NAME = `--dropdown-anchor-${index}`;
|
|
15
|
-
DROPDOWN.style.setProperty("--dropdown-anchor", ANCHOR_NAME);
|
|
16
|
-
|
|
17
|
-
// == IDs + ACCESSIBILITY
|
|
18
|
-
if (!BUTTON.id) BUTTON.id = `dropdown-btn-${index}`;
|
|
19
|
-
if (!CONTENT.id) CONTENT.id = `dropdown-panel-${index}`;
|
|
20
|
-
BUTTON.setAttribute("type", "button");
|
|
21
|
-
BUTTON.setAttribute("aria-haspopup", "true");
|
|
22
|
-
BUTTON.setAttribute("aria-controls", CONTENT.id);
|
|
23
|
-
BUTTON.setAttribute("aria-expanded", "false");
|
|
24
|
-
CONTENT.setAttribute("aria-labelledby", BUTTON.id);
|
|
25
|
-
|
|
26
|
-
// == POPVER SETUP
|
|
27
|
-
if (!CONTENT.hasAttribute("popover")) CONTENT.setAttribute("popover", "auto");
|
|
28
|
-
BUTTON.setAttribute("popovertarget", CONTENT.id);
|
|
29
|
-
BUTTON.setAttribute("popovertargetaction", "toggle");
|
|
30
|
-
|
|
31
|
-
//-( Safari / legacy fallback: popover is in the top layer, so fixed coords are viewport-based.
|
|
32
|
-
const HAS_ANCHOR_POSITIONING =
|
|
33
|
-
CSS.supports("anchor-name: --dropdown-anchor") &&
|
|
34
|
-
CSS.supports("position-anchor: --dropdown-anchor") &&
|
|
35
|
-
CSS.supports("position-area: bottom") &&
|
|
36
|
-
CSS.supports("top: anchor(bottom)");
|
|
37
|
-
const USE_FALLBACK_POSITIONING = pgs(DROPDOWN).contains("tooltip") || !HAS_ANCHOR_POSITIONING;
|
|
38
|
-
|
|
39
|
-
const updatePopoverPosition = () => {
|
|
40
|
-
if (!USE_FALLBACK_POSITIONING) return;
|
|
41
|
-
|
|
42
|
-
const buttonRect = BUTTON.getBoundingClientRect();
|
|
43
|
-
const style = getComputedStyle(DROPDOWN);
|
|
44
|
-
const offset = parseFloat(style.getPropertyValue("--dropdown-offset")) || 10;
|
|
45
|
-
const padding = parseFloat(style.getPropertyValue("--dropdown-padding")) || 0;
|
|
46
|
-
const arrowSize = parseFloat(style.getPropertyValue("--dropdown-arrow-size")) || 12;
|
|
47
|
-
const viewportGap = 8;
|
|
48
|
-
const viewportWidth = window.innerWidth;
|
|
49
|
-
const maxWidth = viewportWidth - viewportGap * 2;
|
|
50
|
-
const contentStyle = getComputedStyle(CONTENT);
|
|
51
|
-
const cssMaxWidth = parseFloat(contentStyle.maxWidth);
|
|
52
|
-
const dropdownMaxWidth = Number.isFinite(cssMaxWidth) ? Math.min(cssMaxWidth, maxWidth) : maxWidth;
|
|
53
|
-
const contentWidth = Math.min(
|
|
54
|
-
Math.max(CONTENT.scrollWidth + padding * 2, buttonRect.width),
|
|
55
|
-
dropdownMaxWidth
|
|
56
|
-
);
|
|
57
|
-
const top = buttonRect.bottom + offset;
|
|
58
|
-
const centeredLeft = buttonRect.left + buttonRect.width / 2 - contentWidth / 2;
|
|
59
|
-
const left = Math.min(
|
|
60
|
-
Math.max(centeredLeft, viewportGap),
|
|
61
|
-
viewportWidth - contentWidth - viewportGap
|
|
62
|
-
);
|
|
63
|
-
const buttonCenter = buttonRect.left + buttonRect.width / 2;
|
|
64
|
-
const arrowLeft = Math.min(
|
|
65
|
-
Math.max(buttonCenter - left, padding + arrowSize),
|
|
66
|
-
contentWidth - padding - arrowSize
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
DROPDOWN.style.setProperty("--dropdown-fallback-top", `${top}px`);
|
|
70
|
-
DROPDOWN.style.setProperty("--dropdown-fallback-left", `${left}px`);
|
|
71
|
-
DROPDOWN.style.setProperty("--dropdown-arrow-left", `${arrowLeft}px`);
|
|
72
|
-
};
|
|
1
|
+
// + dropdown
|
|
2
|
+
const API = new WeakMap();
|
|
3
|
+
const OPEN_DROPDOWNS = new Set();
|
|
4
|
+
const VIEWPORT_GAP = 8;
|
|
5
|
+
let dropdownId = 0;
|
|
73
6
|
|
|
74
|
-
|
|
75
|
-
|
|
7
|
+
function nextDropdownId() {
|
|
8
|
+
dropdownId += 1;
|
|
9
|
+
return dropdownId;
|
|
10
|
+
}
|
|
76
11
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
12
|
+
function isDropdownContent(element) {
|
|
13
|
+
return element instanceof Element && pgs(element).contains("dropdown-content");
|
|
14
|
+
}
|
|
82
15
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
16
|
+
function getDropdownTrigger(dropdown, content) {
|
|
17
|
+
const children = Array.from(dropdown.children).filter(child => child !== content);
|
|
18
|
+
const dropdownButton = children.find(child => pgs(child).contains("dropdown-button"));
|
|
86
19
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
20
|
+
return dropdownButton || children.find(child => !isDropdownContent(child)) || dropdown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getDropdownContent(dropdown) {
|
|
24
|
+
return Array.from(dropdown.children).find(isDropdownContent) || pgs(dropdown).querySelector("dropdown-content");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getDropdowns(root) {
|
|
28
|
+
const dropdowns = root instanceof Element && pgs(root).contains("dropdown") ? [root] : [];
|
|
29
|
+
dropdowns.push(...pgs(root).querySelectorAll("dropdown"));
|
|
30
|
+
return dropdowns;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getposition(dropdown) {
|
|
34
|
+
const option = pgs(dropdown).option;
|
|
35
|
+
const optionValue = option.getValueBrackets("position");
|
|
36
|
+
const raw = (optionValue || "bottom center").trim().toLowerCase();
|
|
37
|
+
const parts = raw.split(/\s+/).filter(Boolean);
|
|
38
|
+
const side = parts.find(part => ["top", "right", "bottom", "left"].includes(part)) || "bottom";
|
|
39
|
+
const align = parts.find(part => ["top", "right", "bottom", "left", "center"].includes(part) && part !== side) || "center";
|
|
40
|
+
|
|
41
|
+
return { side, align };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function clamp(value, min, max) {
|
|
45
|
+
return Math.min(Math.max(value, min), max);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function updateposition(dropdown) {
|
|
49
|
+
const data = API.get(dropdown);
|
|
50
|
+
if (!data || !data.isOpen()) return;
|
|
51
|
+
|
|
52
|
+
const { trigger, content } = data;
|
|
53
|
+
const { side, align } = getposition(dropdown);
|
|
54
|
+
const triggerRect = trigger.getBoundingClientRect();
|
|
55
|
+
const contentRect = content.getBoundingClientRect();
|
|
56
|
+
const viewportWidth = document.documentElement.clientWidth;
|
|
57
|
+
const maxLeft = Math.max(VIEWPORT_GAP, viewportWidth - contentRect.width - VIEWPORT_GAP);
|
|
58
|
+
let left = triggerRect.left + (triggerRect.width - contentRect.width) / 2;
|
|
59
|
+
let top = triggerRect.bottom + VIEWPORT_GAP;
|
|
60
|
+
|
|
61
|
+
if (side === "top" || side === "bottom") {
|
|
62
|
+
top = side === "top"
|
|
63
|
+
? triggerRect.top - contentRect.height - VIEWPORT_GAP
|
|
64
|
+
: triggerRect.bottom + VIEWPORT_GAP;
|
|
65
|
+
|
|
66
|
+
if (align === "left") left = triggerRect.left;
|
|
67
|
+
if (align === "right") left = triggerRect.right - contentRect.width;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (side === "left" || side === "right") {
|
|
71
|
+
left = side === "left"
|
|
72
|
+
? triggerRect.left - contentRect.width - VIEWPORT_GAP
|
|
73
|
+
: triggerRect.right + VIEWPORT_GAP;
|
|
74
|
+
top = triggerRect.top + (triggerRect.height - contentRect.height) / 2;
|
|
75
|
+
|
|
76
|
+
if (align === "top") top = triggerRect.top;
|
|
77
|
+
if (align === "bottom") top = triggerRect.bottom - contentRect.height;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (side === "left" && left < VIEWPORT_GAP) {
|
|
81
|
+
left = triggerRect.right + VIEWPORT_GAP;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (side === "right" && left + contentRect.width > viewportWidth - VIEWPORT_GAP) {
|
|
85
|
+
left = triggerRect.left - contentRect.width - VIEWPORT_GAP;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
left = clamp(left, VIEWPORT_GAP, maxLeft);
|
|
89
|
+
|
|
90
|
+
content.style.setProperty("--dropdown-left", `${Math.round(left)}px`);
|
|
91
|
+
content.style.setProperty("--dropdown-top", `${Math.round(top)}px`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function updateOpenDropdowns() {
|
|
95
|
+
OPEN_DROPDOWNS.forEach(updateposition);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function closeDropdown(dropdown) {
|
|
99
|
+
const data = API.get(dropdown);
|
|
100
|
+
if (!data || !data.isOpen()) return;
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
pgs(dropdown).state.remove("open");
|
|
103
|
+
data.trigger.setAttribute("aria-expanded", "false");
|
|
104
|
+
OPEN_DROPDOWNS.delete(dropdown);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function openDropdown(dropdown) {
|
|
108
|
+
const data = API.get(dropdown);
|
|
109
|
+
if (!data || data.isOpen()) return;
|
|
110
|
+
|
|
111
|
+
OPEN_DROPDOWNS.forEach(item => {
|
|
112
|
+
if (item !== dropdown) closeDropdown(item);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
pgs(dropdown).state.add("open");
|
|
116
|
+
data.trigger.setAttribute("aria-expanded", "true");
|
|
117
|
+
OPEN_DROPDOWNS.add(dropdown);
|
|
118
|
+
updateposition(dropdown);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function toggleDropdown(dropdown) {
|
|
122
|
+
const data = API.get(dropdown);
|
|
123
|
+
if (!data) return;
|
|
124
|
+
|
|
125
|
+
if (data.isOpen()) closeDropdown(dropdown);
|
|
126
|
+
else openDropdown(dropdown);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function isInsideAnyDropdown(target) {
|
|
130
|
+
return Array.from(OPEN_DROPDOWNS).some(dropdown => dropdown.contains(target));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function PGS_dropdown_init(root = document) {
|
|
134
|
+
getDropdowns(root).forEach((DROPDOWN) => {
|
|
135
|
+
if (API.has(DROPDOWN)) return;
|
|
136
|
+
|
|
137
|
+
const CONTENT = getDropdownContent(DROPDOWN);
|
|
138
|
+
if (!CONTENT) return;
|
|
139
|
+
|
|
140
|
+
const TRIGGER = getDropdownTrigger(DROPDOWN, CONTENT);
|
|
141
|
+
const id = nextDropdownId();
|
|
142
|
+
|
|
143
|
+
if (!TRIGGER.id) TRIGGER.id = `dropdown-btn-${id}`;
|
|
144
|
+
if (!CONTENT.id) CONTENT.id = `dropdown-panel-${id}`;
|
|
145
|
+
|
|
146
|
+
if (TRIGGER.matches("button") && !TRIGGER.hasAttribute("type")) {
|
|
147
|
+
TRIGGER.setAttribute("type", "button");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
TRIGGER.setAttribute("aria-haspopup", "true");
|
|
151
|
+
TRIGGER.setAttribute("aria-controls", CONTENT.id);
|
|
152
|
+
TRIGGER.setAttribute("aria-expanded", String(pgs(DROPDOWN).state.contains("open")));
|
|
153
|
+
CONTENT.setAttribute("aria-labelledby", TRIGGER.id);
|
|
154
|
+
|
|
155
|
+
const data = {
|
|
156
|
+
element: DROPDOWN,
|
|
157
|
+
trigger: TRIGGER,
|
|
158
|
+
content: CONTENT,
|
|
159
|
+
open: () => openDropdown(DROPDOWN),
|
|
160
|
+
close: () => closeDropdown(DROPDOWN),
|
|
161
|
+
toggle: () => toggleDropdown(DROPDOWN),
|
|
162
|
+
refresh: () => {
|
|
163
|
+
PGS_dropdown_init(DROPDOWN.parentNode || document);
|
|
164
|
+
updateposition(DROPDOWN);
|
|
165
|
+
return API.get(DROPDOWN);
|
|
166
|
+
},
|
|
167
|
+
isOpen: () => pgs(DROPDOWN).state.contains("open")
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
TRIGGER.addEventListener("click", (event) => {
|
|
171
|
+
if (isDropdownContent(event.target)) return;
|
|
172
|
+
event.preventDefault();
|
|
173
|
+
event.stopPropagation();
|
|
174
|
+
toggleDropdown(DROPDOWN);
|
|
100
175
|
});
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
176
|
+
|
|
177
|
+
CONTENT.addEventListener("click", event => event.stopPropagation());
|
|
178
|
+
API.set(DROPDOWN, data);
|
|
179
|
+
|
|
180
|
+
if (data.isOpen()) OPEN_DROPDOWNS.add(DROPDOWN);
|
|
181
|
+
updateposition(DROPDOWN);
|
|
104
182
|
});
|
|
105
183
|
}
|
|
106
184
|
|
|
185
|
+
document.addEventListener("click", (event) => {
|
|
186
|
+
if (isInsideAnyDropdown(event.target)) return;
|
|
187
|
+
OPEN_DROPDOWNS.forEach(closeDropdown);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
document.addEventListener("keydown", (event) => {
|
|
191
|
+
if (event.key !== "Escape") return;
|
|
192
|
+
OPEN_DROPDOWNS.forEach(closeDropdown);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
window.addEventListener("resize", updateOpenDropdowns);
|
|
196
|
+
window.addEventListener("scroll", updateOpenDropdowns, true);
|
|
197
|
+
|
|
107
198
|
// # INIT
|
|
108
|
-
|
|
199
|
+
PGS_dropdown_init();
|
|
200
|
+
|
|
201
|
+
// # API
|
|
202
|
+
function PGS_dropdown_api(selector) {
|
|
203
|
+
return API.get(selector);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export const PGS_dropdown = {
|
|
207
|
+
init: PGS_dropdown_init,
|
|
208
|
+
api: PGS_dropdown_api
|
|
209
|
+
};
|