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,116 @@
|
|
|
1
|
+
// Node.js version of sol-query — same options as the web component,
|
|
2
|
+
// returns plain JS data instead of rendering HTML.
|
|
3
|
+
//
|
|
4
|
+
// Usage:
|
|
5
|
+
// import { solQuery } from 'sol-components/node';
|
|
6
|
+
// const data = await solQuery({ endpoint, sparql: 'SELECT ...' });
|
|
7
|
+
// const data = await solQuery({ endpoint, pattern: '?s foaf:name ?o' });
|
|
8
|
+
// const data = await solQuery({ endpoint, sparql: 'SELECT ...', vars: { name: 'Alice' } });
|
|
9
|
+
|
|
10
|
+
import * as rdflib from 'rdflib';
|
|
11
|
+
import { toPlainResults, NativeSparqlAdapter } from '../core/utils.js';
|
|
12
|
+
import {
|
|
13
|
+
ACCEPT_TYPES,
|
|
14
|
+
detectFormat,
|
|
15
|
+
parsePatternParts,
|
|
16
|
+
patternVarNames,
|
|
17
|
+
matchStore,
|
|
18
|
+
isRdfDoc,
|
|
19
|
+
bindingsToResults,
|
|
20
|
+
selectVars,
|
|
21
|
+
} from '../core/rdf-core.js';
|
|
22
|
+
import {
|
|
23
|
+
assertSafeQuery,
|
|
24
|
+
sanitizeVarValue,
|
|
25
|
+
substituteVariables,
|
|
26
|
+
} from '../core/sparql-safety.js';
|
|
27
|
+
|
|
28
|
+
export { assertSafeQuery, sanitizeVarValue, substituteVariables };
|
|
29
|
+
|
|
30
|
+
// ─── Load & parse RDF from a URL ────────────────────────────────────────────
|
|
31
|
+
async function loadRdfStore(endpoint, fetchFn = globalThis.fetch) {
|
|
32
|
+
const store = rdflib.graph();
|
|
33
|
+
let lastError = null;
|
|
34
|
+
|
|
35
|
+
for (const accept of ACCEPT_TYPES) {
|
|
36
|
+
try {
|
|
37
|
+
const resp = await fetchFn(endpoint, { headers: { Accept: accept } });
|
|
38
|
+
if (!resp.ok) { lastError = new Error(`HTTP ${resp.status}`); if (resp.status === 405) break; continue; }
|
|
39
|
+
const text = await resp.text();
|
|
40
|
+
const ct = (resp.headers.get('content-type') || '').split(';')[0].trim();
|
|
41
|
+
if (ct.includes('html') || /<html/i.test(text)) { lastError = new Error('Server returned HTML'); continue; }
|
|
42
|
+
try {
|
|
43
|
+
rdflib.parse(text, store, endpoint, detectFormat(ct, accept));
|
|
44
|
+
return store;
|
|
45
|
+
} catch (parseErr) { lastError = parseErr; }
|
|
46
|
+
} catch (err) { lastError = err; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Last resort: no Accept header
|
|
50
|
+
try {
|
|
51
|
+
const resp = await fetchFn(endpoint);
|
|
52
|
+
if (resp.ok) {
|
|
53
|
+
const text = await resp.text();
|
|
54
|
+
const ct = (resp.headers.get('content-type') || '').split(';')[0].trim();
|
|
55
|
+
if (!ct.includes('html') && !/<html/i.test(text)) {
|
|
56
|
+
rdflib.parse(text, store, endpoint, detectFormat(ct, ''));
|
|
57
|
+
return store;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
} catch (err) { lastError = err; }
|
|
61
|
+
|
|
62
|
+
throw lastError || new Error('Failed to load or parse RDF');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─── SPARQL execution (Node) ────────────────────────────────────────────────
|
|
66
|
+
async function _localSparql(queryText, endpoint, fetchFn) {
|
|
67
|
+
const store = await loadRdfStore(endpoint, fetchFn);
|
|
68
|
+
const parsed = rdflib.SPARQLToQuery(queryText, false, store);
|
|
69
|
+
if (!parsed) throw new Error('Could not parse SPARQL query');
|
|
70
|
+
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const bindings = [];
|
|
73
|
+
try {
|
|
74
|
+
store.query(
|
|
75
|
+
parsed,
|
|
76
|
+
b => bindings.push(b),
|
|
77
|
+
new rdflib.Fetcher(store),
|
|
78
|
+
() => resolve(bindingsToResults(bindings, queryText)),
|
|
79
|
+
);
|
|
80
|
+
} catch (err) { reject(err); }
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function execSparql(query, endpoint, fetchFn = globalThis.fetch) {
|
|
85
|
+
if (isRdfDoc(endpoint)) {
|
|
86
|
+
return _localSparql(query, endpoint, fetchFn);
|
|
87
|
+
}
|
|
88
|
+
return new NativeSparqlAdapter().executeQuery(query, endpoint, fetchFn);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ─── Main entry point ───────────────────────────────────────────────────────
|
|
92
|
+
export async function solQuery(opts = {}) {
|
|
93
|
+
const { endpoint, sparql, query, pattern, wanted, vars, columns, fetch: fetchFn } = opts;
|
|
94
|
+
const pat = pattern || wanted;
|
|
95
|
+
const queryText = sparql ?? query;
|
|
96
|
+
if (!endpoint) throw new Error('endpoint is required');
|
|
97
|
+
if (!queryText && !pat) throw new Error('sparql or pattern is required');
|
|
98
|
+
|
|
99
|
+
const _fetch = fetchFn || globalThis.fetch;
|
|
100
|
+
let data;
|
|
101
|
+
|
|
102
|
+
if (queryText) {
|
|
103
|
+
const processed = substituteVariables(queryText, vars);
|
|
104
|
+
assertSafeQuery(processed);
|
|
105
|
+
data = await execSparql(processed, endpoint, _fetch);
|
|
106
|
+
} else {
|
|
107
|
+
const store = await loadRdfStore(endpoint, _fetch);
|
|
108
|
+
const names = patternVarNames(pat);
|
|
109
|
+
const [s, p, o] = parsePatternParts(pat, rdflib, {}, endpoint);
|
|
110
|
+
data = matchStore(store, s, p, o, names);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return toPlainResults(data, columns);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export default solQuery;
|
package/package.json
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sol-components",
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Web components and Node.js tools for querying, including, and editing RDF/Linked Data on Solid Pods",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"node": "./node/sol-query.js",
|
|
9
|
+
"default": "./web/sol-query.js"
|
|
10
|
+
},
|
|
11
|
+
"./query": {
|
|
12
|
+
"node": "./node/sol-query.js",
|
|
13
|
+
"default": "./web/sol-query.js"
|
|
14
|
+
},
|
|
15
|
+
"./include": {
|
|
16
|
+
"node": "./node/sol-include.js",
|
|
17
|
+
"default": "./web/sol-include.js"
|
|
18
|
+
},
|
|
19
|
+
"./form": {
|
|
20
|
+
"node": "./node/sol-form.js",
|
|
21
|
+
"default": "./web/sol-form.js"
|
|
22
|
+
},
|
|
23
|
+
"./login": {
|
|
24
|
+
"node": "./node/sol-login.js",
|
|
25
|
+
"default": "./web/sol-login.js"
|
|
26
|
+
},
|
|
27
|
+
"./menu": {
|
|
28
|
+
"node": "./node/sol-menu.js",
|
|
29
|
+
"default": "./web/sol-menu.js"
|
|
30
|
+
},
|
|
31
|
+
"./web": "./web/sol-full.js",
|
|
32
|
+
"./web/*": "./web/*",
|
|
33
|
+
"./node/*": "./node/*",
|
|
34
|
+
"./core/*": "./core/*",
|
|
35
|
+
"./dist/*": "./dist/*"
|
|
36
|
+
},
|
|
37
|
+
"bin": {
|
|
38
|
+
"sol-menu-cli": "./node/sol-menu.js"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"core/",
|
|
42
|
+
"web/",
|
|
43
|
+
"node/",
|
|
44
|
+
"dist/",
|
|
45
|
+
"README.md",
|
|
46
|
+
"LICENSE"
|
|
47
|
+
],
|
|
48
|
+
"scripts": {
|
|
49
|
+
"test": "node --experimental-vm-modules node_modules/.bin/jest --coverage",
|
|
50
|
+
"test:ci": "node --experimental-vm-modules node_modules/.bin/jest",
|
|
51
|
+
"build:importmaps": "node tools/build-importmaps.mjs",
|
|
52
|
+
"build:vendor": "node tools/vendor.mjs",
|
|
53
|
+
"build": "npm run build:importmaps && npm run build:vendor",
|
|
54
|
+
"lint": "eslint .",
|
|
55
|
+
"docs": "jsdoc -c jsdoc.config.json"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"dompurify": "^3.4.0",
|
|
59
|
+
"ical.js": "^2.2.1",
|
|
60
|
+
"marked": "^18.0.0",
|
|
61
|
+
"rdflib": "^2.3.6"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"@comunica/query-sparql": "^5.1.3",
|
|
65
|
+
"@inrupt/solid-client-authn-browser": "^4.0.0",
|
|
66
|
+
"@inrupt/solid-client-authn-node": "^3.0.0",
|
|
67
|
+
"jsdom": "^26.1.0",
|
|
68
|
+
"openid-client": "^5.0.0",
|
|
69
|
+
"solid-ui": "^3.1.0"
|
|
70
|
+
},
|
|
71
|
+
"peerDependenciesMeta": {
|
|
72
|
+
"@comunica/query-sparql": {
|
|
73
|
+
"optional": true
|
|
74
|
+
},
|
|
75
|
+
"@inrupt/solid-client-authn-browser": {
|
|
76
|
+
"optional": true
|
|
77
|
+
},
|
|
78
|
+
"@inrupt/solid-client-authn-node": {
|
|
79
|
+
"optional": true
|
|
80
|
+
},
|
|
81
|
+
"openid-client": {
|
|
82
|
+
"optional": true
|
|
83
|
+
},
|
|
84
|
+
"solid-ui": {
|
|
85
|
+
"optional": true
|
|
86
|
+
},
|
|
87
|
+
"jsdom": {
|
|
88
|
+
"optional": true
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"devDependencies": {
|
|
92
|
+
"@babel/core": "^7.24.0",
|
|
93
|
+
"@babel/preset-env": "^7.24.0",
|
|
94
|
+
"@eslint/js": "^8.57.1",
|
|
95
|
+
"@rdfjs/dataset": "^2.0.2",
|
|
96
|
+
"@rdfjs/namespace": "^2.0.1",
|
|
97
|
+
"@rollup/plugin-commonjs": "^29.0.2",
|
|
98
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
99
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
100
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
101
|
+
"babel-jest": "^29.7.0",
|
|
102
|
+
"clownface": "^2.0.3",
|
|
103
|
+
"esbuild": "^0.28.0",
|
|
104
|
+
"eslint": "^9.39.4",
|
|
105
|
+
"jest": "^29.7.0",
|
|
106
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
107
|
+
"jsdoc": "^4.0.5",
|
|
108
|
+
"n3": "^2.0.3",
|
|
109
|
+
"rdf-ext": "^2.6.0",
|
|
110
|
+
"rdf-validate-shacl": "^0.6.5",
|
|
111
|
+
"rollup": "^4.13.0"
|
|
112
|
+
},
|
|
113
|
+
"jest": {
|
|
114
|
+
"testEnvironment": "node",
|
|
115
|
+
"transform": {
|
|
116
|
+
"^.+\\.js$": "babel-jest"
|
|
117
|
+
},
|
|
118
|
+
"moduleNameMapper": {
|
|
119
|
+
"^rdflib$": "<rootDir>/tests/__mocks__/rdflib-esm.js",
|
|
120
|
+
"^https://esm\\.sh/rdflib@2$": "<rootDir>/tests/__mocks__/rdflib-esm.js",
|
|
121
|
+
"^@inrupt/solid-client-authn-node$": "<rootDir>/tests/__mocks__/solid-client-authn-node.js",
|
|
122
|
+
"^openid-client$": "<rootDir>/tests/__mocks__/openid-client.js",
|
|
123
|
+
"^dompurify$": "<rootDir>/tests/__mocks__/dompurify.js",
|
|
124
|
+
"^marked$": "<rootDir>/tests/__mocks__/marked.js"
|
|
125
|
+
},
|
|
126
|
+
"setupFiles": [
|
|
127
|
+
"<rootDir>/tests/setup.js"
|
|
128
|
+
],
|
|
129
|
+
"testMatch": [
|
|
130
|
+
"**/tests/**/*.test.js"
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* menu-from-rdf.js — opt-in add-on: switch on `from-rdf` for the menu family.
|
|
3
|
+
*
|
|
4
|
+
* Importing this module IS the activation. It is the only place in the menu
|
|
5
|
+
* family (sol-tabs, sol-menu, sol-dropdown-button) that pulls in rdflib — via
|
|
6
|
+
* core/menu-rdf.js → core/rdf.js. Without it, those components are declarative-
|
|
7
|
+
* only (light-DOM <a> / <menu> children) and rdflib never enters their graph.
|
|
8
|
+
*
|
|
9
|
+
* <script type="module" src="…/sol-tabs.js"></script> <!-- declarative, no rdflib -->
|
|
10
|
+
* <script type="module" src="…/menu-from-rdf.js"></script> <!-- + from-rdf, pulls rdflib -->
|
|
11
|
+
* <sol-tabs from-rdf="./tabs.ttl#Tabs"></sol-tabs>
|
|
12
|
+
*
|
|
13
|
+
* This layer carries no component definitions — load the components themselves
|
|
14
|
+
* (individually, or via an aggregator like sol-full / sol-basic) however you
|
|
15
|
+
* already do. installFromRdfLoader() wires every menu consumer that is or
|
|
16
|
+
* becomes registered, in any import order (see core/menu-consumer.js).
|
|
17
|
+
* sol-dropdown-button inherits the loader from SolMenu via the static prototype
|
|
18
|
+
* chain.
|
|
19
|
+
*/
|
|
20
|
+
import { installFromRdfLoader } from '../core/menu-consumer.js';
|
|
21
|
+
import { loadMenuFromUri } from '../core/menu-rdf.js';
|
|
22
|
+
|
|
23
|
+
installFromRdfLoader(loadMenuFromUri);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
var THEME_KEY = 'swc-theme';
|
|
3
|
+
var FONT_KEY = 'swc-font-size';
|
|
4
|
+
|
|
5
|
+
function apply(theme, fontSize) {
|
|
6
|
+
if (theme) document.documentElement.setAttribute('data-theme', theme);
|
|
7
|
+
if (fontSize) document.documentElement.style.setProperty('--font-size', fontSize);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
apply(
|
|
12
|
+
localStorage.getItem(THEME_KEY) || 'light',
|
|
13
|
+
localStorage.getItem(FONT_KEY) || '20px'
|
|
14
|
+
);
|
|
15
|
+
} catch (e) {}
|
|
16
|
+
|
|
17
|
+
window.addEventListener('message', function (ev) {
|
|
18
|
+
var d = ev.data;
|
|
19
|
+
if (d && d.type === 'swc-prefs') apply(d.theme, d.fontSize);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (window.parent !== window) {
|
|
23
|
+
try { window.parent.postMessage({ type: 'swc-ready' }, '*'); } catch (e) {}
|
|
24
|
+
}
|
|
25
|
+
})();
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <sol-accordion> — Accordion (collapsible sections) web component.
|
|
3
|
+
*
|
|
4
|
+
* Wraps child `<div>` elements into `<details>`/`<summary>` pairs.
|
|
5
|
+
* Each child div should contain two inner divs: the first becomes the
|
|
6
|
+
* summary (header), the second becomes the collapsible content.
|
|
7
|
+
* Only one section is open at a time (exclusive mode via `<details name>`).
|
|
8
|
+
*
|
|
9
|
+
* The first panel opens by default. Add `start-closed` to start with
|
|
10
|
+
* every panel collapsed instead.
|
|
11
|
+
*
|
|
12
|
+
* @element sol-accordion
|
|
13
|
+
* @attr {boolean} start-closed - leave every panel collapsed on first render
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <sol-accordion>
|
|
17
|
+
* <div><div>Section 1</div><div>Content 1</div></div>
|
|
18
|
+
* <div><div>Section 2</div><div>Content 2</div></div>
|
|
19
|
+
* </sol-accordion>
|
|
20
|
+
*/
|
|
21
|
+
import { ensureDocStyle } from '../core/adopt.js';
|
|
22
|
+
import { CSS as ACCORDION_CSS } from './styles/sol-accordion-css.js';
|
|
23
|
+
import { define } from '../core/define.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Accordion (collapsible sections) web component.
|
|
27
|
+
*
|
|
28
|
+
* Wraps child `<div>` elements into `<details>`/`<summary>` pairs.
|
|
29
|
+
* Only one section is open at a time.
|
|
30
|
+
*
|
|
31
|
+
* @class SolAccordion
|
|
32
|
+
* @extends HTMLElement
|
|
33
|
+
*/
|
|
34
|
+
class SolAccordion extends HTMLElement {
|
|
35
|
+
connectedCallback() {
|
|
36
|
+
const groupName = `sol-accordion-${Math.random().toString(36).slice(2, 9)}`;
|
|
37
|
+
const startClosed = this.hasAttribute('start-closed');
|
|
38
|
+
|
|
39
|
+
const authorDivs = Array.from(this.children).filter(el => el.tagName === 'DIV');
|
|
40
|
+
|
|
41
|
+
if (!authorDivs.length) {
|
|
42
|
+
this.textContent = 'No accordion panels found';
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const wrapper = document.createElement('div');
|
|
47
|
+
wrapper.className = 'sol-accordion-wrapper';
|
|
48
|
+
wrapper.setAttribute('role', 'region');
|
|
49
|
+
wrapper.setAttribute('aria-label', 'Accordion');
|
|
50
|
+
|
|
51
|
+
authorDivs.forEach((srcDiv, i) => {
|
|
52
|
+
const childDivs = Array.from(srcDiv.children).filter(el => el.tagName === 'DIV');
|
|
53
|
+
|
|
54
|
+
const openInitial = !startClosed && i === 0;
|
|
55
|
+
const det = document.createElement('details');
|
|
56
|
+
det.name = groupName;
|
|
57
|
+
if (openInitial) det.open = true;
|
|
58
|
+
|
|
59
|
+
const sum = document.createElement('summary');
|
|
60
|
+
sum.setAttribute('role', 'button');
|
|
61
|
+
sum.setAttribute('aria-expanded', openInitial ? 'true' : 'false');
|
|
62
|
+
sum.setAttribute('tabindex', '0');
|
|
63
|
+
sum.id = `panel-${i}-summary`;
|
|
64
|
+
|
|
65
|
+
if (childDivs.length >= 2) {
|
|
66
|
+
sum.textContent = childDivs[0].textContent.trim() || `Panel ${i + 1}`;
|
|
67
|
+
|
|
68
|
+
const body = document.createElement('div');
|
|
69
|
+
body.className = 'accordion-body';
|
|
70
|
+
body.setAttribute('role', 'region');
|
|
71
|
+
body.setAttribute('aria-labelledby', `panel-${i}-summary`);
|
|
72
|
+
|
|
73
|
+
for (let j = 1; j < childDivs.length; j++) {
|
|
74
|
+
const contentDiv = document.createElement('div');
|
|
75
|
+
contentDiv.className = 'accordion-content-section';
|
|
76
|
+
const clone = childDivs[j].cloneNode(true);
|
|
77
|
+
while (clone.firstChild) contentDiv.appendChild(clone.firstChild);
|
|
78
|
+
body.appendChild(contentDiv);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
det.appendChild(sum);
|
|
82
|
+
det.appendChild(body);
|
|
83
|
+
} else {
|
|
84
|
+
sum.textContent = srcDiv.textContent.trim() || `Panel ${i + 1}`;
|
|
85
|
+
const body = document.createElement('div');
|
|
86
|
+
body.className = 'accordion-body';
|
|
87
|
+
body.textContent = 'No content';
|
|
88
|
+
det.appendChild(sum);
|
|
89
|
+
det.appendChild(body);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
det.addEventListener('toggle', () => {
|
|
93
|
+
sum.setAttribute('aria-expanded', det.open ? 'true' : 'false');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
sum.addEventListener('keypress', e => {
|
|
97
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
det.open = !det.open;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
wrapper.appendChild(det);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
this.innerHTML = '';
|
|
107
|
+
ensureDocStyle(this.getRootNode(), 'sol-accordion-styles', ACCORDION_CSS);
|
|
108
|
+
this.appendChild(wrapper);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
define('sol-accordion', SolAccordion);
|
package/web/sol-basic.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sol-basic.js — bundle entry: the no-RDF, html-first tier.
|
|
3
|
+
*
|
|
4
|
+
* Registers the everyday UI primitives that work from plain HTML and never
|
|
5
|
+
* need the RDF / Solid stack at runtime:
|
|
6
|
+
* sol-button, sol-dropdown-button, sol-include, sol-menu, sol-tabs,
|
|
7
|
+
* sol-accordion, sol-rolodex
|
|
8
|
+
* …plus the registered-by-tag helpers these conjure / instantiate at runtime:
|
|
9
|
+
* sol-default — singleton holding shared non-CSS defaults (proxy, region…)
|
|
10
|
+
* sol-modal — the "modal" display surface + the editor-self gear popup
|
|
11
|
+
* sol-window — the "floating" display surface
|
|
12
|
+
* The author-facing surface for the conjured ones is the region KEYWORD
|
|
13
|
+
* (region="modal" / "floating"), not the tag — see core/display-target.js.
|
|
14
|
+
*
|
|
15
|
+
* sol-menu's EDITOR (sol-tree-edit + sol-breadcrumb) is NOT here: editing a
|
|
16
|
+
* menu reads/writes it as RDF (SHACL + Turtle), so it's part of the solid-ui
|
|
17
|
+
* editing stack — loaded via sol-loader's `rdf` capability (or the importmap +
|
|
18
|
+
* module recipe). sol-menu conjures sol-tree-edit by tag when it's present.
|
|
19
|
+
*
|
|
20
|
+
* Deliberately NOT here (they need the RDF / Solid stack):
|
|
21
|
+
* sol-login, sol-form, sol-settings, sol-query, sol-solidos.
|
|
22
|
+
* Also NOT here: `menu-from-rdf` — driving the menu family from RDF is the
|
|
23
|
+
* opt-in add-on that pulls rdflib; import the `menu-from-rdf` module (it
|
|
24
|
+
* resolves via the importmap) when a page wants `from-rdf`. Keeping it out is
|
|
25
|
+
* what keeps this tier truly dependency-free.
|
|
26
|
+
*
|
|
27
|
+
* dompurify and marked (sol-include's sanitiser / Markdown renderer) are the
|
|
28
|
+
* only third-party code, and they're bundled IN. There is no rdflib peer.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import './sol-include.js';
|
|
32
|
+
import './sol-button.js';
|
|
33
|
+
import './sol-dropdown-button.js';
|
|
34
|
+
import './sol-menu.js';
|
|
35
|
+
import './sol-tabs.js';
|
|
36
|
+
import './sol-accordion.js';
|
|
37
|
+
import './sol-rolodex.js';
|
|
38
|
+
|
|
39
|
+
// Registered-by-tag helpers the primitives conjure / instantiate at runtime:
|
|
40
|
+
import './sol-default.js'; // singleton holding shared non-CSS defaults (proxy, region…)
|
|
41
|
+
import './sol-modal.js'; // modal display surface + editor-self gear popup
|
|
42
|
+
import './sol-window.js'; // floating-window display surface
|
|
43
|
+
|
|
44
|
+
// Surface the JS API on `window.SolBasic.*` for hosts that need the class
|
|
45
|
+
// symbols, not just the registered custom-element tags.
|
|
46
|
+
export { SolButton } from './sol-button.js';
|
|
47
|
+
export { SolDropdownButton } from './sol-dropdown-button.js';
|
|
48
|
+
export { SolInclude } from './sol-include.js';
|
|
49
|
+
export { SolMenu } from './sol-menu.js';
|
|
50
|
+
export { SolTabs } from './sol-tabs.js';
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <sol-breadcrumb> — clickable path-strip primitive.
|
|
3
|
+
*
|
|
4
|
+
* Pure UI component. Knows nothing about RDF, navigation, or routing.
|
|
5
|
+
* Wraps declarative `<span data-key="...">` children into a breadcrumb
|
|
6
|
+
* with `>` separators. Earlier segments are clickable; the last is the
|
|
7
|
+
* current location and is shown in a non-clickable style.
|
|
8
|
+
*
|
|
9
|
+
* Click emits a bubbling, composed `sol-breadcrumb-navigate` event
|
|
10
|
+
* with `detail: { key, index, label }`. The host decides what
|
|
11
|
+
* "navigate" means — sol-tree-edit pops its breadcrumb stack;
|
|
12
|
+
* podz might swap a path display; a router might call pushState.
|
|
13
|
+
*
|
|
14
|
+
* @element sol-breadcrumb
|
|
15
|
+
*
|
|
16
|
+
* @fires sol-breadcrumb-navigate — detail: { key, index, label }
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* <sol-breadcrumb>
|
|
20
|
+
* <span data-key="root">Main Menu</span>
|
|
21
|
+
* <span data-key="notes">Notes</span>
|
|
22
|
+
* <span data-key="daily">Daily</span>
|
|
23
|
+
* </sol-breadcrumb>
|
|
24
|
+
*
|
|
25
|
+
* Children mutate freely; an attached MutationObserver re-renders so
|
|
26
|
+
* the host can push/pop segments by `element.append()` / `.removeChild()`.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { define } from '../core/define.js';
|
|
30
|
+
import { ensureDocStyle } from '../core/adopt.js';
|
|
31
|
+
|
|
32
|
+
const CSS = `
|
|
33
|
+
sol-breadcrumb {
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: baseline;
|
|
36
|
+
flex-wrap: wrap;
|
|
37
|
+
gap: 0.35rem 0.55rem;
|
|
38
|
+
font-family: var(--font-ui, system-ui, sans-serif);
|
|
39
|
+
font-size: 0.92rem;
|
|
40
|
+
line-height: 1.5;
|
|
41
|
+
}
|
|
42
|
+
sol-breadcrumb .sol-breadcrumb-segment {
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
color: var(--accent, #1F618D);
|
|
45
|
+
background: none;
|
|
46
|
+
border: 0;
|
|
47
|
+
padding: 0;
|
|
48
|
+
font: inherit;
|
|
49
|
+
text-decoration: none;
|
|
50
|
+
border-radius: 2px;
|
|
51
|
+
}
|
|
52
|
+
sol-breadcrumb .sol-breadcrumb-segment:hover,
|
|
53
|
+
sol-breadcrumb .sol-breadcrumb-segment:focus-visible {
|
|
54
|
+
text-decoration: underline;
|
|
55
|
+
outline: none;
|
|
56
|
+
}
|
|
57
|
+
sol-breadcrumb .sol-breadcrumb-current {
|
|
58
|
+
color: var(--text, #2c3e50);
|
|
59
|
+
font-weight: 600;
|
|
60
|
+
cursor: default;
|
|
61
|
+
}
|
|
62
|
+
sol-breadcrumb .sol-breadcrumb-sep {
|
|
63
|
+
color: var(--text-muted, #8a8a8a);
|
|
64
|
+
user-select: none;
|
|
65
|
+
}
|
|
66
|
+
sol-breadcrumb [data-key] {
|
|
67
|
+
display: none;
|
|
68
|
+
}
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
class SolBreadcrumb extends HTMLElement {
|
|
72
|
+
connectedCallback() {
|
|
73
|
+
ensureDocStyle(this.getRootNode(), 'sol-breadcrumb-styles', CSS);
|
|
74
|
+
this._render();
|
|
75
|
+
// Watch for children being added/removed so callers can mutate
|
|
76
|
+
// the path declaratively (push by appendChild, pop by remove).
|
|
77
|
+
if (!this._observer) {
|
|
78
|
+
this._observer = new MutationObserver(() => this._render());
|
|
79
|
+
this._observer.observe(this, { childList: true, characterData: true, subtree: true });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
disconnectedCallback() {
|
|
84
|
+
if (this._observer) { this._observer.disconnect(); this._observer = null; }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_render() {
|
|
88
|
+
// Pull the data-key segments declared by the author; ignore the
|
|
89
|
+
// rendered chrome we add ourselves.
|
|
90
|
+
const segments = Array.from(this.querySelectorAll(':scope > [data-key]'));
|
|
91
|
+
// Remove any previously-rendered chrome.
|
|
92
|
+
for (const el of Array.from(this.children)) {
|
|
93
|
+
if (!el.hasAttribute('data-key')) el.remove();
|
|
94
|
+
}
|
|
95
|
+
if (segments.length === 0) return;
|
|
96
|
+
|
|
97
|
+
const lastIdx = segments.length - 1;
|
|
98
|
+
segments.forEach((seg, i) => {
|
|
99
|
+
const key = seg.dataset.key;
|
|
100
|
+
const label = seg.textContent.trim();
|
|
101
|
+
let node;
|
|
102
|
+
if (i === lastIdx) {
|
|
103
|
+
node = document.createElement('span');
|
|
104
|
+
node.className = 'sol-breadcrumb-current';
|
|
105
|
+
node.setAttribute('aria-current', 'page');
|
|
106
|
+
node.textContent = label;
|
|
107
|
+
} else {
|
|
108
|
+
node = document.createElement('button');
|
|
109
|
+
node.type = 'button';
|
|
110
|
+
node.className = 'sol-breadcrumb-segment';
|
|
111
|
+
node.textContent = label;
|
|
112
|
+
node.addEventListener('click', () => {
|
|
113
|
+
this.dispatchEvent(new CustomEvent('sol-breadcrumb-navigate', {
|
|
114
|
+
bubbles: true, composed: true,
|
|
115
|
+
detail: { key, index: i, label },
|
|
116
|
+
}));
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
this.appendChild(node);
|
|
120
|
+
if (i !== lastIdx) {
|
|
121
|
+
const sep = document.createElement('span');
|
|
122
|
+
sep.className = 'sol-breadcrumb-sep';
|
|
123
|
+
sep.setAttribute('aria-hidden', 'true');
|
|
124
|
+
sep.textContent = '›';
|
|
125
|
+
this.appendChild(sep);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
define('sol-breadcrumb', SolBreadcrumb);
|