mypgs 1.5.0 → 1.6.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 (48) hide show
  1. package/AGENTS.md +6 -3
  2. package/README.md +22 -373
  3. package/assets/javascript/_imports.js +4 -0
  4. package/assets/javascript/_pgs.js +58 -0
  5. package/assets/javascript/base/_darkmode.js +27 -105
  6. package/assets/javascript/base/_svg.js +103 -0
  7. package/assets/javascript/components/_menu.js +1 -3
  8. package/assets/javascript/components/_search.js +391 -0
  9. package/assets/javascript/index.js +2 -0
  10. package/assets/javascript/layout/_header.js +26 -7
  11. package/assets/scss/base/_color.scss +8 -15
  12. package/assets/scss/base/_general.scss +36 -48
  13. package/assets/scss/base/_heading.scss +14 -16
  14. package/assets/scss/base/_variables.scss +4 -2
  15. package/assets/scss/components/_card.scss +10 -0
  16. package/assets/scss/components/_dropdown.scss +2 -1
  17. package/assets/scss/components/_logo.scss +2 -2
  18. package/assets/scss/components/_menu.scss +20 -27
  19. package/assets/scss/components/_modals.scss +11 -1
  20. package/assets/scss/components/_search.scss +134 -0
  21. package/assets/scss/index.scss +8 -4
  22. package/assets/scss/layout/_header.scss +1 -1
  23. package/assets/scss/layout/_pageShell.scss +1 -0
  24. package/dist/css/index.css +658 -620
  25. package/dist/css/index.css.map +1 -1
  26. package/dist/css/index.min.css +1 -1
  27. package/dist/index.d.ts +78 -0
  28. package/dist/javascript/index.js +904 -370
  29. package/dist/javascript/index.js.map +1 -1
  30. package/dist/javascript/index.min.js +1 -1
  31. package/docs/componenti-e-markup.md +90 -0
  32. package/docs/convenzioni.md +12 -0
  33. package/docs/export-e-sviluppo.md +109 -0
  34. package/docs/helper-javascript.md +127 -0
  35. package/docs/utilizzo-css-scss.md +36 -0
  36. package/package.json +1 -6
  37. package/plugins/vite-plugin-pgs.d.ts +10 -0
  38. package/templates/html/components/logo.html +1 -1
  39. package/templates/html/components/menu.html +13 -1
  40. package/templates/html/components/{searchbar.html → search.html} +7 -5
  41. package/templates/html/demo.js +1 -8
  42. package/templates/html/layout/header.html +11 -5
  43. package/templates/react/components/logo.jsx +1 -1
  44. package/templates/react/components/{searchbar.jsx → search.jsx} +9 -7
  45. package/templates/react/patterns/header.jsx +16 -10
  46. package/assets/scss/components/_searchbar.scss +0 -70
  47. package/react.d.ts +0 -11
  48. package/react.js +0 -1
@@ -1,121 +1,43 @@
1
1
  //# DARKMODE
2
2
 
3
+ const EVENT_SVG_CHANGE_COLOR = "pgs:svg:changeColor";
3
4
 
