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,415 @@
1
+ /**
2
+ * <sol-live-edit> — live split-pane editor web component.
3
+ * Attributes: source="URL" format="turtle|csv|markdown|html|mermaid|jsonld|graphviz"
4
+ * readonly (boolean — disables save button)
5
+ * Properties: fetchFn (Function) — authenticated fetch; content (string get/set)
6
+ * Events: sol-change({content}), sol-save({content,url}), sol-load({content,url})
7
+ */
8
+
9
+ import { buildEditor } from './utils/code-mirror-editor.js';
10
+ import { CSS, sheet as LIVE_EDIT_SHEET } from './styles/sol-live-edit-css.js';
11
+ import { adopt } from '../core/adopt.js';
12
+ import { define } from '../core/define.js';
13
+
14
+ // Pre-loaded module registry — kept as an optional escape hatch for
15
+ // hosts that want to inject custom renderers/help/examples. Standard
16
+ // hosts no longer need it: the dynamic import()s below use static
17
+ // string literals, so bundlers (rollup with inlineDynamicImports, esbuild
18
+ // in IIFE) inline the modules at build time and runtime import() returns
19
+ // the bundled module Promise without a network fetch.
20
+ const _preloaded = { renderers: {}, help: {}, examples: {} };
21
+
22
+ const RENDERERS = {
23
+ turtle: () => _preloaded.renderers.turtle || import('./utils/renderers/turtle.js').then(m => m.renderTurtle),
24
+ jsonld: () => _preloaded.renderers.jsonld || import('./utils/renderers/jsonld.js').then(m => m.renderJsonLd),
25
+ csv: () => _preloaded.renderers.csv || import('./utils/renderers/csv.js').then(m => m.renderCSV),
26
+ markdown: () => _preloaded.renderers.markdown || import('./utils/renderers/markdown.js').then(m => m.renderMarkdown),
27
+ mermaid: () => _preloaded.renderers.mermaid || import('./utils/renderers/mermaid.js').then(m => m.renderMermaid),
28
+ html: () => _preloaded.renderers.html || import('./utils/renderers/html.js').then(m => m.renderHtml),
29
+ graphviz: () => _preloaded.renderers.graphviz || import('./utils/renderers/graphviz.js').then(m => m.renderGraphviz),
30
+ };
31
+ const _rendererCache = {};
32
+
33
+ const HELP = {
34
+ turtle: () => _preloaded.help.turtle || import('./utils/live-edit-help/turtle.js').then(m => m.turtleHelp),
35
+ jsonld: () => _preloaded.help.jsonld || import('./utils/live-edit-help/jsonld.js').then(m => m.jsonldHelp),
36
+ csv: () => _preloaded.help.csv || import('./utils/live-edit-help/csv.js').then(m => m.csvHelp),
37
+ markdown: () => _preloaded.help.markdown || import('./utils/live-edit-help/markdown.js').then(m => m.markdownHelp),
38
+ mermaid: () => _preloaded.help.mermaid || import('./utils/live-edit-help/mermaid.js').then(m => m.mermaidHelp),
39
+ graphviz: () => _preloaded.help.graphviz || import('./utils/live-edit-help/graphviz.js').then(m => m.graphvizHelp),
40
+ };
41
+
42
+ const EXAMPLES = {
43
+ turtle: () => _preloaded.examples.turtle || import('../data/live-edit/turtle.js').then(m => m.example),
44
+ jsonld: () => _preloaded.examples.jsonld || import('../data/live-edit/jsonld.js').then(m => m.example),
45
+ csv: () => _preloaded.examples.csv || import('../data/live-edit/csv.js').then(m => m.example),
46
+ markdown: () => _preloaded.examples.markdown || import('../data/live-edit/markdown.js').then(m => m.example),
47
+ mermaid: () => _preloaded.examples.mermaid || import('../data/live-edit/mermaid.js').then(m => m.example),
48
+ html: () => _preloaded.examples.html || import('../data/live-edit/html.js').then(m => m.example),
49
+ graphviz: () => _preloaded.examples.graphviz || import('../data/live-edit/graphviz.js').then(m => m.example),
50
+ };
51
+
52
+ const ZOOM_FMTS = new Set(['turtle','jsonld','graphviz','markdown','mermaid']);
53
+
54
+ const EXT = {ttl:'turtle',n3:'turtle',turtle:'turtle',jsonld:'jsonld',csv:'csv',tsv:'csv',
55
+ md:'markdown',markdown:'markdown',mmd:'mermaid',mermaid:'mermaid',
56
+ html:'html',htm:'html',dot:'graphviz',gv:'graphviz'};
57
+
58
+ const MIME = {'text/turtle':'turtle','text/n3':'turtle','application/ld+json':'jsonld',
59
+ 'text/csv':'csv','text/tab-separated-values':'csv','text/markdown':'markdown',
60
+ 'text/x-markdown':'markdown','text/html':'html','text/x-mermaid':'mermaid',
61
+ 'text/vnd.graphviz':'graphviz','text/x-dot':'graphviz'};
62
+
63
+ const FMIME = {turtle:'text/turtle',jsonld:'application/ld+json',csv:'text/csv',
64
+ markdown:'text/markdown',html:'text/html',mermaid:'text/x-mermaid',
65
+ graphviz:'text/vnd.graphviz'};
66
+
67
+ const CM_EXT = {turtle:'ttl',jsonld:'jsonld',markdown:'md',html:'html'};
68
+
69
+ /**
70
+ * Live split-pane editor web component.
71
+ *
72
+ * CodeMirror syntax highlighting, preview rendering, and format-specific help.
73
+ *
74
+ * @class SolLiveEdit
75
+ * @extends HTMLElement
76
+ * @attr {string} source - URL to load
77
+ * @attr {string} format - turtle|csv|markdown|html|mermaid|jsonld|graphviz
78
+ * @attr {boolean} readonly - disables save button
79
+ * @property {Function} fetchFn - authenticated fetch function
80
+ * @property {string} content - editor content (get/set)
81
+ * @fires sol-change - detail: { content }
82
+ * @fires sol-save - detail: { content, url }
83
+ * @fires sol-load - detail: { content, url }
84
+ * @fires sol-zoom - detail: { zoom, pct }
85
+ * @fires sol-format - detail: { format, canZoom, canStats }
86
+ */
87
+ class SolLiveEdit extends HTMLElement {
88
+ static get observedAttributes() { return ['source','format','readonly']; }
89
+ static registerModules({ renderers, help, examples } = {}) {
90
+ if (renderers) Object.assign(_preloaded.renderers, renderers);
91
+ if (help) Object.assign(_preloaded.help, help);
92
+ if (examples) Object.assign(_preloaded.examples, examples);
93
+ }
94
+ constructor() {
95
+ super();
96
+ this._fn=null;this._cm=null;this._db=null;this._sim=null;this._fmt=null;this._statsOn=false;
97
+ this._cfg={view:'both',keys:'default'};this._zoom=1.0;
98
+ this.attachShadow({mode:'open'});
99
+ }
100
+ get fetchFn(){return this._fn;} set fetchFn(f){this._fn=f;}
101
+ get content(){return this._cm?this._cm.getValue():'';}
102
+ set content(t){this._setContent(t);}
103
+ get format(){return this._fmt;}
104
+
105
+ async connectedCallback(){await this._init();}
106
+ attributeChangedCallback(n,_,v){
107
+ if(!this.shadowRoot.firstChild)return;
108
+ if(n==='source')this._loadSrc(v);
109
+ if(n==='format')this._setFmt(v);
110
+ }
111
+
112
+ // ── Public API for host page buttons ────────────────────────────────────────
113
+ get canZoom() { return ZOOM_FMTS.has(this._fmt); }
114
+ get canStats() { return this._fmt === 'csv'; }
115
+ get zoom() { return Math.round(this._zoom * 100); }
116
+ zoomIn() { this._setZoom(this._zoom + 0.1); }
117
+ zoomOut() { this._setZoom(this._zoom - 0.1); }
118
+ save() { this._save(); }
119
+ toggleSettings(){ this._toggleCfg(); }
120
+ toggleHelp() { this._toggleHelp(); }
121
+ toggleStats() { this._toggleStats(); }
122
+
123
+ async _init(){
124
+ const s=this.shadowRoot;
125
+ s.innerHTML=`
126
+ <div class="sle-toolbar" id="toolbar">
127
+ <button type="button" class="sle-btn" id="statsBtn" hidden>Statistics</button>
128
+ <div class="sle-zoom" id="zoomctl" hidden>
129
+ <button type="button" id="zmOut" title="Zoom out" aria-label="Zoom out">−</button>
130
+ <span id="zmPct">100%</span>
131
+ <button type="button" id="zmIn" title="Zoom in" aria-label="Zoom in">+</button>
132
+ </div>
133
+ <button type="button" class="sle-btn sle-btn-primary" id="svBtn">Save</button>
134
+ <button type="button" class="sle-btn" id="cfgBtn" title="Settings" aria-label="Settings">⚙</button>
135
+ <button type="button" class="sle-btn" id="hlpBtn" title="Help" aria-label="Help">?</button>
136
+ </div>
137
+ <div class="er" id="er"></div>
138
+ <div class="cf" id="cf">
139
+ <div class="cg">
140
+ <b>View</b>
141
+ <label><input type="radio" name="sle-view" value="both"> Both</label>
142
+ <label><input type="radio" name="sle-view" value="editor"> Editor only</label>
143
+ <label><input type="radio" name="sle-view" value="preview"> Preview only</label>
144
+ </div>
145
+ <div class="cg">
146
+ <b>Key bindings</b>
147
+ <label><input type="radio" name="sle-keys" value="default"> Default</label>
148
+ <label><input type="radio" name="sle-keys" value="emacs"> Emacs</label>
149
+ <label><input type="radio" name="sle-keys" value="vim"> Vim</label>
150
+ </div>
151
+ </div>
152
+ <div class="modal-backdrop" id="modal">
153
+ <div class="modal-box" id="modalBox">
154
+ <button class="modal-close" id="modalClose">\u00d7</button>
155
+ <div id="modalBody"></div>
156
+ </div>
157
+ </div>
158
+ <div class="body">
159
+ <div class="ep" id="ep"></div>
160
+ <div class="sle-resizer" id="resizer" title="Drag to resize"></div>
161
+ <div class="pp" id="pp">
162
+ <div id="po" style="width:100%;height:100%;position:relative"></div>
163
+ </div>
164
+ </div>`;
165
+ s.adoptedStyleSheets = [];
166
+ adopt(s, { sheet: LIVE_EDIT_SHEET, css: CSS });
167
+
168
+ // Settings dropdown change handler
169
+ s.getElementById('cf').addEventListener('change',e=>{
170
+ const nm=e.target.name;if(!nm?.startsWith('sle-'))return;
171
+ const k=nm.slice(4);this._cfg[k]=e.target.value;this._saveCfg();
172
+ if(k==='view')this._applyView();
173
+ else{const p=this.content;this._buildEditor().then(()=>this._setContent(p));}
174
+ });
175
+
176
+ // Close modal on backdrop click or close button
177
+ s.getElementById('modal').addEventListener('click',e=>{
178
+ if(e.target===s.getElementById('modal'))this._closeModal();
179
+ });
180
+ s.getElementById('modalClose').addEventListener('click',()=>this._closeModal());
181
+
182
+ // Toolbar — Save / Settings / Help / Zoom / Statistics. Zoom and
183
+ // Statistics are shown per-format (see _setFmt); Save is hidden
184
+ // when the component is readonly.
185
+ s.getElementById('zmOut').addEventListener('click',()=>this.zoomOut());
186
+ s.getElementById('zmIn').addEventListener('click',()=>this.zoomIn());
187
+ s.getElementById('statsBtn').addEventListener('click',()=>this.toggleStats());
188
+ s.getElementById('svBtn').addEventListener('click',()=>this.save());
189
+ s.getElementById('cfgBtn').addEventListener('click',e=>{e.stopPropagation();this.toggleSettings();});
190
+ s.getElementById('hlpBtn').addEventListener('click',()=>this.toggleHelp());
191
+ if(this.hasAttribute('readonly'))s.getElementById('svBtn').style.display='none';
192
+
193
+ // Close settings dropdown when clicking outside
194
+ s.addEventListener('click',e=>{
195
+ const cf=s.getElementById('cf');
196
+ if(cf.classList.contains('on')&&!cf.contains(e.target))cf.classList.remove('on');
197
+ });
198
+
199
+ // Resizer — drag the bar between editor and preview panes.
200
+ const rz=s.getElementById('resizer');
201
+ rz.addEventListener('pointerdown',e=>{
202
+ e.preventDefault();
203
+ const body=s.querySelector('.body'),ep=s.getElementById('ep');
204
+ rz.setPointerCapture(e.pointerId);
205
+ rz.classList.add('dragging');
206
+ const move=ev=>{
207
+ const r=body.getBoundingClientRect(),min=120;
208
+ const w=Math.max(min,Math.min(ev.clientX-r.left,r.width-min));
209
+ ep.style.flex='0 0 '+w+'px';
210
+ };
211
+ const up=()=>{
212
+ rz.classList.remove('dragging');
213
+ rz.removeEventListener('pointermove',move);
214
+ rz.removeEventListener('pointerup',up);
215
+ };
216
+ rz.addEventListener('pointermove',move);
217
+ rz.addEventListener('pointerup',up);
218
+ });
219
+
220
+ this._loadCfg();
221
+ const src=this.getAttribute('source');
222
+ const fmt=this.getAttribute('format')||EXT[src?.split('.').pop()?.toLowerCase()]||'markdown';
223
+ await this._setFmt(fmt);
224
+ this._applyView();
225
+ if(src){await this._loadSrc(src);}
226
+ else{const ex=await(EXAMPLES[fmt]||EXAMPLES.markdown)().catch(()=>'');await this._setContent(ex);}
227
+ }
228
+
229
+ async _setFmt(fmt){
230
+ this._fmt=fmt||'markdown';
231
+ this._zoom=1.0;
232
+ const po=this.shadowRoot.getElementById('po');
233
+ if(po){po.style.transform='';po.style.transformOrigin='';po.style.width='100%';po.style.height='';}
234
+ const zc=this.shadowRoot.getElementById('zoomctl');
235
+ if(zc)zc.hidden=!this.canZoom;
236
+ const zp=this.shadowRoot.getElementById('zmPct');
237
+ if(zp)zp.textContent='100%';
238
+ const sb=this.shadowRoot.getElementById('statsBtn');
239
+ if(sb)sb.hidden=!this.canStats;
240
+ this.dispatchEvent(new CustomEvent('sol-format',{detail:{format:this._fmt,canZoom:this.canZoom,canStats:this.canStats},bubbles:true,composed:true}));
241
+ await this._buildEditor();
242
+ }
243
+
244
+ async _buildEditor(){
245
+ const pane=this.shadowRoot.getElementById('ep');
246
+ if(!pane)return;
247
+ if(this._cm){this._cm.destroy();this._cm=null;}
248
+ const extKey=CM_EXT[this._fmt]||null;
249
+ const editorMod=this.constructor.editorModule;
250
+ if(editorMod){
251
+ try{
252
+ const {createEditor}=await import(editorMod);
253
+ this._cm=await createEditor(pane,'',extKey?`f.${extKey}`:'f.txt',
254
+ {dark:false,keyBindings:this._cfg.keys,onChange:()=>this._change()});
255
+ return;
256
+ }catch(_){/* fall through to built-in editor */}
257
+ }
258
+ const view=await buildEditor(pane,extKey,this.shadowRoot,()=>this._change());
259
+ this._cm={
260
+ getValue:()=>view.state.doc.toString(),
261
+ setValue:(v)=>view.dispatch({changes:{from:0,to:view.state.doc.length,insert:v}}),
262
+ destroy:()=>view.destroy(),
263
+ };
264
+ }
265
+
266
+ _change(){
267
+ clearTimeout(this._db);this._db=setTimeout(()=>this._render(),400);
268
+ this.dispatchEvent(new CustomEvent('sol-change',{detail:{content:this.content},bubbles:true,composed:true}));
269
+ }
270
+
271
+ async _setContent(text){
272
+ if(!this._cm)return;
273
+ this._cm.setValue(text);
274
+ await this._render();
275
+ }
276
+
277
+ async _render(){
278
+ if(this._sim){this._sim();this._sim=null;}
279
+ const out=this.shadowRoot.getElementById('po');
280
+ const er=this.shadowRoot.getElementById('er');
281
+ if(!out)return;
282
+ if(!_rendererCache[this._fmt]&&RENDERERS[this._fmt])
283
+ _rendererCache[this._fmt]=await RENDERERS[this._fmt]();
284
+ const fn=_rendererCache[this._fmt];
285
+ if(!fn){out.innerHTML='<p style="padding:1rem">No preview.</p>';return;}
286
+ try{
287
+ const r=await fn(this.content,out);
288
+ if(typeof r==='function')this._sim=r;
289
+ er.textContent='';er.classList.remove('on');
290
+ }catch(e){er.textContent=e.message;er.classList.add('on');}
291
+ }
292
+
293
+ async _loadSrc(url){
294
+ if(!url)return;
295
+ const fmt=EXT[url.split('?')[0].split('.').pop()?.toLowerCase()]||this._fmt||'markdown';
296
+ if(fmt!==this._fmt)await this._setFmt(fmt);
297
+ try{
298
+ const fn=this._fn||fetch;
299
+ const resp=await fn(url,{cache:'no-store'});
300
+ if(!resp.ok)throw new Error(`HTTP ${resp.status}`);
301
+ const text=await resp.text();
302
+ const mf=MIME[(resp.headers.get('content-type')||'').split(';')[0].trim()];
303
+ if(mf&&mf!==this._fmt)await this._setFmt(mf);
304
+ await this._setContent(text);
305
+ if(!this.hasAttribute('readonly')){
306
+ const sb=this.shadowRoot.getElementById('svBtn');if(sb)sb.style.display='';
307
+ }
308
+ this.dispatchEvent(new CustomEvent('sol-load',{detail:{content:text,url},bubbles:true,composed:true}));
309
+ }catch(e){
310
+ const er=this.shadowRoot.getElementById('er');
311
+ if(er){er.textContent=`Load failed: ${e.message}`;er.classList.add('on');}
312
+ }
313
+ }
314
+
315
+ async _save(){
316
+ const url=this.getAttribute('source');const text=this.content;
317
+ this.dispatchEvent(new CustomEvent('sol-save',{detail:{content:text,url},bubbles:true,composed:true}));
318
+ if(!url)return;
319
+ try{
320
+ const fn=this._fn||fetch;
321
+ const r=await fn(url,{method:'PUT',headers:{'Content-Type':FMIME[this._fmt]||'text/plain'},body:text});
322
+ if(!r.ok)throw new Error(`HTTP ${r.status}`);
323
+ }catch(e){
324
+ const er=this.shadowRoot.getElementById('er');
325
+ if(er){er.textContent=`Save failed: ${e.message}`;er.classList.add('on');}
326
+ }
327
+ }
328
+
329
+ // ── Modal helpers ───────────────────────────────────────────────────────────
330
+ _openModal(html,wide){
331
+ const s=this.shadowRoot;
332
+ const box=s.getElementById('modalBox');
333
+ s.getElementById('modalBody').innerHTML=html;
334
+ box.classList.toggle('wide',!!wide);
335
+ s.getElementById('modal').classList.add('on');
336
+ }
337
+ _closeModal(){
338
+ this.shadowRoot.getElementById('modal').classList.remove('on');
339
+ }
340
+
341
+ // ── Help → modal ───────────────────────────────────────────────────────────
342
+ async _toggleHelp(){
343
+ const modal=this.shadowRoot.getElementById('modal');
344
+ if(modal.classList.contains('on')){this._closeModal();return;}
345
+ const ldr=HELP[this._fmt];
346
+ const html=ldr?_helpHtml(await ldr()):'<p>No help available for this format.</p>';
347
+ this._openModal(html);
348
+ }
349
+
350
+ // ── Settings → dropdown ────────────────────────────────────────────────────
351
+ _toggleCfg(){
352
+ const cf=this.shadowRoot.getElementById('cf');
353
+ if(!cf)return;
354
+ const opening=!cf.classList.contains('on');
355
+ cf.classList.toggle('on');
356
+ if(opening){
357
+ this.shadowRoot.querySelectorAll('[name^="sle-"]').forEach(r=>{
358
+ r.checked=r.value===this._cfg[r.name.slice(4)];
359
+ });
360
+ }
361
+ }
362
+
363
+ // ── Statistics → modal ─────────────────────────────────────────────────────
364
+ async _toggleStats(){
365
+ const modal=this.shadowRoot.getElementById('modal');
366
+ if(modal.classList.contains('on')){this._closeModal();return;}
367
+ try{
368
+ const csvMod = _preloaded.renderers._csvModule || await import('./utils/renderers/csv.js');
369
+ const {parseCSV,calcStats,renderStats}=csvMod;
370
+ const container=document.createElement('div');
371
+ renderStats(calcStats(parseCSV(this.content)),container);
372
+ this._openModal(container.innerHTML,true);
373
+ }catch(e){
374
+ this._openModal(`<p style="color:#c0392b">${_esc(e.message)}</p>`);
375
+ }
376
+ }
377
+
378
+ _loadCfg(){try{Object.assign(this._cfg,JSON.parse(localStorage.getItem('sle-cfg')||'{}'));}catch(_){}this._cfg.view='both';}
379
+ _saveCfg(){try{localStorage.setItem('sle-cfg',JSON.stringify(this._cfg));}catch(_){}}
380
+ _setZoom(z){
381
+ this._zoom=Math.max(0.2,Math.min(5.0,Math.round(z*10)/10));
382
+ const pct=Math.round(this._zoom*100);
383
+ const zp=this.shadowRoot.getElementById('zmPct');
384
+ if(zp)zp.textContent=pct+'%';
385
+ const po=this.shadowRoot.getElementById('po');
386
+ const pp=this.shadowRoot.getElementById('pp');
387
+ if(po){
388
+ po.style.width=`${100*this._zoom}%`;
389
+ po.style.height=`${100*this._zoom}%`;
390
+ }
391
+ if(pp&&this._zoom>1){
392
+ requestAnimationFrame(()=>{
393
+ pp.scrollLeft=(pp.scrollWidth-pp.clientWidth)/2;
394
+ pp.scrollTop=(pp.scrollHeight-pp.clientHeight)/2;
395
+ });
396
+ }
397
+ this.dispatchEvent(new CustomEvent('sol-zoom',{detail:{zoom:this._zoom,pct},bubbles:true,composed:true}));
398
+ }
399
+ _applyView(){const v=this._cfg.view,sr=this.shadowRoot,ep=sr.getElementById('ep'),pp=sr.getElementById('pp'),rz=sr.getElementById('resizer');if(ep)ep.style.display=v==='preview'?'none':'';if(pp)pp.style.display=v==='editor'?'none':'';if(rz)rz.style.display=v==='both'?'':'none';}
400
+ }
401
+
402
+ function _helpHtml(h){
403
+ if(!h)return'';
404
+ let s=`<h2>${h.title}</h2>`;
405
+ for(const sec of h.sections){
406
+ s+=`<h3>${sec.heading}</h3>`;
407
+ for(const item of sec.items)s+=`<h4>${item.title}</h4><p>${item.description}</p><code>${_esc(item.code)}</code>`;
408
+ }
409
+ return s;
410
+ }
411
+ function _esc(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
412
+
413
+ define('sol-live-edit',SolLiveEdit);
414
+ export { SolLiveEdit };
415
+ export default SolLiveEdit;