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.
Files changed (150) hide show
  1. package/README.md +7 -0
  2. package/core/activate.js +27 -0
  3. package/core/adopt.js +71 -0
  4. package/core/auth-core.js +73 -0
  5. package/core/auth-fetch.js +154 -0
  6. package/core/component-mount.js +110 -0
  7. package/core/defaults.js +48 -0
  8. package/core/define.js +15 -0
  9. package/core/display-target.js +166 -0
  10. package/core/edit-placements.js +28 -0
  11. package/core/editor-self.js +127 -0
  12. package/core/editor.js +162 -0
  13. package/core/events.js +27 -0
  14. package/core/extension-points.js +189 -0
  15. package/core/form-utils.js +210 -0
  16. package/core/from-query.js +138 -0
  17. package/core/from-rdf.js +52 -0
  18. package/core/here.js +33 -0
  19. package/core/include-core.js +73 -0
  20. package/core/inrupt-global.js +18 -0
  21. package/core/menu-consumer.js +41 -0
  22. package/core/menu-rdf.js +154 -0
  23. package/core/pod-ops.js +392 -0
  24. package/core/pod-registry.js +82 -0
  25. package/core/popup-proxy.js +255 -0
  26. package/core/rdf-core.js +280 -0
  27. package/core/rdf-render.js +136 -0
  28. package/core/rdf-utils.js +411 -0
  29. package/core/rdf.js +154 -0
  30. package/core/services.js +106 -0
  31. package/core/shape-to-form.js +741 -0
  32. package/core/sparql-safety.js +20 -0
  33. package/core/utils.js +196 -0
  34. package/dist/importmap-cdn.json +49 -0
  35. package/dist/importmap-local.json +49 -0
  36. package/dist/sol-loader.manifest.json +140 -0
  37. package/dist/vendor/@comunica-query-sparql.js +137851 -0
  38. package/dist/vendor/@inrupt-solid-client-authn-browser.js +7503 -0
  39. package/dist/vendor/dompurify.js +1476 -0
  40. package/dist/vendor/ical.js.js +9739 -0
  41. package/dist/vendor/marked.js +85 -0
  42. package/dist/vendor/n3.js +14670 -0
  43. package/dist/vendor/rdf-validate-shacl.js +6970 -0
  44. package/dist/vendor/rdflib.js +35172 -0
  45. package/dist/vendor/solid-logic.js +6819 -0
  46. package/dist/vendor/solid-ui.js +21945 -0
  47. package/node/sol-form.js +133 -0
  48. package/node/sol-include.js +55 -0
  49. package/node/sol-login.js +632 -0
  50. package/node/sol-menu.js +639 -0
  51. package/node/sol-query.js +116 -0
  52. package/package.json +133 -0
  53. package/web/menu-from-rdf.js +23 -0
  54. package/web/scripts/prefs.js +25 -0
  55. package/web/sol-accordion.js +114 -0
  56. package/web/sol-basic.js +50 -0
  57. package/web/sol-breadcrumb.js +131 -0
  58. package/web/sol-button.js +244 -0
  59. package/web/sol-calendar.js +465 -0
  60. package/web/sol-default.js +118 -0
  61. package/web/sol-dropdown-button.js +222 -0
  62. package/web/sol-feed.js +1336 -0
  63. package/web/sol-form.js +949 -0
  64. package/web/sol-full.js +43 -0
  65. package/web/sol-gallery.js +303 -0
  66. package/web/sol-include.js +246 -0
  67. package/web/sol-live-edit.js +415 -0
  68. package/web/sol-login.js +856 -0
  69. package/web/sol-menu.js +593 -0
  70. package/web/sol-modal.js +377 -0
  71. package/web/sol-pod-extras.js +17 -0
  72. package/web/sol-pod-ops.js +680 -0
  73. package/web/sol-pod.js +1039 -0
  74. package/web/sol-query.js +546 -0
  75. package/web/sol-rolodex.js +95 -0
  76. package/web/sol-search.js +402 -0
  77. package/web/sol-settings.js +199 -0
  78. package/web/sol-solidos.js +93 -0
  79. package/web/sol-tabs.js +445 -0
  80. package/web/sol-time.js +194 -0
  81. package/web/sol-tree-edit.js +492 -0
  82. package/web/sol-wac.js +456 -0
  83. package/web/sol-weather.js +337 -0
  84. package/web/sol-window.js +142 -0
  85. package/web/styles/buttons-css.js +108 -0
  86. package/web/styles/help.css +242 -0
  87. package/web/styles/root.css +112 -0
  88. package/web/styles/sol-accordion-css.js +97 -0
  89. package/web/styles/sol-calendar-css.js +154 -0
  90. package/web/styles/sol-feed-css.js +475 -0
  91. package/web/styles/sol-form-css.js +471 -0
  92. package/web/styles/sol-gallery-css.js +181 -0
  93. package/web/styles/sol-include-css.js +95 -0
  94. package/web/styles/sol-live-edit-css.js +84 -0
  95. package/web/styles/sol-live-edit.css +101 -0
  96. package/web/styles/sol-login-css.js +116 -0
  97. package/web/styles/sol-menu-css.js +145 -0
  98. package/web/styles/sol-modal-css.js +134 -0
  99. package/web/styles/sol-pod-css.js +187 -0
  100. package/web/styles/sol-pod-modal-css.js +203 -0
  101. package/web/styles/sol-query-css.js +140 -0
  102. package/web/styles/sol-query-help.css +267 -0
  103. package/web/styles/sol-query-one-pager.css +67 -0
  104. package/web/styles/sol-search-css.js +157 -0
  105. package/web/styles/sol-solidos-css.js +7 -0
  106. package/web/styles/sol-tabs-css.js +114 -0
  107. package/web/styles/sol-time-css.js +30 -0
  108. package/web/styles/sol-wac-css.js +73 -0
  109. package/web/styles/sol-weather-css.js +59 -0
  110. package/web/styles/solid-logo.svg +9 -0
  111. package/web/styles/view-accordion-css.js +66 -0
  112. package/web/styles/view-anchorlist-css.js +22 -0
  113. package/web/styles/view-autocomplete-css.js +59 -0
  114. package/web/styles/view-rolodex-css.js +102 -0
  115. package/web/styles/view-select-css.js +21 -0
  116. package/web/utils/calendar-fetch.js +388 -0
  117. package/web/utils/code-mirror-editor.js +82 -0
  118. package/web/utils/commons-fetch.js +108 -0
  119. package/web/utils/feed-edit.js +159 -0
  120. package/web/utils/feed-edit.smoke.mjs +74 -0
  121. package/web/utils/feed-fetch.js +573 -0
  122. package/web/utils/live-edit-help/csv.js +64 -0
  123. package/web/utils/live-edit-help/graphviz.js +41 -0
  124. package/web/utils/live-edit-help/jsonld.js +55 -0
  125. package/web/utils/live-edit-help/markdown.js +52 -0
  126. package/web/utils/live-edit-help/mermaid.js +48 -0
  127. package/web/utils/live-edit-help/turtle.js +85 -0
  128. package/web/utils/rdf-config.js +125 -0
  129. package/web/utils/renderers/csv.js +124 -0
  130. package/web/utils/renderers/d3-force.js +82 -0
  131. package/web/utils/renderers/graphviz.js +13 -0
  132. package/web/utils/renderers/html.js +10 -0
  133. package/web/utils/renderers/jsonld.js +63 -0
  134. package/web/utils/renderers/markdown.js +19 -0
  135. package/web/utils/renderers/mermaid.js +54 -0
  136. package/web/utils/renderers/turtle.js +51 -0
  137. package/web/utils/sol-query-triple-patterns.js +151 -0
  138. package/web/utils/sol-query-ui.js +250 -0
  139. package/web/utils/sol-query-views.js +32 -0
  140. package/web/views/_helpers.js +34 -0
  141. package/web/views/accordion.js +133 -0
  142. package/web/views/anchorlist.js +59 -0
  143. package/web/views/auto-complete.js +183 -0
  144. package/web/views/dl.js +38 -0
  145. package/web/views/list.js +19 -0
  146. package/web/views/menu.js +56 -0
  147. package/web/views/rolodex.js +126 -0
  148. package/web/views/select.js +79 -0
  149. package/web/views/table.js +73 -0
  150. package/web/views/tabs.js +57 -0
