sonance-brand-mcp 1.3.78 → 1.3.80
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.
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
removePreviewStyles,
|
|
65
65
|
injectPreviewStyles,
|
|
66
66
|
extractPreviewCSSFromCode,
|
|
67
|
+
getActiveModalContent,
|
|
67
68
|
} from "./utils";
|
|
68
69
|
import { Section, ColorSwatch, SelectField } from "./components/common";
|
|
69
70
|
import { InspectorOverlay } from "./components/InspectorOverlay";
|
|
@@ -795,14 +796,28 @@ export function SonanceDevTools() {
|
|
|
795
796
|
return rect; // No scroll parent, use original rect
|
|
796
797
|
};
|
|
797
798
|
|
|
799
|
+
// Helper: Detect active modal and filter elements to only those in the topmost layer
|
|
800
|
+
const activeModalContent = getActiveModalContent();
|
|
801
|
+
const isInActiveLayer = (el: Element): boolean => {
|
|
802
|
+
// Always exclude DevTools panel
|
|
803
|
+
if (el.closest("[data-sonance-devtools]")) return false;
|
|
804
|
+
|
|
805
|
+
// If a modal is active, only include elements inside the modal content
|
|
806
|
+
if (activeModalContent) {
|
|
807
|
+
return activeModalContent.contains(el) || el === activeModalContent;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
return true;
|
|
811
|
+
};
|
|
812
|
+
|
|
798
813
|
// Scan for tagged components
|
|
799
814
|
if (inspectorEnabled || activeTab === "components") {
|
|
800
815
|
// 1. Scan for explicitly tagged components
|
|
801
816
|
const taggedComponents = document.querySelectorAll("[data-sonance-name]");
|
|
802
817
|
taggedComponents.forEach((el) => {
|
|
803
|
-
// Skip DevTools
|
|
804
|
-
if (el
|
|
805
|
-
|
|
818
|
+
// Skip elements outside the active layer (DevTools, behind modals)
|
|
819
|
+
if (!isInActiveLayer(el)) return;
|
|
820
|
+
|
|
806
821
|
const name = el.getAttribute("data-sonance-name");
|
|
807
822
|
if (name) {
|
|
808
823
|
const rawRect = el.getBoundingClientRect();
|
|
@@ -848,9 +863,9 @@ export function SonanceDevTools() {
|
|
|
848
863
|
Object.entries(genericSelectors).forEach(([genericName, selector]) => {
|
|
849
864
|
const elements = document.querySelectorAll(selector);
|
|
850
865
|
elements.forEach((el) => {
|
|
851
|
-
// Skip DevTools
|
|
852
|
-
if (el
|
|
853
|
-
|
|
866
|
+
// Skip elements outside the active layer (DevTools, behind modals)
|
|
867
|
+
if (!isInActiveLayer(el)) return;
|
|
868
|
+
|
|
854
869
|
const rawRect = el.getBoundingClientRect();
|
|
855
870
|
const rect = getVisibleRect(el, rawRect);
|
|
856
871
|
if (rect && rect.width > 0 && rect.height > 0) {
|
|
@@ -890,6 +905,9 @@ export function SonanceDevTools() {
|
|
|
890
905
|
if (activeTab === "logos") {
|
|
891
906
|
const images = document.querySelectorAll("img");
|
|
892
907
|
images.forEach((img) => {
|
|
908
|
+
// Skip elements outside the active layer (DevTools, behind modals)
|
|
909
|
+
if (!isInActiveLayer(img)) return;
|
|
910
|
+
|
|
893
911
|
const src = img.src || img.getAttribute("src") || "";
|
|
894
912
|
const alt = img.alt || "";
|
|
895
913
|
|
|
@@ -944,9 +962,9 @@ export function SonanceDevTools() {
|
|
|
944
962
|
const addedElements = new Set<Element>();
|
|
945
963
|
|
|
946
964
|
textElements.forEach((el) => {
|
|
947
|
-
// Skip elements
|
|
948
|
-
if (el
|
|
949
|
-
|
|
965
|
+
// Skip elements outside the active layer (DevTools, behind modals)
|
|
966
|
+
if (!isInActiveLayer(el)) return;
|
|
967
|
+
|
|
950
968
|
// Skip if this element is inside another text element we're already tracking
|
|
951
969
|
// This prevents duplicate labels for nested structures
|
|
952
970
|
const parentTextEl = el.parentElement?.closest("h1, h2, h3, h4, h5, h6, p, a, label, blockquote, figcaption, li");
|
|
@@ -402,6 +402,11 @@ export function ChatInterface({
|
|
|
402
402
|
type="text"
|
|
403
403
|
value={input}
|
|
404
404
|
onChange={(e) => setInput(e.target.value)}
|
|
405
|
+
onPointerDown={(e) => {
|
|
406
|
+
// Force focus capture - prevents modal focus trapping from blocking input
|
|
407
|
+
e.stopPropagation();
|
|
408
|
+
setTimeout(() => inputRef.current?.focus(), 0);
|
|
409
|
+
}}
|
|
405
410
|
onKeyDown={(e) => {
|
|
406
411
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
407
412
|
e.preventDefault();
|
|
@@ -88,9 +88,11 @@ export function InspectorOverlay({
|
|
|
88
88
|
const clickX = e.clientX;
|
|
89
89
|
const clickY = e.clientY;
|
|
90
90
|
|
|
91
|
-
// Don't capture clicks on the DevTools panel
|
|
91
|
+
// Don't capture clicks on the DevTools panel or its portal container
|
|
92
92
|
const devToolsPanel = document.querySelector('[data-sonance-devtools="true"]');
|
|
93
|
+
const devToolsRoot = document.getElementById('sonance-devtools-root');
|
|
93
94
|
if (devToolsPanel?.contains(e.target as Node)) return;
|
|
95
|
+
if (devToolsRoot?.contains(e.target as Node)) return;
|
|
94
96
|
|
|
95
97
|
// Find ALL elements that contain the click point
|
|
96
98
|
const matchingElements = elements.filter(el =>
|
|
@@ -329,6 +329,55 @@ export function extractPreviewCSSFromCode(
|
|
|
329
329
|
return rules.join("\n");
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
+
/**
|
|
333
|
+
* Finds the topmost modal/dialog content container if one is open.
|
|
334
|
+
* Returns the content element (not the backdrop) or null if no modal is active.
|
|
335
|
+
*
|
|
336
|
+
* Detection strategy:
|
|
337
|
+
* 1. Look for Radix UI dialogs with [data-state="open"]
|
|
338
|
+
* 2. Look for role="dialog" elements
|
|
339
|
+
* 3. Look for native <dialog open> elements
|
|
340
|
+
* 4. Look for custom Dialog with z-50 fixed positioning
|
|
341
|
+
* 5. Look for Drawer/Sheet overlays
|
|
342
|
+
*/
|
|
343
|
+
export function getActiveModalContent(): Element | null {
|
|
344
|
+
if (typeof document === "undefined") return null;
|
|
345
|
+
|
|
346
|
+
// Priority-ordered list of modal content selectors
|
|
347
|
+
const contentSelectors = [
|
|
348
|
+
// Radix UI Dialog content
|
|
349
|
+
'[data-state="open"][role="dialog"]',
|
|
350
|
+
// Custom Dialog content (has data-sonance-name="dialog")
|
|
351
|
+
'.fixed.inset-0.z-50 [data-sonance-name="dialog"]',
|
|
352
|
+
'.fixed.inset-0.z-50 > .relative.z-10',
|
|
353
|
+
// Native dialog
|
|
354
|
+
'dialog[open]',
|
|
355
|
+
// aria-modal content
|
|
356
|
+
'[aria-modal="true"]',
|
|
357
|
+
// Sheet/Drawer content
|
|
358
|
+
'[data-state="open"][data-sheet-content]',
|
|
359
|
+
'[data-state="open"][role="complementary"]',
|
|
360
|
+
];
|
|
361
|
+
|
|
362
|
+
for (const selector of contentSelectors) {
|
|
363
|
+
const modal = document.querySelector(selector);
|
|
364
|
+
if (modal) {
|
|
365
|
+
// Return innermost dialog content, not the wrapper
|
|
366
|
+
return modal;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Fallback: check for z-50 fixed full-screen overlays
|
|
371
|
+
const fixedOverlays = document.querySelectorAll('.fixed.inset-0.z-50');
|
|
372
|
+
for (const overlay of fixedOverlays) {
|
|
373
|
+
// Find the content within (skip if it's just a backdrop)
|
|
374
|
+
const content = overlay.querySelector('.relative.z-10, [data-sonance-name]');
|
|
375
|
+
if (content) return content;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
|
|
332
381
|
// Helper to check if a category should show scope options
|
|
333
382
|
export function shouldShowScopeOptions(category: string): boolean {
|
|
334
383
|
if (category === "all") return true; // Show for "all" selection
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.80",
|
|
4
4
|
"description": "MCP Server for Sonance Brand Guidelines and Component Library - gives Claude instant access to brand colors, typography, and UI components.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|