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,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);
@@ -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);