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,337 @@
1
+ /**
2
+ * <sol-weather> — current-conditions web component.
3
+ *
4
+ * Renders a compact one-line card (icon · place · temperature · rain stat)
5
+ * using the Open-Meteo public API. Either pass `lat` + `lon` directly or
6
+ * pass `place` and the component will geocode it once.
7
+ *
8
+ * Attributes:
9
+ * lat decimal latitude
10
+ * lon decimal longitude
11
+ * place free-text place name (geocoded via Open-Meteo)
12
+ * units "metric" | "imperial" | "both" (default: both)
13
+ * hours-window max-precip lookahead, in hours (default: 12)
14
+ * source "file.ttl#Subject" Turtle config in schema.org
15
+ * PropertyValue form. Setting names map to the
16
+ * matching HTML attributes:
17
+ * "latitude" → lat
18
+ * "longitude" → lon
19
+ * "place" → place
20
+ * "units" → units
21
+ * "forecast-window" → hours-window
22
+ * HTML attributes override the TTL.
23
+ *
24
+ * Refreshes every ten minutes; aborts in-flight fetches on
25
+ * disconnect / re-fetch. Open-Meteo has no API key requirement.
26
+ *
27
+ * @element sol-weather
28
+ *
29
+ * @example
30
+ * <sol-weather place="Portland, OR"></sol-weather>
31
+ * <sol-weather lat="45.52" lon="-122.68" units="imperial"></sol-weather>
32
+ * <sol-weather source="data/weather-settings.ttl#Settings"></sol-weather>
33
+ */
34
+ import { adopt } from '../core/adopt.js';
35
+ import { define } from '../core/define.js';
36
+ import { attachEditorSelfGear } from '../core/editor-self.js';
37
+ import { CSS as WEATHER_CSS, sheet as WEATHER_SHEET } from './styles/sol-weather-css.js';
38
+ import { loadConfig } from './utils/rdf-config.js';
39
+
40
+ /** Open-Meteo WMO weather codes → [short description, emoji]. */
41
+ const WEATHER_MAP = {
42
+ 0: ['Clear sky', '☀️'],
43
+ 1: ['Mainly clear', '🌤️'],
44
+ 2: ['Partly cloudy', '⛅'],
45
+ 3: ['Overcast', '☁️'],
46
+ 45: ['Fog', '🌫️'],
47
+ 48: ['Rime fog', '🌫️'],
48
+ 51: ['Light drizzle', '🌦️'],
49
+ 53: ['Moderate drizzle', '🌦️'],
50
+ 55: ['Dense drizzle', '🌧️'],
51
+ 56: ['Light freezing drizzle', '🥶🌧️'],
52
+ 57: ['Dense freezing drizzle', '🥶🌧️'],
53
+ 61: ['Slight rain', '🌧️'],
54
+ 63: ['Moderate rain', '🌧️'],
55
+ 65: ['Heavy rain', '🌧️'],
56
+ 66: ['Light freezing rain', '🥶🌧️'],
57
+ 67: ['Heavy freezing rain', '🥶🌧️'],
58
+ 71: ['Slight snow', '🌨️'],
59
+ 73: ['Moderate snow', '🌨️'],
60
+ 75: ['Heavy snow', '❄️'],
61
+ 80: ['Slight rain showers', '🌧️'],
62
+ 81: ['Moderate rain showers', '🌧️'],
63
+ 82: ['Violent rain showers', '⛈️'],
64
+ 95: ['Thunderstorm', '⛈️'],
65
+ 96: ['Thunderstorm w/ hail', '⛈️'],
66
+ 99: ['Severe thunderstorm', '⛈️'],
67
+ };
68
+
69
+ function cToF(c) { return c * 9 / 5 + 32; }
70
+
71
+ /**
72
+ * Compact current-conditions web component (Open-Meteo).
73
+ *
74
+ * @class SolWeather
75
+ * @extends HTMLElement
76
+ */
77
+ class SolWeather extends HTMLElement {
78
+ static get observedAttributes() {
79
+ return ['lat', 'lon', 'place', 'units', 'hours-window', 'source'];
80
+ }
81
+
82
+ /** SHACL shape declaring the fixed schema (predicates + datatypes +
83
+ * cardinalities). sol-form's shape-driven mode generates a labelled
84
+ * field per property; dk-settings discovery picks this up. The
85
+ * legacy `editor` (ui:Form TTL) getter was dropped in the
86
+ * direct-predicate vocab migration — see
87
+ * swc/claude/plans/PLAN-vocab-migration.md. */
88
+ static get shape() {
89
+ return new URL('../shapes/weather-settings.shacl', import.meta.url).href;
90
+ }
91
+
92
+ constructor() {
93
+ super();
94
+ this.attachShadow({ mode: 'open' });
95
+ this._controller = null; // AbortController for the active fetch
96
+ this._timer = null; // re-fetch interval
97
+ this._refreshMs = 10 * 60 * 1000;
98
+ }
99
+
100
+ async connectedCallback() {
101
+ adopt(this.shadowRoot, { sheet: WEATHER_SHEET, css: WEATHER_CSS });
102
+
103
+ this._card = document.createElement('div');
104
+ this._card.className = 'card';
105
+ this._card.setAttribute('part', 'card');
106
+ this._card.setAttribute('role', 'region');
107
+ this._card.setAttribute('aria-live', 'polite');
108
+ this._card.innerHTML = `
109
+ <span class="icon" part="icon">·</span>
110
+ <span class="place" part="place"></span>
111
+ <span class="temp" part="temp"></span>
112
+ <span class="desc" part="desc"></span>
113
+ <span class="stat" part="rain"></span>
114
+ `;
115
+ this._error = document.createElement('div');
116
+ this._error.className = 'error';
117
+ this._error.setAttribute('role', 'alert');
118
+ this._error.hidden = true;
119
+ this.shadowRoot.append(this._card, this._error);
120
+
121
+ this._el = {
122
+ icon: this._card.querySelector('.icon'),
123
+ place: this._card.querySelector('.place'),
124
+ temp: this._card.querySelector('.temp'),
125
+ desc: this._card.querySelector('.desc'),
126
+ rain: this._card.querySelector('.stat'),
127
+ };
128
+
129
+ // Pull defaults from the configured RDF source; any HTML attribute
130
+ // that was already set wins (so a page can override one field
131
+ // inline while the rest come from the TTL).
132
+ await this._applySource();
133
+
134
+ await this._update();
135
+ this._timer = setInterval(() => this._update(), this._refreshMs);
136
+
137
+ if (this.hasAttribute('editor-self')) attachEditorSelfGear(this);
138
+ }
139
+
140
+ /**
141
+ * Apply config from `source` to attributes the component already
142
+ * observes. Mapping (predicate URI → HTML attribute):
143
+ * geo:lat → lat
144
+ * geo:long → lon
145
+ * schema:addressLocality → place
146
+ * ui:temperatureUnit → units ("metric"/"imperial"/"both")
147
+ * time:hours → hours-window
148
+ * Skips any attribute already set in HTML. See
149
+ * claude/plans/PLAN-vocab-migration.md for the predicate choices.
150
+ */
151
+ async _applySource() {
152
+ const source = this.getAttribute('source');
153
+ if (!source) return;
154
+ const GEO = 'http://www.w3.org/2003/01/geo/wgs84_pos#';
155
+ const SCHEMA = 'http://schema.org/';
156
+ const TIME = 'http://www.w3.org/2006/time#';
157
+ const UI = 'http://www.w3.org/ns/ui#';
158
+
159
+ try {
160
+ const cfg = await loadConfig(source);
161
+ const setIf = (attr, val) => {
162
+ if (val != null && !this.hasAttribute(attr)) {
163
+ this.setAttribute(attr, String(val));
164
+ }
165
+ };
166
+ setIf('lat', cfg[GEO + 'lat']);
167
+ setIf('lon', cfg[GEO + 'long']);
168
+ setIf('place', cfg[SCHEMA + 'addressLocality']);
169
+ setIf('hours-window', cfg[TIME + 'hours']);
170
+
171
+ // ui:temperatureUnit is single-valued. Map the three instances
172
+ // onto the legacy 'units' attribute the renderer already speaks.
173
+ const tu = cfg[UI + 'temperatureUnit'];
174
+ if (tu != null) {
175
+ const value = Array.isArray(tu) ? tu[0] : tu;
176
+ const units = value === UI + 'Fahrenheit' ? 'imperial'
177
+ : value === UI + 'Celsius' ? 'metric'
178
+ : value === UI + 'Both' ? 'both'
179
+ : null;
180
+ if (units) setIf('units', units);
181
+ }
182
+ } catch (err) {
183
+ console.warn(`[sol-weather] source ${source}: ${err.message}`);
184
+ }
185
+ }
186
+
187
+ disconnectedCallback() {
188
+ if (this._controller) this._controller.abort();
189
+ if (this._timer) { clearInterval(this._timer); this._timer = null; }
190
+ }
191
+
192
+ /**
193
+ * Re-read `source` and re-fetch weather. Public hook used by external
194
+ * editors (e.g. dk-settings) after a configuration file changes.
195
+ */
196
+ async reload() {
197
+ await this._applySource();
198
+ await this._update();
199
+ }
200
+
201
+ attributeChangedCallback(name, oldV, newV) {
202
+ if (oldV !== newV && this.isConnected) this._update();
203
+ }
204
+
205
+ get lat() { return this.getAttribute('lat'); }
206
+ get lon() { return this.getAttribute('lon'); }
207
+ get placeAttr() { return this.getAttribute('place'); }
208
+ get units() { return (this.getAttribute('units') || 'both').toLowerCase(); }
209
+ get hoursWindow() { return Math.max(1, Number(this.getAttribute('hours-window')) || 12); }
210
+
211
+ /** Show an error in the inline error strip. The card stays visible so a
212
+ * later successful refresh just replaces the strip. */
213
+ _showError(msg) {
214
+ if (!this._error) return;
215
+ this._error.hidden = false;
216
+ this._error.textContent = msg;
217
+ }
218
+ _clearError() {
219
+ if (!this._error) return;
220
+ this._error.hidden = true;
221
+ this._error.textContent = '';
222
+ }
223
+
224
+ async _update() {
225
+ // `attributeChangedCallback` fires for each statically-set attribute
226
+ // before `connectedCallback` runs, so we can be called before the
227
+ // shadow tree is built. Bail until setup finishes; the tail of
228
+ // `connectedCallback` will re-invoke us.
229
+ if (!this._card) return;
230
+ if (typeof navigator !== 'undefined' && navigator.onLine === false) {
231
+ // Offline — hide the card so it doesn't display stale numbers as if
232
+ // they were current; the next online refresh will repopulate it.
233
+ this._card.style.display = 'none';
234
+ return;
235
+ }
236
+ this._card.style.display = '';
237
+
238
+ if (this._controller) this._controller.abort();
239
+ this._controller = new AbortController();
240
+ const signal = this._controller.signal;
241
+
242
+ this._clearError();
243
+ this._el.place.textContent = 'Loading…';
244
+ this._el.temp.textContent = '';
245
+ this._el.icon.textContent = '·';
246
+ this._el.desc.textContent = '';
247
+ this._el.rain.textContent = '';
248
+
249
+ let lat = this.lat, lon = this.lon;
250
+ const placeName = this.placeAttr;
251
+
252
+ try {
253
+ if ((!lat || !lon) && placeName) {
254
+ const hits = await this._geocode(placeName, signal);
255
+ if (!hits.length) throw new Error('Place not found');
256
+ lat = hits[0].latitude;
257
+ lon = hits[0].longitude;
258
+ }
259
+ if (!lat || !lon) throw new Error('Provide lat & lon, or a place');
260
+
261
+ const data = await this._fetchWeather(Number(lat), Number(lon), signal);
262
+ this._render(data, { lat: Number(lat), lon: Number(lon) });
263
+ } catch (err) {
264
+ if (err.name === 'AbortError') return;
265
+ this._clearError();
266
+ this._card.style.display = 'none';
267
+ }
268
+ }
269
+
270
+ async _geocode(q, signal) {
271
+ const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(q)}&count=1&language=en&format=json`;
272
+ const res = await fetch(url, { signal });
273
+ if (!res.ok) throw new Error('Geocoding failed: ' + res.status);
274
+ const json = await res.json();
275
+ return json.results || [];
276
+ }
277
+
278
+ async _fetchWeather(lat, lon, signal) {
279
+ const hourly = [
280
+ 'temperature_2m',
281
+ 'precipitation_probability',
282
+ 'weathercode',
283
+ ].join(',');
284
+ const url = `https://api.open-meteo.com/v1/forecast`
285
+ + `?latitude=${encodeURIComponent(lat)}`
286
+ + `&longitude=${encodeURIComponent(lon)}`
287
+ + `&current_weather=true&hourly=${hourly}&timezone=auto`;
288
+ const res = await fetch(url, { signal });
289
+ if (!res.ok) throw new Error('Weather fetch failed: ' + res.status);
290
+ return res.json();
291
+ }
292
+
293
+ _render(data, coords) {
294
+ const cw = data.current_weather || {};
295
+ const hourly = data.hourly || {};
296
+ const times = hourly.time || [];
297
+
298
+ // Match the current-weather sample against the hourly index; fall
299
+ // back to the nearest hour when the timestamp isn't a perfect match
300
+ // (the API rounds differently across endpoints).
301
+ let idx = times.indexOf(cw.time);
302
+ if (idx === -1 && cw.time) {
303
+ const target = Date.parse(cw.time);
304
+ let best = 0, bestDiff = Infinity;
305
+ for (let i = 0; i < times.length; i++) {
306
+ const diff = Math.abs(Date.parse(times[i]) - target);
307
+ if (diff < bestDiff) { bestDiff = diff; best = i; }
308
+ }
309
+ idx = best;
310
+ }
311
+ if (idx < 0) idx = 0;
312
+
313
+ const probs = Array.isArray(hourly.precipitation_probability) ? hourly.precipitation_probability : [];
314
+ const windowProbs = probs.slice(idx, idx + this.hoursWindow);
315
+ const maxProb = windowProbs.length ? Math.max(...windowProbs) : null;
316
+
317
+ const code = cw.weathercode;
318
+ const mapping = WEATHER_MAP[code] || ['Unknown', '❔'];
319
+ const cTemp = Number(cw.temperature);
320
+ const fTemp = cToF(cTemp);
321
+
322
+ let tempText;
323
+ if (this.units === 'metric') tempText = `${cTemp.toFixed(1)}°C`;
324
+ else if (this.units === 'imperial') tempText = `${fTemp.toFixed(1)}°F`;
325
+ else tempText = `${cTemp.toFixed(1)}°C / ${fTemp.toFixed(1)}°F`;
326
+
327
+ this._el.icon.textContent = mapping[1];
328
+ this._el.place.textContent = this.placeAttr
329
+ || `${coords.lat.toFixed(2)}, ${coords.lon.toFixed(2)}`;
330
+ this._el.temp.textContent = tempText;
331
+ this._el.desc.textContent = mapping[0];
332
+ this._el.rain.textContent = maxProb != null ? `rain ${maxProb}%` : '';
333
+ }
334
+ }
335
+
336
+ define('sol-weather', SolWeather);
337
+ export { SolWeather };
@@ -0,0 +1,142 @@
1
+ /**
2
+ * <sol-window> — a lightweight in-page floating window.
3
+ *
4
+ * A draggable (by its title bar), resizable panel layered over the page,
5
+ * used as the `floating` region surface (see core/display-target.js).
6
+ * Unlike <sol-modal> it has no backdrop and doesn't trap focus — several
7
+ * can coexist, and the page behind stays interactive.
8
+ *
9
+ * Usage (typically created by the dispatcher, not hand-authored):
10
+ * const w = document.createElement('sol-window');
11
+ * w.setAttribute('title', 'Notes');
12
+ * document.body.appendChild(w);
13
+ * w.body.appendChild(someElement);
14
+ *
15
+ * Attributes: title
16
+ * Methods: close()
17
+ * Properties: body (the content container to mount into)
18
+ * Events: sol-close (bubbling, composed) when dismissed
19
+ *
20
+ * The content container is exposed as `::part(body)` and the title bar as
21
+ * `::part(titlebar)` for external styling.
22
+ */
23
+
24
+ import { define } from '../core/define.js';
25
+
26
+ // Stacking counter so a freshly-opened / focused window comes to the front.
27
+ let zTop = 1000;
28
+
29
+ const STYLE = `
30
+ :host {
31
+ position: fixed;
32
+ top: 10vh;
33
+ left: 50%;
34
+ transform: translateX(-50%);
35
+ min-width: 240px;
36
+ min-height: 140px;
37
+ width: 480px;
38
+ height: 360px;
39
+ display: flex;
40
+ flex-direction: column;
41
+ background: var(--surface, #fff);
42
+ color: var(--text, #111);
43
+ border: 1px solid var(--border, #ccc);
44
+ border-radius: 8px;
45
+ box-shadow: 0 10px 40px rgba(0,0,0,.3);
46
+ overflow: hidden;
47
+ resize: both;
48
+ z-index: 1000;
49
+ }
50
+ .titlebar {
51
+ display: flex;
52
+ align-items: center;
53
+ gap: 8px;
54
+ padding: 6px 10px;
55
+ cursor: move;
56
+ user-select: none;
57
+ background: var(--surface-2, color-mix(in srgb, var(--surface, #fff) 85%, #000));
58
+ border-bottom: 1px solid var(--border, #ccc);
59
+ }
60
+ .title { flex: 1 1 auto; font: var(--font-ui, 600 13px system-ui); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
61
+ .close {
62
+ flex: 0 0 auto;
63
+ border: none; background: transparent; color: inherit;
64
+ font-size: 16px; line-height: 1; cursor: pointer; padding: 2px 6px; border-radius: 4px;
65
+ }
66
+ .close:hover { background: var(--hover, rgba(0,0,0,.1)); }
67
+ .body { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 10px; }
68
+ `;
69
+
70
+ class SolWindow extends HTMLElement {
71
+ static get observedAttributes() { return ['title']; }
72
+
73
+ constructor() {
74
+ super();
75
+ this.attachShadow({ mode: 'open' });
76
+ }
77
+
78
+ connectedCallback() {
79
+ if (this._rendered) return;
80
+ this._rendered = true;
81
+ this.shadowRoot.innerHTML = `
82
+ <style>${STYLE}</style>
83
+ <div class="titlebar" part="titlebar">
84
+ <span class="title">${this.getAttribute('title') || ''}</span>
85
+ <button class="close" part="close" type="button" aria-label="Close">✕</button>
86
+ </div>
87
+ <div class="body" part="body"></div>`;
88
+
89
+ this.style.zIndex = String(++zTop);
90
+ // Bring to front on any interaction.
91
+ this.addEventListener('pointerdown', () => { this.style.zIndex = String(++zTop); });
92
+
93
+ this.shadowRoot.querySelector('.close').addEventListener('click', () => this.close());
94
+ this._wireDrag(this.shadowRoot.querySelector('.titlebar'));
95
+ }
96
+
97
+ attributeChangedCallback(name, oldV, newV) {
98
+ if (name === 'title' && this.shadowRoot) {
99
+ const t = this.shadowRoot.querySelector('.title');
100
+ if (t) t.textContent = newV || '';
101
+ }
102
+ }
103
+
104
+ /** Content container callers mount into. */
105
+ get body() { return this.shadowRoot.querySelector('.body'); }
106
+
107
+ close() {
108
+ this.dispatchEvent(new CustomEvent('sol-close', { bubbles: true, composed: true }));
109
+ this.remove();
110
+ }
111
+
112
+ // Drag the window by its title bar. Uses fixed left/top in px; the
113
+ // initial centering transform is cleared on first drag so movement is
114
+ // absolute and predictable.
115
+ _wireDrag(handle) {
116
+ let startX, startY, originLeft, originTop;
117
+ const onMove = (e) => {
118
+ this.style.left = `${originLeft + (e.clientX - startX)}px`;
119
+ this.style.top = `${originTop + (e.clientY - startY)}px`;
120
+ };
121
+ const onUp = () => {
122
+ window.removeEventListener('pointermove', onMove);
123
+ window.removeEventListener('pointerup', onUp);
124
+ };
125
+ handle.addEventListener('pointerdown', (e) => {
126
+ if (e.target.closest('.close')) return;
127
+ const rect = this.getBoundingClientRect();
128
+ this.style.transform = 'none';
129
+ this.style.left = `${rect.left}px`;
130
+ this.style.top = `${rect.top}px`;
131
+ startX = e.clientX; startY = e.clientY;
132
+ originLeft = rect.left; originTop = rect.top;
133
+ window.addEventListener('pointermove', onMove);
134
+ window.addEventListener('pointerup', onUp);
135
+ e.preventDefault();
136
+ });
137
+ }
138
+ }
139
+
140
+ define('sol-window', SolWindow);
141
+ export { SolWindow };
142
+ export default SolWindow;
@@ -0,0 +1,108 @@
1
+ // Shared button + form-control rules used across all sol-* components.
2
+ // Each component's *-css.js string prepends BTN_CSS so the rules reach
3
+ // the component's shadow scope (or light-DOM root, via ensureDocStyle).
4
+ //
5
+ // Base class: .sol-btn — default (medium) button
6
+ // Sizes: .sol-btn-sm — compact (used in headers, auth, chips)
7
+ // .sol-btn-icon — square icon/nav button (prev/next arrows)
8
+ // Variants: .sol-btn-primary, .sol-btn-danger, .sol-btn-ghost
9
+ // Inputs: .sol-input, .sol-select-control
10
+ //
11
+ // All colors resolve from root.css vars with sensible fallbacks.
12
+
13
+ import { sheetFrom } from '../../core/adopt.js';
14
+
15
+ export const BTN_CSS = `
16
+ .sol-btn {
17
+ font: inherit;
18
+ font-family: var(--font-ui, inherit);
19
+ background: var(--surface, #fff);
20
+ color: var(--text, #212121);
21
+ border: 1px solid var(--border, #d0d0d0);
22
+ border-radius: 4px;
23
+ padding: 0.35em 0.8em;
24
+ line-height: 1.2;
25
+ cursor: pointer;
26
+ white-space: nowrap;
27
+ box-sizing: border-box;
28
+ }
29
+ .sol-btn:hover {
30
+ background: var(--hover, #eaf2fb);
31
+ border-color: var(--accent, #3498db);
32
+ color: var(--text, #212121);
33
+ }
34
+ .sol-btn:focus-visible {
35
+ outline: 2px solid var(--accent, #3498db);
36
+ outline-offset: 1px;
37
+ }
38
+ .sol-btn[disabled],
39
+ .sol-btn:disabled {
40
+ opacity: 0.55;
41
+ cursor: not-allowed;
42
+ }
43
+
44
+ .sol-btn-sm {
45
+ padding: 3px 10px;
46
+ font-size: 0.82em;
47
+ border-radius: 4px;
48
+ }
49
+
50
+ .sol-btn-icon {
51
+ padding: 0.25rem 0.6rem;
52
+ font-size: 1.15em;
53
+ line-height: 1;
54
+ }
55
+
56
+ .sol-btn-primary {
57
+ background: var(--accent, #3498db);
58
+ color: #fff;
59
+ border-color: var(--accent, #3498db);
60
+ }
61
+ .sol-btn-primary:hover {
62
+ background: var(--accent-dark, #2980b9);
63
+ border-color: var(--accent-dark, #2980b9);
64
+ color: #fff;
65
+ }
66
+ .sol-btn-primary:disabled,
67
+ .sol-btn-primary[disabled] { background: #ccc; border-color: #ccc; color: #fff; }
68
+
69
+ .sol-btn-danger {
70
+ background: var(--error, #e74c3c);
71
+ color: #fff;
72
+ border-color: var(--error, #e74c3c);
73
+ }
74
+ .sol-btn-danger:hover {
75
+ background: color-mix(in srgb, var(--error, #e74c3c) 85%, #000);
76
+ border-color: color-mix(in srgb, var(--error, #e74c3c) 85%, #000);
77
+ color: #fff;
78
+ }
79
+
80
+ .sol-btn-ghost {
81
+ background: transparent;
82
+ border-color: transparent;
83
+ color: var(--text-muted, #666);
84
+ }
85
+ .sol-btn-ghost:hover {
86
+ background: var(--hover, #f0f0f0);
87
+ color: var(--text, #212121);
88
+ border-color: transparent;
89
+ }
90
+
91
+ .sol-input, .sol-select-control {
92
+ font: inherit;
93
+ background: var(--surface, #fff);
94
+ color: var(--text, #212121);
95
+ border: 1px solid var(--border, #d0d0d0);
96
+ border-radius: 4px;
97
+ padding: 0.35em 0.55em;
98
+ box-sizing: border-box;
99
+ }
100
+ .sol-input:focus, .sol-select-control:focus {
101
+ outline: 2px solid var(--accent, #3498db);
102
+ outline-offset: 1px;
103
+ border-color: var(--accent, #3498db);
104
+ }
105
+ `;
106
+
107
+ export const sheet = sheetFrom(BTN_CSS);
108
+ export default sheet;