@xsynaptic/path-suggestions 0.1.0 → 0.2.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.
- package/README.md +3 -1
- package/dist/element.d.mts +9 -0
- package/dist/element.mjs +7 -3
- package/dist/element.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,7 +41,9 @@ document.querySelector('path-suggestions').entries = [{ url: '/about/', title: '
|
|
|
41
41
|
| `redirect-threshold` | `0.92` | Score at or above which the best candidate triggers a redirect |
|
|
42
42
|
| `limit` | `5` | Most suggestions to render |
|
|
43
43
|
| `min-length` | `3` | Paths shorter than this carry too little signal to score |
|
|
44
|
-
| `link
|
|
44
|
+
| `link-*` | | Forwarded to each anchor with the prefix stripped |
|
|
45
|
+
|
|
46
|
+
`link-class` sets the class, `link-data-astro-history` sets `data-astro-history`, and so on for whatever a client-side router reads off a link. The `link-` prefix is reserved for this, and `link-href` is ignored.
|
|
45
47
|
|
|
46
48
|
### State and events
|
|
47
49
|
|
package/dist/element.d.mts
CHANGED
|
@@ -18,12 +18,21 @@ declare class PathSuggestionsElement extends HTMLElement {
|
|
|
18
18
|
run(): Promise<void>;
|
|
19
19
|
private fetchEntries;
|
|
20
20
|
private finish;
|
|
21
|
+
private getLinkAttributes;
|
|
21
22
|
private getList;
|
|
22
23
|
private readNumber;
|
|
23
24
|
private redirect;
|
|
24
25
|
private render;
|
|
25
26
|
}
|
|
26
27
|
declare global {
|
|
28
|
+
interface DocumentEventMap {
|
|
29
|
+
'path-suggestions:done': CustomEvent<PathSuggestionsDoneDetail>;
|
|
30
|
+
'path-suggestions:redirect': CustomEvent<PathSuggestionsRedirectDetail>;
|
|
31
|
+
}
|
|
32
|
+
interface HTMLElementEventMap {
|
|
33
|
+
'path-suggestions:done': CustomEvent<PathSuggestionsDoneDetail>;
|
|
34
|
+
'path-suggestions:redirect': CustomEvent<PathSuggestionsRedirectDetail>;
|
|
35
|
+
}
|
|
27
36
|
interface HTMLElementTagNameMap {
|
|
28
37
|
'path-suggestions': PathSuggestionsElement;
|
|
29
38
|
}
|
package/dist/element.mjs
CHANGED
|
@@ -5,6 +5,7 @@ const pathSuggestionsRedirectEvent = "path-suggestions:redirect";
|
|
|
5
5
|
const pathSuggestionsDoneEvent = "path-suggestions:done";
|
|
6
6
|
const defaultMinLength = 3;
|
|
7
7
|
const listAttribute = "data-path-suggestions-list";
|
|
8
|
+
const linkAttributePrefix = "link-";
|
|
8
9
|
var PathSuggestionsElement = class extends HTMLElement {
|
|
9
10
|
get entries() {
|
|
10
11
|
return this.#entries;
|
|
@@ -22,7 +23,7 @@ var PathSuggestionsElement = class extends HTMLElement {
|
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
connectedCallback() {
|
|
25
|
-
this.run();
|
|
26
|
+
queueMicrotask(() => void this.run());
|
|
26
27
|
}
|
|
27
28
|
async run() {
|
|
28
29
|
this.dataset.state = "loading";
|
|
@@ -73,6 +74,9 @@ var PathSuggestionsElement = class extends HTMLElement {
|
|
|
73
74
|
detail: { count }
|
|
74
75
|
}));
|
|
75
76
|
}
|
|
77
|
+
getLinkAttributes() {
|
|
78
|
+
return [...this.attributes].filter((attribute) => attribute.name.startsWith(linkAttributePrefix)).map((attribute) => [attribute.name.slice(5), attribute.value]).filter(([name]) => name !== "href");
|
|
79
|
+
}
|
|
76
80
|
getList() {
|
|
77
81
|
const existing = this.querySelector(`ul[${CSS.escape(listAttribute)}]`);
|
|
78
82
|
if (existing) return existing;
|
|
@@ -99,14 +103,14 @@ var PathSuggestionsElement = class extends HTMLElement {
|
|
|
99
103
|
}
|
|
100
104
|
render(items) {
|
|
101
105
|
const list = this.getList();
|
|
102
|
-
const
|
|
106
|
+
const linkAttributes = this.getLinkAttributes();
|
|
103
107
|
list.replaceChildren();
|
|
104
108
|
for (const item of items) {
|
|
105
109
|
const listItem = document.createElement("li");
|
|
106
110
|
const link = document.createElement("a");
|
|
107
111
|
link.href = item.url;
|
|
108
112
|
link.textContent = item.title;
|
|
109
|
-
|
|
113
|
+
for (const [name, value] of linkAttributes) link.setAttribute(name, value);
|
|
110
114
|
listItem.append(link);
|
|
111
115
|
list.append(listItem);
|
|
112
116
|
}
|
package/dist/element.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"element.mjs","names":["#entries"],"sources":["../src/element.ts"],"sourcesContent":["import type { PathEntry, PathSuggestion } from './index.js';\n\nimport { getPathSuggestions, pathSuggestionDefaults } from './index.js';\n\nexport const pathSuggestionsElementName = 'path-suggestions';\n\nexport const pathSuggestionsRedirectEvent = 'path-suggestions:redirect';\nexport const pathSuggestionsDoneEvent = 'path-suggestions:done';\n\nexport interface PathSuggestionsDoneDetail {\n\tcount: number;\n}\n\nexport interface PathSuggestionsRedirectDetail {\n\turl: string;\n}\n\ntype PathSuggestionsState = 'empty' | 'list' | 'redirect';\n\n// A path this short carries too little signal to score against anything\nconst defaultMinLength = 3;\n\n// Marks the list this element owns, so host-authored children survive a re-render\nconst listAttribute = 'data-path-suggestions-list';\n\nexport class PathSuggestionsElement extends HTMLElement {\n\t// Score against an inlined list instead of fetching one; scoring reruns when set after connection\n\tget entries(): Array<PathEntry> | undefined {\n\t\treturn this.#entries;\n\t}\n\n\tset entries(value: Array<PathEntry> | undefined) {\n\t\tthis.#entries = value;\n\n\t\tif (this.isConnected) void this.run();\n\t}\n\n\t#entries: Array<PathEntry> | undefined;\n\n\tconstructor() {\n\t\tsuper();\n\n\t\t// A value assigned before this element upgraded sits in an own property, shadowing the accessor\n\t\tif (Object.hasOwn(this, 'entries')) {\n\t\t\tthis.#entries = this.entries;\n\t\t\tReflect.deleteProperty(this, 'entries');\n\t\t}\n\t}\n\n\tconnectedCallback(): void {\n\t\
|
|
1
|
+
{"version":3,"file":"element.mjs","names":["#entries"],"sources":["../src/element.ts"],"sourcesContent":["import type { PathEntry, PathSuggestion } from './index.js';\n\nimport { getPathSuggestions, pathSuggestionDefaults } from './index.js';\n\nexport const pathSuggestionsElementName = 'path-suggestions';\n\nexport const pathSuggestionsRedirectEvent = 'path-suggestions:redirect';\nexport const pathSuggestionsDoneEvent = 'path-suggestions:done';\n\nexport interface PathSuggestionsDoneDetail {\n\tcount: number;\n}\n\nexport interface PathSuggestionsRedirectDetail {\n\turl: string;\n}\n\ntype PathSuggestionsState = 'empty' | 'list' | 'redirect';\n\n// A path this short carries too little signal to score against anything\nconst defaultMinLength = 3;\n\n// Marks the list this element owns, so host-authored children survive a re-render\nconst listAttribute = 'data-path-suggestions-list';\n\n// The element takes no option of its own under this prefix\nconst linkAttributePrefix = 'link-';\n\nexport class PathSuggestionsElement extends HTMLElement {\n\t// Score against an inlined list instead of fetching one; scoring reruns when set after connection\n\tget entries(): Array<PathEntry> | undefined {\n\t\treturn this.#entries;\n\t}\n\n\tset entries(value: Array<PathEntry> | undefined) {\n\t\tthis.#entries = value;\n\n\t\tif (this.isConnected) void this.run();\n\t}\n\n\t#entries: Array<PathEntry> | undefined;\n\n\tconstructor() {\n\t\tsuper();\n\n\t\t// A value assigned before this element upgraded sits in an own property, shadowing the accessor\n\t\tif (Object.hasOwn(this, 'entries')) {\n\t\t\tthis.#entries = this.entries;\n\t\t\tReflect.deleteProperty(this, 'entries');\n\t\t}\n\t}\n\n\tconnectedCallback(): void {\n\t\t// Without this a redirect can dispatch before a host script has attached its listeners\n\t\tqueueMicrotask(() => void this.run());\n\t}\n\n\tasync run(): Promise<void> {\n\t\tthis.dataset.state = 'loading';\n\n\t\tconst path = globalThis.location.pathname;\n\n\t\tif (path.length < this.readNumber('min-length', defaultMinLength)) {\n\t\t\tthis.finish(0, 'empty');\n\t\t\treturn;\n\t\t}\n\n\t\tconst entries = this.entries ?? (await this.fetchEntries());\n\n\t\tif (!entries) {\n\t\t\tthis.finish(0, 'empty');\n\t\t\treturn;\n\t\t}\n\n\t\tconst result = getPathSuggestions({\n\t\t\tentries,\n\t\t\tlimit: this.readNumber('limit', pathSuggestionDefaults.limit),\n\t\t\tpath,\n\t\t\tredirectThreshold: this.readNumber(\n\t\t\t\t'redirect-threshold',\n\t\t\t\tpathSuggestionDefaults.redirectThreshold,\n\t\t\t),\n\t\t\tthreshold: this.readNumber('threshold', pathSuggestionDefaults.threshold),\n\t\t});\n\n\t\tif (!result) {\n\t\t\tthis.finish(0, 'empty');\n\t\t\treturn;\n\t\t}\n\n\t\tif (result.type === 'redirect') {\n\t\t\tthis.redirect(result.url);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.render(result.items);\n\t\tthis.finish(result.items.length, 'list');\n\t}\n\n\tprivate async fetchEntries(): Promise<Array<PathEntry> | undefined> {\n\t\tconst source = this.getAttribute('src');\n\n\t\tif (!source) return;\n\n\t\ttry {\n\t\t\tconst response = await fetch(source);\n\n\t\t\tif (!response.ok) return;\n\n\t\t\tconst entries: unknown = await response.json();\n\n\t\t\treturn Array.isArray(entries) ? (entries as Array<PathEntry>) : undefined;\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t}\n\n\tprivate finish(count: number, state: PathSuggestionsState): void {\n\t\tthis.dataset.state = state;\n\t\tthis.dispatchEvent(\n\t\t\tnew CustomEvent<PathSuggestionsDoneDetail>(pathSuggestionsDoneEvent, {\n\t\t\t\tbubbles: true,\n\t\t\t\tdetail: { count },\n\t\t\t}),\n\t\t);\n\t}\n\n\tprivate getLinkAttributes(): Array<[string, string]> {\n\t\treturn [...this.attributes]\n\t\t\t.filter((attribute) => attribute.name.startsWith(linkAttributePrefix))\n\t\t\t.map((attribute): [string, string] => [\n\t\t\t\tattribute.name.slice(linkAttributePrefix.length),\n\t\t\t\tattribute.value,\n\t\t\t])\n\t\t\t.filter(([name]) => name !== 'href');\n\t}\n\n\tprivate getList(): HTMLUListElement {\n\t\tconst existing = this.querySelector<HTMLUListElement>(`ul[${CSS.escape(listAttribute)}]`);\n\n\t\tif (existing) return existing;\n\n\t\tconst list = document.createElement('ul');\n\n\t\tlist.setAttribute(listAttribute, '');\n\t\tthis.append(list);\n\n\t\treturn list;\n\t}\n\n\tprivate readNumber(attribute: string, fallback: number): number {\n\t\tconst raw = this.getAttribute(attribute);\n\n\t\tif (raw === null) return fallback;\n\n\t\tconst parsed = Number(raw);\n\n\t\treturn Number.isFinite(parsed) ? parsed : fallback;\n\t}\n\n\t// Cancel the event to navigate some other way, such as an SPA router\n\tprivate redirect(url: string): void {\n\t\tconst event = new CustomEvent<PathSuggestionsRedirectDetail>(pathSuggestionsRedirectEvent, {\n\t\t\tbubbles: true,\n\t\t\tcancelable: true,\n\t\t\tdetail: { url },\n\t\t});\n\n\t\tconst isProceed = this.dispatchEvent(event);\n\n\t\tthis.finish(1, 'redirect');\n\n\t\tif (isProceed) globalThis.location.replace(url);\n\t}\n\n\tprivate render(items: Array<PathSuggestion>): void {\n\t\tconst list = this.getList();\n\t\tconst linkAttributes = this.getLinkAttributes();\n\n\t\tlist.replaceChildren();\n\n\t\tfor (const item of items) {\n\t\t\tconst listItem = document.createElement('li');\n\t\t\tconst link = document.createElement('a');\n\n\t\t\tlink.href = item.url;\n\t\t\tlink.textContent = item.title;\n\n\t\t\tfor (const [name, value] of linkAttributes) link.setAttribute(name, value);\n\n\t\t\tlistItem.append(link);\n\t\t\tlist.append(listItem);\n\t\t}\n\t}\n}\n\ndeclare global {\n\tinterface DocumentEventMap {\n\t\t'path-suggestions:done': CustomEvent<PathSuggestionsDoneDetail>;\n\t\t'path-suggestions:redirect': CustomEvent<PathSuggestionsRedirectDetail>;\n\t}\n\n\tinterface HTMLElementEventMap {\n\t\t'path-suggestions:done': CustomEvent<PathSuggestionsDoneDetail>;\n\t\t'path-suggestions:redirect': CustomEvent<PathSuggestionsRedirectDetail>;\n\t}\n\n\tinterface HTMLElementTagNameMap {\n\t\t'path-suggestions': PathSuggestionsElement;\n\t}\n}\n\nif (!customElements.get(pathSuggestionsElementName)) {\n\tcustomElements.define(pathSuggestionsElementName, PathSuggestionsElement);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED