@matdata/yasgui 5.0.1 → 5.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@matdata/yasgui",
3
3
  "description": "Yet Another SPARQL GUI",
4
- "version": "5.0.1",
4
+ "version": "5.1.0",
5
5
  "main": "build/yasgui.min.js",
6
6
  "types": "build/ts/src/index.d.ts",
7
7
  "license": "MIT",
package/src/Tab.ts CHANGED
@@ -356,8 +356,13 @@ export class Tab extends EventEmitter {
356
356
  }
357
357
 
358
358
  private initYasqe() {
359
+ // Set theme based on current yasgui theme
360
+ const currentTheme = this.yasgui.getTheme();
361
+ const cmTheme = currentTheme === "dark" ? "material-palenight" : "default";
362
+
359
363
  const yasqeConf: Partial<YasqeConfig> = {
360
364
  ...this.yasgui.config.yasqe,
365
+ theme: cmTheme,
361
366
  value: this.persistentJson.yasqe.value,
362
367
  editorHeight: this.persistentJson.yasqe.editorHeight ? this.persistentJson.yasqe.editorHeight : undefined,
363
368
  persistenceId: null, //yasgui handles persistent storing
@@ -18,6 +18,33 @@ $minTabHeight: 35px;
18
18
  border-bottom: 2px solid transparent;
19
19
  box-sizing: border-box;
20
20
  }
21
+ .themeToggle {
22
+ cursor: pointer;
23
+ height: 100%;
24
+ margin-left: auto;
25
+ margin-right: 10px;
26
+ padding: 6px 8px;
27
+ background: transparent;
28
+ border: none;
29
+ color: var(--yasgui-button-text, #505050);
30
+ fill: var(--yasgui-button-text, #505050);
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ border-radius: 4px;
35
+
36
+ &:hover {
37
+ color: var(--yasgui-button-hover, #000);
38
+ fill: var(--yasgui-button-hover, #000);
39
+ background: var(--yasgui-bg-secondary, #f7f7f7);
40
+ }
41
+
42
+ svg {
43
+ width: 20px;
44
+ height: 20px;
45
+ }
46
+ }
47
+
21
48
  .addTab {
22
49
  cursor: pointer;
23
50
  height: 100%;
@@ -80,7 +107,7 @@ $minTabHeight: 35px;
80
107
  }
81
108
  &.active a {
82
109
  border-bottom-color: $activeColor;
83
- color: #555;
110
+ color: var(--yasgui-text-primary, #555);
84
111
  }
85
112
  input {
86
113
  display: none;
@@ -100,7 +127,7 @@ $minTabHeight: 35px;
100
127
  }
101
128
  a {
102
129
  font-weight: 600;
103
- color: color.adjust(#555, $lightness: 20%);
130
+ color: var(--yasgui-text-secondary, color.adjust(#555, $lightness: 20%));
104
131
  font-size: 15px;
105
132
  line-height: 1.5rem;
106
133
  font-weight: 500;
@@ -110,17 +137,17 @@ $minTabHeight: 35px;
110
137
  overflow: hidden;
111
138
  &:hover {
112
139
  border-bottom-color: $hoverColor;
113
- color: #555;
140
+ color: var(--yasgui-text-primary, #555);
114
141
  }
115
142
  &:focus {
116
143
  border-bottom-color: #faa857;
117
- color: #555;
144
+ color: var(--yasgui-text-primary, #555);
118
145
  }
119
146
  .closeTab {
120
- color: #000;
147
+ color: var(--yasgui-text-primary, #000);
121
148
  margin-left: 7px;
122
149
  font-size: 15px;
123
- text-shadow: 0 1px 0 #fff;
150
+ text-shadow: 0 1px 0 var(--yasgui-bg-primary, #fff);
124
151
  opacity: 0.2;
125
152
  font-weight: 700;
126
153
  padding: 2px;
@@ -3,6 +3,15 @@ import TabContextMenu from "./TabContextMenu";
3
3
  import { hasClass, addClass, removeClass } from "@matdata/yasgui-utils";
4
4
  import sortablejs from "sortablejs";
5
5
  import "./TabElements.scss";
6
+
7
+ // Theme toggle icons
8
+ const MOON_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
9
+ <path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/>
10
+ </svg>`;
11
+
12
+ const SUN_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
13
+ <path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/>
14
+ </svg>`;
6
15
  export interface TabList {}
7
16
  export class TabListEl {
8
17
  private tabList: TabList;
@@ -1,3 +1,28 @@
1
+ .themeToggle {
2
+ cursor: pointer;
3
+ background: transparent;
4
+ color: inherit;
5
+ border: none;
6
+ padding: 6px 8px;
7
+ margin-left: 10px;
8
+ display: flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+
12
+ svg {
13
+ width: 20px;
14
+ height: 20px;
15
+ }
16
+
17
+ &:hover {
18
+ opacity: 0.7;
19
+ }
20
+
21
+ &:active {
22
+ opacity: 0.5;
23
+ }
24
+ }
25
+
1
26
  .tabPrefixButton {
2
27
  cursor: pointer;
3
28
  background: transparent;
@@ -35,7 +60,7 @@
35
60
  }
36
61
 
37
62
  .tabSettingsModal {
38
- background: white;
63
+ background: var(--yasgui-bg-primary, white);
39
64
  border-radius: 8px;
40
65
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
41
66
  max-width: 800px;
@@ -51,12 +76,13 @@
51
76
  justify-content: space-between;
52
77
  align-items: center;
53
78
  padding: 20px;
54
- border-bottom: 1px solid #e0e0e0;
79
+ border-bottom: 1px solid var(--yasgui-border-color, #e0e0e0);
55
80
 
56
81
  h2 {
57
82
  margin: 0;
58
83
  font-size: 20px;
59
84
  font-weight: 600;
85
+ color: var(--yasgui-text-primary, #000);
60
86
  }
61
87
 
62
88
  .closeButton {
@@ -65,13 +91,13 @@
65
91
  font-size: 32px;
66
92
  line-height: 1;
67
93
  cursor: pointer;
68
- color: #666;
94
+ color: var(--yasgui-text-secondary, #666);
69
95
  padding: 0;
70
96
  width: 32px;
71
97
  height: 32px;
72
98
 
73
99
  &:hover {
74
- color: #333;
100
+ color: var(--yasgui-text-primary, #333);
75
101
  }
76
102
  }
77
103
  }
@@ -86,7 +112,7 @@
86
112
  display: flex;
87
113
  gap: 10px;
88
114
  margin-bottom: 20px;
89
- border-bottom: 2px solid #e0e0e0;
115
+ border-bottom: 2px solid var(--yasgui-border-color, #e0e0e0);
90
116
  }
91
117
 
92
118
  .modalTabButton {
@@ -96,18 +122,18 @@
96
122
  font-size: 14px;
97
123
  font-weight: 600;
98
124
  cursor: pointer;
99
- color: #666;
125
+ color: var(--yasgui-text-secondary, #666);
100
126
  border-bottom: 3px solid transparent;
101
127
  margin-bottom: -2px;
102
128
  transition: all 0.2s;
103
129
 
104
130
  &:hover {
105
- color: #337ab7;
131
+ color: var(--yasgui-accent-color, #337ab7);
106
132
  }
107
133
 
108
134
  &.active {
109
- color: #337ab7;
110
- border-bottom-color: #337ab7;
135
+ color: var(--yasgui-accent-color, #337ab7);
136
+ border-bottom-color: var(--yasgui-accent-color, #337ab7);
111
137
  }
112
138
  }
113
139
 
@@ -128,11 +154,12 @@
128
154
  margin-bottom: 8px;
129
155
  font-size: 14px;
130
156
  display: block;
157
+ color: var(--yasgui-text-primary, #000);
131
158
  }
132
159
 
133
160
  .settingsHelp {
134
161
  font-size: 12px;
135
- color: #666;
162
+ color: var(--yasgui-text-secondary, #666);
136
163
  margin-bottom: 10px;
137
164
  font-style: italic;
138
165
  }
@@ -140,13 +167,15 @@
140
167
  .settingsSelect {
141
168
  width: 100%;
142
169
  padding: 8px;
143
- border: 1px solid #ccc;
170
+ border: 1px solid var(--yasgui-input-border, #ccc);
144
171
  border-radius: 4px;
145
172
  font-size: 14px;
173
+ background-color: var(--yasgui-bg-secondary, white);
174
+ color: var(--yasgui-text-primary, #000);
146
175
 
147
176
  &:focus {
148
177
  outline: none;
149
- border-color: #337ab7;
178
+ border-color: var(--yasgui-input-focus, #337ab7);
150
179
  box-shadow: 0 0 0 2px rgba(51, 122, 183, 0.1);
151
180
  }
152
181
  }
@@ -154,17 +183,19 @@
154
183
  .prefixTextarea {
155
184
  width: 100%;
156
185
  padding: 10px;
157
- border: 1px solid #ccc;
186
+ border: 1px solid var(--yasgui-input-border, #ccc);
158
187
  border-radius: 4px;
159
188
  font-family: monospace;
160
189
  font-size: 13px;
161
190
  resize: vertical;
162
191
  box-sizing: border-box;
163
192
  min-height: 200px;
193
+ background-color: var(--yasgui-bg-secondary, white);
194
+ color: var(--yasgui-text-primary, #000);
164
195
 
165
196
  &:focus {
166
197
  outline: none;
167
- border-color: #337ab7;
198
+ border-color: var(--yasgui-input-focus, #337ab7);
168
199
  box-shadow: 0 0 0 2px rgba(51, 122, 183, 0.1);
169
200
  }
170
201
  }
@@ -186,12 +217,13 @@
186
217
  font-size: 14px;
187
218
  user-select: none;
188
219
  margin: 0;
220
+ color: var(--yasgui-text-primary, #000);
189
221
  }
190
222
  }
191
223
 
192
224
  .modalFooter {
193
225
  padding: 15px 20px;
194
- border-top: 1px solid #e0e0e0;
226
+ border-top: 1px solid var(--yasgui-border-color, #e0e0e0);
195
227
  display: flex;
196
228
  justify-content: flex-end;
197
229
  gap: 10px;
@@ -208,19 +240,19 @@
208
240
  }
209
241
 
210
242
  .primaryButton {
211
- background: #337ab7;
243
+ background: var(--yasgui-accent-color, #337ab7);
212
244
  color: white;
213
245
 
214
246
  &:hover {
215
- background: #286090;
247
+ background: var(--yasgui-link-hover, #286090);
216
248
  }
217
249
  }
218
250
 
219
251
  .secondaryButton {
220
- background: #e0e0e0;
221
- color: #333;
252
+ background: var(--yasgui-bg-tertiary, #e0e0e0);
253
+ color: var(--yasgui-text-primary, #333);
222
254
 
223
255
  &:hover {
224
- background: #d0d0d0;
256
+ background: var(--yasgui-border-color, #d0d0d0);
225
257
  }
226
258
  }
@@ -1,7 +1,20 @@
1
- import { addClass, removeClass, drawSvgStringAsElement } from "@matdata/yasgui-utils";
1
+ import { addClass, removeClass } from "@matdata/yasgui-utils";
2
2
  import "./TabSettingsModal.scss";
3
3
  import Tab from "./Tab";
4
4
 
5
+ // Theme toggle icons
6
+ const MOON_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
7
+ <path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/>
8
+ </svg>`;
9
+
10
+ const SUN_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
11
+ <path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/>
12
+ </svg>`;
13
+
14
+ const SETTINGS_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
15
+ <path d="M19.43 12.98c.04-.32.07-.64.07-.98 0-.34-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.09-.16-.26-.25-.44-.25-.06 0-.12.01-.17.03l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.06-.02-.12-.03-.18-.03-.17 0-.34.09-.43.25l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98 0 .33.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.09.16.26.25.44.25.06 0 .12-.01.17-.03l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.06.02.12.03.18.03.17 0 .34-.09.43-.25l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zm-7.43 2.52c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"/>
16
+ </svg>`;
17
+
5
18
  const AcceptOptionsMap: { key: string; value: string }[] = [
6
19
  { key: "JSON", value: "application/sparql-results+json" },
7
20
  { key: "XML", value: "application/sparql-results+xml" },
@@ -24,6 +37,7 @@ export default class TabSettingsModal {
24
37
  private modalOverlay!: HTMLElement;
25
38
  private modalContent!: HTMLElement;
26
39
  private settingsButton!: HTMLButtonElement;
40
+ private themeToggleButton!: HTMLButtonElement;
27
41
  private prefixButton!: HTMLButtonElement;
28
42
  private prefixTextarea!: HTMLTextAreaElement;
29
43
  private autoCaptureCheckbox!: HTMLInputElement;
@@ -36,19 +50,26 @@ export default class TabSettingsModal {
36
50
  private init(controlBarEl: HTMLElement) {
37
51
  // Settings button
38
52
  this.settingsButton = document.createElement("button");
53
+ addClass(this.settingsButton, "tabContextButton");
39
54
  this.settingsButton.setAttribute("aria-label", "Settings");
40
55
  this.settingsButton.title = "Settings";
41
- this.settingsButton.appendChild(
42
- drawSvgStringAsElement(
43
- `<svg width="100.06" height="100.05" data-name="Layer 1" version="1.1" viewBox="0 0 100.06 100.05" xmlns="http://www.w3.org/2000/svg">
44
- <title>Settings</title>
45
- <path d="m95.868 58.018-3-3.24a42.5 42.5 0 0 0 0-9.43l3-3.22c1.79-1.91 5-4.44 4-6.85l-4.11-10c-1-2.41-5.08-1.91-7.69-2l-4.43-0.16a43.24 43.24 0 0 0-6.64-6.66l-0.14-4.43c-0.08-2.6 0.43-6.69-2-7.69l-10-4.15c-2.4-1-4.95 2.25-6.85 4l-3.23 3a42.49 42.49 0 0 0-9.44 0l-3.21-3c-1.9-1.78-4.44-5-6.85-4l-10 4.11c-2.41 1-1.9 5.09-2 7.69l-0.16 4.42a43.24 43.24 0 0 0-6.67 6.65l-4.42 0.14c-2.6 0.08-6.69-0.43-7.69 2l-4.15 10c-1 2.4 2.25 4.94 4 6.84l3 3.23a42.49 42.49 0 0 0 0 9.44l-3 3.22c-1.78 1.9-5 4.43-4 6.84l4.11 10c1 2.41 5.09 1.91 7.7 2l4.41 0.15a43.24 43.24 0 0 0 6.66 6.68l0.13 4.41c0.08 2.6-0.43 6.7 2 7.7l10 4.15c2.4 1 4.94-2.25 6.84-4l3.24-3a42.5 42.5 0 0 0 9.42 0l3.22 3c1.91 1.79 4.43 5 6.84 4l10-4.11c2.41-1 1.91-5.08 2-7.7l0.15-4.42a43.24 43.24 0 0 0 6.68-6.65l4.42-0.14c2.6-0.08 6.7 0.43 7.7-2l4.15-10c1.04-2.36-2.22-4.9-3.99-6.82zm-45.74 15.7c-12.66 0-22.91-10.61-22.91-23.7s10.25-23.7 22.91-23.7 22.91 10.61 22.91 23.7-10.25 23.7-22.91 23.7z"/>
46
- </svg>`,
47
- ),
48
- );
49
- addClass(this.settingsButton, "tabContextButton");
50
- controlBarEl.appendChild(this.settingsButton);
56
+ this.settingsButton.innerHTML = SETTINGS_ICON;
51
57
  this.settingsButton.onclick = () => this.open();
58
+ controlBarEl.appendChild(this.settingsButton);
59
+
60
+ // Theme toggle button (if enabled)
61
+ if (this.tab.yasgui.config.showThemeToggle) {
62
+ this.themeToggleButton = document.createElement("button");
63
+ addClass(this.themeToggleButton, "themeToggle");
64
+ this.themeToggleButton.setAttribute("aria-label", "Toggle between light and dark theme");
65
+ this.themeToggleButton.title = "Toggle theme";
66
+ this.themeToggleButton.innerHTML = this.getThemeToggleIcon();
67
+ this.themeToggleButton.addEventListener("click", () => {
68
+ this.tab.yasgui.toggleTheme();
69
+ this.themeToggleButton.innerHTML = this.getThemeToggleIcon();
70
+ });
71
+ controlBarEl.appendChild(this.themeToggleButton);
72
+ }
52
73
 
53
74
  // Prefix button
54
75
  this.prefixButton = document.createElement("button");
@@ -77,7 +98,7 @@ export default class TabSettingsModal {
77
98
  const header = document.createElement("div");
78
99
  addClass(header, "modalHeader");
79
100
  const title = document.createElement("h2");
80
- title.textContent = "Tab Settings";
101
+ title.textContent = "Settings";
81
102
  header.appendChild(title);
82
103
 
83
104
  const closeBtn = document.createElement("button");
@@ -416,6 +437,13 @@ export default class TabSettingsModal {
416
437
  this.tab.yasgui.persistentConfig.setPrefixes(deduplicated);
417
438
  }
418
439
 
440
+ private getThemeToggleIcon(): string {
441
+ const currentTheme = this.tab.yasgui.getTheme();
442
+ // In dark mode, show moon icon (clicking will switch to light)
443
+ // In light mode, show sun icon (clicking will switch to dark)
444
+ return currentTheme === "dark" ? MOON_ICON : SUN_ICON;
445
+ }
446
+
419
447
  public destroy() {
420
448
  if (this.modalOverlay && this.modalOverlay.parentNode) {
421
449
  this.modalOverlay.parentNode.removeChild(this.modalOverlay);
@@ -0,0 +1,120 @@
1
+ import { Storage as YStorage } from "@matdata/yasgui-utils";
2
+
3
+ export type Theme = "light" | "dark";
4
+
5
+ export class ThemeManager {
6
+ private static STORAGE_KEY = "yasgui_theme";
7
+ private storage: YStorage;
8
+ private currentTheme: Theme;
9
+ private rootElement: HTMLElement;
10
+
11
+ constructor(rootElement: HTMLElement) {
12
+ this.storage = new YStorage("yasgui");
13
+ this.rootElement = rootElement;
14
+ this.currentTheme = this.getStoredTheme() || this.getSystemTheme();
15
+ this.applyTheme(this.currentTheme);
16
+ }
17
+
18
+ /**
19
+ * Get the currently active theme
20
+ */
21
+ public getTheme(): Theme {
22
+ return this.currentTheme;
23
+ }
24
+
25
+ /**
26
+ * Set and apply a new theme
27
+ */
28
+ public setTheme(theme: Theme): void {
29
+ this.currentTheme = theme;
30
+ this.applyTheme(theme);
31
+ // Store theme preference with 1 year expiration
32
+ this.storage.set(ThemeManager.STORAGE_KEY, theme, 60 * 60 * 24 * 365, () => {
33
+ console.warn("Failed to store theme preference due to quota exceeded");
34
+ });
35
+ }
36
+
37
+ /**
38
+ * Toggle between light and dark themes
39
+ */
40
+ public toggleTheme(): Theme {
41
+ const newTheme = this.currentTheme === "light" ? "dark" : "light";
42
+ this.setTheme(newTheme);
43
+ return newTheme;
44
+ }
45
+
46
+ /**
47
+ * Apply theme to the DOM
48
+ */
49
+ private applyTheme(theme: Theme): void {
50
+ // Set data-theme attribute on root element
51
+ this.rootElement.setAttribute("data-theme", theme);
52
+
53
+ // Also set on document root for global styles
54
+ document.documentElement.setAttribute("data-theme", theme);
55
+
56
+ // Update Yasqe CodeMirror theme
57
+ this.updateCodeMirrorTheme(theme);
58
+ }
59
+
60
+ /**
61
+ * Update CodeMirror theme for all Yasqe editors
62
+ */
63
+ private updateCodeMirrorTheme(theme: Theme): void {
64
+ const cmTheme = theme === "dark" ? "material-palenight" : "default";
65
+
66
+ // Find all CodeMirror instances within the root element
67
+ const cmElements = this.rootElement.querySelectorAll(".CodeMirror");
68
+ cmElements.forEach((element) => {
69
+ const cm = (element as any).CodeMirror;
70
+ if (cm && cm.setOption) {
71
+ cm.setOption("theme", cmTheme);
72
+ }
73
+ });
74
+ }
75
+
76
+ /**
77
+ * Get theme from localStorage
78
+ */
79
+ private getStoredTheme(): Theme | null {
80
+ const stored = this.storage.get<Theme>(ThemeManager.STORAGE_KEY);
81
+ if (stored === "light" || stored === "dark") {
82
+ return stored;
83
+ }
84
+ return null;
85
+ }
86
+
87
+ /**
88
+ * Detect system theme preference
89
+ */
90
+ private getSystemTheme(): Theme {
91
+ if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
92
+ return "dark";
93
+ }
94
+ return "light";
95
+ }
96
+
97
+ /**
98
+ * Listen for system theme changes
99
+ */
100
+ public listenToSystemTheme(): void {
101
+ if (window.matchMedia) {
102
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
103
+ mediaQuery.addEventListener("change", (e) => {
104
+ // Only update if user hasn't manually set a theme
105
+ if (!this.storage.get(ThemeManager.STORAGE_KEY)) {
106
+ const newTheme = e.matches ? "dark" : "light";
107
+ this.currentTheme = newTheme;
108
+ this.applyTheme(newTheme);
109
+ }
110
+ });
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Refresh theme application (useful after new content is loaded)
116
+ */
117
+ public refresh(): void {
118
+ this.applyTheme(this.currentTheme);
119
+ }
120
+ }
package/src/defaults.ts CHANGED
@@ -28,6 +28,8 @@ export default function initialize(): Config<CatalogueItem> {
28
28
  persistenceLabelConfig: "config",
29
29
  yasqe: Yasqe.defaults,
30
30
  yasr: Yasr.defaults,
31
+ theme: undefined,
32
+ showThemeToggle: true,
31
33
  endpointCatalogueOptions: {
32
34
  getData: () => {
33
35
  return [
package/src/index.ts CHANGED
@@ -12,8 +12,11 @@ import { default as Yasr, Config as YasrConfig } from "@matdata/yasr";
12
12
  import { addClass, removeClass } from "@matdata/yasgui-utils";
13
13
  import GeoPlugin from "yasgui-geo-tg";
14
14
  import GraphPlugin from "@matdata/yasgui-graph-plugin";
15
+ import { ThemeManager, Theme } from "./ThemeManager";
15
16
  import "./index.scss";
17
+ import "./themes.scss";
16
18
  import "../../yasr/src/scss/global.scss";
19
+ import "codemirror/theme/material-palenight.css";
17
20
 
18
21
  // Register plugins to Yasr
19
22
  Yasr.registerPlugin("Geo", GeoPlugin);
@@ -50,6 +53,8 @@ export interface Config<EndpointObject extends CatalogueItem = CatalogueItem> {
50
53
  requestConfig: YasguiRequestConfig;
51
54
  contextMenuContainer: HTMLElement | undefined;
52
55
  nonSslDomain?: string;
56
+ theme?: Theme;
57
+ showThemeToggle?: boolean;
53
58
  }
54
59
  export type PartialConfig = {
55
60
  [P in keyof Config]?: Config[P] extends object ? Partial<Config[P]> : Config[P];
@@ -96,6 +101,7 @@ export class Yasgui extends EventEmitter {
96
101
  public tabPanelsEl: HTMLDivElement;
97
102
  public config: Config;
98
103
  public persistentConfig: PersistentConfig;
104
+ public themeManager: ThemeManager;
99
105
  public static Tab = Tab;
100
106
  constructor(parent: HTMLElement, config: PartialConfig) {
101
107
  super();
@@ -104,6 +110,13 @@ export class Yasgui extends EventEmitter {
104
110
  parent.appendChild(this.rootEl);
105
111
 
106
112
  this.config = merge({}, Yasgui.defaults, config);
113
+
114
+ // Initialize theme manager
115
+ this.themeManager = new ThemeManager(this.rootEl);
116
+ if (this.config.theme) {
117
+ this.themeManager.setTheme(this.config.theme);
118
+ }
119
+ this.themeManager.listenToSystemTheme();
107
120
  this.persistentConfig = new PersistentConfig(this);
108
121
 
109
122
  this.tabElements = new TabElements(this);
@@ -348,6 +361,24 @@ export class Yasgui extends EventEmitter {
348
361
  this.addTab(true, config.tab, { atIndex: config.index });
349
362
  }
350
363
  }
364
+ /**
365
+ * Get the current theme
366
+ */
367
+ public getTheme(): Theme {
368
+ return this.themeManager.getTheme();
369
+ }
370
+ /**
371
+ * Set the theme
372
+ */
373
+ public setTheme(theme: Theme): void {
374
+ this.themeManager.setTheme(theme);
375
+ }
376
+ /**
377
+ * Toggle between light and dark themes
378
+ */
379
+ public toggleTheme(): Theme {
380
+ return this.themeManager.toggleTheme();
381
+ }
351
382
  public destroy() {
352
383
  this.removeAllListeners();
353
384
  this.tabElements.destroy();
@@ -370,4 +401,5 @@ export function getRandomId() {
370
401
  return Math.random().toString(36).substring(7);
371
402
  }
372
403
 
404
+ export type { Theme } from "./ThemeManager";
373
405
  export default Yasgui;
package/src/tab.scss CHANGED
@@ -32,9 +32,13 @@
32
32
  background: none;
33
33
  align-self: center;
34
34
  padding-left: 10px;
35
+ padding-right: 10px;
35
36
  cursor: pointer;
36
- color: #505050;
37
- fill: #505050;
37
+ color: var(--yasgui-button-text, #505050);
38
+ fill: var(--yasgui-button-text, #505050);
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
38
42
 
39
43
  .svgImg {
40
44
  width: 15px;
@@ -43,13 +47,13 @@
43
47
  }
44
48
  // IE11 Needs this specified otherwise it will not resize the svg
45
49
  svg {
46
- max-width: 15px;
47
- max-height: 15px;
50
+ width: 20px;
51
+ height: 20px;
48
52
  }
49
53
 
50
54
  &:hover {
51
- color: black;
52
- fill: black;
55
+ color: var(--yasgui-button-hover, black);
56
+ fill: var(--yasgui-button-hover, black);
53
57
  }
54
58
  }
55
59