@pure-ds/core 0.6.1 → 0.6.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/dist/types/public/assets/js/pds-manager.d.ts.map +1 -1
- package/dist/types/public/assets/js/pds.d.ts.map +1 -1
- package/dist/types/public/assets/pds/components/pds-theme.d.ts +0 -10
- package/dist/types/public/assets/pds/components/pds-theme.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-enhancers.d.ts.map +1 -1
- package/package.json +1 -1
- package/public/assets/js/app.js +5 -5
- package/public/assets/js/pds-manager.js +55 -55
- package/public/assets/js/pds.js +5 -5
- package/public/assets/pds/components/pds-theme.js +1 -39
- package/src/js/pds-core/pds-enhancers.js +72 -41
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PDS Enhancers - Single Source of Truth
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* This file defines all progressive enhancements for the Pure Design System.
|
|
5
5
|
* Each enhancer has:
|
|
6
6
|
* - selector: CSS selector to target elements
|
|
@@ -30,24 +30,28 @@ function enhanceAccordion(elem) {
|
|
|
30
30
|
if (elem.dataset.enhancedAccordion) return;
|
|
31
31
|
elem.dataset.enhancedAccordion = "true";
|
|
32
32
|
|
|
33
|
-
elem.addEventListener(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
33
|
+
elem.addEventListener(
|
|
34
|
+
"toggle",
|
|
35
|
+
(event) => {
|
|
36
|
+
// Only handle toggle events from direct child details elements
|
|
37
|
+
// to avoid closing parent details when nested accordions are used
|
|
38
|
+
if (event.target.open && event.target.parentElement === elem) {
|
|
39
|
+
elem.querySelectorAll(":scope > details[open]").forEach((details) => {
|
|
40
|
+
if (details !== event.target) {
|
|
41
|
+
details.open = false;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
true,
|
|
47
|
+
);
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
function enhanceDropdown(elem) {
|
|
47
51
|
if (elem.dataset.enhancedDropdown) return;
|
|
48
52
|
elem.dataset.enhancedDropdown = "true";
|
|
49
|
-
const menu = elem.lastElementChild
|
|
50
|
-
|
|
53
|
+
const menu = elem.lastElementChild;
|
|
54
|
+
|
|
51
55
|
if (!menu) return;
|
|
52
56
|
|
|
53
57
|
const trigger =
|
|
@@ -90,7 +94,7 @@ function enhanceDropdown(elem) {
|
|
|
90
94
|
menu?.offsetHeight || 0,
|
|
91
95
|
menu?.scrollHeight || 0,
|
|
92
96
|
menuRect.height || 0,
|
|
93
|
-
200
|
|
97
|
+
200,
|
|
94
98
|
);
|
|
95
99
|
const spaceBelow = Math.max(0, window.innerHeight - rect.bottom);
|
|
96
100
|
const spaceAbove = Math.max(0, rect.top);
|
|
@@ -105,7 +109,12 @@ function enhanceDropdown(elem) {
|
|
|
105
109
|
elem.getAttribute("data-dropdown-align") ||
|
|
106
110
|
"auto"
|
|
107
111
|
).toLowerCase();
|
|
108
|
-
if (
|
|
112
|
+
if (
|
|
113
|
+
align === "left" ||
|
|
114
|
+
align === "right" ||
|
|
115
|
+
align === "start" ||
|
|
116
|
+
align === "end"
|
|
117
|
+
) {
|
|
109
118
|
return align === "start" ? "left" : align === "end" ? "right" : align;
|
|
110
119
|
}
|
|
111
120
|
const rect = elem.getBoundingClientRect();
|
|
@@ -114,7 +123,7 @@ function enhanceDropdown(elem) {
|
|
|
114
123
|
menu?.offsetWidth || 0,
|
|
115
124
|
menu?.scrollWidth || 0,
|
|
116
125
|
menuRect.width || 0,
|
|
117
|
-
240
|
|
126
|
+
240,
|
|
118
127
|
);
|
|
119
128
|
const spaceRight = Math.max(0, window.innerWidth - rect.left);
|
|
120
129
|
const spaceLeft = Math.max(0, rect.right);
|
|
@@ -123,16 +132,42 @@ function enhanceDropdown(elem) {
|
|
|
123
132
|
return spaceLeft > spaceRight ? "right" : "left";
|
|
124
133
|
};
|
|
125
134
|
|
|
135
|
+
// Store click handler reference for cleanup
|
|
136
|
+
let clickHandler = null;
|
|
137
|
+
|
|
126
138
|
const openMenu = () => {
|
|
127
139
|
elem.dataset.dropdownDirection = resolveDirection();
|
|
128
140
|
elem.dataset.dropdownAlign = resolveAlign();
|
|
129
141
|
menu.setAttribute("aria-hidden", "false");
|
|
130
142
|
trigger?.setAttribute("aria-expanded", "true");
|
|
143
|
+
|
|
144
|
+
// Add click-outside handler when opening
|
|
145
|
+
if (!clickHandler) {
|
|
146
|
+
clickHandler = (event) => {
|
|
147
|
+
// Use composedPath() to handle Shadow DOM
|
|
148
|
+
const path = event.composedPath ? event.composedPath() : [event.target];
|
|
149
|
+
const clickedInside = path.some((node) => node === elem);
|
|
150
|
+
|
|
151
|
+
if (!clickedInside) {
|
|
152
|
+
closeMenu();
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
// Use a slight delay to avoid closing immediately if this was triggered by a click
|
|
156
|
+
setTimeout(() => {
|
|
157
|
+
document.addEventListener("click", clickHandler);
|
|
158
|
+
}, 0);
|
|
159
|
+
}
|
|
131
160
|
};
|
|
132
161
|
|
|
133
162
|
const closeMenu = () => {
|
|
134
163
|
menu.setAttribute("aria-hidden", "true");
|
|
135
164
|
trigger?.setAttribute("aria-expanded", "false");
|
|
165
|
+
|
|
166
|
+
// Remove click-outside handler when closing
|
|
167
|
+
if (clickHandler) {
|
|
168
|
+
document.removeEventListener("click", clickHandler);
|
|
169
|
+
clickHandler = null;
|
|
170
|
+
}
|
|
136
171
|
};
|
|
137
172
|
|
|
138
173
|
const toggleMenu = () => {
|
|
@@ -149,12 +184,6 @@ function enhanceDropdown(elem) {
|
|
|
149
184
|
toggleMenu();
|
|
150
185
|
});
|
|
151
186
|
|
|
152
|
-
document.addEventListener("click", (event) => {
|
|
153
|
-
if (!elem.contains(event.target)) {
|
|
154
|
-
closeMenu();
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
|
|
158
187
|
elem.addEventListener("keydown", (event) => {
|
|
159
188
|
if (event.key === "Escape") {
|
|
160
189
|
closeMenu();
|
|
@@ -163,8 +192,15 @@ function enhanceDropdown(elem) {
|
|
|
163
192
|
});
|
|
164
193
|
|
|
165
194
|
elem.addEventListener("focusout", (event) => {
|
|
166
|
-
if
|
|
167
|
-
|
|
195
|
+
// Only close if focus is explicitly moving to an element outside the dropdown
|
|
196
|
+
// Don't close if relatedTarget is null (which happens when clicking non-focusable elements inside)
|
|
197
|
+
// Use composedPath() to handle Shadow DOM properly
|
|
198
|
+
if (event.relatedTarget) {
|
|
199
|
+
const path = event.composedPath ? event.composedPath() : [event.relatedTarget];
|
|
200
|
+
const focusedInside = path.some((node) => node === elem);
|
|
201
|
+
if (!focusedInside) {
|
|
202
|
+
closeMenu();
|
|
203
|
+
}
|
|
168
204
|
}
|
|
169
205
|
});
|
|
170
206
|
}
|
|
@@ -298,20 +334,18 @@ function enhanceRange(elem) {
|
|
|
298
334
|
}
|
|
299
335
|
|
|
300
336
|
function enhanceRequired(elem) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
337
|
+
if (elem.dataset.enhancedRequired) return;
|
|
338
|
+
elem.dataset.enhancedRequired = "true";
|
|
339
|
+
|
|
305
340
|
const enhanceRequiredField = (input) => {
|
|
306
341
|
let label;
|
|
307
|
-
if(input.closest("[role$=group]")) {
|
|
342
|
+
if (input.closest("[role$=group]")) {
|
|
343
|
+
// Handles both radiogroup and group
|
|
308
344
|
label = input.closest("[role$=group]").querySelector("legend");
|
|
309
|
-
}
|
|
310
|
-
else{
|
|
345
|
+
} else {
|
|
311
346
|
label = input.closest("label");
|
|
312
347
|
}
|
|
313
348
|
if (!label) return;
|
|
314
|
-
|
|
315
349
|
|
|
316
350
|
if (label.querySelector(".required-asterisk")) return;
|
|
317
351
|
|
|
@@ -339,15 +373,14 @@ function enhanceRequired(elem) {
|
|
|
339
373
|
legend.textContent = "* Required fields";
|
|
340
374
|
form.insertBefore(
|
|
341
375
|
legend,
|
|
342
|
-
form.querySelector(".form-actions") || form.lastElementChild
|
|
376
|
+
form.querySelector(".form-actions") || form.lastElementChild,
|
|
343
377
|
);
|
|
344
378
|
}
|
|
345
|
-
}
|
|
379
|
+
};
|
|
346
380
|
|
|
347
381
|
elem.querySelectorAll("[required]").forEach((input) => {
|
|
348
382
|
enhanceRequiredField(input);
|
|
349
383
|
});
|
|
350
|
-
|
|
351
384
|
}
|
|
352
385
|
|
|
353
386
|
function enhanceOpenGroup(elem) {
|
|
@@ -362,7 +395,7 @@ function enhanceOpenGroup(elem) {
|
|
|
362
395
|
addInput.classList.add("input-text", "input-sm");
|
|
363
396
|
addInput.style.width = "auto";
|
|
364
397
|
const firstInput = elem.querySelector(
|
|
365
|
-
'input[type="radio"], input[type="checkbox"]'
|
|
398
|
+
'input[type="radio"], input[type="checkbox"]',
|
|
366
399
|
);
|
|
367
400
|
|
|
368
401
|
elem.appendChild(addInput);
|
|
@@ -373,9 +406,7 @@ function enhanceOpenGroup(elem) {
|
|
|
373
406
|
event.preventDefault();
|
|
374
407
|
|
|
375
408
|
const type = firstInput.type === "radio" ? "radio" : "checkbox";
|
|
376
|
-
const id = `open-group-${Math.random()
|
|
377
|
-
.toString(36)
|
|
378
|
-
.substring(2, 11)}`;
|
|
409
|
+
const id = `open-group-${Math.random().toString(36).substring(2, 11)}`;
|
|
379
410
|
const label = document.createElement("label");
|
|
380
411
|
|
|
381
412
|
const span = document.createElement("span");
|
|
@@ -510,7 +541,7 @@ const enhancerRunners = new Map([
|
|
|
510
541
|
/**
|
|
511
542
|
* Complete enhancers with runtime functions.
|
|
512
543
|
* Used by PDS.enhancer() and AutoDefiner at runtime.
|
|
513
|
-
*
|
|
544
|
+
*
|
|
514
545
|
* This is the canonical runtime array of enhancer objects.
|
|
515
546
|
*/
|
|
516
547
|
export const defaultPDSEnhancers = enhancerDefinitions.map((meta) => ({
|