@@ -0,0 +1,210 @@
1
+ // Environment-agnostic helpers for walking ui:Form definitions and
2
+ // managing rdf:Collection data in an rdflib store. Used by both the
3
+ // browser <sol-form> component and the Node.js sol-form API.
4
+
5
+ import * as $rdf from 'rdflib';
6
+
7
+ export const UI = 'http://www.w3.org/ns/ui#';
8
+ export const RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
9
+
10
+ // Thin shim so the rest of the file reads the same whether the caller
11
+ // passes the core/rdf.js singleton or raw rdflib.
12
+ const rdf = {
13
+ sym: (u) => $rdf.sym(u),
14
+ literal: (v, d) => $rdf.literal(v, d),
15
+ blankNode: (id) => $rdf.blankNode(id),
16
+ Collection: $rdf.Collection,
17
+ };
18
+
19
+ export const MAX_DEPTH = 5;
20
+
21
+ export function fieldType(store, field) {
22
+ const typeP = rdf.sym(RDF + 'type');
23
+ const types = store.each(field, typeP);
24
+ for (const t of types) {
25
+ if (t.value && t.value.startsWith(UI)) return t.value;
26
+ }
27
+ return UI + 'SingleLineTextField';
28
+ }
29
+
30
+ export function readFormParts(store, form) {
31
+ const node = store.any(form, rdf.sym(UI + 'parts'));
32
+ if (node && node.elements) return node.elements;
33
+ const alt = store.each(form, rdf.sym(UI + 'part'));
34
+ return alt.length ? alt : [];
35
+ }
36
+
37
+ export function readList(store, subject, property, doc) {
38
+ const val = store.any(subject, property, null, doc);
39
+ if (!val) return [];
40
+ if (val.elements) return [...val.elements];
41
+ const FIRST = rdf.sym(RDF + 'first'), REST = rdf.sym(RDF + 'rest'), NIL = rdf.sym(RDF + 'nil');
42
+ const result = [];
43
+ let cur = val;
44
+ while (cur && cur.termType === 'BlankNode') {
45
+ const f = store.any(cur, FIRST);
46
+ if (!f) break;
47
+ result.push(f);
48
+ const next = store.any(cur, REST);
49
+ if (!next || next.value === NIL.value) break;
50
+ cur = next;
51
+ }
52
+ return result;
53
+ }
54
+
55
+ export function syncCollection(store, subject, property, items, doc) {
56
+ for (const st of [...store.statementsMatching(subject, property, null, doc)]) {
57
+ if (st.object.termType === 'BlankNode') removeChain(store, st.object, doc);
58
+ store.remove(st);
59
+ }
60
+ if (!items.length) return;
61
+ const col = new (rdf.Collection)(items);
62
+ store.add(subject, property, col, doc);
63
+ }
64
+
65
+ export function removeChain(store, node, doc) {
66
+ const FIRST = rdf.sym(RDF + 'first'), REST = rdf.sym(RDF + 'rest');
67
+ let cur = node;
68
+ while (cur && cur.termType === 'BlankNode') {
69
+ const rests = store.statementsMatching(cur, REST, null, doc);
70
+ const next = rests.length ? rests[0].object : null;
71
+ for (const s of store.statementsMatching(cur, FIRST, null, doc)) store.remove(s);
72
+ for (const s of rests) store.remove(s);
73
+ if (!next || next.termType !== 'BlankNode') break;
74
+ cur = next;
75
+ }
76
+ }
77
+
78
+ export function removeItemData(store, item, doc) {
79
+ for (const st of [...store.statementsMatching(item, null, null, doc)]) {
80
+ if (st.object.termType === 'BlankNode') removeItemData(store, st.object, doc);
81
+ if (st.object.elements) {
82
+ for (const el of st.object.elements) {
83
+ if (el.termType === 'BlankNode') removeItemData(store, el, doc);
84
+ }
85
+ }
86
+ }
87
+ for (const s of [...store.statementsMatching(item, null, null, doc)]) store.remove(s);
88
+ }
89
+
90
+ export function setDefaults(store, form, subject, doc) {
91
+ for (const field of readFormParts(store, form)) {
92
+ const property = store.any(field, rdf.sym(UI + 'property'));
93
+ const dflt = store.any(field, rdf.sym(UI + 'default'));
94
+ if (!property || !dflt) continue;
95
+ const nn = store.anyValue(field, rdf.sym(UI + 'namedNode')) === 'true';
96
+ store.add(subject, property, nn ? rdf.sym(dflt.value) : rdf.literal(dflt.value), doc);
97
+ }
98
+ }
99
+
100
+ // Walk a form definition and populate an rdflib store from a plain JS
101
+ // data object. Used by the Node.js programmatic API; the browser version
102
+ // uses DOM-based rendering instead.
103
+ export function populateStore(store, form, subject, doc, data, depth) {
104
+ if (!data || depth > MAX_DEPTH) return;
105
+ const fields = readFormParts(store, form);
106
+
107
+ for (const field of fields) {
108
+ const type = fieldType(store, field);
109
+ const property = store.any(field, rdf.sym(UI + 'property'));
110
+
111
+ if (type === UI + 'Form' || type === UI + 'Group') {
112
+ populateStore(store, field, subject, doc, data, depth);
113
+ continue;
114
+ }
115
+
116
+ if (type === UI + 'Options') {
117
+ const dependsOn = store.any(field, rdf.sym(UI + 'dependingOn'));
118
+ if (!dependsOn) continue;
119
+ const cur = store.any(subject, dependsOn, null, doc);
120
+ if (!cur) continue;
121
+ const cases = store.each(field, rdf.sym(UI + 'case'));
122
+ for (const c of cases) {
123
+ const forVal = store.any(c, rdf.sym(UI + 'for'));
124
+ if (forVal && forVal.value === cur.value) {
125
+ const useForm = store.any(c, rdf.sym(UI + 'use'));
126
+ if (useForm) populateStore(store, useForm, subject, doc, data, depth);
127
+ break;
128
+ }
129
+ }
130
+ continue;
131
+ }
132
+
133
+ if (!property) continue;
134
+ const localName = property.value.replace(/.*[/#]/, '');
135
+
136
+ if (type === UI + 'Multiple') {
137
+ const arr = data[localName];
138
+ if (!Array.isArray(arr) || !arr.length) continue;
139
+ const partForm = store.any(field, rdf.sym(UI + 'part'));
140
+ if (!partForm) continue;
141
+ const items = [];
142
+ for (const itemData of arr) {
143
+ const node = rdf.blankNode();
144
+ setDefaults(store, partForm, node, doc);
145
+ populateStore(store, partForm, node, doc, itemData, depth + 1);
146
+ items.push(node);
147
+ }
148
+ syncCollection(store, subject, property, items, doc);
149
+ continue;
150
+ }
151
+
152
+ if (type === UI + 'Choice') {
153
+ const val = data[localName];
154
+ for (const s of [...store.statementsMatching(subject, property, null, doc)]) store.remove(s);
155
+ if (val == null) {
156
+ const dflt = store.any(field, rdf.sym(UI + 'default'));
157
+ if (dflt) {
158
+ const nn = store.anyValue(field, rdf.sym(UI + 'namedNode')) === 'true';
159
+ store.add(subject, property, nn ? rdf.sym(dflt.value) : rdf.literal(dflt.value), doc);
160
+ }
161
+ continue;
162
+ }
163
+ const nn = store.anyValue(field, rdf.sym(UI + 'namedNode')) === 'true';
164
+ const resolved = nn ? resolveChoiceValue(store, field, String(val)) : null;
165
+ if (nn && resolved) {
166
+ store.add(subject, property, rdf.sym(resolved), doc);
167
+ } else {
168
+ store.add(subject, property, rdf.literal(String(val)), doc);
169
+ }
170
+ continue;
171
+ }
172
+
173
+ const val = data[localName];
174
+ for (const s of [...store.statementsMatching(subject, property, null, doc)]) store.remove(s);
175
+ if (val != null && String(val).trim()) {
176
+ store.add(subject, property, rdf.literal(String(val)), doc);
177
+ } else {
178
+ const dflt = store.anyValue(field, rdf.sym(UI + 'default'));
179
+ if (dflt) store.add(subject, property, rdf.literal(dflt), doc);
180
+ }
181
+ }
182
+ }
183
+
184
+ // Resolve a short Choice value (e.g. "ui:Link" or "Link") to the full
185
+ // URI by matching against the declared ui:option values.
186
+ function resolveChoiceValue(store, field, val) {
187
+ const opts = store.each(field, rdf.sym(UI + 'option'));
188
+ for (const opt of opts) {
189
+ if (opt.value === val) return val;
190
+ if (opt.value.endsWith('#' + val) || opt.value.endsWith('/' + val)) return opt.value;
191
+ const local = opt.value.replace(/.*[/#]/, '');
192
+ if (local === val) return opt.value;
193
+ }
194
+ if (val.includes(':') || val.includes('/')) return val;
195
+ return null;
196
+ }
197
+
198
+ // Find the first ui:Form node in a store, optionally matching a fragment.
199
+ export function findForm(store, sourceUri) {
200
+ const formType = rdf.sym(UI + 'Form');
201
+ const typeP = rdf.sym(RDF + 'type');
202
+ const docUrl = sourceUri.split('#')[0];
203
+ const fragment = sourceUri.includes('#') ? sourceUri.split('#')[1] : null;
204
+ if (fragment) {
205
+ const candidate = rdf.sym(docUrl + '#' + fragment);
206
+ if (store.holds(candidate, typeP, formType)) return candidate;
207
+ }
208
+ const forms = store.each(null, typeP, formType);
209
+ return forms[0] || null;
210
+ }
@@ -0,0 +1,138 @@
1
+ // core/from-query.js — the `data-from-query` capability attribute (part of the
2
+ // `sparql` capability). Makes ANY element query-driven WITHOUT pulling in the
3
+ // <sol-query> component: it runs the query through the shared engine
4
+ // (core/rdf-utils.js) and renders the result INTO the host element based on the
5
+ // host's TAG — there is no `view` here. The split with <sol-query> is deliberate:
6
+ // • want swc to choose the view → use the <sol-query> element (view-driven)
7
+ // • want to choose yourself → use this attribute (container-driven)
8
+ //
9
+ // <ul data-from-query endpoint="data.ttl" sparql="SELECT ?name …"></ul> <!-- → <li> per row -->
10
+ // <select data-from-query endpoint="…" sparql="SELECT ?label ?uri …"></select> <!-- → <option> per row -->
11
+ // <div data-from-query endpoint="…" sparql="…"></div> <!-- no DOM: read el.swcData -->
12
+ //
13
+ // Output by host tag: <ul>/<ol> → one <li> per row; <select> → one <option> per
14
+ // row (+ a `sol-select` event on change); anything else → nothing is rendered and
15
+ // the W3C SPARQL 1.1 Query Results JSON is left on `el.swcData` for you to use.
16
+ //
17
+ // Config attributes (endpoint, pattern, sparql, query, var-<name>) may be written
18
+ // bare OR `data-`-prefixed (data-endpoint, …, data-var-<name>) to keep the markup
19
+ // spec-valid HTML; bare wins if both are given. `data-from-query` is the trigger.
20
+ import { activate } from './activate.js';
21
+ import { rdf } from './rdf.js';
22
+ import { getAuthFetch } from './auth-fetch.js';
23
+ import { substituteVariables, assertSafeQuery } from './sparql-safety.js';
24
+ import { execSparql, loadRdfStore, parsePatternParts, matchStore, fetchQueryFromRdf } from './rdf-utils.js';
25
+
26
+ // Config attributes may be written bare (`endpoint`) or `data-`-prefixed
27
+ // (`data-endpoint`) — the latter keeps the host markup spec-valid HTML. Bare
28
+ // wins when both are present. (`data-from-query` itself stays the trigger.)
29
+ function attr(el, name) {
30
+ const v = el.getAttribute(name);
31
+ return v != null ? v : el.getAttribute('data-' + name);
32
+ }
33
+ function readVars(el) {
34
+ const vars = {};
35
+ // `var-foo` or `data-var-foo`; data-* is read first so a bare `var-foo` wins.
36
+ for (const a of Array.from(el.attributes)) if (a.name.indexOf('data-var-') === 0) vars[a.name.slice(9)] = a.value;
37
+ for (const a of Array.from(el.attributes)) if (a.name.indexOf('var-') === 0) vars[a.name.slice(4)] = a.value;
38
+ return vars;
39
+ }
40
+ function endpointsOf(el) {
41
+ const raw = attr(el, 'endpoint');
42
+ return raw ? raw.trim().split(/[\s,]+/).filter(Boolean) : [];
43
+ }
44
+ function isStoredRef(s) { return !/\s/.test(s) && /^https?:\/\/|^\/|^\.\.?\//.test(s.trim()); }
45
+
46
+ // Build the W3C SPARQL Results JSON for the element's query config (same engine
47
+ // the <sol-query> element uses: SPARQL via execSparql, triple-pattern via rdflib).
48
+ async function buildData(el) {
49
+ const eps = endpointsOf(el);
50
+ if (!eps.length) throw new Error('data-from-query needs an `endpoint`');
51
+ const pattern = attr(el, 'pattern');
52
+ const sparql = attr(el, 'sparql') || attr(el, 'query') ||
53
+ (pattern ? '' : (el.getAttribute('data-from-query') || ''));
54
+
55
+ if (sparql) {
56
+ let q = isStoredRef(sparql) ? await fetchQueryFromRdf(sparql) : sparql;
57
+ q = substituteVariables(q, readVars(el));
58
+ assertSafeQuery(q);
59
+ const target = eps.length > 1 ? eps : eps[0];
60
+ const fetchUrl = Array.isArray(target) ? target[0] : target;
61
+ return execSparql(q, target, getAuthFetch(fetchUrl));
62
+ }
63
+ if (pattern) {
64
+ const store = await loadRdfStore(eps[0]);
65
+ const [s, p, o] = parsePatternParts(pattern, rdf, {}, eps[0]);
66
+ return matchStore(store, s, p, o);
67
+ }
68
+ throw new Error('data-from-query needs `sparql`, `query`, `pattern`, or a query value');
69
+ }
70
+
71
+ // ── render: the host's tag decides the shape (no `view`) ─────────────────────
72
+ function cellText(cell) {
73
+ if (!cell) return '';
74
+ if (cell.type === 'uri') return cell.value.replace(/.*[/#]([^/#]+)\/?$/, '$1') || cell.value;
75
+ return cell.value ?? '';
76
+ }
77
+ function cellValue(cell) { return cell ? (cell.value ?? '') : ''; }
78
+
79
+ // Display text for a row across however many SELECT variables there are.
80
+ function rowText(vars, row) {
81
+ return vars.map((v) => cellText(row[v])).filter(Boolean).join(' — ');
82
+ }
83
+
84
+ function fillList(el, vars, rows) {
85
+ el.replaceChildren(...rows.map((row) => {
86
+ const li = document.createElement('li');
87
+ li.textContent = rowText(vars, row);
88
+ return li;
89
+ }));
90
+ }
91
+
92
+ function fillSelect(el, vars, rows) {
93
+ const opts = rows.map((row, i) => {
94
+ const opt = document.createElement('option');
95
+ // 1 col → text & value are the cell; 2 cols → text col0, value col1;
96
+ // 3+ cols → text "col0 — col1", value is the last column.
97
+ if (vars.length === 1) { opt.textContent = cellText(row[vars[0]]); opt.value = cellValue(row[vars[0]]); }
98
+ else if (vars.length === 2) { opt.textContent = cellText(row[vars[0]]); opt.value = cellValue(row[vars[1]]); }
99
+ else { opt.textContent = `${cellText(row[vars[0]])} — ${cellText(row[vars[1]])}`; opt.value = cellValue(row[vars[vars.length - 1]]); }
100
+ opt.dataset.rowIndex = String(i);
101
+ return opt;
102
+ });
103
+ const placeholder = document.createElement('option');
104
+ placeholder.value = ''; placeholder.disabled = true; placeholder.selected = true;
105
+ placeholder.textContent = `— ${rows.length} result${rows.length === 1 ? '' : 's'} —`;
106
+ el.replaceChildren(placeholder, ...opts);
107
+ el._swcRows = rows;
108
+ if (!el._swcSelectWired) { // emit sol-select like the select view
109
+ el._swcSelectWired = true;
110
+ el.addEventListener('change', () => {
111
+ const chosen = el.options[el.selectedIndex];
112
+ const i = chosen ? parseInt(chosen.dataset.rowIndex, 10) : -1;
113
+ el.dispatchEvent(new CustomEvent('sol-select', {
114
+ bubbles: true, composed: true,
115
+ detail: { value: el.value, row: el._swcRows ? el._swcRows[i] : undefined, index: i },
116
+ }));
117
+ });
118
+ }
119
+ }
120
+
121
+ // The host's tag picks the shape. Unknown tags render nothing — the W3C JSON is
122
+ // left on `el.swcData` for the page to consume.
123
+ function renderInto(el, data) {
124
+ el.swcData = data;
125
+ const vars = data.head.vars;
126
+ const rows = data.results.bindings;
127
+ const tag = el.localName;
128
+ if (tag === 'ul' || tag === 'ol') return fillList(el, vars, rows);
129
+ if (tag === 'select') return fillSelect(el, vars, rows);
130
+ // anything else: leave the DOM untouched; the JSON is on el.swcData.
131
+ }
132
+
133
+ activate('[data-from-query]', (el) => {
134
+ if (el.localName === 'sol-query') return; // the element handles itself
135
+ buildData(el)
136
+ .then((data) => renderInto(el, data))
137
+ .catch((e) => { el.innerHTML = `<div class="error">${(e && e.message) || e}</div>`; console.error('[data-from-query]', e); });
138
+ });
@@ -0,0 +1,52 @@
1
+ // core/from-rdf.js — the `data-from-rdf` capability attribute (part of the `rdf`
2
+ // capability). It LOADS RDF from a Turtle document (it does not render) and hands
3
+ // the element the loaded data as a **W3C SPARQL 1.1 Query Results JSON** object —
4
+ // the SAME format `data-from-query` returns — expressed as the document's triples
5
+ // bound to `?s ?p ?o`. The calling component then does something with it.
6
+ //
7
+ // Delivery (symmetric with data-from-query's custom views): the object is set on
8
+ // the element as `el.swcData`, and, if the element carries `view="…/view.js"`,
9
+ // that module's `render(container, data, el)` is called with it. No built-in
10
+ // rendering — `data-from-rdf` never paints HTML itself.
11
+ import { activate } from './activate.js';
12
+ import { rdf } from './rdf.js';
13
+
14
+ function termToBinding(t) {
15
+ if (!t) return undefined;
16
+ if (t.termType === 'BlankNode') return { type: 'bnode', value: t.value };
17
+ if (t.termType === 'Literal') {
18
+ const b = { type: 'literal', value: t.value };
19
+ if (t.language) b['xml:lang'] = t.language;
20
+ else if (t.datatype && t.datatype.value &&
21
+ t.datatype.value !== 'http://www.w3.org/2001/XMLSchema#string') b.datatype = t.datatype.value;
22
+ return b;
23
+ }
24
+ return { type: 'uri', value: t.value }; // NamedNode
25
+ }
26
+
27
+ // The W3C SPARQL Results JSON envelope for a set of triples (vars s, p, o).
28
+ function toW3C(statements) {
29
+ const bindings = statements.map((st) => ({
30
+ s: termToBinding(st.subject), p: termToBinding(st.predicate), o: termToBinding(st.object),
31
+ }));
32
+ return { head: { vars: ['s', 'p', 'o'] }, results: { bindings } };
33
+ }
34
+
35
+ activate('[data-from-rdf]', async (el) => {
36
+ const ref = el.getAttribute('data-from-rdf');
37
+ if (!ref) return;
38
+ const doc = new URL(ref, document.baseURI).href.split('#')[0];
39
+ try {
40
+ await rdf.load(doc);
41
+ const data = toW3C(rdf.store.statementsMatching(null, null, null, rdf.sym(doc)));
42
+ el.swcData = data; // host component can read it
43
+ const view = el.getAttribute('view');
44
+ if (view) {
45
+ const mod = await import(new URL(view, document.baseURI).href);
46
+ const fn = mod.render ?? mod.default;
47
+ if (typeof fn === 'function') fn(el, data, el); // (container, data, el)
48
+ }
49
+ } catch (e) {
50
+ console.error('[data-from-rdf] failed to load', doc, e);
51
+ }
52
+ });
package/core/here.js ADDED
@@ -0,0 +1,33 @@
1
+ // Resolve a sibling module URL that works in both ESM and IIFE/UMD contexts.
2
+ //
3
+ // Background: `import.meta.url` is the natural way to locate sibling modules,
4
+ // but esbuild's IIFE output (used by podz when it bundles sol-components
5
+ // source into a single script) flattens `import.meta.url` to `undefined`.
6
+ // rollup's UMD output is the same. Result: dynamic-import paths like
7
+ // `new URL('./sibling.js', import.meta.url)` silently break in those bundles.
8
+ //
9
+ // `siblingUrl(name, importMetaUrl)` accepts the caller's `import.meta.url`
10
+ // (which the bundler may eat) and falls back through:
11
+ // 1. import.meta.url — when present (real ESM in the browser)
12
+ // 2. document.currentScript.src — when the host script is a classic <script>
13
+ // 3. document.baseURI — last-resort: the page's base URL
14
+ //
15
+ // Callers pass `import.meta.url` explicitly so the bundler can statically see
16
+ // the use-site and substitute when it can; the fallback covers the rest.
17
+
18
+ export function siblingUrl(name, importMetaUrl) {
19
+ // import.meta.url, when present, is always best.
20
+ if (importMetaUrl) {
21
+ try { return new URL(name, importMetaUrl).href; } catch {}
22
+ }
23
+ // Inside a classic <script> tag we have document.currentScript.
24
+ if (typeof document !== 'undefined') {
25
+ const cur = document.currentScript;
26
+ if (cur && cur.src) {
27
+ try { return new URL(name, cur.src).href; } catch {}
28
+ }
29
+ // Final fallback: resolve against the page itself.
30
+ try { return new URL(name, document.baseURI).href; } catch {}
31
+ }
32
+ return name;
33
+ }
@@ -0,0 +1,73 @@
1
+ let _marked = null;
2
+
3
+ export async function getMarked() {
4
+ if (_marked) return _marked;
5
+ const g = typeof globalThis !== 'undefined' ? globalThis : {};
6
+ if (g.marked?.parse) { _marked = g.marked; return _marked; }
7
+ try {
8
+ const mod = await import('marked');
9
+ _marked = mod.marked ?? mod.default ?? mod;
10
+ return _marked;
11
+ } catch {}
12
+ return null;
13
+ }
14
+
15
+ export function detectIncludeFormat(contentType, url) {
16
+ const ct = (contentType || '').split(';')[0].trim();
17
+ const ext = url.split('?')[0].split('#')[0].split('.').pop().toLowerCase();
18
+ const isMarkdown = ct.includes('markdown') || ct.includes('text/x-markdown')
19
+ || ['md', 'markdown'].includes(ext);
20
+ const isHtml = ct.includes('html') || ext === 'html' || ext === 'htm';
21
+ return { isMarkdown, isHtml };
22
+ }
23
+
24
+ /**
25
+ * Fetch a URL and return processed content.
26
+ * @param {string} source - URL to fetch
27
+ * @param {object} [opts]
28
+ * @param {boolean} [opts.raw] - return raw text without processing
29
+ * @param {boolean} [opts.trusted] - skip sanitization
30
+ * @param {function} [opts.sanitize] - async (html) => sanitized html
31
+ * @param {function} [opts.fetchFn] - fetch implementation
32
+ * @param {AbortSignal} [opts.signal] - cancel the underlying fetch
33
+ * @returns {Promise<{type: 'html'|'raw', content: string}>}
34
+ */
35
+ export async function fetchIncludeContent(source, { raw = false, trusted = false, sanitize, fetchFn = globalThis.fetch, signal } = {}) {
36
+ const resp = await fetchFn(source, signal ? { signal } : undefined);
37
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
38
+ const ct = (resp.headers.get('content-type') || '').split(';')[0].trim();
39
+ const text = await resp.text();
40
+ const { isMarkdown, isHtml } = detectIncludeFormat(ct, source);
41
+
42
+ if (raw) return { type: 'raw', content: text };
43
+
44
+ if (isMarkdown) {
45
+ const markedLib = await getMarked();
46
+ if (!markedLib) throw new Error('marked library not available — add to importmap');
47
+ let html = typeof markedLib.parse === 'function' ? markedLib.parse(text) : markedLib(text);
48
+ if (!trusted && sanitize) html = await sanitize(html);
49
+ return { type: 'html', content: html };
50
+ }
51
+
52
+ if (isHtml) {
53
+ const html = (!trusted && sanitize) ? await sanitize(text) : text;
54
+ return { type: 'html', content: html };
55
+ }
56
+
57
+ return { type: 'raw', content: text };
58
+ }
59
+
60
+ /**
61
+ * Filter HTML with a CSS selector.
62
+ * @param {string} html - HTML string
63
+ * @param {string} selector - CSS selector
64
+ * @param {function} createContainer - (html) => DOM element with parsed content
65
+ * @returns {string|null} filtered HTML or null if no matches
66
+ */
67
+ export function filterWithSelector(html, selector, createContainer) {
68
+ if (!selector) return html;
69
+ const container = createContainer(html);
70
+ const els = Array.from(container.querySelectorAll(selector));
71
+ if (!els.length) return null;
72
+ return els.map(el => el.outerHTML).join('\n');
73
+ }
@@ -0,0 +1,18 @@
1
+ // core/inrupt-global.js — publish the inrupt Session class as the global
2
+ // <sol-login> expects, from the manifest-mapped ESM build.
3
+ //
4
+ // <sol-login> reads the inrupt Session class from `window.solidClientAuthn`
5
+ // (a UMD-style global) and throws if it is absent (see web/sol-login.js
6
+ // getSessionClass). But the loader manifest maps
7
+ // `@inrupt/solid-client-authn-browser` to an ESM build that sets no global, so
8
+ // the `auth` capability could only work when the page ALSO loaded a separate
9
+ // UMD <script> first. This shim imports the same mapped specifier and publishes
10
+ // it at `window.solidClientAuthn`, so `data-extend-with="auth"` is self-contained
11
+ // on every stage. It is listed BEFORE `sol-login` in the manifest's `auth`
12
+ // capability, so the global is set by the time sol-login runs.
13
+ import * as inrupt from '@inrupt/solid-client-authn-browser';
14
+
15
+ if (typeof window !== 'undefined' && !window.solidClientAuthn) {
16
+ // sol-login looks up `.Session`; the ESM namespace exposes it directly.
17
+ window.solidClientAuthn = inrupt;
18
+ }
@@ -0,0 +1,41 @@
1
+ // Shared registry of components that can consume a ui:Menu RDF document via
2
+ // `from-rdf` (sol-tabs, sol-menu, and SolMenu subclasses like sol-dropdown-button).
3
+ //
4
+ // Kept deliberately rdflib-free: a base component registers itself here at module
5
+ // load and otherwise stays declarative-only. The opt-in `web/menu-from-rdf.js`
6
+ // add-on calls installFromRdfLoader() with the rdflib-backed loader; we then set
7
+ // it on each consumer's static `fromRdfLoader`. Without that add-on imported,
8
+ // `from-rdf` is inert and rdflib never enters the module graph.
9
+ //
10
+ // The registry lives on a Symbol.for() global (mirroring the solid-logic store
11
+ // singleton) so a single page shares ONE registry even when the components and
12
+ // the add-on arrive as separately-built UMD bundles, each with its own inlined
13
+ // copy of this module. Wiring is order-independent: register-after-install and
14
+ // install-after-register both end up wired.
15
+
16
+ const KEY = Symbol.for('sol-components.menu-consumers');
17
+ const reg = (globalThis[KEY] ||= { consumers: new Set(), loader: null, pending: new Set() });
18
+
19
+ export function registerMenuConsumer(klass) {
20
+ reg.consumers.add(klass);
21
+ if (reg.loader) klass.fromRdfLoader = reg.loader; // add-on already active
22
+ }
23
+
24
+ // Called by a component from `_loadFromRdf` when no loader is installed yet:
25
+ // the element parks itself and renders nothing for now. If/when the add-on
26
+ // arrives it is driven via reload() — so activation is order-independent (the
27
+ // add-on may load before OR after the component, sync, deferred or as ESM).
28
+ // Returns true when parked (caller should return), false if a loader is ready.
29
+ export function deferUntilLoader(el) {
30
+ if (reg.loader) return false;
31
+ reg.pending.add(el);
32
+ return true;
33
+ }
34
+
35
+ export function installFromRdfLoader(loader) {
36
+ reg.loader = loader;
37
+ for (const klass of reg.consumers) klass.fromRdfLoader = loader;
38
+ const waiting = [...reg.pending];
39
+ reg.pending.clear();
40
+ for (const el of waiting) { try { el.reload?.(); } catch { /* el gone / not ready */ } }
41
+ }