@seed-ship/mcp-ui-solid 6.24.0 → 6.25.1

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 (124) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +47 -0
  3. package/dist/components/ActionGroupRenderer.cjs +6 -6
  4. package/dist/components/ActionGroupRenderer.cjs.map +1 -1
  5. package/dist/components/ActionGroupRenderer.js +6 -6
  6. package/dist/components/ActionGroupRenderer.js.map +1 -1
  7. package/dist/components/AutocompleteDropdown.cjs +6 -6
  8. package/dist/components/AutocompleteDropdown.cjs.map +1 -1
  9. package/dist/components/AutocompleteDropdown.js +6 -6
  10. package/dist/components/AutocompleteDropdown.js.map +1 -1
  11. package/dist/components/AutocompleteFormField.cjs +6 -5
  12. package/dist/components/AutocompleteFormField.cjs.map +1 -1
  13. package/dist/components/AutocompleteFormField.d.ts.map +1 -1
  14. package/dist/components/AutocompleteFormField.js +6 -5
  15. package/dist/components/AutocompleteFormField.js.map +1 -1
  16. package/dist/components/ChartJSRenderer.cjs +216 -6
  17. package/dist/components/ChartJSRenderer.cjs.map +1 -1
  18. package/dist/components/ChartJSRenderer.d.ts.map +1 -1
  19. package/dist/components/ChartJSRenderer.js +217 -7
  20. package/dist/components/ChartJSRenderer.js.map +1 -1
  21. package/dist/components/ChatPrompt.cjs +5 -5
  22. package/dist/components/ChatPrompt.cjs.map +1 -1
  23. package/dist/components/ChatPrompt.js +5 -5
  24. package/dist/components/ChatPrompt.js.map +1 -1
  25. package/dist/components/ComponentToolbar.cjs +5 -5
  26. package/dist/components/ComponentToolbar.cjs.map +1 -1
  27. package/dist/components/ComponentToolbar.js +5 -5
  28. package/dist/components/ComponentToolbar.js.map +1 -1
  29. package/dist/components/DegradedFallback.cjs +1 -1
  30. package/dist/components/DegradedFallback.cjs.map +1 -1
  31. package/dist/components/DegradedFallback.js +1 -1
  32. package/dist/components/DegradedFallback.js.map +1 -1
  33. package/dist/components/ExpandableWrapper.cjs +10 -7
  34. package/dist/components/ExpandableWrapper.cjs.map +1 -1
  35. package/dist/components/ExpandableWrapper.d.ts.map +1 -1
  36. package/dist/components/ExpandableWrapper.js +10 -7
  37. package/dist/components/ExpandableWrapper.js.map +1 -1
  38. package/dist/components/FormFieldRenderer.cjs +13 -12
  39. package/dist/components/FormFieldRenderer.cjs.map +1 -1
  40. package/dist/components/FormFieldRenderer.d.ts.map +1 -1
  41. package/dist/components/FormFieldRenderer.js +13 -12
  42. package/dist/components/FormFieldRenderer.js.map +1 -1
  43. package/dist/components/FormRenderer.cjs +1 -1
  44. package/dist/components/FormRenderer.cjs.map +1 -1
  45. package/dist/components/FormRenderer.js +1 -1
  46. package/dist/components/FormRenderer.js.map +1 -1
  47. package/dist/components/GenerativeUIErrorBoundary.cjs +1 -1
  48. package/dist/components/GenerativeUIErrorBoundary.cjs.map +1 -1
  49. package/dist/components/GenerativeUIErrorBoundary.js +1 -1
  50. package/dist/components/GenerativeUIErrorBoundary.js.map +1 -1
  51. package/dist/components/GhostText.cjs +2 -2
  52. package/dist/components/GhostText.cjs.map +1 -1
  53. package/dist/components/GhostText.js +2 -2
  54. package/dist/components/GhostText.js.map +1 -1
  55. package/dist/components/GraphRenderer.cjs +100 -29
  56. package/dist/components/GraphRenderer.cjs.map +1 -1
  57. package/dist/components/GraphRenderer.d.ts +1 -1
  58. package/dist/components/GraphRenderer.d.ts.map +1 -1
  59. package/dist/components/GraphRenderer.js +100 -29
  60. package/dist/components/GraphRenderer.js.map +1 -1
  61. package/dist/components/MissingValue.cjs +2 -2
  62. package/dist/components/MissingValue.cjs.map +1 -1
  63. package/dist/components/MissingValue.js +2 -2
  64. package/dist/components/MissingValue.js.map +1 -1
  65. package/dist/components/ModalRenderer.cjs +39 -31
  66. package/dist/components/ModalRenderer.cjs.map +1 -1
  67. package/dist/components/ModalRenderer.d.ts.map +1 -1
  68. package/dist/components/ModalRenderer.js +40 -32
  69. package/dist/components/ModalRenderer.js.map +1 -1
  70. package/dist/components/PortalDropdownMenu.cjs +6 -3
  71. package/dist/components/PortalDropdownMenu.cjs.map +1 -1
  72. package/dist/components/PortalDropdownMenu.d.ts.map +1 -1
  73. package/dist/components/PortalDropdownMenu.js +7 -4
  74. package/dist/components/PortalDropdownMenu.js.map +1 -1
  75. package/dist/components/ScratchpadPanel.cjs +38 -44
  76. package/dist/components/ScratchpadPanel.cjs.map +1 -1
  77. package/dist/components/ScratchpadPanel.d.ts.map +1 -1
  78. package/dist/components/ScratchpadPanel.js +38 -44
  79. package/dist/components/ScratchpadPanel.js.map +1 -1
  80. package/dist/components/UIResourceRenderer.cjs +20 -20
  81. package/dist/components/UIResourceRenderer.cjs.map +1 -1
  82. package/dist/components/UIResourceRenderer.js +20 -20
  83. package/dist/components/UIResourceRenderer.js.map +1 -1
  84. package/dist/components/chart-missing-plugin.cjs +4 -4
  85. package/dist/components/chart-missing-plugin.cjs.map +1 -1
  86. package/dist/components/chart-missing-plugin.d.ts +3 -1
  87. package/dist/components/chart-missing-plugin.d.ts.map +1 -1
  88. package/dist/components/chart-missing-plugin.js +4 -4
  89. package/dist/components/chart-missing-plugin.js.map +1 -1
  90. package/dist/utils/math-markdown.cjs +1 -1
  91. package/dist/utils/math-markdown.cjs.map +1 -1
  92. package/dist/utils/math-markdown.js +1 -1
  93. package/dist/utils/math-markdown.js.map +1 -1
  94. package/dist/utils/theme.cjs +91 -0
  95. package/dist/utils/theme.cjs.map +1 -0
  96. package/dist/utils/theme.d.ts +11 -0
  97. package/dist/utils/theme.d.ts.map +1 -0
  98. package/dist/utils/theme.js +91 -0
  99. package/dist/utils/theme.js.map +1 -0
  100. package/docs/SECURITY-DEPENDENCIES.md +81 -0
  101. package/docs/THEMING.md +205 -0
  102. package/package.json +5 -3
  103. package/src/components/ActionGroupRenderer.tsx +6 -6
  104. package/src/components/AutocompleteDropdown.tsx +17 -17
  105. package/src/components/AutocompleteFormField.tsx +10 -9
  106. package/src/components/ChartJSRenderer.tsx +483 -239
  107. package/src/components/ChatPrompt.tsx +16 -16
  108. package/src/components/ComponentToolbar.tsx +5 -5
  109. package/src/components/DegradedFallback.tsx +9 -9
  110. package/src/components/ExpandableWrapper.tsx +15 -11
  111. package/src/components/FormFieldRenderer.tsx +52 -51
  112. package/src/components/FormRenderer.tsx +12 -12
  113. package/src/components/GenerativeUIErrorBoundary.tsx +6 -6
  114. package/src/components/GhostText.tsx +7 -7
  115. package/src/components/GraphRenderer.tsx +83 -67
  116. package/src/components/MissingValue.tsx +2 -2
  117. package/src/components/ModalRenderer.tsx +11 -5
  118. package/src/components/PortalDropdownMenu.tsx +6 -2
  119. package/src/components/ScratchpadPanel.tsx +144 -141
  120. package/src/components/UIResourceRenderer.tsx +101 -101
  121. package/src/components/chart-missing-plugin.ts +5 -3
  122. package/src/styles/autocomplete.css +53 -53
  123. package/src/utils/math-markdown.ts +1 -1
  124. package/src/utils/theme.ts +101 -0
