@pagefind/component-ui 1.5.0-alpha.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/README.md +66 -0
- package/components/base-element.ts +110 -0
- package/components/index.ts +31 -0
- package/components/instance-manager.ts +91 -0
- package/components/pagefind-config.ts +44 -0
- package/components/pagefind-filter-dropdown.ts +702 -0
- package/components/pagefind-filter-pane.ts +525 -0
- package/components/pagefind-input.ts +224 -0
- package/components/pagefind-keyboard-hints.ts +62 -0
- package/components/pagefind-modal-body.ts +19 -0
- package/components/pagefind-modal-footer.ts +16 -0
- package/components/pagefind-modal-header.ts +59 -0
- package/components/pagefind-modal-trigger.ts +195 -0
- package/components/pagefind-modal.ts +209 -0
- package/components/pagefind-results.ts +586 -0
- package/components/pagefind-searchbox.ts +888 -0
- package/components/pagefind-summary.ts +138 -0
- package/core/announcer.ts +134 -0
- package/core/focus-utils.ts +89 -0
- package/core/instance.ts +714 -0
- package/core/translations.ts +79 -0
- package/css/pagefind-component-ui.css +1448 -0
- package/npm_dist/cjs/component-ui.cjs +6285 -0
- package/npm_dist/cjs/instance.cjs +2849 -0
- package/npm_dist/mjs/component-ui.mjs +6268 -0
- package/npm_dist/mjs/instance.mjs +2826 -0
- package/package.json +48 -0
- package/types-entry.ts +27 -0
- package/types.ts +126 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { PagefindElement } from "./base-element";
|
|
2
|
+
import { Instance } from "../core/instance";
|
|
3
|
+
import type { PagefindError } from "../types";
|
|
4
|
+
|
|
5
|
+
const asyncSleep = (ms = 100): Promise<void> =>
|
|
6
|
+
new Promise((r) => setTimeout(r, ms));
|
|
7
|
+
|
|
8
|
+
export class PagefindInput extends PagefindElement {
|
|
9
|
+
static get observedAttributes(): string[] {
|
|
10
|
+
return ["placeholder", "debounce", "autofocus"];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
inputEl: HTMLInputElement | null = null;
|
|
14
|
+
clearEl: HTMLButtonElement | null = null;
|
|
15
|
+
searchID: number = 0;
|
|
16
|
+
|
|
17
|
+
placeholder: string = "";
|
|
18
|
+
debounce: number = 300;
|
|
19
|
+
autofocus: boolean = false;
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
super();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
readAttributes(): void {
|
|
26
|
+
if (this.hasAttribute("placeholder")) {
|
|
27
|
+
this.placeholder = this.getAttribute("placeholder") || "";
|
|
28
|
+
}
|
|
29
|
+
if (this.hasAttribute("debounce")) {
|
|
30
|
+
this.debounce =
|
|
31
|
+
parseInt(this.getAttribute("debounce") || "300", 10) || 300;
|
|
32
|
+
}
|
|
33
|
+
if (this.hasAttribute("autofocus")) {
|
|
34
|
+
this.autofocus = this.hasAttribute("autofocus");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
init(): void {
|
|
39
|
+
this.readAttributes();
|
|
40
|
+
this.render();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
render(): void {
|
|
44
|
+
this.innerHTML = "";
|
|
45
|
+
|
|
46
|
+
const inputId = this.instance!.generateId("pfmod-input");
|
|
47
|
+
|
|
48
|
+
const searchLabel =
|
|
49
|
+
this.instance?.translate("search_label") || "Search this site";
|
|
50
|
+
const clearText = this.instance?.translate("clear_search") || "Clear";
|
|
51
|
+
const placeholderText =
|
|
52
|
+
this.placeholder || this.instance?.translate("placeholder") || "Search";
|
|
53
|
+
|
|
54
|
+
if (this.instance?.direction === "rtl") {
|
|
55
|
+
this.setAttribute("dir", "rtl");
|
|
56
|
+
} else {
|
|
57
|
+
this.removeAttribute("dir");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const wrapper = document.createElement("form");
|
|
61
|
+
wrapper.className = "pf-input-wrapper";
|
|
62
|
+
wrapper.setAttribute("role", "search");
|
|
63
|
+
wrapper.setAttribute("aria-label", searchLabel);
|
|
64
|
+
wrapper.setAttribute("action", "javascript:void(0);");
|
|
65
|
+
|
|
66
|
+
const label = document.createElement("label");
|
|
67
|
+
label.setAttribute("for", inputId);
|
|
68
|
+
label.setAttribute("data-pf-sr-hidden", "true");
|
|
69
|
+
label.textContent = searchLabel;
|
|
70
|
+
wrapper.appendChild(label);
|
|
71
|
+
|
|
72
|
+
this.inputEl = document.createElement("input");
|
|
73
|
+
this.inputEl.id = inputId;
|
|
74
|
+
this.inputEl.className = "pf-input";
|
|
75
|
+
this.inputEl.setAttribute("autocapitalize", "none");
|
|
76
|
+
this.inputEl.setAttribute("enterkeyhint", "search");
|
|
77
|
+
this.inputEl.setAttribute("placeholder", placeholderText);
|
|
78
|
+
if (this.autofocus) {
|
|
79
|
+
this.inputEl.setAttribute("autofocus", "autofocus");
|
|
80
|
+
}
|
|
81
|
+
wrapper.appendChild(this.inputEl);
|
|
82
|
+
|
|
83
|
+
this.clearEl = document.createElement("button");
|
|
84
|
+
this.clearEl.className = "pf-input-clear";
|
|
85
|
+
this.clearEl.setAttribute("data-pf-suppressed", "true");
|
|
86
|
+
this.clearEl.textContent = clearText;
|
|
87
|
+
wrapper.appendChild(this.clearEl);
|
|
88
|
+
|
|
89
|
+
this.appendChild(wrapper);
|
|
90
|
+
|
|
91
|
+
this.setupEventHandlers();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
setupEventHandlers(): void {
|
|
95
|
+
if (!this.inputEl || !this.clearEl) return;
|
|
96
|
+
|
|
97
|
+
this.inputEl.addEventListener("input", async (e) => {
|
|
98
|
+
const target = e.target as HTMLInputElement;
|
|
99
|
+
if (this.instance && typeof target?.value === "string") {
|
|
100
|
+
this.updateState(target.value);
|
|
101
|
+
|
|
102
|
+
const thisSearchID = ++this.searchID;
|
|
103
|
+
await asyncSleep(this.debounce);
|
|
104
|
+
|
|
105
|
+
if (thisSearchID !== this.searchID) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.instance?.triggerSearch(target.value);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
this.inputEl.addEventListener("keydown", (e) => {
|
|
114
|
+
if (e.key === "Escape") {
|
|
115
|
+
++this.searchID;
|
|
116
|
+
if (this.inputEl) this.inputEl.value = "";
|
|
117
|
+
this.instance?.triggerSearch("");
|
|
118
|
+
this.updateState("");
|
|
119
|
+
}
|
|
120
|
+
if (e.key === "ArrowDown") {
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
if (this.inputEl) {
|
|
123
|
+
this.instance?.focusNextResults(this.inputEl);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
this.inputEl.addEventListener("focus", () => {
|
|
129
|
+
this.instance?.triggerLoad();
|
|
130
|
+
const navigateText =
|
|
131
|
+
this.instance?.translate("keyboard_navigate") || "navigate";
|
|
132
|
+
const clearText = this.instance?.translate("keyboard_clear") || "clear";
|
|
133
|
+
this.instance?.registerShortcut(
|
|
134
|
+
{ label: "↓", description: navigateText },
|
|
135
|
+
this,
|
|
136
|
+
);
|
|
137
|
+
this.instance?.registerShortcut(
|
|
138
|
+
{ label: "esc", description: clearText },
|
|
139
|
+
this,
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
this.inputEl.addEventListener("blur", () => {
|
|
144
|
+
this.instance?.deregisterAllShortcuts(this);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
this.clearEl.addEventListener("click", () => {
|
|
148
|
+
if (this.inputEl) {
|
|
149
|
+
this.inputEl.value = "";
|
|
150
|
+
this.instance?.triggerSearch("");
|
|
151
|
+
this.updateState("");
|
|
152
|
+
this.inputEl.focus();
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
updateState(term: string): void {
|
|
158
|
+
if (this.clearEl) {
|
|
159
|
+
if (term && term?.length) {
|
|
160
|
+
this.clearEl.removeAttribute("data-pf-suppressed");
|
|
161
|
+
} else {
|
|
162
|
+
this.clearEl.setAttribute("data-pf-suppressed", "true");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
register(instance: Instance): void {
|
|
168
|
+
instance.registerInput(this, {
|
|
169
|
+
keyboardNavigation: true,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
instance.on(
|
|
173
|
+
"search",
|
|
174
|
+
(term: unknown) => {
|
|
175
|
+
if (this.inputEl && document.activeElement !== this.inputEl) {
|
|
176
|
+
this.inputEl.value = term as string;
|
|
177
|
+
this.updateState(term as string);
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
this,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
instance.on(
|
|
184
|
+
"error",
|
|
185
|
+
(error: unknown) => {
|
|
186
|
+
const err = error as PagefindError;
|
|
187
|
+
this.showError({
|
|
188
|
+
message: err.message || "Search initialization failed",
|
|
189
|
+
details: err.bundlePath
|
|
190
|
+
? `Bundle path: ${err.bundlePath}`
|
|
191
|
+
: undefined,
|
|
192
|
+
});
|
|
193
|
+
},
|
|
194
|
+
this,
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
instance.on(
|
|
198
|
+
"translations",
|
|
199
|
+
() => {
|
|
200
|
+
const currentValue = this.inputEl?.value || "";
|
|
201
|
+
this.render();
|
|
202
|
+
if (this.inputEl && currentValue) {
|
|
203
|
+
this.inputEl.value = currentValue;
|
|
204
|
+
this.updateState(currentValue);
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
this,
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
update(): void {
|
|
212
|
+
this.render();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
focus(): void {
|
|
216
|
+
if (this.inputEl) {
|
|
217
|
+
this.inputEl.focus();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (!customElements.get("pagefind-input")) {
|
|
223
|
+
customElements.define("pagefind-input", PagefindInput);
|
|
224
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { PagefindElement } from "./base-element";
|
|
2
|
+
import { Instance } from "../core/instance";
|
|
3
|
+
|
|
4
|
+
export class PagefindKeyboardHints extends PagefindElement {
|
|
5
|
+
init(): void {
|
|
6
|
+
this.classList.add("pf-keyboard-hints");
|
|
7
|
+
// Keyboard hints are visual aids for sighted users, not meaningful for screen readers
|
|
8
|
+
this.setAttribute("aria-hidden", "true");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
render(): void {
|
|
12
|
+
this.innerHTML = "";
|
|
13
|
+
|
|
14
|
+
if (this.instance?.direction === "rtl") {
|
|
15
|
+
this.setAttribute("dir", "rtl");
|
|
16
|
+
} else {
|
|
17
|
+
this.removeAttribute("dir");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const shortcuts = this.instance?.getActiveShortcuts() || [];
|
|
21
|
+
|
|
22
|
+
if (shortcuts.length === 0) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Deduplicate by label
|
|
27
|
+
// for example, only show ↑↓ once even if multiple components have it
|
|
28
|
+
const seen = new Set<string>();
|
|
29
|
+
for (const shortcut of shortcuts) {
|
|
30
|
+
if (seen.has(shortcut.label)) continue;
|
|
31
|
+
seen.add(shortcut.label);
|
|
32
|
+
|
|
33
|
+
const hint = document.createElement("div");
|
|
34
|
+
hint.className = "pf-keyboard-hint";
|
|
35
|
+
|
|
36
|
+
const key = document.createElement("kbd");
|
|
37
|
+
key.className = "pf-keyboard-key";
|
|
38
|
+
key.textContent = shortcut.label;
|
|
39
|
+
hint.appendChild(key);
|
|
40
|
+
|
|
41
|
+
hint.appendChild(document.createTextNode(` ${shortcut.description}`));
|
|
42
|
+
this.appendChild(hint);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
register(instance: Instance): void {
|
|
47
|
+
instance.registerUtility(this, "keyboard-hints");
|
|
48
|
+
this.render();
|
|
49
|
+
|
|
50
|
+
instance.on(
|
|
51
|
+
"translations",
|
|
52
|
+
() => {
|
|
53
|
+
this.render();
|
|
54
|
+
},
|
|
55
|
+
this,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!customElements.get("pagefind-keyboard-hints")) {
|
|
61
|
+
customElements.define("pagefind-keyboard-hints", PagefindKeyboardHints);
|
|
62
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { PagefindElement } from "./base-element";
|
|
2
|
+
import { Instance } from "../core/instance";
|
|
3
|
+
|
|
4
|
+
export class PagefindModalBody extends PagefindElement {
|
|
5
|
+
init(): void {
|
|
6
|
+
this.classList.add("pf-modal-body");
|
|
7
|
+
// Prevent scrollable container from being in tab order,
|
|
8
|
+
// as all children should be interactable
|
|
9
|
+
this.setAttribute("tabindex", "-1");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
register(_instance: Instance): void {
|
|
13
|
+
/* structural - unregistered */
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!customElements.get("pagefind-modal-body")) {
|
|
18
|
+
customElements.define("pagefind-modal-body", PagefindModalBody);
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PagefindElement } from "./base-element";
|
|
2
|
+
import { Instance } from "../core/instance";
|
|
3
|
+
|
|
4
|
+
export class PagefindModalFooter extends PagefindElement {
|
|
5
|
+
init(): void {
|
|
6
|
+
this.classList.add("pf-modal-footer");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
register(_instance: Instance): void {
|
|
10
|
+
/* structural - unregistered */
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!customElements.get("pagefind-modal-footer")) {
|
|
15
|
+
customElements.define("pagefind-modal-footer", PagefindModalFooter);
|
|
16
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { PagefindElement } from "./base-element";
|
|
2
|
+
import { Instance } from "../core/instance";
|
|
3
|
+
|
|
4
|
+
interface ModalElement extends HTMLElement {
|
|
5
|
+
close?: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class PagefindModalHeader extends PagefindElement {
|
|
9
|
+
private closeBtn: HTMLButtonElement | null = null;
|
|
10
|
+
|
|
11
|
+
init(): void {
|
|
12
|
+
this.classList.add("pf-modal-header");
|
|
13
|
+
|
|
14
|
+
const content = document.createElement("div");
|
|
15
|
+
content.className = "pf-modal-header-content";
|
|
16
|
+
while (this.firstChild) {
|
|
17
|
+
content.appendChild(this.firstChild);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Create close button visible on mobile only
|
|
21
|
+
this.closeBtn = document.createElement("button");
|
|
22
|
+
this.closeBtn.type = "button";
|
|
23
|
+
this.closeBtn.className = "pf-modal-close";
|
|
24
|
+
this.closeBtn.setAttribute(
|
|
25
|
+
"aria-label",
|
|
26
|
+
this.instance?.translate("keyboard_close") || "Close",
|
|
27
|
+
);
|
|
28
|
+
this.closeBtn.innerHTML = `<svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M15 5L5 15M5 5l10 10"/></svg>`;
|
|
29
|
+
this.closeBtn.addEventListener("click", () => {
|
|
30
|
+
const modal = this.closest("pagefind-modal") as ModalElement | null;
|
|
31
|
+
if (modal && typeof modal.close === "function") {
|
|
32
|
+
modal.close();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
this.append(content, this.closeBtn);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
register(instance: Instance): void {
|
|
40
|
+
instance.registerUtility(this, "modal-header");
|
|
41
|
+
|
|
42
|
+
instance.on(
|
|
43
|
+
"translations",
|
|
44
|
+
() => {
|
|
45
|
+
if (this.closeBtn) {
|
|
46
|
+
this.closeBtn.setAttribute(
|
|
47
|
+
"aria-label",
|
|
48
|
+
instance.translate("keyboard_close") || "Close",
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
this,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!customElements.get("pagefind-modal-header")) {
|
|
58
|
+
customElements.define("pagefind-modal-header", PagefindModalHeader);
|
|
59
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { PagefindElement } from "./base-element";
|
|
2
|
+
import { Instance, PagefindComponent } from "../core/instance";
|
|
3
|
+
|
|
4
|
+
interface ModalComponent extends PagefindComponent {
|
|
5
|
+
dialogEl?: HTMLDialogElement;
|
|
6
|
+
open?: () => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface NavigatorUAData {
|
|
10
|
+
platform?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class PagefindModalTrigger extends PagefindElement {
|
|
14
|
+
static get observedAttributes(): string[] {
|
|
15
|
+
return ["placeholder", "shortcut", "hide-shortcut", "compact"];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
buttonEl: HTMLButtonElement | null = null;
|
|
19
|
+
private _userPlaceholder: string | null = null;
|
|
20
|
+
shortcut: string = "k";
|
|
21
|
+
hideShortcut: boolean = false;
|
|
22
|
+
compact: boolean = false;
|
|
23
|
+
isMac: boolean = false;
|
|
24
|
+
private _keydownHandler: ((e: KeyboardEvent) => void) | null = null;
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
super();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get placeholder(): string {
|
|
31
|
+
return (
|
|
32
|
+
this._userPlaceholder ||
|
|
33
|
+
this.instance?.translate("keyboard_search") ||
|
|
34
|
+
"Search"
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
init(): void {
|
|
39
|
+
this.isMac = this.detectMac();
|
|
40
|
+
this.readAttributes();
|
|
41
|
+
this.render();
|
|
42
|
+
this.setupKeyboardShortcut();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private detectMac(): boolean {
|
|
46
|
+
try {
|
|
47
|
+
const uaData = (
|
|
48
|
+
navigator as Navigator & { userAgentData?: NavigatorUAData }
|
|
49
|
+
).userAgentData;
|
|
50
|
+
if (uaData?.platform) {
|
|
51
|
+
return uaData.platform.toLowerCase().includes("mac");
|
|
52
|
+
}
|
|
53
|
+
} catch (e) {}
|
|
54
|
+
return /mac/i.test(navigator.userAgent);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private readAttributes(): void {
|
|
58
|
+
if (this.hasAttribute("placeholder")) {
|
|
59
|
+
this._userPlaceholder = this.getAttribute("placeholder");
|
|
60
|
+
}
|
|
61
|
+
if (this.hasAttribute("shortcut")) {
|
|
62
|
+
this.shortcut = (this.getAttribute("shortcut") || "k").toLowerCase();
|
|
63
|
+
}
|
|
64
|
+
if (this.hasAttribute("hide-shortcut")) {
|
|
65
|
+
this.hideShortcut = this.getAttribute("hide-shortcut") !== "false";
|
|
66
|
+
}
|
|
67
|
+
if (this.hasAttribute("compact")) {
|
|
68
|
+
this.compact = this.getAttribute("compact") !== "false";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
render(): void {
|
|
73
|
+
this.innerHTML = "";
|
|
74
|
+
|
|
75
|
+
if (this.instance?.direction === "rtl") {
|
|
76
|
+
this.setAttribute("dir", "rtl");
|
|
77
|
+
} else {
|
|
78
|
+
this.removeAttribute("dir");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.buttonEl = document.createElement("button");
|
|
82
|
+
this.buttonEl.className = "pf-trigger-btn";
|
|
83
|
+
this.buttonEl.type = "button";
|
|
84
|
+
this.buttonEl.setAttribute("aria-haspopup", "dialog");
|
|
85
|
+
this.buttonEl.setAttribute("aria-expanded", "false");
|
|
86
|
+
this.buttonEl.setAttribute("aria-label", this.placeholder || "Search");
|
|
87
|
+
|
|
88
|
+
const icon = document.createElement("span");
|
|
89
|
+
icon.className = "pf-trigger-icon";
|
|
90
|
+
icon.setAttribute("aria-hidden", "true");
|
|
91
|
+
this.buttonEl.appendChild(icon);
|
|
92
|
+
|
|
93
|
+
if (!this.compact) {
|
|
94
|
+
const text = document.createElement("span");
|
|
95
|
+
text.className = "pf-trigger-text";
|
|
96
|
+
text.textContent = this.placeholder;
|
|
97
|
+
this.buttonEl.appendChild(text);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!this.hideShortcut) {
|
|
101
|
+
const shortcutContainer = document.createElement("span");
|
|
102
|
+
shortcutContainer.className = "pf-trigger-shortcut";
|
|
103
|
+
shortcutContainer.setAttribute("aria-hidden", "true");
|
|
104
|
+
|
|
105
|
+
const modKey = document.createElement("span");
|
|
106
|
+
modKey.className = "pf-trigger-key";
|
|
107
|
+
modKey.textContent = this.isMac ? "\u2318" : "Ctrl";
|
|
108
|
+
shortcutContainer.appendChild(modKey);
|
|
109
|
+
|
|
110
|
+
const shortcutKey = document.createElement("span");
|
|
111
|
+
shortcutKey.className = "pf-trigger-key";
|
|
112
|
+
shortcutKey.textContent = this.shortcut.toUpperCase();
|
|
113
|
+
shortcutContainer.appendChild(shortcutKey);
|
|
114
|
+
|
|
115
|
+
this.buttonEl.appendChild(shortcutContainer);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.appendChild(this.buttonEl);
|
|
119
|
+
|
|
120
|
+
this.buttonEl.addEventListener("click", () => {
|
|
121
|
+
this.openModal();
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private setupKeyboardShortcut(): void {
|
|
126
|
+
this._keydownHandler = (e: KeyboardEvent) => {
|
|
127
|
+
const modifierPressed = this.isMac ? e.metaKey : e.ctrlKey;
|
|
128
|
+
const keyPressed = e.key.toLowerCase() === this.shortcut;
|
|
129
|
+
|
|
130
|
+
if (modifierPressed && keyPressed) {
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
this.openModal();
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
document.addEventListener("keydown", this._keydownHandler);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
openModal(): void {
|
|
140
|
+
const modals = (this.instance?.getUtilities("modal") ||
|
|
141
|
+
[]) as ModalComponent[];
|
|
142
|
+
const modal = modals[0];
|
|
143
|
+
|
|
144
|
+
if (modal && typeof modal.open === "function") {
|
|
145
|
+
modal.open();
|
|
146
|
+
if (this.buttonEl) {
|
|
147
|
+
this.buttonEl.setAttribute("aria-expanded", "true");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
handleModalClose(): void {
|
|
153
|
+
if (this.buttonEl) {
|
|
154
|
+
this.buttonEl.setAttribute("aria-expanded", "false");
|
|
155
|
+
this.buttonEl.focus();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
register(instance: Instance): void {
|
|
160
|
+
instance.registerUtility(this, "modal-trigger");
|
|
161
|
+
|
|
162
|
+
instance.on(
|
|
163
|
+
"translations",
|
|
164
|
+
() => {
|
|
165
|
+
this.render();
|
|
166
|
+
},
|
|
167
|
+
this,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
reconcileAria(): void {
|
|
172
|
+
const modals = (this.instance?.getUtilities("modal") ||
|
|
173
|
+
[]) as ModalComponent[];
|
|
174
|
+
const modal = modals[0];
|
|
175
|
+
if (modal?.dialogEl?.id && this.buttonEl) {
|
|
176
|
+
this.buttonEl.setAttribute("aria-controls", modal.dialogEl.id);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
cleanup(): void {
|
|
181
|
+
if (this._keydownHandler) {
|
|
182
|
+
document.removeEventListener("keydown", this._keydownHandler);
|
|
183
|
+
this._keydownHandler = null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
update(): void {
|
|
188
|
+
this.readAttributes();
|
|
189
|
+
this.render();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!customElements.get("pagefind-modal-trigger")) {
|
|
194
|
+
customElements.define("pagefind-modal-trigger", PagefindModalTrigger);
|
|
195
|
+
}
|