nudge-ui 0.2.3 → 0.2.5
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/dist/cli/agentCommand.js +4 -28
- package/dist/client.mjs +870 -212
- package/dist/hosts/next/wrapper.js +1 -7
- package/dist/hosts/projectBridge.js +3 -17
- package/dist/inspector/agent/McpConnectionDialog.js +7 -3
- package/dist/inspector/agent/activity.d.ts +1 -1
- package/dist/inspector/agent/activity.js +3 -2
- package/dist/inspector/agent/client.d.ts +2 -2
- package/dist/inspector/agent/client.js +28 -29
- package/dist/inspector/agent/connectionStatus.d.ts +3 -1
- package/dist/inspector/agent/connectionStatus.js +25 -0
- package/dist/inspector/agent/httpTransport.js +31 -147
- package/dist/inspector/agent/protocol.d.ts +1 -1
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +5 -7
- package/dist/inspector/agent/verification.js +67 -20
- package/dist/inspector/boot.d.ts +3 -0
- package/dist/inspector/boot.js +297 -0
- package/dist/inspector/canvas/AgentActivityOverlay.d.ts +2 -1
- package/dist/inspector/canvas/AgentActivityOverlay.js +6 -3
- package/dist/inspector/canvas/CanvasCard.js +23 -12
- package/dist/inspector/canvas/CanvasElementOverlay.js +97 -128
- package/dist/inspector/canvas/CanvasToolbar.js +2 -1
- package/dist/inspector/canvas/CanvasWorkspace.js +119 -162
- package/dist/inspector/canvas/LockedWorkspaceNotice.d.ts +3 -2
- package/dist/inspector/canvas/LockedWorkspaceNotice.js +10 -6
- package/dist/inspector/canvas/PageCard.d.ts +10 -0
- package/dist/inspector/canvas/PageCard.js +51 -0
- package/dist/inspector/canvas/SketchToolOptions.d.ts +5 -0
- package/dist/inspector/canvas/SketchToolOptions.js +86 -0
- package/dist/inspector/canvas/StaleChangeIndicator.d.ts +1 -1
- package/dist/inspector/canvas/StaleChangeIndicator.js +1 -1
- package/dist/inspector/canvas/ToolOptionsDrawer.d.ts +21 -0
- package/dist/inspector/canvas/ToolOptionsDrawer.js +77 -0
- package/dist/inspector/canvas/activeCanvasDocument.d.ts +3 -2
- package/dist/inspector/canvas/assets/eraser.svg +28 -0
- package/dist/inspector/canvas/assets/pen.svg +25 -0
- package/dist/inspector/canvas/canvasFrame.d.ts +16 -0
- package/dist/inspector/canvas/canvasFrame.js +10 -0
- package/dist/inspector/canvas/canvasGestures.d.ts +0 -1
- package/dist/inspector/canvas/canvasGestures.js +0 -6
- package/dist/inspector/canvas/canvasStore.d.ts +6 -0
- package/dist/inspector/canvas/canvasStore.js +36 -1
- package/dist/inspector/canvas/frameConnection.d.ts +48 -0
- package/dist/inspector/canvas/frameConnection.js +73 -0
- package/dist/inspector/canvas/frameProtocol.d.ts +18 -1
- package/dist/inspector/canvas/frameProtocol.js +36 -0
- package/dist/inspector/canvas/frameScrollbarStyles.d.ts +3 -0
- package/dist/inspector/canvas/frameScrollbarStyles.js +21 -0
- package/dist/inspector/canvas/pageSurface.d.ts +11 -0
- package/dist/inspector/canvas/pageSurface.js +34 -0
- package/dist/inspector/canvas/projection.d.ts +19 -9
- package/dist/inspector/canvas/projection.js +58 -31
- package/dist/inspector/canvas/rendererBootstrap.js +4 -1
- package/dist/inspector/canvas/rendererElementSelector.js +9 -1
- package/dist/inspector/canvas/rendererMessageRouter.d.ts +4 -5
- package/dist/inspector/canvas/rendererMessageRouter.js +12 -16
- package/dist/inspector/canvas/rendererSelectionProxy.d.ts +7 -1
- package/dist/inspector/canvas/rendererSelectionProxy.js +8 -36
- package/dist/inspector/canvas/rendererStylesheet.js +15 -0
- package/dist/inspector/canvas/sessionStore.d.ts +2 -2
- package/dist/inspector/canvas/sessionStore.js +12 -9
- package/dist/inspector/canvas/staleChangeDetector.d.ts +1 -1
- package/dist/inspector/canvas/staleChangeDetector.js +10 -22
- package/dist/inspector/changes/codecs.d.ts +2 -0
- package/dist/inspector/changes/codecs.js +27 -3
- package/dist/inspector/changes/draftPreview.d.ts +12 -0
- package/dist/inspector/changes/draftPreview.js +133 -0
- package/dist/inspector/changes/editModel.d.ts +18 -0
- package/dist/inspector/changes/editModel.js +16 -3
- package/dist/inspector/changes/managedStyleProjection.js +2 -1
- package/dist/inspector/changes/previewDiagnostics.js +1 -1
- package/dist/inspector/changes/types.d.ts +18 -1
- package/dist/inspector/comments/CommentsOverlay.js +18 -16
- package/dist/inspector/comments/element.js +10 -3
- package/dist/inspector/comments/store.js +3 -2
- package/dist/inspector/comments/verification.js +1 -1
- package/dist/inspector/componentSemantics/ComponentPropsSection.js +2 -1
- package/dist/inspector/componentSemantics/adapterRegistry.d.ts +6 -1
- package/dist/inspector/componentSemantics/adapterRegistry.js +23 -1
- package/dist/inspector/componentSemantics/changeAction.js +1 -1
- package/dist/inspector/componentSemantics/reactRuntime.d.ts +2 -1
- package/dist/inspector/componentSemantics/reactRuntime.js +38 -0
- package/dist/inspector/componentSemantics/runtimeBridge.d.ts +4 -2
- package/dist/inspector/componentSemantics/runtimeBridge.js +17 -0
- package/dist/inspector/componentSemantics/textBinding.d.ts +1 -2
- package/dist/inspector/componentSemantics/textBinding.js +4 -45
- package/dist/inspector/componentSemantics/types.d.ts +11 -0
- package/dist/inspector/drafts/draftStore.d.ts +109 -0
- package/dist/inspector/drafts/draftStore.js +451 -0
- package/dist/inspector/drafts/model.d.ts +9 -1
- package/dist/inspector/drafts/model.js +5 -1
- package/dist/inspector/drafts/store.d.ts +53 -15
- package/dist/inspector/drafts/store.js +143 -169
- package/dist/inspector/index.d.ts +2 -4
- package/dist/inspector/index.js +2 -264
- package/dist/inspector/inline-text/inlineTextEditor.d.ts +1 -1
- package/dist/inspector/inline-text/inlineTextEditor.js +1 -2
- package/dist/inspector/inline-text/textEditChange.d.ts +1 -1
- package/dist/inspector/inspection/bridge.d.ts +1 -1
- package/dist/inspector/inspection/bridge.js +2 -1
- package/dist/inspector/inspection/browserCssInspection.d.ts +8 -0
- package/dist/inspector/inspection/browserCssInspection.js +28 -6
- package/dist/inspector/inspection/inspectionSnapshot.d.ts +6 -0
- package/dist/inspector/inspection/inspectionSnapshot.js +10 -0
- package/dist/inspector/inspection/performanceDiagnostics.d.ts +18 -0
- package/dist/inspector/inspection/performanceDiagnostics.js +31 -0
- package/dist/inspector/inspection/useBrowserCssInspection.js +17 -38
- package/dist/inspector/iterations/capture.d.ts +2 -1
- package/dist/inspector/iterations/capture.js +2 -1
- package/dist/inspector/iterations/client.d.ts +2 -1
- package/dist/inspector/iterations/coordinator.d.ts +4 -3
- package/dist/inspector/iterations/coordinator.js +5 -3
- package/dist/inspector/iterations/prompt.js +9 -8
- package/dist/inspector/overlay/interactionStyles.js +17 -0
- package/dist/inspector/pageInspector.d.ts +42 -0
- package/dist/inspector/pageInspector.js +342 -0
- package/dist/inspector/projection/draftProjection.d.ts +10 -2
- package/dist/inspector/projection/draftProjection.js +9 -1
- package/dist/inspector/projection/managedStylesheet.js +24 -4
- package/dist/inspector/projection/renderedInstance.d.ts +15 -2
- package/dist/inspector/projection/renderedInstance.js +60 -7
- package/dist/inspector/projection/structuralProjection.d.ts +0 -9
- package/dist/inspector/projection/structuralProjection.js +16 -46
- package/dist/inspector/projection/textProjection.d.ts +8 -2
- package/dist/inspector/projection/textProjection.js +77 -24
- package/dist/inspector/prompt/clipboardHandoff.d.ts +14 -0
- package/dist/inspector/prompt/clipboardHandoff.js +47 -16
- package/dist/inspector/prompt/generatePrompt.d.ts +1 -1
- package/dist/inspector/prompt/generatePrompt.js +248 -148
- package/dist/inspector/prompt/promptDocument.d.ts +3 -0
- package/dist/inspector/prompt/promptDocument.js +8 -0
- package/dist/inspector/prompt/promptSettings.js +7 -5
- package/dist/inspector/runtime/inspectorStorage.d.ts +17 -0
- package/dist/inspector/runtime/inspectorStorage.js +42 -0
- package/dist/inspector/runtime/ownedStylesheets.d.ts +20 -0
- package/dist/inspector/runtime/ownedStylesheets.js +72 -0
- package/dist/inspector/runtime/runtimeConfig.d.ts +4 -0
- package/dist/inspector/runtime/runtimeConfig.js +10 -1
- package/dist/inspector/runtime/runtimeIdentityClaims.d.ts +29 -0
- package/dist/inspector/runtime/runtimeIdentityClaims.js +188 -0
- package/dist/inspector/runtime/staticHtmlRuntimeIdentity.d.ts +15 -0
- package/dist/inspector/runtime/staticHtmlRuntimeIdentity.js +113 -29
- package/dist/inspector/selection/editScope.d.ts +1 -0
- package/dist/inspector/selection/editScope.js +13 -2
- package/dist/inspector/selection/sourceSite.d.ts +12 -0
- package/dist/inspector/selection/sourceSite.js +28 -0
- package/dist/inspector/shell/AgentStatusButton.js +2 -2
- package/dist/inspector/shell/AppShell.js +6 -2
- package/dist/inspector/shell/ChangesLog.js +27 -9
- package/dist/inspector/shell/CopyPromptButton.js +74 -26
- package/dist/inspector/shell/EmptyState.js +3 -3
- package/dist/inspector/shell/InspectorShell.d.ts +0 -3
- package/dist/inspector/shell/InspectorShell.js +21 -13
- package/dist/inspector/shell/PeekOriginalButton.js +3 -3
- package/dist/inspector/shell/interactionStatePreview.d.ts +9 -0
- package/dist/inspector/shell/interactionStatePreview.js +112 -0
- package/dist/inspector/shell/originalPreview.d.ts +3 -2
- package/dist/inspector/shell/originalPreview.js +10 -37
- package/dist/inspector/shell/stateValue.js +2 -3
- package/dist/inspector/sketch/SketchEditor.js +5 -4
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +2 -1
- package/dist/inspector/sketch/SketchFrameOverlay.js +5 -4
- package/dist/inspector/sketch/SketchOverlay.d.ts +3 -5
- package/dist/inspector/sketch/SketchOverlay.js +139 -263
- package/dist/inspector/sketch/SketchPromptPanel.d.ts +1 -1
- package/dist/inspector/sketch/SketchPromptPanel.js +8 -1
- package/dist/inspector/sketch/SketchThumbnail.d.ts +5 -0
- package/dist/inspector/sketch/SketchThumbnail.js +18 -0
- package/dist/inspector/sketch/SketchWorkspace.d.ts +1 -2
- package/dist/inspector/sketch/SketchWorkspace.js +69 -34
- package/dist/inspector/sketch/capture.js +19 -3
- package/dist/inspector/sketch/copySketch.d.ts +10 -0
- package/dist/inspector/sketch/copySketch.js +52 -0
- package/dist/inspector/sketch/drawingTools.d.ts +17 -0
- package/dist/inspector/sketch/drawingTools.js +47 -0
- package/dist/inspector/sketch/frameHeading.d.ts +9 -0
- package/dist/inspector/sketch/frameHeading.js +30 -0
- package/dist/inspector/sketch/freehand.js +3 -3
- package/dist/inspector/sketch/geometry.d.ts +6 -0
- package/dist/inspector/sketch/geometry.js +19 -0
- package/dist/inspector/sketch/history.d.ts +30 -0
- package/dist/inspector/sketch/history.js +58 -0
- package/dist/inspector/sketch/interaction.d.ts +1 -3
- package/dist/inspector/sketch/interaction.js +6 -9
- package/dist/inspector/sketch/model.d.ts +35 -1
- package/dist/inspector/sketch/model.js +116 -61
- package/dist/inspector/sketch/persistence.d.ts +14 -0
- package/dist/inspector/sketch/persistence.js +63 -7
- package/dist/inspector/sketch/raster.d.ts +6 -2
- package/dist/inspector/sketch/raster.js +43 -17
- package/dist/inspector/sketch/store.d.ts +1 -1
- package/dist/inspector/sketch/store.js +1 -1
- package/dist/inspector/sketch/videoFrames.d.ts +6 -0
- package/dist/inspector/sketch/videoFrames.js +81 -0
- package/dist/inspector/styleEditors/BackgroundEditor.js +3 -2
- package/dist/inspector/styleEditors/BorderEditor.js +30 -130
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +8 -3
- package/dist/inspector/styleEditors/ImageBackgroundEditor.js +1 -1
- package/dist/inspector/styleEditors/LayoutSection.js +2 -2
- package/dist/inspector/styleEditors/borderModel.d.ts +42 -0
- package/dist/inspector/styleEditors/borderModel.js +114 -0
- package/dist/inspector/styleEditors/layoutValue.js +2 -3
- package/dist/inspector/tokens/TokenField.js +40 -7
- package/dist/inspector/tokens/TokensPanel.js +2 -1
- package/dist/inspector/tokens/editActions.d.ts +3 -3
- package/dist/inspector/tokens/editActions.js +15 -3
- package/dist/inspector/tokens/resolution/containerQueryEvaluator.d.ts +15 -0
- package/dist/inspector/tokens/resolution/containerQueryEvaluator.js +86 -0
- package/dist/inspector/tokens/resolution/cssomCollector.js +44 -9
- package/dist/inspector/tokens/resolution/nestedSelectors.d.ts +6 -0
- package/dist/inspector/tokens/resolution/nestedSelectors.js +67 -0
- package/dist/inspector/tokens/resolution/ruleMatching.js +23 -4
- package/dist/inspector/tokens/resolution.d.ts +6 -3
- package/dist/inspector/tokens/resolution.js +91 -112
- package/dist/inspector/tokens/tokenEdits.d.ts +1 -1
- package/dist/inspector/tokens/tokenEdits.js +1 -1
- package/dist/inspector/ui/InlineStyleWarning.js +2 -2
- package/dist/inspector/ui/InspectorStyle.d.ts +8 -0
- package/dist/inspector/ui/InspectorStyle.js +26 -0
- package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
- package/dist/inspector/ui/PopoverListbox.js +3 -3
- package/dist/inspector/ui/Toast.d.ts +14 -0
- package/dist/inspector/ui/Toast.js +30 -0
- package/dist/inspector/ui/assets/canvasIllustration.d.ts +2 -0
- package/dist/inspector/ui/assets/canvasIllustration.js +21 -0
- package/dist/inspector/ui/assets/gridIllustration.d.ts +2 -0
- package/dist/inspector/ui/assets/{grid.svg → gridIllustration.js} +3 -2
- package/dist/inspector/ui/customCursors.d.ts +5 -0
- package/dist/inspector/ui/customCursors.js +32 -6
- package/dist/inspector/ui/styles.js +12 -10
- package/dist/inspector/workspace/commands.js +1 -2
- package/dist/inspector/workspace/controller.js +1 -2
- package/dist/inspector/workspace/handoff.js +3 -2
- package/dist/inspector/workspace/sentItems.d.ts +16 -0
- package/dist/inspector/workspace/sentItems.js +33 -0
- package/dist/inspector/workspace/timeline.d.ts +24 -10
- package/dist/inspector/workspace/timeline.js +118 -96
- package/dist/internal/agent-protocol.d.ts +50 -13
- package/dist/internal/agent-protocol.js +145 -19
- package/dist/nudge-ui.mjs +81 -65
- package/dist/project/mcpPackage.d.ts +8 -0
- package/dist/project/mcpPackage.js +60 -0
- package/package.json +13 -3
- package/dist/inspector/canvas/measurementProjection.d.ts +0 -8
- package/dist/inspector/canvas/measurementProjection.js +0 -16
- package/dist/inspector/changes/changesLog.d.ts +0 -47
- package/dist/inspector/changes/changesLog.js +0 -247
- package/dist/inspector/changes/draftChanges.d.ts +0 -56
- package/dist/inspector/changes/draftChanges.js +0 -300
- package/dist/inspector/conformance/projection.d.ts +0 -2
- package/dist/inspector/conformance/projection.js +0 -3
- package/dist/inspector/inline-text/textChangeBoundary.d.ts +0 -6
- package/dist/inspector/inline-text/textChangeBoundary.js +0 -1
- package/dist/inspector/overlay/InspectorOverlay.d.ts +0 -5
- package/dist/inspector/overlay/InspectorOverlay.js +0 -332
- package/dist/inspector/overlay/elementSelector.d.ts +0 -1
- package/dist/inspector/overlay/elementSelector.js +0 -111
- package/dist/inspector/sketch/SketchChanges.d.ts +0 -2
- package/dist/inspector/sketch/SketchChanges.js +0 -72
- package/dist/inspector/sketch/SketchEntryButton.d.ts +0 -4
- package/dist/inspector/sketch/SketchEntryButton.js +0 -11
- package/dist/inspector/sketch/SketchLayersPanel.d.ts +0 -2
- package/dist/inspector/sketch/SketchLayersPanel.js +0 -71
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { SKETCH_ANNOTATION_RADIUS, SKETCH_LIMITS, SKETCH_STROKE_COLOR, } from "./model.js";
|
|
1
|
+
import { SKETCH_ANNOTATION_RADIUS, SKETCH_LIMITS, SKETCH_ACCENT_COLOR, SKETCH_RECTANGLE_FILL_OPACITY, SKETCH_STROKE_COLOR, } from "./model.js";
|
|
2
2
|
import { sketchStrokeOutline } from "./freehand.js";
|
|
3
3
|
const PNG_SIGNATURE = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
4
4
|
const SKETCH_NOTE_PADDING = 24;
|
|
5
|
-
const
|
|
5
|
+
const SKETCH_NOTE_TITLE_LINE_HEIGHT = 34;
|
|
6
6
|
const SKETCH_NOTE_LINE_HEIGHT = 32;
|
|
7
7
|
const SKETCH_NOTE_FONT = "24px system-ui, sans-serif";
|
|
8
8
|
const SKETCH_NOTE_TITLE_FONT = "600 24px system-ui, sans-serif";
|
|
9
|
+
const SKETCH_NOTE_FALLBACK_HEADING = "Sketch notes";
|
|
9
10
|
export function readPngDimensions(bytes) {
|
|
10
11
|
if (bytes.length < 24)
|
|
11
12
|
return null;
|
|
@@ -63,9 +64,15 @@ export function drawSketchStroke(ctx, stroke, scaleX = 1, scaleY = scaleX) {
|
|
|
63
64
|
if (stroke.kind === "rectangle") {
|
|
64
65
|
ctx.lineCap = "round";
|
|
65
66
|
ctx.lineJoin = "round";
|
|
66
|
-
|
|
67
|
+
const color = stroke.color || SKETCH_STROKE_COLOR;
|
|
68
|
+
ctx.strokeStyle = color;
|
|
69
|
+
ctx.fillStyle = color;
|
|
67
70
|
ctx.lineWidth = stroke.width * Math.max(scaleX, scaleY);
|
|
68
71
|
drawPointPath(ctx, stroke.points, scaleX, scaleY);
|
|
72
|
+
ctx.save();
|
|
73
|
+
ctx.globalAlpha = SKETCH_RECTANGLE_FILL_OPACITY;
|
|
74
|
+
ctx.fill();
|
|
75
|
+
ctx.restore();
|
|
69
76
|
ctx.stroke();
|
|
70
77
|
}
|
|
71
78
|
else {
|
|
@@ -93,7 +100,7 @@ export function drawSketchAnnotation(ctx, annotation, scaleX = 1, scaleY = scale
|
|
|
93
100
|
const y = annotation.point.y * scaleY;
|
|
94
101
|
const radius = SKETCH_ANNOTATION_RADIUS * scale;
|
|
95
102
|
ctx.save();
|
|
96
|
-
ctx.fillStyle =
|
|
103
|
+
ctx.fillStyle = SKETCH_ACCENT_COLOR;
|
|
97
104
|
ctx.beginPath();
|
|
98
105
|
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
99
106
|
ctx.fill();
|
|
@@ -159,6 +166,14 @@ function wrapText(ctx, text, maxWidth) {
|
|
|
159
166
|
lines.push(line);
|
|
160
167
|
return lines;
|
|
161
168
|
}
|
|
169
|
+
/** Wraps the frame heading that names the sketch target to the panel width. */
|
|
170
|
+
function wrapSketchHeading(ctx, width, heading) {
|
|
171
|
+
const text = heading.trim();
|
|
172
|
+
if (!text)
|
|
173
|
+
return [];
|
|
174
|
+
ctx.font = SKETCH_NOTE_TITLE_FONT;
|
|
175
|
+
return wrapText(ctx, text, Math.max(1, width - SKETCH_NOTE_PADDING * 2));
|
|
176
|
+
}
|
|
162
177
|
/** Wraps the description and numbered annotation notes to the panel width. */
|
|
163
178
|
function wrapSketchNotes(ctx, width, description, annotations) {
|
|
164
179
|
const notes = [
|
|
@@ -172,29 +187,34 @@ function wrapSketchNotes(ctx, width, description, annotations) {
|
|
|
172
187
|
const textWidth = Math.max(1, width - SKETCH_NOTE_PADDING * 2);
|
|
173
188
|
return notes.flatMap((note) => wrapText(ctx, note, textWidth));
|
|
174
189
|
}
|
|
175
|
-
function sketchNotePanelHeight(
|
|
176
|
-
if (
|
|
190
|
+
function sketchNotePanelHeight(headingLineCount, noteLineCount) {
|
|
191
|
+
if (headingLineCount + noteLineCount === 0)
|
|
177
192
|
return 0;
|
|
178
|
-
return SKETCH_NOTE_PADDING * 2
|
|
193
|
+
return SKETCH_NOTE_PADDING * 2
|
|
194
|
+
+ headingLineCount * SKETCH_NOTE_TITLE_LINE_HEIGHT
|
|
195
|
+
+ noteLineCount * SKETCH_NOTE_LINE_HEIGHT;
|
|
179
196
|
}
|
|
180
|
-
function drawSketchNotes(ctx, width, top,
|
|
197
|
+
function drawSketchNotes(ctx, width, top, headingLines, noteLines) {
|
|
181
198
|
ctx.save();
|
|
182
|
-
ctx.fillStyle =
|
|
183
|
-
ctx.fillRect(0, top, width, sketchNotePanelHeight(
|
|
199
|
+
ctx.fillStyle = SKETCH_ACCENT_COLOR;
|
|
200
|
+
ctx.fillRect(0, top, width, sketchNotePanelHeight(headingLines.length, noteLines.length));
|
|
184
201
|
ctx.fillStyle = "#ffffff";
|
|
185
202
|
ctx.textAlign = "left";
|
|
186
203
|
ctx.textBaseline = "top";
|
|
187
204
|
ctx.font = SKETCH_NOTE_TITLE_FONT;
|
|
188
|
-
|
|
205
|
+
let y = top + SKETCH_NOTE_PADDING;
|
|
206
|
+
for (const line of headingLines) {
|
|
207
|
+
ctx.fillText(line, SKETCH_NOTE_PADDING, y);
|
|
208
|
+
y += SKETCH_NOTE_TITLE_LINE_HEIGHT;
|
|
209
|
+
}
|
|
189
210
|
ctx.font = SKETCH_NOTE_FONT;
|
|
190
|
-
|
|
191
|
-
for (const line of lines) {
|
|
211
|
+
for (const line of noteLines) {
|
|
192
212
|
ctx.fillText(line, SKETCH_NOTE_PADDING, y);
|
|
193
213
|
y += SKETCH_NOTE_LINE_HEIGHT;
|
|
194
214
|
}
|
|
195
215
|
ctx.restore();
|
|
196
216
|
}
|
|
197
|
-
export async function renderAnnotatedPng(originalImage, imageWidth, imageHeight, strokes, annotations = [], description = "") {
|
|
217
|
+
export async function renderAnnotatedPng(originalImage, imageWidth, imageHeight, strokes, annotations = [], description = "", heading = SKETCH_NOTE_FALLBACK_HEADING) {
|
|
198
218
|
await validatePngBlob(originalImage, { width: imageWidth, height: imageHeight });
|
|
199
219
|
if (typeof document === "undefined")
|
|
200
220
|
throw new Error("Sketch rendering requires a browser document.");
|
|
@@ -205,8 +225,9 @@ export async function renderAnnotatedPng(originalImage, imageWidth, imageHeight,
|
|
|
205
225
|
const context = canvas.getContext("2d");
|
|
206
226
|
if (!context)
|
|
207
227
|
throw new Error("The browser could not create a sketch canvas.");
|
|
228
|
+
const headingLines = wrapSketchHeading(context, imageWidth, heading);
|
|
208
229
|
const noteLines = wrapSketchNotes(context, imageWidth, description, annotations);
|
|
209
|
-
const outputHeight = imageHeight + sketchNotePanelHeight(noteLines.length);
|
|
230
|
+
const outputHeight = imageHeight + sketchNotePanelHeight(headingLines.length, noteLines.length);
|
|
210
231
|
if (outputHeight > SKETCH_LIMITS.imageEdge) {
|
|
211
232
|
throw new Error("Sketch notes are too long to fit below the captured image.");
|
|
212
233
|
}
|
|
@@ -215,8 +236,9 @@ export async function renderAnnotatedPng(originalImage, imageWidth, imageHeight,
|
|
|
215
236
|
context.drawImage(image.source, 0, 0, imageWidth, imageHeight);
|
|
216
237
|
drawSketchStrokes(context, strokes);
|
|
217
238
|
drawSketchAnnotations(context, annotations);
|
|
218
|
-
if (noteLines.length > 0)
|
|
219
|
-
drawSketchNotes(context, imageWidth, imageHeight, noteLines);
|
|
239
|
+
if (headingLines.length > 0 || noteLines.length > 0) {
|
|
240
|
+
drawSketchNotes(context, imageWidth, imageHeight, headingLines, noteLines);
|
|
241
|
+
}
|
|
220
242
|
const result = await canvasPng(canvas);
|
|
221
243
|
if (result.size > SKETCH_LIMITS.imageBytes) {
|
|
222
244
|
throw new Error("This sketch is too detailed to save within the 2 MiB image limit.");
|
|
@@ -238,6 +260,10 @@ export async function blobToBase64(blob) {
|
|
|
238
260
|
throw new Error("This browser cannot prepare sketch attachments.");
|
|
239
261
|
return btoa(binary);
|
|
240
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* Accepts a pending blob so callers can start the write inside the user gesture
|
|
265
|
+
* (required by Safari) and resolve the image once rendering finishes.
|
|
266
|
+
*/
|
|
241
267
|
export async function copyImageToClipboard(blob) {
|
|
242
268
|
if (typeof navigator === "undefined" || !navigator.clipboard?.write || typeof ClipboardItem === "undefined") {
|
|
243
269
|
throw new Error("Image clipboard access is unavailable. Use Download image instead.");
|
|
@@ -35,7 +35,7 @@ export declare function markSketchesHandingOff(entries: readonly {
|
|
|
35
35
|
readonly id: string;
|
|
36
36
|
readonly revision: number;
|
|
37
37
|
}[], batchRevision: number, localBatchId: string, agentRequestId?: string): Promise<boolean>;
|
|
38
|
-
export declare function settleSketchDispatch(batchRevision: number | undefined, state: "working" | "completed" | "failed" | "interrupted", agentRequestId?: string, localBatchId?: string): Promise<boolean>;
|
|
38
|
+
export declare function settleSketchDispatch(batchRevision: number | undefined, state: "delivered" | "working" | "completed" | "failed" | "interrupted", agentRequestId?: string, localBatchId?: string): Promise<boolean>;
|
|
39
39
|
export declare function markSketchesHandedOff(entries: readonly {
|
|
40
40
|
readonly id: string;
|
|
41
41
|
readonly revision: number;
|
|
@@ -325,7 +325,7 @@ export function settleSketchDispatch(batchRevision, state, agentRequestId, local
|
|
|
325
325
|
}
|
|
326
326
|
return true;
|
|
327
327
|
}
|
|
328
|
-
const nextHandoffs = changes.map((handoff) => updateHandoff(handoff, state === "working" ? {
|
|
328
|
+
const nextHandoffs = changes.map((handoff) => updateHandoff(handoff, (state === "delivered" || state === "working") ? {
|
|
329
329
|
state: "handing-off",
|
|
330
330
|
transport: "direct",
|
|
331
331
|
...(agentRequestId ? { agentRequestId } : {}),
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Supplies still images to the DOM renderer while live videos stay mounted. */
|
|
2
|
+
export declare function prepareSketchVideoFrames(targetDocument: Document): {
|
|
3
|
+
readonly filter: (node: Node) => boolean;
|
|
4
|
+
readonly prepareClone: (node: Node) => void;
|
|
5
|
+
readonly restore: () => void;
|
|
6
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { SKETCH_LIMITS } from "./model.js";
|
|
2
|
+
const FRAME_ATTRIBUTE = "data-nudge-ui-video-frame";
|
|
3
|
+
const EMPTY_FRAME = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
|
|
4
|
+
function* videoElements(root) {
|
|
5
|
+
yield* root.querySelectorAll("video");
|
|
6
|
+
for (const element of root.querySelectorAll("*")) {
|
|
7
|
+
if (element.shadowRoot)
|
|
8
|
+
yield* videoElements(element.shadowRoot);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function videoFrame(video) {
|
|
12
|
+
const image = video.ownerDocument.createElement("img");
|
|
13
|
+
image.src = video.poster || EMPTY_FRAME;
|
|
14
|
+
image.width = video.videoWidth || video.clientWidth || 300;
|
|
15
|
+
image.height = video.videoHeight || video.clientHeight || 150;
|
|
16
|
+
const style = video.ownerDocument.defaultView?.getComputedStyle(video);
|
|
17
|
+
if (style) {
|
|
18
|
+
for (const property of style) {
|
|
19
|
+
image.style.setProperty(property, style.getPropertyValue(property), style.getPropertyPriority(property));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Preserve the replaced element's layout even when its CSS size is auto.
|
|
23
|
+
image.style.width = style?.width && style.width !== "auto" ? style.width : `${video.clientWidth}px`;
|
|
24
|
+
image.style.height = style?.height && style.height !== "auto" ? style.height : `${video.clientHeight}px`;
|
|
25
|
+
if (video.readyState >= 2 && video.videoWidth > 0 && video.videoHeight > 0) {
|
|
26
|
+
const canvas = video.ownerDocument.createElement("canvas");
|
|
27
|
+
const scale = Math.min(1, SKETCH_LIMITS.imageEdge / Math.max(video.videoWidth, video.videoHeight));
|
|
28
|
+
canvas.width = Math.max(1, Math.round(video.videoWidth * scale));
|
|
29
|
+
canvas.height = Math.max(1, Math.round(video.videoHeight * scale));
|
|
30
|
+
try {
|
|
31
|
+
const context = canvas.getContext("2d");
|
|
32
|
+
if (context) {
|
|
33
|
+
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
34
|
+
image.src = canvas.toDataURL("image/png");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Cross-origin frames can taint a canvas. Keep the poster or empty frame.
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return image;
|
|
42
|
+
}
|
|
43
|
+
/** Supplies still images to the DOM renderer while live videos stay mounted. */
|
|
44
|
+
export function prepareSketchVideoFrames(targetDocument) {
|
|
45
|
+
const frames = new Map();
|
|
46
|
+
const placeholders = [];
|
|
47
|
+
for (const video of videoElements(targetDocument)) {
|
|
48
|
+
const id = String(frames.size);
|
|
49
|
+
frames.set(id, videoFrame(video));
|
|
50
|
+
const placeholder = targetDocument.createElement("img");
|
|
51
|
+
placeholder.hidden = true;
|
|
52
|
+
placeholder.src = EMPTY_FRAME;
|
|
53
|
+
placeholder.setAttribute(FRAME_ATTRIBUTE, id);
|
|
54
|
+
// The hidden sibling preserves capture order without detaching or seeking
|
|
55
|
+
// the application's video. Only its clone receives the video's styles.
|
|
56
|
+
video.before(placeholder);
|
|
57
|
+
placeholders.push(placeholder);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
filter: (node) => node.nodeType !== 1 || node.localName !== "video",
|
|
61
|
+
prepareClone: (node) => {
|
|
62
|
+
if (node.nodeType !== 1)
|
|
63
|
+
return;
|
|
64
|
+
const element = node;
|
|
65
|
+
const id = element.getAttribute(FRAME_ATTRIBUTE);
|
|
66
|
+
const frame = id === null ? undefined : frames.get(id);
|
|
67
|
+
if (!frame)
|
|
68
|
+
return;
|
|
69
|
+
element.removeAttribute(FRAME_ATTRIBUTE);
|
|
70
|
+
element.removeAttribute("hidden");
|
|
71
|
+
element.style.cssText = frame.style.cssText;
|
|
72
|
+
element.setAttribute("src", frame.src);
|
|
73
|
+
element.setAttribute("width", String(frame.width));
|
|
74
|
+
element.setAttribute("height", String(frame.height));
|
|
75
|
+
},
|
|
76
|
+
restore: () => {
|
|
77
|
+
for (const placeholder of placeholders)
|
|
78
|
+
placeholder.remove();
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -6,7 +6,8 @@ import { interpretTokenValue } from "../../css/value-semantics/tokenInterpretati
|
|
|
6
6
|
import { getNudgeUiTokenEntries } from "../runtime/runtimeConfig.js";
|
|
7
7
|
import { colorValueToHex, TokenValueField } from "../tokens/TokenField.js";
|
|
8
8
|
import { setStyles } from "../tokens/editActions.js";
|
|
9
|
-
import {
|
|
9
|
+
import { getActiveDraftChanges } from "../drafts/store.js";
|
|
10
|
+
import { createChangeHistoryGroup } from "../workspace/timeline.js";
|
|
10
11
|
import { getStateStyleValue } from "../shell/stateValue.js";
|
|
11
12
|
import { ControlSurface } from "../ui/ControlSurface.js";
|
|
12
13
|
import { IconButton } from "../ui/IconButton.js";
|
|
@@ -26,7 +27,7 @@ export function BackgroundEditor(props) {
|
|
|
26
27
|
const elements = selection?.domElements ?? [el];
|
|
27
28
|
const source = readBackgroundLayers((property, fallback) => getStateStyleValue(el, property, fallback));
|
|
28
29
|
const base = source.at(-1);
|
|
29
|
-
const authoredColor = props.tokenRow?.property === "background-color"
|
|
30
|
+
const authoredColor = props.tokenRow?.property === "background-color" || props.tokenRow?.property === "background"
|
|
30
31
|
? props.tokenRow.authored ?? props.tokenRow.declaredValue : undefined;
|
|
31
32
|
if (base?.baseColor && authoredColor) {
|
|
32
33
|
const table = Object.fromEntries((props.entries ?? getNudgeUiTokenEntries()).map((entry) => [entry.name, entry]));
|
|
@@ -3,9 +3,8 @@ import { useEffect, useState } from "react";
|
|
|
3
3
|
import { IconBorderSides, IconBorderStyle2, IconCheck, IconMinus, IconPlus, } from "@tabler/icons-react";
|
|
4
4
|
import { findTokenRow } from "./rowLookup.js";
|
|
5
5
|
import { TokenField } from "../tokens/TokenField.js";
|
|
6
|
-
import {
|
|
6
|
+
import { setStyles } from "../tokens/editActions.js";
|
|
7
7
|
import { FieldRow } from "../ui/FieldRow.js";
|
|
8
|
-
import { SIDE_NAMES } from "../ui/SideValuesField.js";
|
|
9
8
|
import { IconButton } from "../ui/IconButton.js";
|
|
10
9
|
import { ToggleButton } from "../ui/ToggleButton.js";
|
|
11
10
|
import { formatInspectorLabel } from "../ui/labels.js";
|
|
@@ -13,101 +12,22 @@ import { getStateStyleValue } from "../shell/stateValue.js";
|
|
|
13
12
|
import { PopoverListbox } from "../ui/PopoverListbox.js";
|
|
14
13
|
import { ControlSurface } from "../ui/ControlSurface.js";
|
|
15
14
|
import { getNudgeUiTokenEntries } from "../runtime/runtimeConfig.js";
|
|
16
|
-
import { isZeroCssValue } from "./stylePresence.js";
|
|
17
15
|
import { useFieldVisibility } from "./useFieldVisibility.js";
|
|
18
16
|
import { Tooltip } from "../ui/Tooltip.js";
|
|
19
17
|
import { inlineStyleWarningContent } from "../ui/InlineStyleWarning.js";
|
|
20
18
|
import { inlineBlockedBy } from "./inlineAuthored.js";
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
function borderStyleValue(el, rows, property) {
|
|
25
|
-
const row = findTokenRow(rows, property);
|
|
26
|
-
const structured = row?.structure?.style?.trim();
|
|
27
|
-
if (structured)
|
|
28
|
-
return structured.toLowerCase();
|
|
29
|
-
const authored = (row?.authored ?? row?.declaredValue ?? "").trim().toLowerCase();
|
|
30
|
-
if (authored && BORDER_STYLES.includes(authored))
|
|
31
|
-
return authored;
|
|
32
|
-
return (getStateStyleValue(el, property, "none") || "none").toLowerCase();
|
|
19
|
+
import { BORDER_SIDES, BORDER_STYLES, borderDeclarations, borderProperty, hasVisibleBorder, readBorderEditor, } from "./borderModel.js";
|
|
20
|
+
function stateStyleReader(el) {
|
|
21
|
+
return (property, fallback) => getStateStyleValue(el, property, fallback);
|
|
33
22
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|| "0px").trim();
|
|
43
|
-
}
|
|
44
|
-
function borderColorValue(el, rows, property) {
|
|
45
|
-
const row = findTokenRow(rows, property);
|
|
46
|
-
if (row?.structure?.color)
|
|
47
|
-
return row.structure.color.trim();
|
|
48
|
-
return (row?.authored
|
|
49
|
-
|| row?.declaredValue
|
|
50
|
-
|| row?.resolvedValue
|
|
51
|
-
|| getStateStyleValue(el, property, "currentcolor")
|
|
52
|
-
|| "currentcolor").trim();
|
|
53
|
-
}
|
|
54
|
-
/** Effective per-side value used to decide linked vs individual UI. */
|
|
55
|
-
function sideComponentSignature(el, rows, property) {
|
|
56
|
-
const row = findTokenRow(rows, property);
|
|
57
|
-
if (property.endsWith("-width")) {
|
|
58
|
-
return `w:${borderWidthValue(el, rows, property).toLowerCase()}|t:${row?.tokenName ?? ""}`;
|
|
59
|
-
}
|
|
60
|
-
if (property.endsWith("-style")) {
|
|
61
|
-
return `s:${borderStyleValue(el, rows, property)}`;
|
|
62
|
-
}
|
|
63
|
-
if (property.endsWith("-color")) {
|
|
64
|
-
return `c:${borderColorValue(el, rows, property).toLowerCase()}|t:${row?.tokenName ?? ""}`;
|
|
65
|
-
}
|
|
66
|
-
return `${row?.authored ?? ""}|${row?.resolvedValue ?? getStateStyleValue(el, property)}`;
|
|
67
|
-
}
|
|
68
|
-
function sideValueForLink(el, rows, property, fallback = "") {
|
|
69
|
-
if (property.endsWith("-width"))
|
|
70
|
-
return borderWidthValue(el, rows, property) || fallback;
|
|
71
|
-
if (property.endsWith("-style"))
|
|
72
|
-
return borderStyleValue(el, rows, property) || fallback;
|
|
73
|
-
if (property.endsWith("-color"))
|
|
74
|
-
return borderColorValue(el, rows, property) || fallback;
|
|
75
|
-
return fallback;
|
|
76
|
-
}
|
|
77
|
-
function borderLinkDeclaration(el, rows, shorthand, sideProperties) {
|
|
78
|
-
const sharedValue = sideValueForLink(el, rows, sideProperties[0], shorthand === "border-style" ? "solid" : "");
|
|
79
|
-
return sharedValue ? { property: shorthand, value: sharedValue } : null;
|
|
80
|
-
}
|
|
81
|
-
function linkedTokenRow(rows, shorthand, sideProperties) {
|
|
82
|
-
const direct = findTokenRow(rows, shorthand);
|
|
83
|
-
if (direct)
|
|
84
|
-
return direct;
|
|
85
|
-
const first = findTokenRow(rows, sideProperties[0]);
|
|
86
|
-
return first ? { ...first, property: shorthand } : null;
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Sides are linked when every face shares the same effective component value.
|
|
90
|
-
* Uses structured width/style/color parts (not the full authored shorthand string)
|
|
91
|
-
* so four different `border-*` shorthands correctly expand the UI.
|
|
92
|
-
*/
|
|
93
|
-
function valuesAreLinked(el, rows, shorthand, sideProperties) {
|
|
94
|
-
const hasDirectRow = Boolean(findTokenRow(rows, shorthand));
|
|
95
|
-
const hasSideRows = sideProperties.some((property) => Boolean(findTokenRow(rows, property)));
|
|
96
|
-
// A lone shorthand with no per-side overrides is inherently linked.
|
|
97
|
-
if (hasDirectRow && !hasSideRows)
|
|
98
|
-
return true;
|
|
99
|
-
const signatures = sideProperties.map((property) => sideComponentSignature(el, rows, property));
|
|
100
|
-
return new Set(signatures).size === 1;
|
|
101
|
-
}
|
|
102
|
-
/** Initial visibility follows the effective border, including stylesheet resets. */
|
|
103
|
-
function hasBorderPresence(el) {
|
|
104
|
-
return SIDE_NAMES.some((side) => {
|
|
105
|
-
const style = getStateStyleValue(el, `border-${side}-style`, "none").toLowerCase();
|
|
106
|
-
if (INVISIBLE_BORDER_STYLES.has(style))
|
|
107
|
-
return false;
|
|
108
|
-
const width = getStateStyleValue(el, `border-${side}-width`, "0px");
|
|
109
|
-
return !isZeroCssValue(width);
|
|
110
|
-
});
|
|
23
|
+
/** Reports whether the intent produced any declarations to write. */
|
|
24
|
+
function commitBorderIntent(target, source, intent, onAfterEdit) {
|
|
25
|
+
const declarations = borderDeclarations(source, intent);
|
|
26
|
+
if (declarations.length === 0)
|
|
27
|
+
return false;
|
|
28
|
+
setStyles(target, declarations);
|
|
29
|
+
onAfterEdit?.();
|
|
30
|
+
return true;
|
|
111
31
|
}
|
|
112
32
|
/**
|
|
113
33
|
* Linked presentation from cascade data + optional user toggle.
|
|
@@ -147,36 +67,25 @@ export function BorderEditor(props) {
|
|
|
147
67
|
const editTarget = selection?.target ?? el;
|
|
148
68
|
const isGroup = Boolean(selection && selection.elements.length > 1);
|
|
149
69
|
const allEntries = entries ?? getNudgeUiTokenEntries();
|
|
150
|
-
const borderWidthProperties = BORDER_SIDES.map((side) => `${side}-width`);
|
|
151
|
-
const borderStyleProperties = BORDER_SIDES.map((side) => `${side}-style`);
|
|
152
|
-
const borderColorProperties = BORDER_SIDES.map((side) => `${side}-color`);
|
|
153
70
|
const borderBlockedBy = inlineBlockedBy(selectedElements, "border", "border-width", "border-style", "border-color");
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
const borderRow = findTokenRow(tokenRows, "border");
|
|
160
|
-
const hasStructuredBorderRows = tokenRows.some((row) => Boolean(row.structure));
|
|
161
|
-
const rawBorderFallback = !isGroup && Boolean(borderRow && !borderRow.structure && !hasStructuredBorderRows);
|
|
162
|
-
const linkedBorderStyle = borderStyleValue(el, tokenRows, "border-style");
|
|
163
|
-
const showWidthAndColor = !(borderLinked && INVISIBLE_BORDER_STYLES.has(linkedBorderStyle));
|
|
164
|
-
const hasBorder = selectedElements.some(hasBorderPresence);
|
|
71
|
+
const source = { rows: tokenRows, computed: stateStyleReader(el) };
|
|
72
|
+
const border = readBorderEditor(source, { group: isGroup });
|
|
73
|
+
const [borderLinked, setBorderLinked] = useBorderLinkedState(border.sidesLinked, el);
|
|
74
|
+
const showWidthAndColor = !borderLinked || border.linkedStyleDraws;
|
|
75
|
+
const hasBorder = selectedElements.some((element) => hasVisibleBorder(stateStyleReader(element)));
|
|
165
76
|
const visibility = useFieldVisibility(selectedElements, "border", hasBorder);
|
|
166
77
|
const showBorderControls = visibility.visible;
|
|
167
78
|
function handleAddBorder() {
|
|
168
79
|
if (borderBlockedBy)
|
|
169
80
|
return;
|
|
170
|
-
|
|
81
|
+
commitBorderIntent(editTarget, source, { kind: "add" }, onAfterEdit);
|
|
171
82
|
visibility.show();
|
|
172
|
-
onAfterEdit?.();
|
|
173
83
|
}
|
|
174
84
|
function handleRemoveBorder() {
|
|
175
85
|
if (borderBlockedBy)
|
|
176
86
|
return;
|
|
177
|
-
|
|
87
|
+
commitBorderIntent(editTarget, source, { kind: "remove" }, onAfterEdit);
|
|
178
88
|
visibility.hide();
|
|
179
|
-
onAfterEdit?.();
|
|
180
89
|
}
|
|
181
90
|
function handleExpand() {
|
|
182
91
|
setBorderLinked(false);
|
|
@@ -185,23 +94,18 @@ export function BorderEditor(props) {
|
|
|
185
94
|
if (borderBlockedBy)
|
|
186
95
|
return;
|
|
187
96
|
setBorderLinked(true);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
].filter((declaration) => declaration !== null);
|
|
193
|
-
if (declarations.length > 0) {
|
|
194
|
-
setStyles(editTarget, declarations);
|
|
195
|
-
onAfterEdit?.();
|
|
196
|
-
}
|
|
97
|
+
commitBorderIntent(editTarget, source, { kind: "link-sides" }, onAfterEdit);
|
|
98
|
+
}
|
|
99
|
+
function handleSelectStyle(property, style) {
|
|
100
|
+
return commitBorderIntent(editTarget, source, { kind: "set-style", property, style }, onAfterEdit);
|
|
197
101
|
}
|
|
198
|
-
const sideRows =
|
|
102
|
+
const sideRows = BORDER_SIDES.map((side) => ({
|
|
199
103
|
side,
|
|
200
|
-
color: (_jsx(ControlSurface, { children: _jsx(TokenField, { property:
|
|
201
|
-
width: (_jsx(ControlSurface, { children: _jsx(TokenField, { property:
|
|
202
|
-
style: (_jsx(BorderStyleSettingsMenu, { property:
|
|
104
|
+
color: (_jsx(ControlSurface, { children: _jsx(TokenField, { property: borderProperty("color", side), selection: selection, tokenRow: findTokenRow(tokenRows, borderProperty("color", side)), domElement: el, editTarget: editTarget, entries: allEntries, onAfterEdit: onAfterEdit }) })),
|
|
105
|
+
width: (_jsx(ControlSurface, { children: _jsx(TokenField, { property: borderProperty("width", side), selection: selection, tokenRow: findTokenRow(tokenRows, borderProperty("width", side)), domElement: el, editTarget: editTarget, entries: allEntries, onAfterEdit: onAfterEdit, chipVariant: "small" }) })),
|
|
106
|
+
style: (_jsx(BorderStyleSettingsMenu, { property: borderProperty("style", side), tokenRow: findTokenRow(tokenRows, borderProperty("style", side)), domElement: el, selection: selection, dataTest: `border-style-${side}`, onSelectStyle: handleSelectStyle })),
|
|
203
107
|
}));
|
|
204
|
-
return (_jsxs("div", { className: `editor`, "data-test": "border-editor", children: [_jsxs("div", { className: "editor__title-row", children: [_jsx("div", { className: "editor__title", children: "Border" }), showBorderControls ? (_jsx(Tooltip, { content: inlineStyleWarningContent(borderBlockedBy), children: _jsx(IconButton, { variant: "quiet", label: "Remove Border", "data-test": "remove-border", className: "border__remove", disabled: borderBlockedBy !== null, onClick: handleRemoveBorder, children: _jsx(IconMinus, { size: 16, "aria-hidden": "true" }) }) })) : (_jsx(IconButton, { variant: "quiet", label: "Add Border", "data-test": "add-border", className: "border__add", disabled: borderBlockedBy !== null, onClick: handleAddBorder, children: _jsx(IconPlus, { size: 16, "aria-hidden": "true" }) }))] }), showBorderControls && (_jsx("div", { className: "border", "data-expanded": borderLinked ? "false" : "true", children:
|
|
108
|
+
return (_jsxs("div", { className: `editor`, "data-test": "border-editor", children: [_jsxs("div", { className: "editor__title-row", children: [_jsx("div", { className: "editor__title", children: "Border" }), showBorderControls ? (_jsx(Tooltip, { content: inlineStyleWarningContent(borderBlockedBy), children: _jsx(IconButton, { variant: "quiet", label: "Remove Border", "data-test": "remove-border", className: "border__remove", disabled: borderBlockedBy !== null, onClick: handleRemoveBorder, children: _jsx(IconMinus, { size: 16, "aria-hidden": "true" }) }) })) : (_jsx(IconButton, { variant: "quiet", label: "Add Border", "data-test": "add-border", className: "border__add", disabled: borderBlockedBy !== null, onClick: handleAddBorder, children: _jsx(IconPlus, { size: 16, "aria-hidden": "true" }) }))] }), showBorderControls && (_jsx("div", { className: "border", "data-expanded": borderLinked ? "false" : "true", children: border.rawShorthandRow ? (_jsx(FieldRow, { label: "Border", children: _jsx(ControlSurface, { children: _jsx(TokenField, { property: "border", selection: selection, tokenRow: border.rawShorthandRow, domElement: el, editTarget: editTarget, entries: allEntries, onAfterEdit: onAfterEdit }) }) })) : borderLinked ? (_jsxs("div", { className: "border__linked-row", children: [showWidthAndColor ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "border__linked-control border__linked-control--color", children: _jsx(ControlSurface, { children: _jsx(TokenField, { property: "border-color", selection: selection, tokenRow: border.linkedRows.color, domElement: el, editTarget: editTarget, entries: allEntries, onAfterEdit: onAfterEdit }) }) }), _jsx("div", { className: "border__linked-control border__linked-control--width", children: _jsx(ControlSurface, { children: _jsx(TokenField, { property: "border-width", selection: selection, tokenRow: border.linkedRows.width, domElement: el, editTarget: editTarget, entries: allEntries, onAfterEdit: onAfterEdit, chipVariant: "small" }) }) })] })) : null, _jsx(BorderStyleSettingsMenu, { property: "border-style", tokenRow: border.linkedRows.style, domElement: el, selection: selection, onSelectStyle: handleSelectStyle }), _jsx(ToggleButton, { variant: "quiet", size: "default", "data-test": "border-expand", label: "Edit Individual Border Sides", pressed: !borderLinked, onPressedChange: (pressed) => {
|
|
205
109
|
if (pressed)
|
|
206
110
|
handleExpand();
|
|
207
111
|
}, children: _jsx(IconBorderSides, { size: 16, "aria-hidden": "true" }) })] })) : (_jsxs("div", { className: "border__expanded", children: [_jsxs("div", { className: "border__expanded-header", children: [_jsx("span", { className: "side-values__label", children: formatInspectorLabel("Individual Sides") }), _jsx(ToggleButton, { variant: "quiet", size: "default", "data-test": "border-collapse", label: "Link All Border Sides", pressed: !borderLinked, disabled: borderBlockedBy !== null, onPressedChange: (pressed) => {
|
|
@@ -231,7 +135,7 @@ function BorderSideIndicator({ side }) {
|
|
|
231
135
|
const { outline, emphasis } = BORDER_SIDE_ICON_PATHS[side];
|
|
232
136
|
return (_jsxs("svg", { className: "side-values__icon side-values__side-icon", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [_jsx("path", { d: outline, stroke: "currentColor", strokeWidth: "var(--icon-stroke-width)", strokeLinecap: "square", strokeOpacity: "0.2" }), _jsx("line", { ...emphasis, stroke: "currentColor", strokeWidth: "var(--icon-stroke-width)", strokeLinecap: "round" })] }));
|
|
233
137
|
}
|
|
234
|
-
function BorderStyleSettingsMenu({ property, tokenRow, domElement: el,
|
|
138
|
+
function BorderStyleSettingsMenu({ property, tokenRow, domElement: el, selection, dataTest = "border-style-settings", onSelectStyle }) {
|
|
235
139
|
const structured = tokenRow?.structure?.style?.trim().toLowerCase() ?? "";
|
|
236
140
|
const selectedValue = selection?.getProperty(property)?.value;
|
|
237
141
|
const mixed = selectedValue?.kind === "mixed";
|
|
@@ -244,14 +148,10 @@ function BorderStyleSettingsMenu({ property, tokenRow, domElement: el, editTarge
|
|
|
244
148
|
setValue(mixed ? "Mixed" : commonValue || structured || getStateStyleValue(el, property, "none") || "none");
|
|
245
149
|
}, [commonValue, el, mixed, property, structured]);
|
|
246
150
|
function handleChange(next) {
|
|
247
|
-
if (blockedBy)
|
|
248
|
-
return;
|
|
249
|
-
if (!BORDER_STYLES.includes(next))
|
|
151
|
+
if (blockedBy || !onSelectStyle(property, next))
|
|
250
152
|
return;
|
|
251
153
|
setValue(next);
|
|
252
|
-
setStyle(editTarget ?? el, property, next);
|
|
253
154
|
setOpen(false);
|
|
254
|
-
onAfterEdit?.();
|
|
255
155
|
}
|
|
256
156
|
return (_jsx(PopoverListbox, { query: "", value: null, open: open, triggerElement: (_jsx(IconButton, { variant: "quiet", size: "default", label: `Border style: ${formatInspectorLabel(value)}`, "data-test": dataTest, "data-current-style": value, children: _jsx(IconBorderStyle2, { size: 16, "aria-hidden": "true" }) })), triggerDataTest: dataTest, triggerAriaLabel: "Border style settings", triggerTooltip: inlineStyleWarningContent(blockedBy), disabled: blockedBy !== null, items: BORDER_STYLES.map((style) => ({
|
|
257
157
|
value: style,
|
|
@@ -28,7 +28,13 @@ function cornerAuthoredSignature(rows, corner) {
|
|
|
28
28
|
const row = findTokenRow(rows, corner);
|
|
29
29
|
return `${row?.authored ?? row?.declaredValue ?? ""}|${row?.tokenName ?? ""}`;
|
|
30
30
|
}
|
|
31
|
-
function cornersAreLinked(rows) {
|
|
31
|
+
function cornersAreLinked(rows, selection) {
|
|
32
|
+
if (selection && selection.elements.length > 1) {
|
|
33
|
+
const values = BORDER_RADIUS_CORNERS.map((corner) => selection.getProperty(corner)?.value);
|
|
34
|
+
const first = values[0];
|
|
35
|
+
return first?.kind === "common"
|
|
36
|
+
&& values.every((value) => value?.kind === "common" && value.value === first.value);
|
|
37
|
+
}
|
|
32
38
|
const signatures = BORDER_RADIUS_CORNERS.map((corner) => cornerAuthoredSignature(rows, corner));
|
|
33
39
|
return new Set(signatures).size === 1;
|
|
34
40
|
}
|
|
@@ -50,9 +56,8 @@ export function BorderRadiusEditor(props) {
|
|
|
50
56
|
const { element, selection, entries, tokenRows = [], onAfterEdit, embedded = false } = props;
|
|
51
57
|
const el = element.domElement;
|
|
52
58
|
const editTarget = selection?.target ?? el;
|
|
53
|
-
const isGroup = Boolean(selection && selection.elements.length > 1);
|
|
54
59
|
const allEntries = entries ?? getNudgeUiTokenEntries();
|
|
55
|
-
const dataLinked =
|
|
60
|
+
const dataLinked = cornersAreLinked(tokenRows, selection);
|
|
56
61
|
const blockedBy = inlineBlockedBy(selection?.domElements ?? [el], "border-radius");
|
|
57
62
|
const [userUnlinked, setUserUnlinked] = useState(false);
|
|
58
63
|
const [userLinked, setUserLinked] = useState(false);
|
|
@@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from "react";
|
|
|
3
3
|
import { IconPhoto, IconUpload } from "@tabler/icons-react";
|
|
4
4
|
import { TokenValueField } from "../tokens/TokenField.js";
|
|
5
5
|
import { ControlSurface } from "../ui/ControlSurface.js";
|
|
6
|
-
import { getActiveDraftChanges } from "../
|
|
6
|
+
import { getActiveDraftChanges } from "../drafts/store.js";
|
|
7
7
|
import { getStateStyleValue } from "../shell/stateValue.js";
|
|
8
8
|
import { Button } from "../ui/Button.js";
|
|
9
9
|
import { InlineStyleWarning } from "../ui/InlineStyleWarning.js";
|
|
@@ -109,7 +109,7 @@ export function LayoutSection(props) {
|
|
|
109
109
|
const showPositionInsets = selection
|
|
110
110
|
? selection.supportsRole("inset-position")
|
|
111
111
|
: position === "absolute" || position === "fixed";
|
|
112
|
-
return (_jsxs("div", { className: "editor", "data-test": "layout-section", children: [_jsx("div", { className: "editor__title", children: "Layout" }), _jsxs("div", { className: "layout", children: [_jsxs("div", { className: "layout__basic", children: [_jsx(SizeSection, { domElement: el, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsxs("div", { className: "layout__tool-row", children: [_jsx(LayoutDropdown, { property: "display", options: DISPLAY_OPTIONS, domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(LayoutDropdown, { property: "position", options: POSITION_OPTIONS, domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })] })] }), isFlexContainer ? (_jsxs("div", { className: "layout__group", "data-test": "layout-flex-container", children: [_jsxs("div", { className: "layout__flex-heading", children: [_jsx("div", { className: "editor__title", children: "Flex" }), _jsx(FlexSettingsMenu, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })] }), _jsxs("div", { className: "layout__flex-lower", children: [mixedFlexDirection ? (_jsx("div", { className: "layout__alignment-grid", "data-test": "layout-flex-axis-mixed", children: "Choose a shared flex direction to edit physical alignment." })) : (_jsx(FlexAlignmentGrid, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })), _jsxs("div", { className: "layout__gap-column", "data-test": "layout-gap", children: [_jsxs("div", { className: "layout__direction-tools", children: [_jsx(FlexDirectionControl, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(FlexWrapToggle, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })] }), _jsxs("div", { className: "layout__gap-fields", children: [_jsxs("div", { className: "layout__spacing-primary", children: [_jsx(FlexGapField, { property: relevantGap, domElement: el, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, onAfterEdit: notifyAfterEdit }), _jsx(FlexDistributionControl, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })] }), isFlexWrapped ? (_jsx(FlexGapField, { property: lineGap, domElement: el, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, onAfterEdit: notifyAfterEdit })) : null] })] })] })] })) : null, isFlexChild ? (_jsxs("div", { className: "layout__group", "data-test": "layout-flex-child", children: [_jsx("div", { className: "editor__title", children: "Flex Child" }), _jsxs("div", { className: "layout__flex-child-fields", children: [_jsx(FlexChildValueField, { label: "Grow", property: "flex-grow", presets: FLEX_GROW_PRESETS, domElement: el, editTarget: editTarget, selection: selection, inputOnly: true, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(FlexChildValueField, { label: "Shrink", property: "flex-shrink", presets: FLEX_SHRINK_PRESETS, domElement: el, editTarget: editTarget, selection: selection, inputOnly: true, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(FlexChildValueField, { label: "Basis", property: "flex-basis", presets: FLEX_BASIS_PRESETS, domElement: el, editTarget: editTarget, selection: selection, inputOnly: true, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(FlexChildSettingsMenu, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })] })] })) : null, isGridContainer || isGridChild ? (_jsx(GridSection, { domElement: el, showContainer: isGridContainer, showChild: isGridChild, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, revision: computedRevision, onAfterEdit: notifyAfterEdit })) : null, showPositionInsets ? (_jsx(PositionInsets, { domElement: el, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, revision: computedRevision, onAfterEdit: notifyAfterEdit })) : null] })] }));
|
|
112
|
+
return (_jsxs("div", { className: "editor", "data-test": "layout-section", children: [_jsx("div", { className: "editor__title", children: "Layout" }), _jsxs("div", { className: "layout", children: [_jsxs("div", { className: "layout__basic", children: [_jsx(SizeSection, { domElement: el, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsxs("div", { className: "layout__tool-row", children: [_jsx(LayoutDropdown, { property: "display", options: DISPLAY_OPTIONS, domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(LayoutDropdown, { property: "position", options: POSITION_OPTIONS, domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })] })] }), isFlexContainer ? (_jsxs("div", { className: "layout__group", "data-test": "layout-flex-container", children: [_jsxs("div", { className: "layout__flex-heading", children: [_jsx("div", { className: "editor__title", children: "Flex" }), _jsx(FlexSettingsMenu, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })] }), _jsxs("div", { className: "layout__flex-lower", children: [mixedFlexDirection ? (_jsx("div", { className: "layout__alignment-grid", "data-test": "layout-flex-axis-mixed", children: "Choose a shared flex direction to edit physical alignment." })) : (_jsx(FlexAlignmentGrid, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })), _jsxs("div", { className: "layout__gap-column", "data-test": "layout-gap", children: [_jsxs("div", { className: "layout__direction-tools", children: [_jsx(FlexDirectionControl, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(FlexWrapToggle, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })] }), _jsxs("div", { className: "layout__gap-fields", children: [_jsxs("div", { className: "layout__spacing-primary", children: [_jsx(FlexGapField, { property: relevantGap, domElement: el, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, onAfterEdit: notifyAfterEdit }), _jsx(FlexDistributionControl, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })] }), isFlexWrapped ? (_jsx("div", { className: "layout__spacing-primary", children: _jsx(FlexGapField, { property: lineGap, domElement: el, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, onAfterEdit: notifyAfterEdit }) })) : null] })] })] })] })) : null, isFlexChild ? (_jsxs("div", { className: "layout__group", "data-test": "layout-flex-child", children: [_jsx("div", { className: "editor__title", children: "Flex Child" }), _jsxs("div", { className: "layout__flex-child-fields", children: [_jsx(FlexChildValueField, { label: "Grow", property: "flex-grow", presets: FLEX_GROW_PRESETS, domElement: el, editTarget: editTarget, selection: selection, inputOnly: true, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(FlexChildValueField, { label: "Shrink", property: "flex-shrink", presets: FLEX_SHRINK_PRESETS, domElement: el, editTarget: editTarget, selection: selection, inputOnly: true, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(FlexChildValueField, { label: "Basis", property: "flex-basis", presets: FLEX_BASIS_PRESETS, domElement: el, editTarget: editTarget, selection: selection, inputOnly: true, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(FlexChildSettingsMenu, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })] })] })) : null, isGridContainer || isGridChild ? (_jsx(GridSection, { domElement: el, showContainer: isGridContainer, showChild: isGridChild, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, revision: computedRevision, onAfterEdit: notifyAfterEdit })) : null, showPositionInsets ? (_jsx(PositionInsets, { domElement: el, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, revision: computedRevision, onAfterEdit: notifyAfterEdit })) : null] })] }));
|
|
113
113
|
}
|
|
114
114
|
function SizeSection({ domElement: el, editTarget, selection, entries, tokenRows, revision, onAfterEdit }) {
|
|
115
115
|
const [expanded, setExpanded] = useState(false);
|
|
@@ -187,7 +187,7 @@ function FlexWrapToggle({ domElement, editTarget, selection, revision = 0, onAft
|
|
|
187
187
|
setStyle(editTarget ?? domElement, "flex-wrap", isWrapped ? "nowrap" : "wrap");
|
|
188
188
|
onAfterEdit?.();
|
|
189
189
|
}
|
|
190
|
-
return (_jsx(Tooltip, { content: inlineStyleWarningContent(blockedBy), children: _jsx(IconButton, { variant: "
|
|
190
|
+
return (_jsx(Tooltip, { content: inlineStyleWarningContent(blockedBy), children: _jsx(IconButton, { variant: "quiet", size: "default", "data-active": isWrapped, "data-test": "layout-flex-wrap-toggle", label: mixed ? "Set Flex Wrap" : isWrapped ? "Disable Flex Wrap" : "Enable Flex Wrap", "aria-pressed": mixed ? "mixed" : isWrapped, disabled: blockedBy !== null, onClick: toggleWrap, children: _jsx(IconTextWrap, { size: 16, stroke: 1.8, "aria-hidden": "true" }) }) }));
|
|
191
191
|
}
|
|
192
192
|
function FlexSettingsMenu({ domElement, editTarget, selection, revision = 0, onAfterEdit }) {
|
|
193
193
|
const [direction] = useComputedLayoutValue(domElement, "flex-direction", "row", revision);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ResolvedProperty } from "../../css/model/index.js";
|
|
2
|
+
import type { StyleDeclaration } from "../tokens/editActions.js";
|
|
3
|
+
export declare const BORDER_STYLES: readonly ["none", "hidden", "solid", "dashed", "dotted", "double", "groove", "ridge", "inset", "outset"];
|
|
4
|
+
export declare const BORDER_SIDES: readonly ["top", "right", "bottom", "left"];
|
|
5
|
+
export type BorderSide = (typeof BORDER_SIDES)[number];
|
|
6
|
+
export type BorderComponent = "width" | "style" | "color";
|
|
7
|
+
/** Reads a property's computed value for the inspected style state. */
|
|
8
|
+
export type ComputedStyleReader = (property: string, fallback: string) => string;
|
|
9
|
+
/** What the border editor knows about one element: its cascade rows and computed style. */
|
|
10
|
+
export interface BorderSource {
|
|
11
|
+
readonly rows: readonly ResolvedProperty[];
|
|
12
|
+
readonly computed: ComputedStyleReader;
|
|
13
|
+
}
|
|
14
|
+
export interface BorderEditorState {
|
|
15
|
+
/** Every side shares its width, style, and color. Never true for a group selection. */
|
|
16
|
+
readonly sidesLinked: boolean;
|
|
17
|
+
/** An unstructured `border` shorthand row that only a raw field can edit. */
|
|
18
|
+
readonly rawShorthandRow: ResolvedProperty | null;
|
|
19
|
+
/** The linked style draws a line, so linked width and color are meaningful. */
|
|
20
|
+
readonly linkedStyleDraws: boolean;
|
|
21
|
+
/** Rows backing the linked `border-width`, `border-style`, and `border-color` fields. */
|
|
22
|
+
readonly linkedRows: Readonly<Record<BorderComponent, ResolvedProperty | null>>;
|
|
23
|
+
}
|
|
24
|
+
/** User choices the border editor turns into declarations. */
|
|
25
|
+
export type BorderIntent = {
|
|
26
|
+
readonly kind: "add";
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: "remove";
|
|
29
|
+
} | {
|
|
30
|
+
readonly kind: "link-sides";
|
|
31
|
+
} | {
|
|
32
|
+
readonly kind: "set-style";
|
|
33
|
+
readonly property: string;
|
|
34
|
+
readonly style: string;
|
|
35
|
+
};
|
|
36
|
+
export declare function borderProperty(component: BorderComponent, side?: BorderSide): string;
|
|
37
|
+
export declare function readBorderEditor(source: BorderSource, { group }: {
|
|
38
|
+
readonly group: boolean;
|
|
39
|
+
}): BorderEditorState;
|
|
40
|
+
/** Whether any side draws a visible line, including after stylesheet resets. */
|
|
41
|
+
export declare function hasVisibleBorder(computed: ComputedStyleReader): boolean;
|
|
42
|
+
export declare function borderDeclarations(source: BorderSource, intent: BorderIntent): StyleDeclaration[];
|