sol-components 2.1.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 +7 -0
- package/core/activate.js +27 -0
- package/core/adopt.js +71 -0
- package/core/auth-core.js +73 -0
- package/core/auth-fetch.js +154 -0
- package/core/component-mount.js +110 -0
- package/core/defaults.js +48 -0
- package/core/define.js +15 -0
- package/core/display-target.js +166 -0
- package/core/edit-placements.js +28 -0
- package/core/editor-self.js +127 -0
- package/core/editor.js +162 -0
- package/core/events.js +27 -0
- package/core/extension-points.js +189 -0
- package/core/form-utils.js +210 -0
- package/core/from-query.js +138 -0
- package/core/from-rdf.js +52 -0
- package/core/here.js +33 -0
- package/core/include-core.js +73 -0
- package/core/inrupt-global.js +18 -0
- package/core/menu-consumer.js +41 -0
- package/core/menu-rdf.js +154 -0
- package/core/pod-ops.js +392 -0
- package/core/pod-registry.js +82 -0
- package/core/popup-proxy.js +255 -0
- package/core/rdf-core.js +280 -0
- package/core/rdf-render.js +136 -0
- package/core/rdf-utils.js +411 -0
- package/core/rdf.js +154 -0
- package/core/services.js +106 -0
- package/core/shape-to-form.js +741 -0
- package/core/sparql-safety.js +20 -0
- package/core/utils.js +196 -0
- package/dist/importmap-cdn.json +49 -0
- package/dist/importmap-local.json +49 -0
- package/dist/sol-loader.manifest.json +140 -0
- package/dist/vendor/@comunica-query-sparql.js +137851 -0
- package/dist/vendor/@inrupt-solid-client-authn-browser.js +7503 -0
- package/dist/vendor/dompurify.js +1476 -0
- package/dist/vendor/ical.js.js +9739 -0
- package/dist/vendor/marked.js +85 -0
- package/dist/vendor/n3.js +14670 -0
- package/dist/vendor/rdf-validate-shacl.js +6970 -0
- package/dist/vendor/rdflib.js +35172 -0
- package/dist/vendor/solid-logic.js +6819 -0
- package/dist/vendor/solid-ui.js +21945 -0
- package/node/sol-form.js +133 -0
- package/node/sol-include.js +55 -0
- package/node/sol-login.js +632 -0
- package/node/sol-menu.js +639 -0
- package/node/sol-query.js +116 -0
- package/package.json +133 -0
- package/web/menu-from-rdf.js +23 -0
- package/web/scripts/prefs.js +25 -0
- package/web/sol-accordion.js +114 -0
- package/web/sol-basic.js +50 -0
- package/web/sol-breadcrumb.js +131 -0
- package/web/sol-button.js +244 -0
- package/web/sol-calendar.js +465 -0
- package/web/sol-default.js +118 -0
- package/web/sol-dropdown-button.js +222 -0
- package/web/sol-feed.js +1336 -0
- package/web/sol-form.js +949 -0
- package/web/sol-full.js +43 -0
- package/web/sol-gallery.js +303 -0
- package/web/sol-include.js +246 -0
- package/web/sol-live-edit.js +415 -0
- package/web/sol-login.js +856 -0
- package/web/sol-menu.js +593 -0
- package/web/sol-modal.js +377 -0
- package/web/sol-pod-extras.js +17 -0
- package/web/sol-pod-ops.js +680 -0
- package/web/sol-pod.js +1039 -0
- package/web/sol-query.js +546 -0
- package/web/sol-rolodex.js +95 -0
- package/web/sol-search.js +402 -0
- package/web/sol-settings.js +199 -0
- package/web/sol-solidos.js +93 -0
- package/web/sol-tabs.js +445 -0
- package/web/sol-time.js +194 -0
- package/web/sol-tree-edit.js +492 -0
- package/web/sol-wac.js +456 -0
- package/web/sol-weather.js +337 -0
- package/web/sol-window.js +142 -0
- package/web/styles/buttons-css.js +108 -0
- package/web/styles/help.css +242 -0
- package/web/styles/root.css +112 -0
- package/web/styles/sol-accordion-css.js +97 -0
- package/web/styles/sol-calendar-css.js +154 -0
- package/web/styles/sol-feed-css.js +475 -0
- package/web/styles/sol-form-css.js +471 -0
- package/web/styles/sol-gallery-css.js +181 -0
- package/web/styles/sol-include-css.js +95 -0
- package/web/styles/sol-live-edit-css.js +84 -0
- package/web/styles/sol-live-edit.css +101 -0
- package/web/styles/sol-login-css.js +116 -0
- package/web/styles/sol-menu-css.js +145 -0
- package/web/styles/sol-modal-css.js +134 -0
- package/web/styles/sol-pod-css.js +187 -0
- package/web/styles/sol-pod-modal-css.js +203 -0
- package/web/styles/sol-query-css.js +140 -0
- package/web/styles/sol-query-help.css +267 -0
- package/web/styles/sol-query-one-pager.css +67 -0
- package/web/styles/sol-search-css.js +157 -0
- package/web/styles/sol-solidos-css.js +7 -0
- package/web/styles/sol-tabs-css.js +114 -0
- package/web/styles/sol-time-css.js +30 -0
- package/web/styles/sol-wac-css.js +73 -0
- package/web/styles/sol-weather-css.js +59 -0
- package/web/styles/solid-logo.svg +9 -0
- package/web/styles/view-accordion-css.js +66 -0
- package/web/styles/view-anchorlist-css.js +22 -0
- package/web/styles/view-autocomplete-css.js +59 -0
- package/web/styles/view-rolodex-css.js +102 -0
- package/web/styles/view-select-css.js +21 -0
- package/web/utils/calendar-fetch.js +388 -0
- package/web/utils/code-mirror-editor.js +82 -0
- package/web/utils/commons-fetch.js +108 -0
- package/web/utils/feed-edit.js +159 -0
- package/web/utils/feed-edit.smoke.mjs +74 -0
- package/web/utils/feed-fetch.js +573 -0
- package/web/utils/live-edit-help/csv.js +64 -0
- package/web/utils/live-edit-help/graphviz.js +41 -0
- package/web/utils/live-edit-help/jsonld.js +55 -0
- package/web/utils/live-edit-help/markdown.js +52 -0
- package/web/utils/live-edit-help/mermaid.js +48 -0
- package/web/utils/live-edit-help/turtle.js +85 -0
- package/web/utils/rdf-config.js +125 -0
- package/web/utils/renderers/csv.js +124 -0
- package/web/utils/renderers/d3-force.js +82 -0
- package/web/utils/renderers/graphviz.js +13 -0
- package/web/utils/renderers/html.js +10 -0
- package/web/utils/renderers/jsonld.js +63 -0
- package/web/utils/renderers/markdown.js +19 -0
- package/web/utils/renderers/mermaid.js +54 -0
- package/web/utils/renderers/turtle.js +51 -0
- package/web/utils/sol-query-triple-patterns.js +151 -0
- package/web/utils/sol-query-ui.js +250 -0
- package/web/utils/sol-query-views.js +32 -0
- package/web/views/_helpers.js +34 -0
- package/web/views/accordion.js +133 -0
- package/web/views/anchorlist.js +59 -0
- package/web/views/auto-complete.js +183 -0
- package/web/views/dl.js +38 -0
- package/web/views/list.js +19 -0
- package/web/views/menu.js +56 -0
- package/web/views/rolodex.js +126 -0
- package/web/views/select.js +79 -0
- package/web/views/table.js +73 -0
- package/web/views/tabs.js +57 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { adopt } from '../../core/adopt.js';
|
|
2
|
+
import { CSS as ACCORDION_VIEW_CSS, sheet as ACCORDION_SHEET } from '../styles/view-accordion-css.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Built-in view renderer for sol-query — "accordion".
|
|
6
|
+
*
|
|
7
|
+
* Panel mode — query-driven (default): one <details> per result row. Summary
|
|
8
|
+
* = first column; body = remaining columns as key/value pairs.
|
|
9
|
+
*
|
|
10
|
+
* Panel mode — author-supplied: if the <sol-query> host element has child
|
|
11
|
+
* <div>s in its light DOM, each div becomes one panel instead. Summary is
|
|
12
|
+
* the div's `data-summary` attribute, or its first heading child, or
|
|
13
|
+
* "Panel N". The div's HTML becomes the panel body.
|
|
14
|
+
*
|
|
15
|
+
* Mutually-exclusive: opening one panel closes all the others.
|
|
16
|
+
*
|
|
17
|
+
* Usage: <sol-query view="accordion" endpoint="…"></sol-query>
|
|
18
|
+
*/
|
|
19
|
+
export function render(container, data, host) {
|
|
20
|
+
const shortUri = v => v.replace(/.*[/#]([^/#]+)\/?$/, '$1') || v;
|
|
21
|
+
|
|
22
|
+
const renderCellInto = (parent, cell) => {
|
|
23
|
+
if (!cell) return;
|
|
24
|
+
if (cell.type === 'multi') {
|
|
25
|
+
cell.values.forEach((v, i) => {
|
|
26
|
+
if (i > 0) parent.appendChild(document.createTextNode(', '));
|
|
27
|
+
renderCellInto(parent, v);
|
|
28
|
+
});
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (cell.type === 'uri') {
|
|
32
|
+
const a = document.createElement('a');
|
|
33
|
+
a.href = cell.value;
|
|
34
|
+
a.textContent = shortUri(cell.value);
|
|
35
|
+
a.title = cell.value;
|
|
36
|
+
a.dataset.uri = cell.value;
|
|
37
|
+
a.target = '_blank';
|
|
38
|
+
a.rel = 'noopener noreferrer';
|
|
39
|
+
parent.appendChild(a);
|
|
40
|
+
} else {
|
|
41
|
+
parent.appendChild(document.createTextNode(cell.value ?? ''));
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Native single-open behavior via <details name="...">. Each renderer
|
|
46
|
+
// instance gets a unique group name so multiple accordions on a page
|
|
47
|
+
// don't interfere with each other.
|
|
48
|
+
const groupName = `sol-accordion-${Math.random().toString(36).slice(2, 9)}`;
|
|
49
|
+
|
|
50
|
+
const wrapper = document.createElement('div');
|
|
51
|
+
wrapper.className = 'sol-view-accordion';
|
|
52
|
+
|
|
53
|
+
// ── Author-supplied panels: light-DOM <div> children of the host ──────────
|
|
54
|
+
const authorDivs = host
|
|
55
|
+
? Array.from(host.children).filter(el => el.tagName === 'DIV')
|
|
56
|
+
: [];
|
|
57
|
+
|
|
58
|
+
if (authorDivs.length) {
|
|
59
|
+
authorDivs.forEach((srcDiv, i) => {
|
|
60
|
+
const det = document.createElement('details');
|
|
61
|
+
det.name = groupName;
|
|
62
|
+
if (i === 0) det.open = true;
|
|
63
|
+
|
|
64
|
+
const sum = document.createElement('summary');
|
|
65
|
+
const explicit = srcDiv.getAttribute('data-summary');
|
|
66
|
+
const heading = srcDiv.querySelector('h1,h2,h3,h4,h5,h6');
|
|
67
|
+
sum.textContent = explicit ?? heading?.textContent?.trim() ?? `Panel ${i + 1}`;
|
|
68
|
+
det.appendChild(sum);
|
|
69
|
+
|
|
70
|
+
const body = document.createElement('div');
|
|
71
|
+
body.className = 'accordion-body';
|
|
72
|
+
// Clone so the light-DOM source stays untouched; also drops the heading
|
|
73
|
+
// we just lifted into the summary (if any).
|
|
74
|
+
const clone = srcDiv.cloneNode(true);
|
|
75
|
+
if (!explicit && heading) {
|
|
76
|
+
const h = clone.querySelector('h1,h2,h3,h4,h5,h6');
|
|
77
|
+
h?.remove();
|
|
78
|
+
}
|
|
79
|
+
while (clone.firstChild) body.appendChild(clone.firstChild);
|
|
80
|
+
det.appendChild(body);
|
|
81
|
+
wrapper.appendChild(det);
|
|
82
|
+
});
|
|
83
|
+
attachWrapperAndStyle(container, wrapper);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Query-driven panels ───────────────────────────────────────────────────
|
|
88
|
+
const vars = data.head.vars;
|
|
89
|
+
const bindings = data.results.bindings;
|
|
90
|
+
if (!bindings?.length) {
|
|
91
|
+
container.textContent = 'No results';
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const summaryText = row => {
|
|
96
|
+
const firstCell = row[vars[0]];
|
|
97
|
+
if (!firstCell) return '(row)';
|
|
98
|
+
if (firstCell.type === 'uri') return shortUri(firstCell.value);
|
|
99
|
+
return firstCell.value ?? '';
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
bindings.forEach((row, i) => {
|
|
103
|
+
const det = document.createElement('details');
|
|
104
|
+
det.name = groupName;
|
|
105
|
+
if (i === 0) det.open = true;
|
|
106
|
+
|
|
107
|
+
const sum = document.createElement('summary');
|
|
108
|
+
sum.textContent = summaryText(row);
|
|
109
|
+
det.appendChild(sum);
|
|
110
|
+
|
|
111
|
+
const body = document.createElement('dl');
|
|
112
|
+
const restVars = vars.length > 1 ? vars.slice(1) : vars;
|
|
113
|
+
restVars.forEach(v => {
|
|
114
|
+
const cell = row[v];
|
|
115
|
+
if (!cell) return;
|
|
116
|
+
const dt = document.createElement('dt');
|
|
117
|
+
dt.textContent = v;
|
|
118
|
+
const dd = document.createElement('dd');
|
|
119
|
+
renderCellInto(dd, cell);
|
|
120
|
+
body.appendChild(dt);
|
|
121
|
+
body.appendChild(dd);
|
|
122
|
+
});
|
|
123
|
+
det.appendChild(body);
|
|
124
|
+
wrapper.appendChild(det);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
attachWrapperAndStyle(container, wrapper);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function attachWrapperAndStyle(container, wrapper) {
|
|
131
|
+
adopt(container.getRootNode(), { sheet: ACCORDION_SHEET, css: ACCORDION_VIEW_CSS });
|
|
132
|
+
container.appendChild(wrapper);
|
|
133
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { adopt } from '../../core/adopt.js';
|
|
2
|
+
import { CSS as ANCHORLIST_CSS, sheet as ANCHORLIST_SHEET } from '../styles/view-anchorlist-css.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Built-in view renderer for sol-query — "anchorlist".
|
|
6
|
+
* Renders results as a flat unordered list of anchor links.
|
|
7
|
+
*
|
|
8
|
+
* Display rules per row:
|
|
9
|
+
* - Picks the first URI cell as the href.
|
|
10
|
+
* - Label = first non-URI literal cell, else the shortened form of the URI.
|
|
11
|
+
* - If no URI cell exists, renders a plain <li> with the first cell's value.
|
|
12
|
+
*
|
|
13
|
+
* Usage: <sol-query view="anchorlist" endpoint="…"></sol-query>
|
|
14
|
+
*/
|
|
15
|
+
export function render(container, data) {
|
|
16
|
+
const vars = data.head.vars;
|
|
17
|
+
const bindings = data.results.bindings;
|
|
18
|
+
if (!bindings?.length) {
|
|
19
|
+
container.textContent = 'No results';
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const shortUri = v => v.replace(/.*[/#]([^/#]+)\/?$/, '$1') || v;
|
|
24
|
+
|
|
25
|
+
const pickFirst = (row, predicate) => {
|
|
26
|
+
for (const v of vars) {
|
|
27
|
+
const cell = row[v];
|
|
28
|
+
if (cell && predicate(cell)) return cell;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const ul = document.createElement('ul');
|
|
34
|
+
ul.className = 'sol-view-anchorlist';
|
|
35
|
+
|
|
36
|
+
bindings.forEach(row => {
|
|
37
|
+
const li = document.createElement('li');
|
|
38
|
+
const uriCell = pickFirst(row, c => c.type === 'uri');
|
|
39
|
+
const literalCell = pickFirst(row, c => c.type !== 'uri' && c.type !== 'multi' && c.value);
|
|
40
|
+
|
|
41
|
+
if (uriCell) {
|
|
42
|
+
const a = document.createElement('a');
|
|
43
|
+
a.href = uriCell.value;
|
|
44
|
+
a.textContent = literalCell?.value ?? shortUri(uriCell.value);
|
|
45
|
+
a.title = uriCell.value;
|
|
46
|
+
a.dataset.uri = uriCell.value;
|
|
47
|
+
a.target = '_blank';
|
|
48
|
+
a.rel = 'noopener noreferrer';
|
|
49
|
+
li.appendChild(a);
|
|
50
|
+
} else {
|
|
51
|
+
const first = row[vars[0]];
|
|
52
|
+
li.textContent = first?.value ?? '';
|
|
53
|
+
}
|
|
54
|
+
ul.appendChild(li);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
adopt(container.getRootNode(), { sheet: ANCHORLIST_SHEET, css: ANCHORLIST_CSS });
|
|
58
|
+
container.appendChild(ul);
|
|
59
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { adopt } from '../../core/adopt.js';
|
|
2
|
+
import { CSS as AUTOCOMPLETE_CSS, sheet as AUTOCOMPLETE_SHEET } from '../styles/view-autocomplete-css.js';
|
|
3
|
+
|
|
4
|
+
export function render(container, data, host) {
|
|
5
|
+
const vars = data.head.vars;
|
|
6
|
+
const bindings = data.results.bindings;
|
|
7
|
+
if (!bindings?.length) {
|
|
8
|
+
container.textContent = 'No results';
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const shortUri = v => v.replace(/.*[/#]([^/#]+)\/?$/, '$1') || v;
|
|
13
|
+
const cellText = cell => {
|
|
14
|
+
if (!cell) return '';
|
|
15
|
+
if (cell.type === 'uri') return shortUri(cell.value);
|
|
16
|
+
return cell.value ?? '';
|
|
17
|
+
};
|
|
18
|
+
const cellValue = cell => (cell ? cell.value ?? '' : '');
|
|
19
|
+
|
|
20
|
+
const items = bindings.map((row, i) => {
|
|
21
|
+
const cells = vars.map(v => row[v]);
|
|
22
|
+
return {
|
|
23
|
+
value: cellValue(cells[0]),
|
|
24
|
+
label: vars.length > 1 ? cellText(cells[1]) : cellText(cells[0]),
|
|
25
|
+
row,
|
|
26
|
+
index: i,
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const wrapper = document.createElement('div');
|
|
31
|
+
wrapper.className = 'sol-view-autocomplete';
|
|
32
|
+
|
|
33
|
+
const inputWrapper = document.createElement('div');
|
|
34
|
+
inputWrapper.className = 'ac-input-wrapper';
|
|
35
|
+
|
|
36
|
+
const input = document.createElement('input');
|
|
37
|
+
input.type = 'text';
|
|
38
|
+
input.setAttribute('role', 'combobox');
|
|
39
|
+
input.setAttribute('aria-autocomplete', 'list');
|
|
40
|
+
input.setAttribute('aria-expanded', 'false');
|
|
41
|
+
input.placeholder = host?.getAttribute('placeholder') ?? 'type to search...';
|
|
42
|
+
|
|
43
|
+
const goButton = document.createElement('button');
|
|
44
|
+
goButton.type = 'button';
|
|
45
|
+
goButton.textContent = host?.getAttribute('go-label') ?? 'Go';
|
|
46
|
+
goButton.className = 'sol-btn sol-btn-primary ac-go-button';
|
|
47
|
+
|
|
48
|
+
inputWrapper.appendChild(input);
|
|
49
|
+
inputWrapper.appendChild(goButton);
|
|
50
|
+
|
|
51
|
+
const listWrapper = document.createElement('div');
|
|
52
|
+
listWrapper.className = 'ac-list-wrapper';
|
|
53
|
+
listWrapper.hidden = true;
|
|
54
|
+
|
|
55
|
+
const list = document.createElement('ul');
|
|
56
|
+
list.className = 'ac-list';
|
|
57
|
+
list.setAttribute('role', 'listbox');
|
|
58
|
+
|
|
59
|
+
listWrapper.appendChild(list);
|
|
60
|
+
|
|
61
|
+
let activeIndex = -1;
|
|
62
|
+
let visible = items;
|
|
63
|
+
let selectedItem = null;
|
|
64
|
+
|
|
65
|
+
const fire = item => {
|
|
66
|
+
input.value = item.label;
|
|
67
|
+
input.setAttribute('aria-expanded', 'false');
|
|
68
|
+
listWrapper.hidden = true;
|
|
69
|
+
selectedItem = item;
|
|
70
|
+
host?.dispatchEvent(new CustomEvent('sol-select', {
|
|
71
|
+
bubbles: true, composed: true,
|
|
72
|
+
detail: { value: item.value, row: item.row, index: item.index },
|
|
73
|
+
}));
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const handleAutoComplete = () => {
|
|
77
|
+
if (!selectedItem) {
|
|
78
|
+
alert('No item selected');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const callbackName = host?.getAttribute('callback');
|
|
83
|
+
if (callbackName && typeof window[callbackName] === 'function') {
|
|
84
|
+
window[callbackName](selectedItem.value, selectedItem.label);
|
|
85
|
+
} else {
|
|
86
|
+
alert(`ID: ${selectedItem.value}\nLabel: ${selectedItem.label}`);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
goButton.addEventListener('click', handleAutoComplete);
|
|
91
|
+
|
|
92
|
+
const renderList = () => {
|
|
93
|
+
list.innerHTML = '';
|
|
94
|
+
visible.forEach((item, i) => {
|
|
95
|
+
const li = document.createElement('li');
|
|
96
|
+
li.textContent = item.label;
|
|
97
|
+
li.setAttribute('role', 'option');
|
|
98
|
+
li.dataset.index = String(i);
|
|
99
|
+
if (i === activeIndex) {
|
|
100
|
+
li.classList.add('active');
|
|
101
|
+
li.scrollIntoView({ block: 'nearest' });
|
|
102
|
+
}
|
|
103
|
+
list.appendChild(li);
|
|
104
|
+
});
|
|
105
|
+
const hasItems = visible.length > 0;
|
|
106
|
+
listWrapper.hidden = !hasItems;
|
|
107
|
+
input.setAttribute('aria-expanded', hasItems ? 'true' : 'false');
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
list.addEventListener('mousedown', e => {
|
|
111
|
+
const li = e.target.closest('li');
|
|
112
|
+
if (!li) return;
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
const idx = parseInt(li.dataset.index, 10);
|
|
115
|
+
if (!isNaN(idx) && visible[idx]) {
|
|
116
|
+
fire(visible[idx]);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
list.addEventListener('mousemove', e => {
|
|
121
|
+
const li = e.target.closest('li');
|
|
122
|
+
if (!li) return;
|
|
123
|
+
const idx = parseInt(li.dataset.index, 10);
|
|
124
|
+
if (!isNaN(idx) && idx !== activeIndex) {
|
|
125
|
+
activeIndex = idx;
|
|
126
|
+
renderList();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const filter = () => {
|
|
131
|
+
const q = input.value.trim().toLowerCase();
|
|
132
|
+
visible = q
|
|
133
|
+
? items.filter(it => it.label.toLowerCase().startsWith(q))
|
|
134
|
+
: items.slice();
|
|
135
|
+
activeIndex = -1;
|
|
136
|
+
renderList();
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
input.addEventListener('input', filter);
|
|
140
|
+
input.addEventListener('click', () => {
|
|
141
|
+
if (!listWrapper.hidden) return;
|
|
142
|
+
input.value = '';
|
|
143
|
+
selectedItem = null;
|
|
144
|
+
filter();
|
|
145
|
+
});
|
|
146
|
+
input.addEventListener('keydown', e => {
|
|
147
|
+
if (listWrapper.hidden && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) {
|
|
148
|
+
filter();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (e.key === 'ArrowDown') {
|
|
152
|
+
e.preventDefault();
|
|
153
|
+
if (activeIndex === -1) {
|
|
154
|
+
activeIndex = 0;
|
|
155
|
+
} else {
|
|
156
|
+
activeIndex = Math.min(activeIndex + 1, visible.length - 1);
|
|
157
|
+
}
|
|
158
|
+
renderList();
|
|
159
|
+
} else if (e.key === 'ArrowUp') {
|
|
160
|
+
e.preventDefault();
|
|
161
|
+
if (activeIndex === -1) {
|
|
162
|
+
activeIndex = visible.length - 1;
|
|
163
|
+
} else {
|
|
164
|
+
activeIndex = Math.max(activeIndex - 1, 0);
|
|
165
|
+
}
|
|
166
|
+
renderList();
|
|
167
|
+
} else if (e.key === 'Enter') {
|
|
168
|
+
if (activeIndex >= 0 && visible[activeIndex]) {
|
|
169
|
+
e.preventDefault();
|
|
170
|
+
fire(visible[activeIndex]);
|
|
171
|
+
}
|
|
172
|
+
} else if (e.key === 'Escape') {
|
|
173
|
+
listWrapper.hidden = true;
|
|
174
|
+
input.setAttribute('aria-expanded', 'false');
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
wrapper.appendChild(inputWrapper);
|
|
179
|
+
wrapper.appendChild(listWrapper);
|
|
180
|
+
|
|
181
|
+
adopt(container.getRootNode(), { sheet: AUTOCOMPLETE_SHEET, css: AUTOCOMPLETE_CSS });
|
|
182
|
+
container.appendChild(wrapper);
|
|
183
|
+
}
|
package/web/views/dl.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { termText, appendCell } from './_helpers.js';
|
|
2
|
+
|
|
3
|
+
export function render(container, data, host, options = {}) {
|
|
4
|
+
const { hideHeader, mkBnodeLink } = options;
|
|
5
|
+
const vars = data.head.vars;
|
|
6
|
+
const bindings = data.results.bindings;
|
|
7
|
+
const dl = document.createElement('dl');
|
|
8
|
+
const flat = !!hideHeader;
|
|
9
|
+
const nameVar = flat ? null : vars[0];
|
|
10
|
+
const restVars = flat ? vars : vars.slice(1);
|
|
11
|
+
|
|
12
|
+
bindings.forEach(row => {
|
|
13
|
+
if (nameVar) {
|
|
14
|
+
const dt = document.createElement('dt');
|
|
15
|
+
const nameCell = row[nameVar];
|
|
16
|
+
dt.textContent = termText(nameCell);
|
|
17
|
+
if (nameCell?.type === 'uri') dt.title = nameCell.value;
|
|
18
|
+
dl.appendChild(dt);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
restVars.forEach(v => {
|
|
22
|
+
const dd = document.createElement('dd');
|
|
23
|
+
const label = document.createElement('span');
|
|
24
|
+
label.className = 'dl-field';
|
|
25
|
+
label.textContent = `${v} `;
|
|
26
|
+
dd.appendChild(label);
|
|
27
|
+
|
|
28
|
+
const valueSpan = document.createElement('span');
|
|
29
|
+
valueSpan.className = 'dl-value';
|
|
30
|
+
appendCell(valueSpan, row[v], mkBnodeLink);
|
|
31
|
+
dd.appendChild(valueSpan);
|
|
32
|
+
|
|
33
|
+
dl.appendChild(dd);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
container.appendChild(dl);
|
|
38
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { render as renderTable } from './table.js';
|
|
2
|
+
import { appendCell } from './_helpers.js';
|
|
3
|
+
|
|
4
|
+
export function render(container, data, host, options = {}) {
|
|
5
|
+
const vars = data.head.vars;
|
|
6
|
+
const bindings = data.results.bindings;
|
|
7
|
+
if (vars.length > 1) return renderTable(container, data, host, options);
|
|
8
|
+
|
|
9
|
+
const { mkBnodeLink } = options;
|
|
10
|
+
const col = vars[0];
|
|
11
|
+
const ul = document.createElement('ul');
|
|
12
|
+
ul.className = 'result-list';
|
|
13
|
+
bindings.forEach(row => {
|
|
14
|
+
const li = document.createElement('li');
|
|
15
|
+
appendCell(li, row[col], mkBnodeLink);
|
|
16
|
+
ul.appendChild(li);
|
|
17
|
+
});
|
|
18
|
+
container.appendChild(ul);
|
|
19
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { siblingUrl } from '../../core/here.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Built-in view renderer for sol-query — "menu".
|
|
5
|
+
* Turns a result set of URIs + labels into a <sol-menu>. Each row becomes
|
|
6
|
+
* one menu item; clicking loads the URL into the content panel.
|
|
7
|
+
*
|
|
8
|
+
* Column selection: uses vars named `link` + `label` when present,
|
|
9
|
+
* otherwise falls back to the first two columns in order (1st = link,
|
|
10
|
+
* 2nd = label). If only one column is returned, it's used for both.
|
|
11
|
+
*
|
|
12
|
+
* Any additional result columns are forwarded as attributes on the anchor,
|
|
13
|
+
* so <sol-menu> can pass them through to the panel component.
|
|
14
|
+
*
|
|
15
|
+
* A `handler` attribute on the host <sol-query> is forwarded to the
|
|
16
|
+
* <sol-menu> element so authors can choose the component each item wraps:
|
|
17
|
+
* <sol-query view="menu" handler="sol-live-edit" …>
|
|
18
|
+
*
|
|
19
|
+
* Usage: <sol-query view="menu" endpoint="…" sparql="SELECT ?link ?label …">
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export async function render(container, data, host) {
|
|
23
|
+
const vars = data.head.vars;
|
|
24
|
+
const bindings = data.results.bindings;
|
|
25
|
+
if (!bindings?.length) { container.textContent = 'No results'; return; }
|
|
26
|
+
|
|
27
|
+
const hasNamed = vars.includes('link') && vars.includes('label');
|
|
28
|
+
const linkVar = hasNamed ? 'link' : vars[0];
|
|
29
|
+
const labelVar = hasNamed ? 'label' : (vars[1] ?? vars[0]);
|
|
30
|
+
const extraVars = vars.filter(v => v !== linkVar && v !== labelVar);
|
|
31
|
+
|
|
32
|
+
await import(siblingUrl('../sol-menu.js', import.meta.url));
|
|
33
|
+
|
|
34
|
+
const menu = document.createElement('sol-menu');
|
|
35
|
+
|
|
36
|
+
const handler = host?.getAttribute?.('handler');
|
|
37
|
+
if (handler) menu.setAttribute('handler', handler);
|
|
38
|
+
const orientation = host?.getAttribute?.('orientation');
|
|
39
|
+
if (orientation) menu.setAttribute('orientation', orientation);
|
|
40
|
+
|
|
41
|
+
for (const row of bindings) {
|
|
42
|
+
const url = row[linkVar]?.value;
|
|
43
|
+
const label = row[labelVar]?.value || url || '';
|
|
44
|
+
if (!url) continue;
|
|
45
|
+
const a = document.createElement('a');
|
|
46
|
+
a.setAttribute('href', url);
|
|
47
|
+
a.textContent = label;
|
|
48
|
+
for (const v of extraVars) {
|
|
49
|
+
const cell = row[v];
|
|
50
|
+
if (cell?.value != null) a.setAttribute(v, cell.value);
|
|
51
|
+
}
|
|
52
|
+
menu.appendChild(a);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
container.appendChild(menu);
|
|
56
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { adopt } from '../../core/adopt.js';
|
|
2
|
+
import { CSS as ROLODEX_CSS, sheet as ROLODEX_SHEET } from '../styles/view-rolodex-css.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Built-in view renderer for sol-query — "rolodex".
|
|
6
|
+
* Shows one result row at a time as a card, with prev/next buttons and an
|
|
7
|
+
* "N of M" counter. Each field (column) becomes a labelled line inside the
|
|
8
|
+
* card. Arrow keys navigate when the card has focus.
|
|
9
|
+
*
|
|
10
|
+
* Navigation wraps around at both ends. Clicking a card dispatches a
|
|
11
|
+
* 'sol-select' event with { value, row, index } on the host element.
|
|
12
|
+
*
|
|
13
|
+
* Usage: <sol-query view="rolodex" endpoint="…"></sol-query>
|
|
14
|
+
*/
|
|
15
|
+
export async function render(container, data, host) {
|
|
16
|
+
const vars = data.head.vars;
|
|
17
|
+
const bindings = data.results.bindings;
|
|
18
|
+
if (!bindings?.length) {
|
|
19
|
+
container.textContent = 'No results';
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const shortUri = v => v.replace(/.*[/#]([^/#]+)\/?$/, '$1') || v;
|
|
24
|
+
|
|
25
|
+
const renderCellInto = (parent, cell) => {
|
|
26
|
+
if (!cell) return;
|
|
27
|
+
if (cell.type === 'multi') {
|
|
28
|
+
cell.values.forEach((v, i) => {
|
|
29
|
+
if (i > 0) parent.appendChild(document.createTextNode(', '));
|
|
30
|
+
renderCellInto(parent, v);
|
|
31
|
+
});
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (cell.type === 'uri') {
|
|
35
|
+
const a = document.createElement('a');
|
|
36
|
+
a.href = cell.value;
|
|
37
|
+
a.textContent = shortUri(cell.value);
|
|
38
|
+
a.title = cell.value;
|
|
39
|
+
a.dataset.uri = cell.value;
|
|
40
|
+
a.target = '_blank';
|
|
41
|
+
a.rel = 'noopener noreferrer';
|
|
42
|
+
parent.appendChild(a);
|
|
43
|
+
} else {
|
|
44
|
+
parent.appendChild(document.createTextNode(cell.value ?? ''));
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const wrapper = document.createElement('div');
|
|
49
|
+
wrapper.className = 'sol-view-rolodex';
|
|
50
|
+
wrapper.tabIndex = 0;
|
|
51
|
+
|
|
52
|
+
const nav = document.createElement('div');
|
|
53
|
+
nav.className = 'rolodex-nav';
|
|
54
|
+
|
|
55
|
+
const prevBtn = document.createElement('button');
|
|
56
|
+
prevBtn.type = 'button';
|
|
57
|
+
prevBtn.className = 'sol-btn sol-btn-icon rolodex-btn';
|
|
58
|
+
prevBtn.setAttribute('aria-label', 'Previous record');
|
|
59
|
+
prevBtn.textContent = '‹';
|
|
60
|
+
|
|
61
|
+
const counter = document.createElement('span');
|
|
62
|
+
counter.className = 'rolodex-counter';
|
|
63
|
+
counter.setAttribute('aria-live', 'polite');
|
|
64
|
+
|
|
65
|
+
const nextBtn = document.createElement('button');
|
|
66
|
+
nextBtn.type = 'button';
|
|
67
|
+
nextBtn.className = 'sol-btn sol-btn-icon rolodex-btn';
|
|
68
|
+
nextBtn.setAttribute('aria-label', 'Next record');
|
|
69
|
+
nextBtn.textContent = '›';
|
|
70
|
+
|
|
71
|
+
nav.appendChild(prevBtn);
|
|
72
|
+
nav.appendChild(counter);
|
|
73
|
+
nav.appendChild(nextBtn);
|
|
74
|
+
|
|
75
|
+
const card = document.createElement('div');
|
|
76
|
+
card.className = 'rolodex-card';
|
|
77
|
+
|
|
78
|
+
wrapper.appendChild(nav);
|
|
79
|
+
wrapper.appendChild(card);
|
|
80
|
+
|
|
81
|
+
let index = 0;
|
|
82
|
+
|
|
83
|
+
const show = i => {
|
|
84
|
+
index = ((i % bindings.length) + bindings.length) % bindings.length;
|
|
85
|
+
const row = bindings[index];
|
|
86
|
+
|
|
87
|
+
card.innerHTML = '';
|
|
88
|
+
const dl = document.createElement('dl');
|
|
89
|
+
vars.forEach(v => {
|
|
90
|
+
const cell = row[v];
|
|
91
|
+
if (!cell) return;
|
|
92
|
+
const dt = document.createElement('dt');
|
|
93
|
+
dt.textContent = v;
|
|
94
|
+
const dd = document.createElement('dd');
|
|
95
|
+
renderCellInto(dd, cell);
|
|
96
|
+
dl.appendChild(dt);
|
|
97
|
+
dl.appendChild(dd);
|
|
98
|
+
});
|
|
99
|
+
card.appendChild(dl);
|
|
100
|
+
counter.textContent = `${index + 1} of ${bindings.length}`;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
prevBtn.addEventListener('click', () => show(index - 1));
|
|
104
|
+
nextBtn.addEventListener('click', () => show(index + 1));
|
|
105
|
+
|
|
106
|
+
wrapper.addEventListener('keydown', e => {
|
|
107
|
+
if (e.key === 'ArrowLeft') { e.preventDefault(); show(index - 1); }
|
|
108
|
+
else if (e.key === 'ArrowRight') { e.preventDefault(); show(index + 1); }
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
card.addEventListener('click', e => {
|
|
112
|
+
if (e.target.closest('a')) return;
|
|
113
|
+
const row = bindings[index];
|
|
114
|
+
const lastVar = vars[vars.length - 1];
|
|
115
|
+
const cell = row[lastVar];
|
|
116
|
+
host?.dispatchEvent(new CustomEvent('sol-select', {
|
|
117
|
+
bubbles: true, composed: true,
|
|
118
|
+
detail: { value: cell?.value ?? '', row, index },
|
|
119
|
+
}));
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
show(0);
|
|
123
|
+
|
|
124
|
+
adopt(container.getRootNode(), { sheet: ROLODEX_SHEET, css: ROLODEX_CSS });
|
|
125
|
+
container.appendChild(wrapper);
|
|
126
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { adopt } from '../../core/adopt.js';
|
|
2
|
+
import { CSS as SELECT_VIEW_CSS, sheet as SELECT_SHEET } from '../styles/view-select-css.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Built-in view renderer for sol-query — "select".
|
|
6
|
+
* Renders results as a <select> dropdown. Each row becomes one <option>.
|
|
7
|
+
*
|
|
8
|
+
* Display rules:
|
|
9
|
+
* - 1 column → option text = that value (URIs are shortened for readability,
|
|
10
|
+
* full URI stored in option.value).
|
|
11
|
+
* - 2 columns → option text = col[0], option.value = col[1].
|
|
12
|
+
* - 3+ cols → option text = "col0 — col1", value = last column.
|
|
13
|
+
*
|
|
14
|
+
* Selection dispatches 'sol-select' with { value, row, index } on the host element
|
|
15
|
+
* so pages can react (filter another query, navigate, etc.). URI values also
|
|
16
|
+
* re-fire the sol-query's own dereference pathway by setting host endpoint, if
|
|
17
|
+
* the host element looks like a <sol-query>.
|
|
18
|
+
*
|
|
19
|
+
* Usage: <sol-query view="select" endpoint="…" wanted="…"></sol-query>
|
|
20
|
+
*/
|
|
21
|
+
export function render(container, data, host) {
|
|
22
|
+
const vars = data.head.vars;
|
|
23
|
+
const bindings = data.results.bindings;
|
|
24
|
+
if (!bindings?.length) {
|
|
25
|
+
container.textContent = 'No results';
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const shortUri = v =>
|
|
30
|
+
v.replace(/.*[/#]([^/#]+)\/?$/, '$1') || v;
|
|
31
|
+
|
|
32
|
+
const cellText = cell => {
|
|
33
|
+
if (!cell) return '';
|
|
34
|
+
if (cell.type === 'uri') return shortUri(cell.value);
|
|
35
|
+
return cell.value ?? '';
|
|
36
|
+
};
|
|
37
|
+
const cellValue = cell => (cell ? cell.value ?? '' : '');
|
|
38
|
+
|
|
39
|
+
const select = document.createElement('select');
|
|
40
|
+
select.className = 'sol-view-select';
|
|
41
|
+
|
|
42
|
+
const placeholder = document.createElement('option');
|
|
43
|
+
placeholder.value = '';
|
|
44
|
+
placeholder.textContent = `— ${bindings.length} result${bindings.length === 1 ? '' : 's'} —`;
|
|
45
|
+
placeholder.disabled = true;
|
|
46
|
+
placeholder.selected = true;
|
|
47
|
+
select.appendChild(placeholder);
|
|
48
|
+
|
|
49
|
+
bindings.forEach((row, i) => {
|
|
50
|
+
const opt = document.createElement('option');
|
|
51
|
+
const cells = vars.map(v => row[v]);
|
|
52
|
+
|
|
53
|
+
if (vars.length === 1) {
|
|
54
|
+
opt.textContent = cellText(cells[0]);
|
|
55
|
+
opt.value = cellValue(cells[0]);
|
|
56
|
+
} else if (vars.length === 2) {
|
|
57
|
+
opt.textContent = cellText(cells[0]);
|
|
58
|
+
opt.value = cellValue(cells[1]);
|
|
59
|
+
} else {
|
|
60
|
+
opt.textContent = `${cellText(cells[0])} — ${cellText(cells[1])}`;
|
|
61
|
+
opt.value = cellValue(cells[cells.length - 1]);
|
|
62
|
+
}
|
|
63
|
+
opt.dataset.rowIndex = String(i);
|
|
64
|
+
select.appendChild(opt);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
select.addEventListener('change', () => {
|
|
68
|
+
const i = parseInt(select.options[select.selectedIndex].dataset.rowIndex, 10);
|
|
69
|
+
const row = bindings[i];
|
|
70
|
+
const value = select.value;
|
|
71
|
+
host?.dispatchEvent(new CustomEvent('sol-select', {
|
|
72
|
+
bubbles: true, composed: true,
|
|
73
|
+
detail: { value, row, index: i },
|
|
74
|
+
}));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
adopt(container.getRootNode(), { sheet: SELECT_SHEET, css: SELECT_VIEW_CSS });
|
|
78
|
+
container.appendChild(select);
|
|
79
|
+
}
|