4
- //= CHANGE COLOR SVG & LOTTIE
5
- //+ SEARCH COLOR
6
- function searchColor(type = "svg") {
7
- const ROOT = getComputedStyle(document.documentElement);
8
- const colors = []
9
- for (let I = 0; I < 20; I++) {
10
- const color = ROOT.getPropertyValue('--' + type + '-color-' + I).toLocaleLowerCase().split("&");
11
- if (!color[0] == "" && !color[1] == "") {
12
- let lightDark = [color[0], color[1]]
13
- colors.push(lightDark)
14
- }
15
- }
16
- return colors;
17
- }
18
- const colors_svg_lottie = [searchColor("svg"), searchColor("lottie")];
19
-
20
-
21
- //+ change COLORS
22
- function darkmodeColorSVG() {
23
- function changecolor(svgDoc, type = "svg") {
24
- let isDarkMode = (document.documentElement.getAttribute("data-darkmode") === "true") ? false : true;
25
-
26
- svgDoc.querySelectorAll('[fill], [stroke]').forEach(fillStroke => {
27
-
28
- for (const colors of colors_svg_lottie) {
29
- for (const color of colors) {
30
- let OLD = (color[0] || '').replace(/\s/g, '');
31
- let NEW = (color[1] || '').replace(/\s/g, '');
32
-
33
- ["fill", "stroke"].forEach(attr => {
34
- const current = fillStroke.getAttribute(attr);
35
-
36
- fillStroke.style.transition = "fill 0.5s ease, stroke 0.5s ease";
37
-
38
- if (!isDarkMode) {
39
- if (current == OLD) fillStroke.setAttribute(attr, NEW)
40
- } else {
41
- if (current == NEW) fillStroke.setAttribute(attr, OLD)
42
- }
43
- });
44
- }
45
- }
46
- });
47
- }
48
-
49
- //== OBJECTS
50
- const objects = document.querySelectorAll('object[type="image/svg+xml"]');
51
- objects.forEach(obj => {
52
-
53
- //=== ALL FILL / STROKE
54
- obj.addEventListener("load", () => {
55
- const svgDoc = obj.contentDocument;
56
- if (svgDoc) changecolor(svgDoc, "svg")
57
- });
58
-
59
- //=== In caso l'object sia già caricato
60
- if (obj.contentDocument) {
61
- const event = new Event("load");
62
- obj.dispatchEvent(event);
63
- }
64
- });
65
-
66
- //== LOTTIE
67
- const lottiePlayers = document.querySelectorAll('lottie-player');
68
- lottiePlayers.forEach(lottiePlayer => {
69
-
70
- //=== ALL FILL / STROKE
71
- lottiePlayer.addEventListener("load", () => {
72
- const svg = lottiePlayer.shadowRoot.querySelector('svg');
73
- if (svg) changecolor(svg, "lottie")
74
- });
5
+ //+ CHANGE ICON
6
+ function changeIcon(selector, isDarkMode) {
7
+ selector.forEach(button => {
8
+ const ICON = button.querySelector("i");
9
+ if (!ICON) return;
75
10
 
76
- //=== In caso lottie sia già caricato
77
- if (lottiePlayer.shadowRoot) {
78
- const event = new Event("load");
79
- lottiePlayer.dispatchEvent(event);
80
- }
11
+ ICON.classList.toggle("fa-moon", !isDarkMode);
12
+ ICON.classList.toggle("fa-sun", isDarkMode);
81
13
  });
82
14
  }
83
15
 
16
+ //+ SET STATUS
17
+ function setDarkmodeStatus(toggle = false, button) {
18
+ let isDarkMode = localStorage.getItem("screenIsDarkMode") === "true";
84
19
 
20
+ if (toggle) {
21
+ isDarkMode = !isDarkMode;
22
+ localStorage.setItem("screenIsDarkMode", isDarkMode);
23
+ }
85
24
 
86
- //= BUTTON DARKMODE
87
- //+ CHANGE ICON AND SVG
88
- let toggleDarkmode = pgs(document).querySelectorAll("toggleDarkmode");
89
- if (localStorage.getItem("screenIsDarkMode") === "true") {
90
- document.body.classList.add("darkmode");
91
- document.querySelector(":root").setAttribute("data-darkmode", "true");
92
- document.body.setAttribute("data-darkmode", "true");
93
- }
25
+ // SET
26
+ pgs(document.documentElement).state.toggle("darkmode", isDarkMode);
27
+ pgs(document.body).state.toggle("darkmode", isDarkMode);
28
+ // END SET
94
29
 
95
- function change(selector, isDarkMode) {
96
- selector.forEach(button => {
97
- const ICON = button.querySelector("i");
98
- if (!ICON) return;
99
- ICON.classList.toggle("fa-moon", !isDarkMode);
100
- ICON.classList.toggle("fa-sun", isDarkMode);
101
- });
102
- darkmodeColorSVG();
30
+ changeIcon(button, isDarkMode);
31
+ document.dispatchEvent(new CustomEvent(EVENT_SVG_CHANGE_COLOR, { detail: { isDarkMode } }));
103
32
  }
104
33
 
105
- toggleDarkmode.forEach(button => {
106
34
 
107
- //== EXECUTE IMMEDIATELY
108
- let isDarkMode = document.documentElement.getAttribute("data-darkmode") === "true";
109
- change(toggleDarkmode, isDarkMode);
110
35
 
111
- //== CLICK BUTTON
112
- button.addEventListener("click", () => {
113
- let isDarkMode = (document.documentElement.getAttribute("data-darkmode") === "true") ? false : true;
114
- localStorage.setItem("screenIsDarkMode", isDarkMode);
36
+ //= INIT
37
+ const toggleDarkmode = pgs(document).querySelectorAll("toggleDarkmode");
38
+ setDarkmodeStatus(false, toggleDarkmode);
115
39
 
116
- document.body.classList.toggle("darkmode", isDarkMode);
117
- document.body.setAttribute("data-darkmode", isDarkMode);
118
- document.querySelector(":root").setAttribute("data-darkmode", isDarkMode);
119
- change(toggleDarkmode, isDarkMode);
120
- });
40
+ //= BUTTON DARKMODE
41
+ toggleDarkmode.forEach(button => {
42
+ button.addEventListener("click", () => setDarkmodeStatus(true, toggleDarkmode));
121
43
  });
@@ -0,0 +1,103 @@
1
+ //# SVG & LOTTIE COLORS
2
+
3
+ const svgColors = {
4
+ eventChangeColor: "pgs:svg:changeColor",
5
+ watchedObjects: new WeakSet(),
6
+ watchedLotties: new WeakSet(),
7
+
8
+ _normalizeColor: (color = "") => {
9
+ return color.replace(/\s/g, "").toLocaleLowerCase();
10
+ },
11
+
12
+ _getCurrentDarkmode: () => {
13
+ return pgs(document.documentElement).state.contains("darkmode");
14
+ },
15
+
16
+ searchColor(type = "svg") {
17
+ const ROOT = getComputedStyle(document.documentElement);
18
+ const colors = [];
19
+
20
+ for (let I = 0; I < 20; I++) {
21
+ const color = ROOT.getPropertyValue("--" + type + "-color-" + I).toLocaleLowerCase().split("&").map(value => value.trim());
22
+ if (color[0] && color[1]) colors.push([color[0], color[1]]);
23
+ }
24
+
25
+ return colors;
26
+ },
27
+
28
+ _changeColor(svgDoc, isDarkMode, colors) {
29
+ if (!svgDoc) return;
30
+
31
+ svgDoc.querySelectorAll("[fill], [stroke]").forEach(fillStroke => {
32
+ for (const color of colors) {
33
+ const OLD = svgColors._normalizeColor(color[0]);
34
+ const NEW = svgColors._normalizeColor(color[1]);
35
+
36
+ ["fill", "stroke"].forEach(attr => {
37
+ const current = svgColors._normalizeColor(fillStroke.getAttribute(attr) || "");
38
+ if (!current) return;
39
+
40
+ fillStroke.style.transition = "fill 0.5s ease, stroke 0.5s ease";
41
+
42
+ if (isDarkMode && current === OLD) fillStroke.setAttribute(attr, NEW);
43
+ if (!isDarkMode && current === NEW) fillStroke.setAttribute(attr, OLD);
44
+ });
45
+ }
46
+ });
47
+ },
48
+
49
+ _getLottieSvg(lottiePlayer) {
50
+ return lottiePlayer.shadowRoot?.querySelector("svg") || null;
51
+ },
52
+
53
+ init() {
54
+ document.addEventListener(svgColors.eventChangeColor, event => {
55
+ svgColors.applyColorsSVG(event.detail?.isDarkMode ?? svgColors._getCurrentDarkmode());
56
+ svgColors.applyColorsLottie(event.detail?.isDarkMode ?? svgColors._getCurrentDarkmode());
57
+ });
58
+
59
+ document.addEventListener("DOMContentLoaded", () => {
60
+ svgColors.applyColorsSVG();
61
+ svgColors.applyColorsLottie();
62
+ });
63
+ },
64
+
65
+ applyColorsSVG(isDarkMode = svgColors._getCurrentDarkmode()) {
66
+ const colorsSvg = svgColors.searchColor("svg");
67
+
68
+ if (!pgs(document).querySelector("svgChangeColor")) return;
69
+
70
+ document.querySelectorAll('object[type="image/svg+xml"]').forEach(obj => {
71
+ if (!svgColors.watchedObjects.has(obj)) {
72
+ obj.addEventListener("load", () => svgColors._changeColor(obj.contentDocument, svgColors._getCurrentDarkmode(), svgColors.searchColor("svg")));
73
+ svgColors.watchedObjects.add(obj);
74
+ }
75
+
76
+ if (obj.contentDocument) svgColors._changeColor(obj.contentDocument, isDarkMode, colorsSvg);
77
+ });
78
+
79
+ },
80
+
81
+ applyColorsLottie(isDarkMode = svgColors._getCurrentDarkmode()) {
82
+ const colorsLottie = svgColors.searchColor("svg");
83
+
84
+ if (!pgs(document).querySelector("lottieChangeColor")) return;
85
+
86
+ document.querySelectorAll("lottie-player").forEach(lottiePlayer => {
87
+ if (!svgColors.watchedLotties.has(lottiePlayer)) {
88
+ lottiePlayer.addEventListener("load", () => svgColors._changeColor(svgColors._getLottieSvg(lottiePlayer), svgColors._getCurrentDarkmode(), svgColors.searchColor("svg")));
89
+ svgColors.watchedLotties.add(lottiePlayer);
90
+ }
91
+
92
+ if (lottiePlayer.shadowRoot) svgColors._changeColor(svgColors._getLottieSvg(lottiePlayer), isDarkMode, colorsLottie);
93
+ });
94
+ },
95
+ };
96
+
97
+ svgColors.init();
98
+
99
+ export const PGS_svg = {
100
+ eventChangeColor: svgColors.eventChangeColor,
101
+ applyColorsSVG: isDarkMode => svgColors.applyColorsSVG(isDarkMode),
102
+ applyColorsLottie: isDarkMode => svgColors.applyColorsLottie(isDarkMode),
103
+ };
@@ -20,6 +20,7 @@ function PGS_menu_init(root = document) {
20
20
  li.querySelector("a").insertAdjacentElement("afterend", button);
21
21
 
22
22
  pgs(li).add("dropdown")
23
+ pgs(li).option.setValueBrackets("position", "bottom right")
23
24
  pgs(button).add("dropdown-button")
24
25
  pgs(button).add("buttonNohover")
25
26
  pgs(ul).add("dropdown-content")
@@ -30,9 +31,6 @@ function PGS_menu_init(root = document) {
30
31
  API.set(MENU, {
31
32
  element: MENU,
32
33
  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
34
  refresh: () => {
37
35
  PGS_menu_init(MENU.parentNode || document);
38
36
  return API.get(MENU);
@@ -0,0 +1,391 @@
1
+ const API = new WeakMap();
2
+ const OPEN_SEARCHES = new Set();
3
+ let searchId = 0;
4
+
5
+ const DEFAULT_OPTIONS = {
6
+ minLength: 2,
7
+ debounce: 200,
8
+ limit: 8,
9
+ submitOnSelect: false,
10
+ searchOnFocus: true,
11
+ source: null,
12
+ onSelect: null,
13
+ };
14
+
15
+ function nextSearchId() {
16
+ searchId += 1;
17
+ return searchId;
18
+ }
19
+
20
+ function getSearches(root) {
21
+ const searches = root instanceof Element && pgs(root).contains("search") ? [root] : [];
22
+ searches.push(...pgs(root).querySelectorAll("search"));
23
+ return searches;
24
+ }
25
+
26
+ function directPgsChild(element, token) {
27
+ return Array.from(element.children).find(child => pgs(child).contains(token));
28
+ }
29
+
30
+ function normalizeItem(item) {
31
+ if (typeof item === "string" || typeof item === "number") {
32
+ const value = String(item).trim();
33
+ return value ? { label: value, value, disabled: false, data: item } : null;
34
+ }
35
+
36
+ if (!item || typeof item !== "object") return null;
37
+
38
+ const label = String(item.label ?? item.value ?? "").trim();
39
+ if (!label) return null;
40
+
41
+ return {
42
+ label,
43
+ value: String(item.value ?? label),
44
+ disabled: Boolean(item.disabled),
45
+ data: Object.prototype.hasOwnProperty.call(item, "data") ? item.data : item,
46
+ };
47
+ }
48
+
49
+ function normalizeOptions(current, options = {}) {
50
+ const next = { ...current, ...options };
51
+ next.minLength = Math.max(0, Number.parseInt(next.minLength, 10) || 0);
52
+ next.debounce = Math.max(0, Number.parseInt(next.debounce, 10) || 0);
53
+ next.limit = Math.max(1, Number.parseInt(next.limit, 10) || DEFAULT_OPTIONS.limit);
54
+ next.submitOnSelect = Boolean(next.submitOnSelect);
55
+ next.searchOnFocus = Boolean(next.searchOnFocus);
56
+ next.source = typeof next.source === "function" || Array.isArray(next.source) ? next.source : null;
57
+ next.onSelect = typeof next.onSelect === "function" ? next.onSelect : null;
58
+ return next;
59
+ }
60
+
61
+ function closeSearch(search) {
62
+ const data = API.get(search);
63
+ if (!data) return;
64
+
65
+ pgs(search).state.remove("open");
66
+ data.input.setAttribute("aria-expanded", "false");
67
+ data.input.removeAttribute("aria-activedescendant");
68
+ data.list.setAttribute("aria-hidden", "true");
69
+ data.setActiveIndex(-1);
70
+ OPEN_SEARCHES.delete(search);
71
+ }
72
+
73
+ function openSearch(search) {
74
+ const data = API.get(search);
75
+ if (!data || data.items().length === 0) return;
76
+
77
+ pgs(search).state.add("open");
78
+ data.input.setAttribute("aria-expanded", "true");
79
+ data.list.setAttribute("aria-hidden", "false");
80
+ OPEN_SEARCHES.add(search);
81
+ }
82
+
83
+ function PGS_search_init(root = document) {
84
+ getSearches(root).forEach(search => {
85
+ if (API.has(search)) return;
86
+
87
+ const input = search.querySelector('input[type="search"]');
88
+ const list = directPgsChild(search, "search-suggestions");
89
+ if (!input || !list) return;
90
+
91
+ const id = nextSearchId();
92
+ if (!input.id) input.id = `search-input-${id}`;
93
+ if (!list.id) list.id = `search-suggestions-${id}`;
94
+
95
+ input.setAttribute("role", "combobox");
96
+ input.setAttribute("aria-autocomplete", "list");
97
+ input.setAttribute("aria-haspopup", "listbox");
98
+ input.setAttribute("aria-controls", list.id);
99
+ input.setAttribute("aria-expanded", "false");
100
+ input.setAttribute("autocomplete", "off");
101
+ list.setAttribute("role", "listbox");
102
+ list.setAttribute("aria-labelledby", input.id);
103
+ list.setAttribute("aria-hidden", "true");
104
+
105
+
106
+ let options = { ...DEFAULT_OPTIONS };
107
+ let items = [];
108
+ let activeIndex = -1;
109
+ let timer = null;
110
+ let controller = null;
111
+ let requestNumber = 0;
112
+
113
+ function setLoading(loading) {
114
+ pgs(search).state.toggle("loading", loading);
115
+ input.setAttribute("aria-busy", String(loading));
116
+ }
117
+
118
+ function setActiveIndex(index) {
119
+ activeIndex = index;
120
+ const elements = Array.from(list.querySelectorAll('[pgs~="search-suggestions-item"]'));
121
+
122
+ elements.forEach((element, itemIndex) => {
123
+ const selected = itemIndex === activeIndex;
124
+ element.setAttribute("aria-selected", String(selected));
125
+ pgs(element).state.toggle("selected", selected);
126
+ });
127
+
128
+ const active = elements[activeIndex];
129
+ if (active) {
130
+ input.setAttribute("aria-activedescendant", active.id);
131
+ active.scrollIntoView({ block: "nearest" });
132
+ } else {
133
+ input.removeAttribute("aria-activedescendant");
134
+ }
135
+ }
136
+
137
+ function moveActive(step) {
138
+ if (!items.length) return;
139
+
140
+ let next = activeIndex;
141
+ for (let checked = 0; checked < items.length; checked += 1) {
142
+ next = (next + step + items.length) % items.length;
143
+ if (!items[next].disabled) {
144
+ setActiveIndex(next);
145
+ return;
146
+ }
147
+ }
148
+ }
149
+
150
+ function clear() {
151
+ items = [];
152
+ activeIndex = -1;
153
+ list.replaceChildren();
154
+ closeSearch(search);
155
+ }
156
+
157
+ function cancel() {
158
+ if (timer !== null) window.clearTimeout(timer);
159
+ timer = null;
160
+ if (controller) controller.abort();
161
+ controller = null;
162
+ requestNumber += 1;
163
+ setLoading(false);
164
+ }
165
+
166
+ function render(nextItems) {
167
+ items = Array.from(nextItems || [])
168
+ .map(normalizeItem)
169
+ .filter(Boolean)
170
+ .slice(0, options.limit);
171
+
172
+ const fragment = document.createDocumentFragment();
173
+ items.forEach((item, index) => {
174
+ const option = document.createElement("li");
175
+ pgs(option).add("search-suggestions-item");
176
+ pgs(option).add("flexRow");
177
+ option.id = `${list.id}-option-${index}`;
178
+ option.dataset.index = String(index);
179
+ option.setAttribute("role", "option");
180
+ option.setAttribute("aria-selected", "false");
181
+ option.setAttribute("aria-disabled", String(item.disabled));
182
+ option.innerHTML = '<i class="fa-solid fa-magnifying-glass"></i>' + item.label;
183
+ fragment.append(option);
184
+
185
+ });
186
+
187
+ activeIndex = -1;
188
+ list.replaceChildren(fragment);
189
+ pgs(search).state.remove("error");
190
+
191
+ if (items.length) openSearch(search);
192
+ else closeSearch(search);
193
+
194
+ return items;
195
+ }
196
+
197
+ async function resolveSource(query, signal) {
198
+ if (Array.isArray(options.source)) {
199
+ const normalizedQuery = query.toLocaleLowerCase();
200
+ return options.source.filter(item => {
201
+ const normalized = normalizeItem(item);
202
+ return normalized && normalized.label.toLocaleLowerCase().includes(normalizedQuery);
203
+ });
204
+ }
205
+
206
+ if (typeof options.source !== "function") return [];
207
+ return await options.source({
208
+ query,
209
+ signal,
210
+ limit: options.limit,
211
+ element: search,
212
+ input,
213
+ });
214
+ }
215
+
216
+ async function runSearch(query = input.value) {
217
+ cancel();
218
+ clear();
219
+
220
+ const normalizedQuery = String(query ?? "").trim();
221
+ if (normalizedQuery.length < options.minLength || !options.source) return [];
222
+
223
+ const currentRequest = requestNumber;
224
+ controller = new AbortController();
225
+ const currentController = controller;
226
+ setLoading(true);
227
+
228
+ try {
229
+ const result = await resolveSource(normalizedQuery, currentController.signal);
230
+ if (currentRequest !== requestNumber || currentController.signal.aborted) return [];
231
+ return render(result);
232
+ } catch (error) {
233
+ if (error?.name === "AbortError") return [];
234
+ if (currentRequest !== requestNumber) return [];
235
+
236
+ clear();
237
+ pgs(search).state.add("error");
238
+ search.dispatchEvent(new CustomEvent("pgs:search:error", {
239
+ bubbles: true,
240
+ detail: { error, query: normalizedQuery },
241
+ }));
242
+ return [];
243
+ } finally {
244
+ if (controller === currentController) controller = null;
245
+ if (currentRequest === requestNumber) setLoading(false);
246
+ }
247
+ }
248
+
249
+ function schedule() {
250
+ cancel();
251
+ clear();
252
+ pgs(search).state.remove("error");
253
+
254
+ if (input.value.trim().length < options.minLength || !options.source) return;
255
+ timer = window.setTimeout(() => {
256
+ timer = null;
257
+ runSearch(input.value);
258
+ }, options.debounce);
259
+ }
260
+
261
+ function select(index = activeIndex, submit = options.submitOnSelect) {
262
+ const item = items[index];
263
+ if (!item || item.disabled) return null;
264
+
265
+ input.value = item.value;
266
+ cancel();
267
+ clear();
268
+
269
+ const detail = { item, index, value: item.value, input, element: search };
270
+ search.dispatchEvent(new CustomEvent("pgs:search:select", { bubbles: true, detail }));
271
+ options.onSelect?.(detail);
272
+
273
+ input.focus();
274
+ if (submit && typeof search.requestSubmit === "function") search.requestSubmit();
275
+ return item;
276
+ }
277
+
278
+ function configure(nextOptions = {}) {
279
+ options = normalizeOptions(options, nextOptions);
280
+ return api;
281
+ }
282
+
283
+ function onInput() {
284
+ schedule();
285
+ }
286
+
287
+ function onFocus() {
288
+ if (items.length) openSearch(search);
289
+ else if (options.searchOnFocus) schedule();
290
+ }
291
+
292
+ function onKeydown(event) {
293
+ if (event.key === "ArrowDown") {
294
+ if (!pgs(search).state.contains("open")) schedule();
295
+ if (items.length) {
296
+ event.preventDefault();
297
+ moveActive(1);
298
+ }
299
+ return;
300
+ }
301
+
302
+ if (event.key === "ArrowUp" && items.length) {
303
+ event.preventDefault();
304
+ moveActive(-1);
305
+ return;
306
+ }
307
+
308
+ if (event.key === "Enter" && activeIndex >= 0) {
309
+ event.preventDefault();
310
+ select(activeIndex);
311
+ return;
312
+ }
313
+
314
+ if (event.key === "Escape") {
315
+ event.preventDefault();
316
+ cancel();
317
+ closeSearch(search);
318
+ return;
319
+ }
320
+
321
+ if (event.key === "Tab") closeSearch(search);
322
+ }
323
+
324
+ function onListPointerDown(event) {
325
+ const option = event.target.closest('[pgs~="search-suggestions-item"]');
326
+ if (!option || !list.contains(option)) return;
327
+ event.preventDefault();
328
+ select(Number.parseInt(option.dataset.index, 10));
329
+ }
330
+
331
+ function onSubmit() {
332
+ cancel();
333
+ closeSearch(search);
334
+ }
335
+
336
+ function destroy() {
337
+ cancel();
338
+ clear();
339
+ input.removeEventListener("input", onInput);
340
+ input.removeEventListener("focus", onFocus);
341
+ input.removeEventListener("keydown", onKeydown);
342
+ list.removeEventListener("pointerdown", onListPointerDown);
343
+ search.removeEventListener("submit", onSubmit);
344
+ API.delete(search);
345
+ }
346
+
347
+ const api = {
348
+ element: search,
349
+ input,
350
+ list,
351
+ configure,
352
+ setSource: source => configure({ source }),
353
+ search: runSearch,
354
+ open: () => openSearch(search),
355
+ close: () => closeSearch(search),
356
+ clear,
357
+ cancel,
358
+ select,
359
+ refresh: () => runSearch(input.value),
360
+ destroy,
361
+ items: () => [...items],
362
+ isOpen: () => pgs(search).state.contains("open"),
363
+ isLoading: () => pgs(search).state.contains("loading"),
364
+ setActiveIndex,
365
+ };
366
+
367
+ input.addEventListener("input", onInput);
368
+ input.addEventListener("focus", onFocus);
369
+ input.addEventListener("keydown", onKeydown);
370
+ list.addEventListener("pointerdown", onListPointerDown);
371
+ search.addEventListener("submit", onSubmit);
372
+ API.set(search, api);
373
+ });
374
+ }
375
+
376
+ document.addEventListener("pointerdown", event => {
377
+ OPEN_SEARCHES.forEach(search => {
378
+ if (!search.contains(event.target)) closeSearch(search);
379
+ });
380
+ });
381
+
382
+ PGS_search_init();
383
+
384
+ export function PGS_search_api(selector) {
385
+ return API.get(selector);
386
+ }
387
+
388
+ export const PGS_search = {
389
+ init: PGS_search_init,
390
+ api: PGS_search_api,
391
+ };
@@ -4,6 +4,7 @@ export { pgs } from "./_pgs.js";
4
4
 
5
5
  //= BASE
6
6
  import "./base/_darkmode.js";
7
+ import "./base/_svg.js";
7
8
  import "./base/_object.js";
8
9
 
9
10
  //= CN
@@ -11,6 +12,7 @@ import "./components/_accordion.js";
11
12
  import "./components/_dropdown.js";
12
13
  import "./components/_menu.js"
13
14
  import "./components/_modals.js";
15
+ import "./components/_search.js";
14
16
  import "./components/_slides.js"
15
17
  import "./components/_steps.js";
16
18
  import "./components/_stepTabs.js";