@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 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
+ }