sketch: remove shadowDOM dependency from tool card components
Replace shadowDOM-based slot system with property-based composition in all
sketch-tool-card-[TOOL_NAME] components to support shadowDOM-free architecture.
Problem Analysis:
- sketch-tool-card component relied on HTML5 template slots which require shadowDOM
- 13 tool card components used sketch-tool-card as composition base via slots
- shadowDOM dependency blocked broader effort to reduce shadowDOM usage
- Need to preserve all existing functionality while removing slot dependency
Solution Implementation:
- Created sketch-tool-card-base component with property-based content injection
- Replaced slot system with summaryContent, inputContent, resultContent properties
- Maintained all existing styling, behavior, and expand/collapse functionality
- Migrated all 13 existing tool card components to use new base component
Components Migrated:
- sketch-tool-card-about-sketch
- sketch-tool-card-browser-clear-console-logs
- sketch-tool-card-browser-click
- sketch-tool-card-browser-eval
- sketch-tool-card-browser-get-text
- sketch-tool-card-browser-navigate
- sketch-tool-card-browser-recent-console-logs
- sketch-tool-card-browser-resize
- sketch-tool-card-browser-scroll-into-view
- sketch-tool-card-browser-type
- sketch-tool-card-browser-wait-for
- sketch-tool-card-read-image
- sketch-tool-card-take-screenshot
Migration Pattern:
- Changed from: <slot name="summary">content</slot>
- Changed to: .summaryContent=html content
- Preserved all component-specific styling and logic
- Maintained existing API surface for parent components
Architecture Benefits:
- Removes shadowDOM requirement from 13+ components
- Enables future shadowDOM-free component development
- Maintains backward compatibility during migration
- Preserves all existing tool card functionality
Files Added:
- sketch/webui/src/web-components/sketch-tool-card-base.ts (new shadowDOM-free base)
Files Modified:
- All 13 sketch-tool-card-[TOOL_NAME].ts components migrated to use new base
Verification:
- TypeScript compilation passes without errors
- Demo pages render correctly with consistent styling
- Expand/collapse behavior preserved across all tool types
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: sa3288c1d986356e5k
diff --git a/webui/src/web-components/sketch-tool-card-take-screenshot.ts b/webui/src/web-components/sketch-tool-card-take-screenshot.ts
index b21686b..8e03b94 100644
--- a/webui/src/web-components/sketch-tool-card-take-screenshot.ts
+++ b/webui/src/web-components/sketch-tool-card-take-screenshot.ts
@@ -1,9 +1,11 @@
-import { css, html, LitElement } from "lit";
+import { html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { ToolCall } from "../types";
+import { SketchTailwindElement } from "./sketch-tailwind-element";
+import "./sketch-tool-card-base";
@customElement("sketch-tool-card-take-screenshot")
-export class SketchToolCardTakeScreenshot extends LitElement {
+export class SketchToolCardTakeScreenshot extends SketchTailwindElement {
@property()
toolCall: ToolCall;
@@ -16,55 +18,6 @@
@state()
loadError: boolean = false;
- static styles = css`
- .summary-text {
- font-style: italic;
- padding: 0.5em;
- }
-
- .screenshot-container {
- margin: 10px 0;
- display: flex;
- flex-direction: column;
- align-items: center;
- }
-
- .screenshot {
- max-width: 100%;
- max-height: 500px;
- border-radius: 4px;
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
- border: 1px solid #ddd;
- }
-
- .loading-indicator {
- margin: 20px;
- color: #666;
- font-style: italic;
- }
-
- .error-message {
- color: #d32f2f;
- font-style: italic;
- margin: 10px 0;
- }
-
- .screenshot-info {
- margin-top: 8px;
- font-size: 12px;
- color: #666;
- }
-
- .selector-info {
- padding: 4px 8px;
- background-color: #f5f5f5;
- border-radius: 4px;
- font-family: monospace;
- margin: 5px 0;
- display: inline-block;
- }
- `;
-
constructor() {
super();
}
@@ -108,48 +61,55 @@
// Construct the URL for the screenshot (using relative URL without leading slash)
const screenshotUrl = screenshotId ? `screenshot/${screenshotId}` : "";
- return html`
- <sketch-tool-card .open=${this.open} .toolCall=${this.toolCall}>
- <span slot="summary" class="summary-text">
- Screenshot of ${selector}
- </span>
- <div slot="input" class="selector-info">
- ${selector !== "(full page)"
- ? `Taking screenshot of element: ${selector}`
- : `Taking full page screenshot`}
- </div>
- <div slot="result">
- ${hasResult
- ? html`
- <div class="screenshot-container">
- ${!this.imageLoaded && !this.loadError
- ? html`<div class="loading-indicator">
- Loading screenshot...
+ const summaryContent = html`<span class="italic p-2">
+ Screenshot of ${selector}
+ </span>`;
+ const inputContent = html`<div
+ class="px-2 py-1 bg-gray-100 rounded font-mono my-1.5 inline-block"
+ >
+ ${selector !== "(full page)"
+ ? `Taking screenshot of element: ${selector}`
+ : `Taking full page screenshot`}
+ </div>`;
+ const resultContent = hasResult
+ ? html`
+ <div class="my-2.5 flex flex-col items-center">
+ ${!this.imageLoaded && !this.loadError
+ ? html`<div class="m-5 text-gray-600 italic">
+ Loading screenshot...
+ </div>`
+ : ""}
+ ${this.loadError
+ ? html`<div class="text-red-700 italic my-2.5">
+ Failed to load screenshot
+ </div>`
+ : html`
+ <img
+ class="max-w-full max-h-[500px] rounded shadow-md border border-gray-300"
+ src="${screenshotUrl}"
+ @load=${() => (this.imageLoaded = true)}
+ @error=${() => (this.loadError = true)}
+ ?hidden=${!this.imageLoaded}
+ />
+ ${this.imageLoaded
+ ? html`<div class="mt-2 text-xs text-gray-600">
+ Screenshot saved and displayed
</div>`
: ""}
- ${this.loadError
- ? html`<div class="error-message">
- Failed to load screenshot
- </div>`
- : html`
- <img
- class="screenshot"
- src="${screenshotUrl}"
- @load=${() => (this.imageLoaded = true)}
- @error=${() => (this.loadError = true)}
- ?hidden=${!this.imageLoaded}
- />
- ${this.imageLoaded
- ? html`<div class="screenshot-info">
- Screenshot saved and displayed
- </div>`
- : ""}
- `}
- </div>
- `
- : ""}
- </div>
- </sketch-tool-card>
+ `}
+ </div>
+ `
+ : "";
+
+ return html`
+ <sketch-tool-card-base
+ .open=${this.open}
+ .toolCall=${this.toolCall}
+ .summaryContent=${summaryContent}
+ .inputContent=${inputContent}
+ .resultContent=${resultContent}
+ >
+ </sketch-tool-card-base>
`;
}
}