@@ -209,6 +209,8 @@ export interface MissingValuesPluginOptions {
209
209
  getNoDataText: () => string;
210
210
  /** Lazy: `Chart.defaults.font.family`, when the build exposes it. */
211
211
  getFontFamily?: () => string | undefined;
212
+ /** Lazy host theme override; omission retains the historical grey. */
213
+ getTextColor?: () => string | undefined;
212
214
  }
213
215
 
214
216
  /** A dataset colour for entry `index`, as a string, or `undefined`. */
@@ -297,7 +299,7 @@ export function drawMissingBarStubs(chart: any): void {
297
299
  }
298
300
 
299
301
  /** Draws `text` centred in the chart area (a chart with nothing to plot). */
300
- export function drawNoDataText(chart: any, text: string, fontFamily?: string): void {
302
+ export function drawNoDataText(chart: any, text: string, fontFamily?: string, color?: string): void {
301
303
  const ctx = chart?.ctx;
302
304
  const area = chart?.chartArea;
303
305
  if (!ctx || !area || !text) return;
@@ -305,7 +307,7 @@ export function drawNoDataText(chart: any, text: string, fontFamily?: string): v
305
307
  const cy = (area.top + (area.bottom ?? area.top + (area.height ?? 0))) / 2;
306
308
  ctx.save();
307
309
  ctx.font = `12px ${fontFamily || 'sans-serif'}`;
308
- ctx.fillStyle = NO_DATA_TEXT_COLOR;
310
+ ctx.fillStyle = color || NO_DATA_TEXT_COLOR;
309
311
  ctx.textAlign = 'center';
310
312
  ctx.textBaseline = 'middle';
311
313
  ctx.fillText(text, cx, cy);
@@ -324,7 +326,7 @@ export function createMissingValuesPlugin(options: MissingValuesPluginOptions) {
324
326
  try {
325
327
  drawMissingBarStubs(chart);
326
328
  if (options.allMissing) {
327
- drawNoDataText(chart, options.getNoDataText(), options.getFontFamily?.());
329
+ drawNoDataText(chart, options.getNoDataText(), options.getFontFamily?.(), options.getTextColor?.());
328
330
  }
329
331
  } catch {
330
332
  // Cosmetic layer: never break the chart.
@@ -37,7 +37,7 @@
37
37
  }
38
38
 
39
39
  .mcp-ghost-text {
40
- color: #9ca3af;
40
+ color: var(--mcp-ui-fg-subtle, #9ca3af);
41
41
  opacity: 0.7;
42
42
  }
43
43
 
@@ -47,11 +47,11 @@
47
47
  top: 50%;
48
48
  transform: translateY(-50%);
49
49
  font-size: 0.75rem;
50
- color: #6b7280;
51
- background-color: rgba(255, 255, 255, 0.95);
50
+ color: var(--mcp-ui-fg-muted, #6b7280);
51
+ background-color: var(--mcp-ui-surface, rgba(255, 255, 255, 0.95));
52
52
  padding: 2px 8px;
53
53
  border-radius: 4px;
54
- border: 1px solid #e5e7eb;
54
+ border: 1px solid var(--mcp-ui-border, #e5e7eb);
55
55
  pointer-events: none;
56
56
  animation: mcp-hint-fade-in 150ms ease;
57
57
  }
@@ -86,10 +86,10 @@
86
86
  left: 0;
87
87
  right: 0;
88
88
  z-index: 50;
89
- background-color: #ffffff;
90
- border: 1px solid #e5e7eb;
89
+ background-color: var(--mcp-ui-surface, #ffffff);
90
+ border: 1px solid var(--mcp-ui-border, #e5e7eb);
91
91
  border-radius: 6px;
92
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
92
+ box-shadow: var(--mcp-ui-shadow, 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06));
93
93
  overflow: hidden;
94
94
  animation: mcp-dropdown-enter 150ms ease;
95
95
  }
@@ -120,11 +120,11 @@
120
120
  }
121
121
 
122
122
  .mcp-autocomplete-option:hover {
123
- background-color: #f3f4f6;
123
+ background-color: var(--mcp-ui-surface-hover, #f3f4f6);
124
124
  }
125
125
 
126
126
  .mcp-autocomplete-option-selected {
127
- background-color: #eff6ff !important;
127
+ background-color: var(--mcp-ui-accent-soft, #eff6ff) !important;
128
128
  }
129
129
 
130
130
  .mcp-autocomplete-option-disabled {
@@ -159,7 +159,7 @@
159
159
  .mcp-autocomplete-option-label {
160
160
  display: block;
161
161
  font-size: 0.875rem;
162
- color: #374151;
162
+ color: var(--mcp-ui-fg, #374151);
163
163
  white-space: nowrap;
164
164
  overflow: hidden;
165
165
  text-overflow: ellipsis;
@@ -168,7 +168,7 @@
168
168
  .mcp-autocomplete-option-description {
169
169
  display: block;
170
170
  font-size: 0.75rem;
171
- color: #6b7280;
171
+ color: var(--mcp-ui-fg-muted, #6b7280);
172
172
  white-space: nowrap;
173
173
  overflow: hidden;
174
174
  text-overflow: ellipsis;
@@ -177,14 +177,14 @@
177
177
 
178
178
  .mcp-autocomplete-highlight {
179
179
  font-weight: 600;
180
- color: #1d4ed8;
180
+ color: var(--mcp-ui-accent, #1d4ed8);
181
181
  }
182
182
 
183
183
  .mcp-autocomplete-loading,
184
184
  .mcp-autocomplete-empty {
185
185
  padding: 12px 16px;
186
186
  font-size: 0.875rem;
187
- color: #6b7280;
187
+ color: var(--mcp-ui-fg-muted, #6b7280);
188
188
  }
189
189
 
190
190
  .mcp-autocomplete-loading {
@@ -199,10 +199,10 @@
199
199
 
200
200
  .mcp-autocomplete-footer {
201
201
  padding: 6px 12px;
202
- border-top: 1px solid #e5e7eb;
203
- background-color: #f9fafb;
202
+ border-top: 1px solid var(--mcp-ui-border, #e5e7eb);
203
+ background-color: var(--mcp-ui-surface-muted, #f9fafb);
204
204
  font-size: 0.75rem;
205
- color: #6b7280;
205
+ color: var(--mcp-ui-fg-muted, #6b7280);
206
206
  }
207
207
 
208
208
  /* =============================================================================
@@ -211,107 +211,107 @@
211
211
 
212
212
  @media (prefers-color-scheme: dark) {
213
213
  .mcp-ghost-text {
214
- color: #6b7280;
214
+ color: var(--mcp-ui-fg-muted, #6b7280);
215
215
  }
216
216
 
217
217
  .mcp-ghost-text-hint {
218
- background-color: rgba(31, 41, 55, 0.95);
219
- border-color: #374151;
220
- color: #9ca3af;
218
+ background-color: var(--mcp-ui-surface, rgba(31, 41, 55, 0.95));
219
+ border-color: var(--mcp-ui-border, #374151);
220
+ color: var(--mcp-ui-fg-subtle, #9ca3af);
221
221
  }
222
222
 
223
223
  .mcp-autocomplete-dropdown {
224
- background-color: #1f2937;
225
- border-color: #374151;
224
+ background-color: var(--mcp-ui-surface, #1f2937);
225
+ border-color: var(--mcp-ui-border, #374151);
226
226
  }
227
227
 
228
228
  .mcp-autocomplete-option:hover {
229
- background-color: #374151;
229
+ background-color: var(--mcp-ui-surface-hover, #374151);
230
230
  }
231
231
 
232
232
  .mcp-autocomplete-option-selected {
233
- background-color: #1e3a5f !important;
233
+ background-color: var(--mcp-ui-accent-soft, #1e3a5f) !important;
234
234
  }
235
235
 
236
236
  .mcp-autocomplete-option-label {
237
- color: #e5e7eb;
237
+ color: var(--mcp-ui-fg, #e5e7eb);
238
238
  }
239
239
 
240
240
  .mcp-autocomplete-option-description {
241
- color: #9ca3af;
241
+ color: var(--mcp-ui-fg-subtle, #9ca3af);
242
242
  }
243
243
 
244
244
  .mcp-autocomplete-highlight {
245
- color: #60a5fa;
245
+ color: var(--mcp-ui-accent, #60a5fa);
246
246
  }
247
247
 
248
248
  .mcp-autocomplete-loading,
249
249
  .mcp-autocomplete-empty {
250
- color: #9ca3af;
250
+ color: var(--mcp-ui-fg-subtle, #9ca3af);
251
251
  }
252
252
 
253
253
  .mcp-autocomplete-footer {
254
- background-color: #111827;
255
- border-color: #374151;
256
- color: #9ca3af;
254
+ background-color: var(--mcp-ui-surface-muted, #111827);
255
+ border-color: var(--mcp-ui-border, #374151);
256
+ color: var(--mcp-ui-fg-subtle, #9ca3af);
257
257
  }
258
258
 
259
259
  .mcp-autocomplete-footer kbd {
260
- background-color: #374151;
261
- color: #d1d5db;
260
+ background-color: var(--mcp-ui-surface-hover, #374151);
261
+ color: var(--mcp-ui-fg, #d1d5db);
262
262
  }
263
263
  }
264
264
 
265
265
  /* Explicit dark mode class support */
266
266
  .dark .mcp-ghost-text {
267
- color: #6b7280;
267
+ color: var(--mcp-ui-fg-muted, #6b7280);
268
268
  }
269
269
 
270
270
  .dark .mcp-ghost-text-hint {
271
- background-color: rgba(31, 41, 55, 0.95);
272
- border-color: #374151;
273
- color: #9ca3af;
271
+ background-color: var(--mcp-ui-surface, rgba(31, 41, 55, 0.95));
272
+ border-color: var(--mcp-ui-border, #374151);
273
+ color: var(--mcp-ui-fg-subtle, #9ca3af);
274
274
  }
275
275
 
276
276
  .dark .mcp-autocomplete-dropdown {
277
- background-color: #1f2937;
278
- border-color: #374151;
277
+ background-color: var(--mcp-ui-surface, #1f2937);
278
+ border-color: var(--mcp-ui-border, #374151);
279
279
  }
280
280
 
281
281
  .dark .mcp-autocomplete-option:hover {
282
- background-color: #374151;
282
+ background-color: var(--mcp-ui-surface-hover, #374151);
283
283
  }
284
284
 
285
285
  .dark .mcp-autocomplete-option-selected {
286
- background-color: #1e3a5f !important;
286
+ background-color: var(--mcp-ui-accent-soft, #1e3a5f) !important;
287
287
  }
288
288
 
289
289
  .dark .mcp-autocomplete-option-label {
290
- color: #e5e7eb;
290
+ color: var(--mcp-ui-fg, #e5e7eb);
291
291
  }
292
292
 
293
293
  .dark .mcp-autocomplete-option-description {
294
- color: #9ca3af;
294
+ color: var(--mcp-ui-fg-subtle, #9ca3af);
295
295
  }
296
296
 
297
297
  .dark .mcp-autocomplete-highlight {
298
- color: #60a5fa;
298
+ color: var(--mcp-ui-accent, #60a5fa);
299
299
  }
300
300
 
301
301
  .dark .mcp-autocomplete-loading,
302
302
  .dark .mcp-autocomplete-empty {
303
- color: #9ca3af;
303
+ color: var(--mcp-ui-fg-subtle, #9ca3af);
304
304
  }
305
305
 
306
306
  .dark .mcp-autocomplete-footer {
307
- background-color: #111827;
308
- border-color: #374151;
309
- color: #9ca3af;
307
+ background-color: var(--mcp-ui-surface-muted, #111827);
308
+ border-color: var(--mcp-ui-border, #374151);
309
+ color: var(--mcp-ui-fg-subtle, #9ca3af);
310
310
  }
311
311
 
312
312
  .dark .mcp-autocomplete-footer kbd {
313
- background-color: #374151;
314
- color: #d1d5db;
313
+ background-color: var(--mcp-ui-surface-hover, #374151);
314
+ color: var(--mcp-ui-fg, #d1d5db);
315
315
  }
316
316
 
317
317
  /* =============================================================================
@@ -327,12 +327,12 @@
327
327
  }
328
328
 
329
329
  .mcp-autocomplete-options::-webkit-scrollbar-thumb {
330
- background-color: #d1d5db;
330
+ background-color: var(--mcp-ui-surface-hover, #d1d5db);
331
331
  border-radius: 3px;
332
332
  }
333
333
 
334
334
  .dark .mcp-autocomplete-options::-webkit-scrollbar-thumb {
335
- background-color: #4b5563;
335
+ background-color: var(--mcp-ui-surface-hover, #4b5563);
336
336
  }
337
337
 
338
338
  /* =============================================================================
@@ -341,7 +341,7 @@
341
341
 
342
342
  /* Focus styles */
343
343
  .mcp-autocomplete-option:focus {
344
- outline: 2px solid #3b82f6;
344
+ outline: 2px solid var(--mcp-ui-focus, #3b82f6);
345
345
  outline-offset: -2px;
346
346
  }
347
347
 
@@ -104,7 +104,7 @@ function isCurrencyLike(raw: string, tex: string, following: string): boolean {
104
104
  return closerLooksLikeLaterOpener
105
105
  }
106
106
 
107
- const CELL_LINK_ATTRIBUTES = 'target="_blank" rel="noopener noreferrer" class="text-blue-600 dark:text-blue-400 hover:underline"'
107
+ const CELL_LINK_ATTRIBUTES = 'target="_blank" rel="noopener noreferrer" class="text-[color:var(--mcp-ui-accent,var(--color-blue-600))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-400))] hover:underline"'
108
108
 
109
109
  function cellLinkExtension(): MarkedExtension {
110
110
  return {
@@ -0,0 +1,101 @@
1
+ import { createEffect, onCleanup } from 'solid-js'
2
+
3
+ const THEME_PREFIX = '--mcp-ui-'
4
+
5
+ /** Read only explicitly supplied host tokens; blank values retain engine defaults. */
6
+ export function readThemeTokens(
7
+ element: Element | undefined,
8
+ names: readonly string[],
9
+ ): Record<string, string | undefined> {
10
+ const result: Record<string, string | undefined> = {}
11
+ const view = element?.ownerDocument.defaultView
12
+ const style = element && view ? view.getComputedStyle(element) : undefined
13
+ for (const name of names) result[name] = style?.getPropertyValue(name).trim() || undefined
14
+ return result
15
+ }
16
+
17
+ /** Observe inherited theme changes, including moving a mounted renderer into a portal. */
18
+ export function observeTheme(element: HTMLElement, callback: () => void): () => void {
19
+ const view = element.ownerDocument.defaultView
20
+ if (!view?.MutationObserver) return () => {}
21
+ let stopped = false
22
+ let pending = false
23
+ const schedule = () => {
24
+ if (pending || stopped) return
25
+ pending = true
26
+ queueMicrotask(() => {
27
+ pending = false
28
+ if (!stopped) callback()
29
+ })
30
+ }
31
+ const observer = new view.MutationObserver((records) => {
32
+ if (records.some((record) => {
33
+ if (record.type === 'attributes') {
34
+ return record.target instanceof view.Element && record.target.contains(element)
35
+ }
36
+ return [...record.addedNodes, ...record.removedNodes].some(
37
+ (node) => node === element || node.contains(element),
38
+ )
39
+ })) schedule()
40
+ })
41
+ // Attribute names are deliberately not filtered: hosts may use any data-* selector.
42
+ observer.observe(element.ownerDocument.documentElement, {
43
+ attributes: true,
44
+ childList: true,
45
+ subtree: true,
46
+ })
47
+ return () => {
48
+ stopped = true
49
+ observer.disconnect()
50
+ }
51
+ }
52
+
53
+ /**
54
+ * A body portal cannot inherit a local host theme. Copy only our effective
55
+ * custom properties and dark state from its stationary anchor, and keep them
56
+ * synchronized for the lifetime of the Solid owner. Geometry stays unchanged.
57
+ */
58
+ export function bindPortalTheme(portal: HTMLElement, source: () => HTMLElement | undefined): void {
59
+ createEffect(() => {
60
+ const anchor = source()
61
+ const view = anchor?.ownerDocument.defaultView
62
+ if (!anchor || !view) return
63
+ const copied = new Set<string>()
64
+ const sync = () => {
65
+ const style = view.getComputedStyle(anchor)
66
+ const inherited = portal.parentElement ? view.getComputedStyle(portal.parentElement) : undefined
67
+ const names = new Set(copied)
68
+ for (const computed of [style, inherited]) {
69
+ if (!computed) continue
70
+ for (let index = 0; index < computed.length; index++) {
71
+ const name = computed.item(index)
72
+ if (name.startsWith(THEME_PREFIX)) names.add(name)
73
+ }
74
+ }
75
+ const values = new Map<string, string>()
76
+ for (const name of names) {
77
+ const value = style.getPropertyValue(name).trim()
78
+ if (value) values.set(name, value)
79
+ // Empty setProperty removes a declaration. Explicitly block a body
80
+ // token when the local scope reset it to the guaranteed-invalid value.
81
+ else if (inherited?.getPropertyValue(name).trim()) values.set(name, 'initial')
82
+ }
83
+ for (const name of copied) {
84
+ if (!values.has(name)) portal.style.removeProperty(name)
85
+ }
86
+ copied.clear()
87
+ for (const [name, value] of values) {
88
+ if (portal.style.getPropertyValue(name) !== value) portal.style.setProperty(name, value)
89
+ copied.add(name)
90
+ }
91
+ portal.classList.toggle('dark', anchor.closest('.dark') !== null)
92
+ }
93
+ sync()
94
+ const stop = observeTheme(anchor, sync)
95
+ onCleanup(() => {
96
+ stop()
97
+ for (const name of copied) portal.style.removeProperty(name)
98
+ portal.classList.remove('dark')
99
+ })
100
+ })
101
+ }