@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
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @pagefind/component-ui
|
|
2
|
+
|
|
3
|
+
Modular web components for Pagefind search.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Via npm
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @pagefind/component-ui
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Import the components and styles:
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
import '@pagefind/component-ui';
|
|
17
|
+
import '@pagefind/component-ui/css';
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
If your bundler doesn't support the `/css` export, import the CSS file directly:
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
import '@pagefind/component-ui/css/pagefind-component-ui.css';
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Via Pagefind's bundled files
|
|
27
|
+
|
|
28
|
+
After running Pagefind, include the generated files from your output directory:
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<link href="/pagefind/pagefind-component-ui.css" rel="stylesheet">
|
|
32
|
+
<script src="/pagefind/pagefind-component-ui.js" type="module"></script>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
Add a modal search to your site:
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<pagefind-modal-trigger></pagefind-modal-trigger>
|
|
41
|
+
<pagefind-modal></pagefind-modal>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or use a searchbox dropdown:
|
|
45
|
+
|
|
46
|
+
```html
|
|
47
|
+
<pagefind-searchbox></pagefind-searchbox>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or build your own layout with individual components:
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<pagefind-input></pagefind-input>
|
|
54
|
+
<pagefind-summary></pagefind-summary>
|
|
55
|
+
<pagefind-results></pagefind-results>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Documentation
|
|
59
|
+
|
|
60
|
+
For full documentation, component references, styling guides, and examples, visit:
|
|
61
|
+
|
|
62
|
+
**https://ui.pagefind.app/**
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { getInstanceManager } from "./instance-manager";
|
|
2
|
+
import { Instance } from "../core/instance";
|
|
3
|
+
|
|
4
|
+
export interface ErrorInfo {
|
|
5
|
+
message: string;
|
|
6
|
+
details?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Base class for all Pagefind web components
|
|
11
|
+
*/
|
|
12
|
+
export class PagefindElement extends HTMLElement {
|
|
13
|
+
instance: Instance | null = null;
|
|
14
|
+
protected _initialized: boolean = false;
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
connectedCallback(): void {
|
|
21
|
+
if (this._initialized) return;
|
|
22
|
+
this._initialized = true;
|
|
23
|
+
|
|
24
|
+
const instanceName = this.getAttribute("instance") || "default";
|
|
25
|
+
const manager = getInstanceManager();
|
|
26
|
+
this.instance = manager.getInstance(instanceName);
|
|
27
|
+
|
|
28
|
+
this.init();
|
|
29
|
+
|
|
30
|
+
if (this.register && typeof this.register === "function") {
|
|
31
|
+
this.register(this.instance);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
disconnectedCallback(): void {
|
|
36
|
+
if (this.cleanup && typeof this.cleanup === "function") {
|
|
37
|
+
this.cleanup();
|
|
38
|
+
}
|
|
39
|
+
this._initialized = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
attributeChangedCallback(
|
|
43
|
+
name: string,
|
|
44
|
+
oldValue: string | null,
|
|
45
|
+
newValue: string | null,
|
|
46
|
+
): void {
|
|
47
|
+
if (!this._initialized || oldValue === newValue) return;
|
|
48
|
+
|
|
49
|
+
const prop = this.kebabToCamel(name);
|
|
50
|
+
|
|
51
|
+
if (newValue === "false") {
|
|
52
|
+
(this as Record<string, unknown>)[prop] = false;
|
|
53
|
+
} else if (newValue === "true") {
|
|
54
|
+
(this as Record<string, unknown>)[prop] = true;
|
|
55
|
+
} else if (newValue === null || newValue === undefined) {
|
|
56
|
+
(this as Record<string, unknown>)[prop] = false;
|
|
57
|
+
} else {
|
|
58
|
+
(this as Record<string, unknown>)[prop] = newValue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (this.update && typeof this.update === "function") {
|
|
62
|
+
this.update();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
protected kebabToCamel(str: string): string {
|
|
67
|
+
return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
protected ensureId(prefix = "pagefind"): string {
|
|
71
|
+
if (!this.id && this.instance) {
|
|
72
|
+
this.id = this.instance.generateId(prefix);
|
|
73
|
+
}
|
|
74
|
+
return this.id;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
init(): void {}
|
|
78
|
+
|
|
79
|
+
reconcileAria(): void {}
|
|
80
|
+
|
|
81
|
+
register(_instance: Instance): void {}
|
|
82
|
+
|
|
83
|
+
cleanup(): void {}
|
|
84
|
+
|
|
85
|
+
update(): void {}
|
|
86
|
+
|
|
87
|
+
showError(error: ErrorInfo): void {
|
|
88
|
+
const errorEl = document.createElement("div");
|
|
89
|
+
errorEl.className = "pf-error";
|
|
90
|
+
errorEl.innerHTML = `
|
|
91
|
+
<strong>Pagefind Error:</strong> ${this.escapeHtml(error.message || "Unknown error")}
|
|
92
|
+
${error.details ? `<br><small>${this.escapeHtml(error.details)}</small>` : ""}
|
|
93
|
+
`;
|
|
94
|
+
this.appendChild(errorEl);
|
|
95
|
+
|
|
96
|
+
this.dispatchEvent(
|
|
97
|
+
new CustomEvent("pagefind-error", {
|
|
98
|
+
detail: error,
|
|
99
|
+
bubbles: true,
|
|
100
|
+
composed: true,
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
protected escapeHtml(text: string): string {
|
|
106
|
+
const div = document.createElement("div");
|
|
107
|
+
div.textContent = text;
|
|
108
|
+
return div.innerHTML;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export { getInstanceManager, configureInstance } from "./instance-manager";
|
|
2
|
+
|
|
3
|
+
import "./pagefind-config";
|
|
4
|
+
import "./pagefind-input";
|
|
5
|
+
import "./pagefind-summary";
|
|
6
|
+
import "./pagefind-results";
|
|
7
|
+
import "./pagefind-filter-pane";
|
|
8
|
+
import "./pagefind-filter-dropdown";
|
|
9
|
+
import "./pagefind-modal";
|
|
10
|
+
import "./pagefind-modal-trigger";
|
|
11
|
+
import "./pagefind-modal-header";
|
|
12
|
+
import "./pagefind-modal-body";
|
|
13
|
+
import "./pagefind-modal-footer";
|
|
14
|
+
import "./pagefind-keyboard-hints";
|
|
15
|
+
import "./pagefind-searchbox";
|
|
16
|
+
|
|
17
|
+
export { PagefindConfig } from "./pagefind-config";
|
|
18
|
+
export { PagefindInput } from "./pagefind-input";
|
|
19
|
+
export { PagefindSummary } from "./pagefind-summary";
|
|
20
|
+
export { PagefindResults } from "./pagefind-results";
|
|
21
|
+
export { PagefindFilterPane } from "./pagefind-filter-pane";
|
|
22
|
+
export { PagefindFilterDropdown } from "./pagefind-filter-dropdown";
|
|
23
|
+
export { PagefindModal } from "./pagefind-modal";
|
|
24
|
+
export { PagefindModalTrigger } from "./pagefind-modal-trigger";
|
|
25
|
+
export { PagefindModalHeader } from "./pagefind-modal-header";
|
|
26
|
+
export { PagefindModalBody } from "./pagefind-modal-body";
|
|
27
|
+
export { PagefindModalFooter } from "./pagefind-modal-footer";
|
|
28
|
+
export { PagefindKeyboardHints } from "./pagefind-keyboard-hints";
|
|
29
|
+
export { PagefindSearchbox } from "./pagefind-searchbox";
|
|
30
|
+
|
|
31
|
+
export { PagefindElement } from "./base-element";
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Instance } from "../core/instance";
|
|
2
|
+
import type { InstanceOptions } from "../types";
|
|
3
|
+
|
|
4
|
+
class InstanceManager {
|
|
5
|
+
private instances: Map<string, Instance> = new Map();
|
|
6
|
+
private defaultOptions: InstanceOptions;
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
this.defaultOptions = {
|
|
10
|
+
bundlePath: this.detectBundlePath(),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
private detectBundlePath(): string {
|
|
15
|
+
try {
|
|
16
|
+
// Important: Check that the element is indeed a <script> node, to avoid a DOM clobbering vulnerability
|
|
17
|
+
if (
|
|
18
|
+
document?.currentScript &&
|
|
19
|
+
document.currentScript.tagName.toUpperCase() === "SCRIPT"
|
|
20
|
+
) {
|
|
21
|
+
const scriptPath = new URL(
|
|
22
|
+
(document.currentScript as HTMLScriptElement).src,
|
|
23
|
+
).pathname.match(/^(.*\/)(?:pagefind[-_])?.*\.js.*$/);
|
|
24
|
+
if (scriptPath) {
|
|
25
|
+
return scriptPath[1];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
} catch (e) {}
|
|
29
|
+
return "/pagefind/";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getInstance(
|
|
33
|
+
name: string = "default",
|
|
34
|
+
options: InstanceOptions = {},
|
|
35
|
+
): Instance {
|
|
36
|
+
const existing = this.instances.get(name);
|
|
37
|
+
if (existing) {
|
|
38
|
+
return existing;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const instanceOptions: InstanceOptions = {
|
|
42
|
+
...this.defaultOptions,
|
|
43
|
+
...options,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const instance = new Instance(name, instanceOptions);
|
|
47
|
+
this.instances.set(name, instance);
|
|
48
|
+
|
|
49
|
+
return instance;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
hasInstance(name: string): boolean {
|
|
53
|
+
return this.instances.has(name);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
removeInstance(name: string): void {
|
|
57
|
+
this.instances.delete(name);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getInstanceNames(): string[] {
|
|
61
|
+
return Array.from(this.instances.keys());
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let instanceManager: InstanceManager | null = null;
|
|
66
|
+
|
|
67
|
+
export function getInstanceManager(): InstanceManager {
|
|
68
|
+
if (!instanceManager) {
|
|
69
|
+
instanceManager = new InstanceManager();
|
|
70
|
+
}
|
|
71
|
+
return instanceManager;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Configure options for a named instance
|
|
76
|
+
*/
|
|
77
|
+
export function configureInstance(
|
|
78
|
+
name: string,
|
|
79
|
+
options: InstanceOptions,
|
|
80
|
+
): Instance {
|
|
81
|
+
const manager = getInstanceManager();
|
|
82
|
+
|
|
83
|
+
if (manager.hasInstance(name)) {
|
|
84
|
+
console.warn(
|
|
85
|
+
`[Pagefind Component UI]: Instance "${name}" already exists, configuration ignored`,
|
|
86
|
+
);
|
|
87
|
+
return manager.getInstance(name);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return manager.getInstance(name, options);
|
|
91
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { PagefindElement } from "./base-element";
|
|
2
|
+
import { Instance } from "../core/instance";
|
|
3
|
+
|
|
4
|
+
export class PagefindConfig extends PagefindElement {
|
|
5
|
+
init(): void {
|
|
6
|
+
this.setAttribute("hidden", "");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
register(instance: Instance): void {
|
|
10
|
+
instance.registerUtility(this);
|
|
11
|
+
|
|
12
|
+
const bundlePath = this.getAttribute("bundle-path");
|
|
13
|
+
if (bundlePath) {
|
|
14
|
+
instance.options.bundlePath = bundlePath;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const baseUrl = this.getAttribute("base-url");
|
|
18
|
+
if (baseUrl) {
|
|
19
|
+
instance.pagefindOptions.baseUrl = baseUrl;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const excerptLength = this.getAttribute("excerpt-length");
|
|
23
|
+
if (excerptLength) {
|
|
24
|
+
instance.pagefindOptions.excerptLength = parseInt(excerptLength, 10);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const lang = this.getAttribute("lang");
|
|
28
|
+
if (lang) {
|
|
29
|
+
instance.setLanguage(lang);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (this.hasAttribute("faceted")) {
|
|
33
|
+
instance.faceted = true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (this.hasAttribute("preload")) {
|
|
37
|
+
instance.triggerLoad();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!customElements.get("pagefind-config")) {
|
|
43
|
+
customElements.define("pagefind-config", PagefindConfig);
|
|
44
|
+
}
|