nudge-ui 0.2.1 → 0.2.3
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/README.md +164 -46
- package/dist/client.mjs +252 -54
- package/dist/css/value-semantics/colorSemantics.d.ts +4 -0
- package/dist/css/value-semantics/colorSemantics.js +17 -0
- package/dist/hosts/astro/astroRuntimeConfig.js +1 -0
- package/dist/hosts/astro/clientTransport.js +41 -32
- package/dist/hosts/astro/integration.d.ts +2 -2
- package/dist/hosts/astro/integration.js +9 -4
- package/dist/hosts/astro/markdownIdentity.d.ts +28 -0
- package/dist/hosts/astro/markdownIdentity.js +81 -0
- package/dist/hosts/environment.d.ts +15 -0
- package/dist/hosts/environment.js +16 -0
- package/dist/hosts/next/manifest.js +1 -1
- package/dist/hosts/next/sidecar.js +12 -2
- package/dist/hosts/next/wrapper.d.ts +2 -0
- package/dist/hosts/next/wrapper.js +2 -1
- package/dist/hosts/projectBridge.d.ts +1 -0
- package/dist/hosts/projectBridge.js +1 -0
- package/dist/hosts/static/manifest.js +3 -1
- package/dist/hosts/static/server.js +4 -0
- package/dist/hosts/vite/vite.d.ts +4 -0
- package/dist/hosts/vite/vite.js +28 -5
- package/dist/inspector/agent/McpConnectionDialog.d.ts +3 -2
- package/dist/inspector/agent/McpConnectionDialog.js +23 -22
- package/dist/inspector/agent/activity.d.ts +18 -0
- package/dist/inspector/agent/activity.js +70 -0
- package/dist/inspector/agent/client.d.ts +8 -0
- package/dist/inspector/agent/client.js +65 -5
- package/dist/inspector/agent/connectionStatus.d.ts +3 -3
- package/dist/inspector/agent/connectionStatus.js +9 -2
- package/dist/inspector/agent/httpTransport.d.ts +6 -0
- package/dist/inspector/agent/httpTransport.js +50 -3
- package/dist/inspector/agent/protocol.d.ts +9 -3
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +11 -1
- package/dist/inspector/agent/verification.js +53 -25
- package/dist/inspector/canvas/AgentActivityOverlay.d.ts +13 -0
- package/dist/inspector/canvas/AgentActivityOverlay.js +111 -0
- package/dist/inspector/canvas/CanvasCard.d.ts +9 -2
- package/dist/inspector/canvas/CanvasCard.js +133 -38
- package/dist/inspector/canvas/CanvasElementOverlay.js +200 -22
- package/dist/inspector/canvas/CanvasToolIcons.d.ts +10 -0
- package/dist/inspector/canvas/CanvasToolIcons.js +20 -0
- package/dist/inspector/canvas/CanvasToolbar.js +8 -4
- package/dist/inspector/canvas/CanvasWorkspace.js +243 -56
- package/dist/inspector/canvas/StaleChangeIndicator.js +3 -13
- package/dist/inspector/canvas/agentPresentation.d.ts +8 -8
- package/dist/inspector/canvas/agentPresentation.js +64 -52
- package/dist/inspector/canvas/canvasGestures.d.ts +1 -1
- package/dist/inspector/canvas/canvasGestures.js +1 -1
- package/dist/inspector/canvas/canvasStore.d.ts +46 -82
- package/dist/inspector/canvas/canvasStore.js +251 -284
- package/dist/inspector/canvas/frameContent.d.ts +21 -0
- package/dist/inspector/canvas/frameContent.js +44 -0
- package/dist/inspector/canvas/frameProtocol.d.ts +34 -4
- package/dist/inspector/canvas/frameProtocol.js +9 -2
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -1
- package/dist/inspector/canvas/keyboardShortcuts.js +5 -3
- package/dist/inspector/canvas/layout.d.ts +52 -0
- package/dist/inspector/canvas/layout.js +106 -0
- package/dist/inspector/canvas/linkEligibility.d.ts +0 -5
- package/dist/inspector/canvas/linkEligibility.js +0 -29
- package/dist/inspector/canvas/projection.d.ts +15 -5
- package/dist/inspector/canvas/projection.js +69 -13
- package/dist/inspector/canvas/rendererBootstrap.js +45 -4
- package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
- package/dist/inspector/canvas/rendererElementSelector.js +102 -45
- package/dist/inspector/canvas/rendererStylesheet.js +2 -2
- package/dist/inspector/canvas/resizeHandleScale.js +1 -1
- package/dist/inspector/canvas/sessionStore.d.ts +11 -26
- package/dist/inspector/canvas/sessionStore.js +43 -183
- package/dist/inspector/canvas/staleChangeDetector.js +9 -3
- package/dist/inspector/canvas/useCanvasLayoutAnimation.d.ts +5 -0
- package/dist/inspector/canvas/useCanvasLayoutAnimation.js +107 -0
- package/dist/inspector/canvas/viewStore.d.ts +38 -0
- package/dist/inspector/canvas/viewStore.js +104 -0
- package/dist/inspector/canvas/wheelPan.d.ts +8 -0
- package/dist/inspector/canvas/wheelPan.js +8 -0
- package/dist/inspector/changes/changesLog.d.ts +6 -5
- package/dist/inspector/changes/changesLog.js +40 -32
- package/dist/inspector/changes/draftChanges.d.ts +56 -0
- package/dist/inspector/changes/draftChanges.js +300 -0
- package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
- package/dist/inspector/changes/previewDiagnostics.js +11 -25
- package/dist/inspector/clientManifest.d.ts +3 -1
- package/dist/inspector/clientManifest.js +1 -0
- package/dist/inspector/comments/CommentsOverlay.d.ts +2 -0
- package/dist/inspector/comments/CommentsOverlay.js +250 -0
- package/dist/inspector/comments/element.d.ts +6 -0
- package/dist/inspector/comments/element.js +47 -0
- package/dist/inspector/comments/store.d.ts +29 -0
- package/dist/inspector/comments/store.js +121 -0
- package/dist/inspector/comments/verification.d.ts +5 -0
- package/dist/inspector/comments/verification.js +52 -0
- package/dist/inspector/drafts/model.d.ts +35 -0
- package/dist/inspector/drafts/model.js +45 -0
- package/dist/inspector/drafts/store.d.ts +28 -0
- package/dist/inspector/drafts/store.js +231 -0
- package/dist/inspector/index.js +26 -35
- package/dist/inspector/inline-text/inlineTextEditor.js +7 -0
- package/dist/inspector/inspection/useBrowserCssInspection.js +15 -1
- package/dist/inspector/iterations/capture.d.ts +2 -0
- package/dist/inspector/iterations/capture.js +173 -0
- package/dist/inspector/iterations/client.d.ts +7 -0
- package/dist/inspector/iterations/client.js +52 -0
- package/dist/inspector/iterations/coordinator.d.ts +15 -0
- package/dist/inspector/iterations/coordinator.js +130 -0
- package/dist/inspector/iterations/prompt.d.ts +3 -0
- package/dist/inspector/iterations/prompt.js +26 -0
- package/dist/inspector/overlay/DomNavigator.d.ts +10 -0
- package/dist/inspector/overlay/DomNavigator.js +46 -0
- package/dist/inspector/overlay/InspectorOverlay.js +6 -2
- package/dist/inspector/overlay/clickPolicy.d.ts +18 -2
- package/dist/inspector/overlay/clickPolicy.js +34 -3
- package/dist/inspector/overlay/elementSelector.d.ts +1 -1
- package/dist/inspector/overlay/elementSelector.js +18 -7
- package/dist/inspector/overlay/interactionStyles.js +3 -1
- package/dist/inspector/overlay/spacingGestures.d.ts +15 -3
- package/dist/inspector/overlay/spacingGestures.js +88 -13
- package/dist/inspector/projection/{workspaceProjection.d.ts → draftProjection.d.ts} +6 -6
- package/dist/inspector/projection/{workspaceProjection.js → draftProjection.js} +4 -4
- package/dist/inspector/projection/renderedInstance.d.ts +2 -1
- package/dist/inspector/projection/renderedInstance.js +9 -5
- package/dist/inspector/projection/structuralProjection.d.ts +2 -1
- package/dist/inspector/projection/structuralProjection.js +19 -15
- package/dist/inspector/projection/textProjection.d.ts +2 -1
- package/dist/inspector/projection/textProjection.js +8 -4
- package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
- package/dist/inspector/prompt/clipboardHandoff.d.ts +10 -18
- package/dist/inspector/prompt/clipboardHandoff.js +92 -143
- package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
- package/dist/inspector/prompt/generatePrompt.js +17 -16
- package/dist/inspector/runtime/runtimeConfig.d.ts +10 -1
- package/dist/inspector/runtime/runtimeConfig.js +24 -0
- package/dist/inspector/selection/hierarchy.d.ts +6 -0
- package/dist/inspector/selection/hierarchy.js +8 -0
- package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
- package/dist/inspector/settings/SettingsDialog.js +22 -9
- package/dist/inspector/shell/AgentStatusButton.d.ts +11 -0
- package/dist/inspector/shell/AgentStatusButton.js +36 -0
- package/dist/inspector/shell/AppShell.js +4 -2
- package/dist/inspector/shell/ChangesLog.js +47 -27
- package/dist/inspector/shell/CopyPromptButton.js +146 -90
- package/dist/inspector/shell/EmptyState.js +9 -8
- package/dist/inspector/shell/InspectorShell.js +14 -25
- package/dist/inspector/shell/PeekOriginalButton.js +24 -23
- package/dist/inspector/shell/originalPreview.js +5 -5
- package/dist/inspector/shell/shortcuts.d.ts +3 -0
- package/dist/inspector/shell/shortcuts.js +11 -0
- package/dist/inspector/sketch/SketchEditor.js +14 -14
- package/dist/inspector/sketch/SketchEntryButton.js +1 -1
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +3 -1
- package/dist/inspector/sketch/SketchFrameOverlay.js +11 -8
- package/dist/inspector/sketch/SketchLayersPanel.js +13 -5
- package/dist/inspector/sketch/SketchOverlay.js +7 -7
- package/dist/inspector/sketch/SketchWorkspace.js +18 -12
- package/dist/inspector/sketch/interaction.d.ts +1 -0
- package/dist/inspector/sketch/interaction.js +8 -4
- package/dist/inspector/sketch/model.d.ts +5 -0
- package/dist/inspector/sketch/model.js +6 -2
- package/dist/inspector/sketch/raster.d.ts +1 -2
- package/dist/inspector/sketch/raster.js +68 -4
- package/dist/inspector/sketch/store.js +48 -23
- package/dist/inspector/styleEditors/BackgroundEditor.d.ts +4 -0
- package/dist/inspector/styleEditors/BackgroundEditor.js +195 -0
- package/dist/inspector/styleEditors/BorderEditor.js +49 -78
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -1
- package/dist/inspector/styleEditors/BoxShadowEditor.d.ts +1 -2
- package/dist/inspector/styleEditors/BoxShadowEditor.js +132 -4
- package/dist/inspector/styleEditors/ColorPicker.d.ts +5 -1
- package/dist/inspector/styleEditors/ColorPicker.js +10 -4
- package/dist/inspector/styleEditors/GapField.js +3 -5
- package/dist/inspector/styleEditors/GridChildSection.d.ts +1 -1
- package/dist/inspector/styleEditors/GridChildSection.js +18 -9
- package/dist/inspector/styleEditors/GridPicker.d.ts +1 -1
- package/dist/inspector/styleEditors/GridPicker.js +8 -2
- package/dist/inspector/styleEditors/GridSection.js +1 -1
- package/dist/inspector/styleEditors/GridValueField.js +19 -14
- package/dist/inspector/styleEditors/ImageBackgroundEditor.d.ts +18 -0
- package/dist/inspector/styleEditors/ImageBackgroundEditor.js +118 -0
- package/dist/inspector/styleEditors/LayoutBlockedIndicator.d.ts +2 -2
- package/dist/inspector/styleEditors/LayoutBlockedIndicator.js +3 -3
- package/dist/inspector/styleEditors/LayoutComboField.js +6 -4
- package/dist/inspector/styleEditors/LayoutDropdown.js +6 -3
- package/dist/inspector/styleEditors/LayoutSection.js +32 -11
- package/dist/inspector/styleEditors/SpacingBox.js +16 -9
- package/dist/inspector/styleEditors/Typography.js +11 -3
- package/dist/inspector/styleEditors/backgroundLayers.d.ts +24 -0
- package/dist/inspector/styleEditors/backgroundLayers.js +51 -0
- package/dist/inspector/styleEditors/gradientValue.d.ts +17 -0
- package/dist/inspector/styleEditors/gradientValue.js +114 -0
- package/dist/inspector/styleEditors/inlineAuthored.d.ts +10 -0
- package/dist/inspector/styleEditors/inlineAuthored.js +114 -0
- package/dist/inspector/styleEditors/layoutValue.d.ts +0 -8
- package/dist/inspector/styleEditors/layoutValue.js +0 -42
- package/dist/inspector/styleEditors/nudgeValue.d.ts +4 -4
- package/dist/inspector/styleEditors/nudgeValue.js +41 -11
- package/dist/inspector/tokens/TokenField.d.ts +7 -0
- package/dist/inspector/tokens/TokenField.js +49 -22
- package/dist/inspector/tokens/TokenLabel.js +5 -14
- package/dist/inspector/tokens/TokensPanel.js +2 -1
- package/dist/inspector/tokens/dragPointerLock.d.ts +8 -0
- package/dist/inspector/tokens/dragPointerLock.js +70 -0
- package/dist/inspector/tokens/editActions.d.ts +1 -1
- package/dist/inspector/tokens/editActions.js +1 -1
- package/dist/inspector/ui/AtRuleContext.js +4 -6
- package/dist/inspector/ui/Button.d.ts +6 -2
- package/dist/inspector/ui/Button.js +4 -3
- package/dist/inspector/ui/ControlSurface.d.ts +2 -2
- package/dist/inspector/ui/ControlSurface.js +4 -3
- package/dist/inspector/ui/Disclosure.d.ts +22 -0
- package/dist/inspector/ui/Disclosure.js +13 -0
- package/dist/inspector/ui/InlineStyleWarning.d.ts +11 -0
- package/dist/inspector/ui/InlineStyleWarning.js +13 -0
- package/dist/inspector/ui/InspectorPopover.d.ts +2 -1
- package/dist/inspector/ui/InspectorPopover.js +4 -5
- package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
- package/dist/inspector/ui/PopoverListbox.js +8 -7
- package/dist/inspector/ui/SegmentedControl.d.ts +2 -1
- package/dist/inspector/ui/SegmentedControl.js +2 -2
- package/dist/inspector/ui/Select.d.ts +6 -1
- package/dist/inspector/ui/Select.js +7 -11
- package/dist/inspector/ui/SideValuesField.js +1 -1
- package/dist/inspector/ui/Tooltip.d.ts +26 -0
- package/dist/inspector/ui/Tooltip.js +32 -0
- package/dist/inspector/ui/classNames.d.ts +2 -0
- package/dist/inspector/ui/classNames.js +4 -0
- package/dist/inspector/ui/customCursors.d.ts +23 -0
- package/dist/inspector/ui/customCursors.js +122 -0
- package/dist/inspector/ui/dragCursor.d.ts +6 -0
- package/dist/inspector/ui/dragCursor.js +13 -0
- package/dist/inspector/ui/portalContainer.d.ts +2 -0
- package/dist/inspector/ui/portalContainer.js +6 -0
- package/dist/inspector/ui/styles.js +34 -24
- package/dist/inspector/workspace/commands.d.ts +9 -0
- package/dist/inspector/workspace/commands.js +82 -0
- package/dist/inspector/workspace/controller.d.ts +4 -0
- package/dist/inspector/workspace/controller.js +66 -0
- package/dist/inspector/workspace/handoff.d.ts +42 -0
- package/dist/inspector/workspace/handoff.js +115 -0
- package/dist/inspector/workspace/timeline.d.ts +30 -0
- package/dist/inspector/workspace/timeline.js +108 -0
- package/dist/internal/agent-protocol.d.ts +19 -2
- package/dist/internal/agent-protocol.js +13 -0
- package/dist/nudge-ui.mjs +560 -299
- package/dist/project/artifacts.d.ts +13 -0
- package/dist/project/artifacts.js +253 -0
- package/dist/transport/artifacts.d.ts +8 -0
- package/dist/transport/artifacts.js +9 -0
- package/dist/transport/editor.d.ts +20 -1
- package/dist/transport/editor.js +44 -11
- package/dist/transport/routes.d.ts +6 -1
- package/dist/transport/routes.js +6 -1
- package/package.json +6 -2
- package/dist/inspector/changes/workspaceChanges.d.ts +0 -43
- package/dist/inspector/changes/workspaceChanges.js +0 -239
- package/dist/inspector/shell/DomNavigation.d.ts +0 -8
- package/dist/inspector/shell/DomNavigation.js +0 -30
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getActiveDraftChanges } from "../changes/draftChanges.js";
|
|
1
2
|
import { useSyncExternalStore } from "react";
|
|
2
3
|
import { captureViewport, } from "./capture.js";
|
|
3
4
|
const EMPTY_SNAPSHOT = {
|
|
@@ -6,6 +7,7 @@ const EMPTY_SNAPSHOT = {
|
|
|
6
7
|
editingId: null,
|
|
7
8
|
error: null,
|
|
8
9
|
initialTool: "pen",
|
|
10
|
+
draftId: null,
|
|
9
11
|
};
|
|
10
12
|
let snapshot = EMPTY_SNAPSHOT;
|
|
11
13
|
let captureController = null;
|
|
@@ -41,21 +43,22 @@ export function useSketchInteractionActive() {
|
|
|
41
43
|
export function beginSketchCapture(initialTool = "pen") {
|
|
42
44
|
captureController?.abort();
|
|
43
45
|
captureController = null;
|
|
44
|
-
publish({ captureState: "sketching", captured: null, editingId: null, error: null, initialTool });
|
|
46
|
+
publish({ captureState: "sketching", captured: null, editingId: null, error: null, initialTool, draftId: getActiveDraftChanges().draftId });
|
|
45
47
|
}
|
|
46
48
|
export function completeSketchCapture(hostElement) {
|
|
47
49
|
captureController?.abort();
|
|
48
50
|
const controller = new AbortController();
|
|
49
51
|
captureController = controller;
|
|
50
52
|
const initialTool = snapshot.initialTool;
|
|
51
|
-
|
|
53
|
+
const draftId = snapshot.draftId;
|
|
54
|
+
publish({ captureState: "capturing", captured: null, editingId: null, error: null, initialTool, draftId });
|
|
52
55
|
const options = { hostElement, signal: controller.signal };
|
|
53
56
|
return captureViewport(options)
|
|
54
57
|
.then((captured) => {
|
|
55
58
|
if (captureController !== controller)
|
|
56
59
|
return captured;
|
|
57
60
|
captureController = null;
|
|
58
|
-
publish({ captureState: "ready", captured, editingId: null, error: null, initialTool });
|
|
61
|
+
publish({ captureState: "ready", captured, editingId: null, error: null, initialTool, draftId });
|
|
59
62
|
return captured;
|
|
60
63
|
})
|
|
61
64
|
.catch((error) => {
|
|
@@ -72,6 +75,7 @@ export function completeSketchCapture(hostElement) {
|
|
|
72
75
|
editingId: null,
|
|
73
76
|
error: error instanceof Error ? error.message : "The viewport could not be captured.",
|
|
74
77
|
initialTool,
|
|
78
|
+
draftId,
|
|
75
79
|
});
|
|
76
80
|
throw error;
|
|
77
81
|
});
|
|
@@ -84,7 +88,7 @@ export function cancelSketchInteraction() {
|
|
|
84
88
|
export function openSketchEditor(sketchId) {
|
|
85
89
|
captureController?.abort();
|
|
86
90
|
captureController = null;
|
|
87
|
-
publish({ captureState: "idle", captured: null, editingId: sketchId, error: null, initialTool: "pen" });
|
|
91
|
+
publish({ captureState: "idle", captured: null, editingId: sketchId, error: null, initialTool: "pen", draftId: null });
|
|
88
92
|
}
|
|
89
93
|
export function closeSketchEditor() {
|
|
90
94
|
if (snapshot.captureState !== "ready" && snapshot.editingId === null)
|
|
@@ -63,6 +63,11 @@ export interface SketchDocument {
|
|
|
63
63
|
readonly strokes: readonly SketchStroke[];
|
|
64
64
|
/** Optional for compatibility with sketches saved before annotations existed. */
|
|
65
65
|
readonly annotations?: readonly SketchAnnotation[];
|
|
66
|
+
/**
|
|
67
|
+
* Size of `annotatedImage`, which can be taller than the capture when a
|
|
68
|
+
* notes panel is appended. `capture.imageWidth`/`imageHeight` size the
|
|
69
|
+
* original screenshot and the stroke coordinate space.
|
|
70
|
+
*/
|
|
66
71
|
readonly imageWidth: number;
|
|
67
72
|
readonly imageHeight: number;
|
|
68
73
|
readonly originalByteSize: number;
|
|
@@ -176,8 +176,12 @@ export function isSketchDocument(value) {
|
|
|
176
176
|
&& document.annotations.length <= SKETCH_LIMITS.annotations
|
|
177
177
|
&& document.annotations.every(isSketchAnnotation)
|
|
178
178
|
&& new Set(document.annotations.map((annotation) => annotation.number)).size === document.annotations.length))
|
|
179
|
-
&&
|
|
180
|
-
&& document.
|
|
179
|
+
&& Number.isSafeInteger(document.imageWidth)
|
|
180
|
+
&& document.imageWidth > 0
|
|
181
|
+
&& document.imageWidth <= SKETCH_LIMITS.imageEdge
|
|
182
|
+
&& Number.isSafeInteger(document.imageHeight)
|
|
183
|
+
&& document.imageHeight > 0
|
|
184
|
+
&& document.imageHeight <= SKETCH_LIMITS.imageEdge
|
|
181
185
|
&& Number.isSafeInteger(document.originalByteSize)
|
|
182
186
|
&& document.originalByteSize > 0
|
|
183
187
|
&& document.originalByteSize <= SKETCH_LIMITS.imageBytes
|
|
@@ -10,8 +10,7 @@ export declare function drawSketchStroke(ctx: CanvasRenderingContext2D, stroke:
|
|
|
10
10
|
export declare function drawSketchStrokes(ctx: CanvasRenderingContext2D, strokes: readonly SketchStroke[], scaleX?: number, scaleY?: number): void;
|
|
11
11
|
export declare function drawSketchAnnotation(ctx: CanvasRenderingContext2D, annotation: Pick<SketchAnnotation, "number" | "point">, scaleX?: number, scaleY?: number): void;
|
|
12
12
|
export declare function drawSketchAnnotations(ctx: CanvasRenderingContext2D, annotations: readonly SketchAnnotation[], scaleX?: number, scaleY?: number): void;
|
|
13
|
-
|
|
14
|
-
export declare function renderAnnotatedPng(originalImage: Blob, imageWidth: number, imageHeight: number, strokes: readonly SketchStroke[], annotations?: readonly SketchAnnotation[]): Promise<{
|
|
13
|
+
export declare function renderAnnotatedPng(originalImage: Blob, imageWidth: number, imageHeight: number, strokes: readonly SketchStroke[], annotations?: readonly SketchAnnotation[], description?: string): Promise<{
|
|
15
14
|
readonly blob: Blob;
|
|
16
15
|
readonly width: number;
|
|
17
16
|
readonly height: number;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { SKETCH_ANNOTATION_RADIUS, SKETCH_LIMITS, 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
|
+
const SKETCH_NOTE_PADDING = 24;
|
|
5
|
+
const SKETCH_NOTE_TITLE_HEIGHT = 34;
|
|
6
|
+
const SKETCH_NOTE_LINE_HEIGHT = 32;
|
|
7
|
+
const SKETCH_NOTE_FONT = "24px system-ui, sans-serif";
|
|
8
|
+
const SKETCH_NOTE_TITLE_FONT = "600 24px system-ui, sans-serif";
|
|
4
9
|
export function readPngDimensions(bytes) {
|
|
5
10
|
if (bytes.length < 24)
|
|
6
11
|
return null;
|
|
@@ -137,8 +142,59 @@ function canvasPng(canvas) {
|
|
|
137
142
|
}, "image/png");
|
|
138
143
|
});
|
|
139
144
|
}
|
|
140
|
-
|
|
141
|
-
|
|
145
|
+
function wrapText(ctx, text, maxWidth) {
|
|
146
|
+
const lines = [];
|
|
147
|
+
let line = "";
|
|
148
|
+
for (const word of text.split(/\s+/).filter(Boolean)) {
|
|
149
|
+
const next = line ? `${line} ${word}` : word;
|
|
150
|
+
if (line && ctx.measureText(next).width > maxWidth) {
|
|
151
|
+
lines.push(line);
|
|
152
|
+
line = word;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
line = next;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (line)
|
|
159
|
+
lines.push(line);
|
|
160
|
+
return lines;
|
|
161
|
+
}
|
|
162
|
+
/** Wraps the description and numbered annotation notes to the panel width. */
|
|
163
|
+
function wrapSketchNotes(ctx, width, description, annotations) {
|
|
164
|
+
const notes = [
|
|
165
|
+
description.trim(),
|
|
166
|
+
...annotations.map((annotation) => {
|
|
167
|
+
const text = annotation.description.trim();
|
|
168
|
+
return text ? `${annotation.number}. ${text}` : "";
|
|
169
|
+
}),
|
|
170
|
+
].filter(Boolean);
|
|
171
|
+
ctx.font = SKETCH_NOTE_FONT;
|
|
172
|
+
const textWidth = Math.max(1, width - SKETCH_NOTE_PADDING * 2);
|
|
173
|
+
return notes.flatMap((note) => wrapText(ctx, note, textWidth));
|
|
174
|
+
}
|
|
175
|
+
function sketchNotePanelHeight(lineCount) {
|
|
176
|
+
if (lineCount === 0)
|
|
177
|
+
return 0;
|
|
178
|
+
return SKETCH_NOTE_PADDING * 2 + SKETCH_NOTE_TITLE_HEIGHT + lineCount * SKETCH_NOTE_LINE_HEIGHT;
|
|
179
|
+
}
|
|
180
|
+
function drawSketchNotes(ctx, width, top, lines) {
|
|
181
|
+
ctx.save();
|
|
182
|
+
ctx.fillStyle = SKETCH_STROKE_COLOR;
|
|
183
|
+
ctx.fillRect(0, top, width, sketchNotePanelHeight(lines.length));
|
|
184
|
+
ctx.fillStyle = "#ffffff";
|
|
185
|
+
ctx.textAlign = "left";
|
|
186
|
+
ctx.textBaseline = "top";
|
|
187
|
+
ctx.font = SKETCH_NOTE_TITLE_FONT;
|
|
188
|
+
ctx.fillText("Sketch notes", SKETCH_NOTE_PADDING, top + SKETCH_NOTE_PADDING);
|
|
189
|
+
ctx.font = SKETCH_NOTE_FONT;
|
|
190
|
+
let y = top + SKETCH_NOTE_PADDING + SKETCH_NOTE_TITLE_HEIGHT;
|
|
191
|
+
for (const line of lines) {
|
|
192
|
+
ctx.fillText(line, SKETCH_NOTE_PADDING, y);
|
|
193
|
+
y += SKETCH_NOTE_LINE_HEIGHT;
|
|
194
|
+
}
|
|
195
|
+
ctx.restore();
|
|
196
|
+
}
|
|
197
|
+
export async function renderAnnotatedPng(originalImage, imageWidth, imageHeight, strokes, annotations = [], description = "") {
|
|
142
198
|
await validatePngBlob(originalImage, { width: imageWidth, height: imageHeight });
|
|
143
199
|
if (typeof document === "undefined")
|
|
144
200
|
throw new Error("Sketch rendering requires a browser document.");
|
|
@@ -146,18 +202,26 @@ export async function renderAnnotatedPng(originalImage, imageWidth, imageHeight,
|
|
|
146
202
|
try {
|
|
147
203
|
const canvas = document.createElement("canvas");
|
|
148
204
|
canvas.width = imageWidth;
|
|
149
|
-
canvas.height = imageHeight;
|
|
150
205
|
const context = canvas.getContext("2d");
|
|
151
206
|
if (!context)
|
|
152
207
|
throw new Error("The browser could not create a sketch canvas.");
|
|
208
|
+
const noteLines = wrapSketchNotes(context, imageWidth, description, annotations);
|
|
209
|
+
const outputHeight = imageHeight + sketchNotePanelHeight(noteLines.length);
|
|
210
|
+
if (outputHeight > SKETCH_LIMITS.imageEdge) {
|
|
211
|
+
throw new Error("Sketch notes are too long to fit below the captured image.");
|
|
212
|
+
}
|
|
213
|
+
// Resizing clears the canvas and resets context state; the context stays valid.
|
|
214
|
+
canvas.height = outputHeight;
|
|
153
215
|
context.drawImage(image.source, 0, 0, imageWidth, imageHeight);
|
|
154
216
|
drawSketchStrokes(context, strokes);
|
|
155
217
|
drawSketchAnnotations(context, annotations);
|
|
218
|
+
if (noteLines.length > 0)
|
|
219
|
+
drawSketchNotes(context, imageWidth, imageHeight, noteLines);
|
|
156
220
|
const result = await canvasPng(canvas);
|
|
157
221
|
if (result.size > SKETCH_LIMITS.imageBytes) {
|
|
158
222
|
throw new Error("This sketch is too detailed to save within the 2 MiB image limit.");
|
|
159
223
|
}
|
|
160
|
-
return { blob: result, width: imageWidth, height:
|
|
224
|
+
return { blob: result, width: imageWidth, height: outputHeight };
|
|
161
225
|
}
|
|
162
226
|
finally {
|
|
163
227
|
image.close();
|
|
@@ -291,34 +291,54 @@ export function markSketchesHandingOff(entries, batchRevision, localBatchId, age
|
|
|
291
291
|
}));
|
|
292
292
|
}
|
|
293
293
|
export function settleSketchDispatch(batchRevision, state, agentRequestId, localBatchId) {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
&& (localBatchId === undefined || handoff.localBatchId === localBatchId)
|
|
299
|
-
&& (handoff.state === "dispatching" || handoff.state === "handing-off"));
|
|
300
|
-
if (changes.length === 0)
|
|
294
|
+
const projectId = activeProjectId;
|
|
295
|
+
const expectedGeneration = generation;
|
|
296
|
+
const hydration = initialization;
|
|
297
|
+
if (batchRevision === undefined || !projectId)
|
|
301
298
|
return Promise.resolve(false);
|
|
302
|
-
return
|
|
303
|
-
if (
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
299
|
+
return enqueue(async () => {
|
|
300
|
+
if (hydration)
|
|
301
|
+
await hydration;
|
|
302
|
+
assertWritable(projectId, expectedGeneration);
|
|
303
|
+
// Resolve the current revisions inside the write queue so a pending save
|
|
304
|
+
// or requeue cannot be removed by an older completion event.
|
|
305
|
+
const changes = snapshot.items.flatMap(({ document, handoff }) => {
|
|
306
|
+
if (!handoff || handoff.sketchRevision !== document.revision
|
|
307
|
+
|| handoff.transport !== "direct" || handoff.batchRevision !== batchRevision)
|
|
308
|
+
return [];
|
|
309
|
+
// The client batch ID stays stable when the server assigns a request ID.
|
|
310
|
+
const matchesRequest = localBatchId !== undefined
|
|
311
|
+
? handoff.localBatchId === localBatchId
|
|
312
|
+
: agentRequestId === undefined || handoff.agentRequestId === agentRequestId;
|
|
313
|
+
if (!matchesRequest)
|
|
314
|
+
return [];
|
|
315
|
+
return handoff.state === "dispatching" || handoff.state === "handing-off" || handoff.state === "unknown"
|
|
316
|
+
|| (state === "completed" && handoff.state === "needs-review") ? [handoff] : [];
|
|
317
|
+
});
|
|
318
|
+
if (changes.length === 0)
|
|
319
|
+
return false;
|
|
310
320
|
if (state === "completed") {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
321
|
+
for (const handoff of changes) {
|
|
322
|
+
await deleteSketchPersistence(projectId, handoff.sketchId);
|
|
323
|
+
assertWritable(projectId, expectedGeneration);
|
|
324
|
+
publish(projectId, snapshot.documents.filter((document) => document.id !== handoff.sketchId), snapshot.handoffs.filter((candidate) => candidate.sketchId !== handoff.sketchId), null);
|
|
325
|
+
}
|
|
326
|
+
return true;
|
|
316
327
|
}
|
|
317
|
-
|
|
328
|
+
const nextHandoffs = changes.map((handoff) => updateHandoff(handoff, state === "working" ? {
|
|
329
|
+
state: "handing-off",
|
|
330
|
+
transport: "direct",
|
|
331
|
+
...(agentRequestId ? { agentRequestId } : {}),
|
|
332
|
+
} : {
|
|
318
333
|
state: "pending",
|
|
319
334
|
transport: "direct",
|
|
320
335
|
clearDelivery: true,
|
|
321
|
-
});
|
|
336
|
+
}));
|
|
337
|
+
await writeSketchHandoffs(projectId, nextHandoffs);
|
|
338
|
+
assertWritable(projectId, expectedGeneration);
|
|
339
|
+
const changedIds = new Set(nextHandoffs.map((handoff) => handoff.id));
|
|
340
|
+
publish(projectId, snapshot.documents, [...snapshot.handoffs.filter((handoff) => !changedIds.has(handoff.id)), ...nextHandoffs], null);
|
|
341
|
+
return true;
|
|
322
342
|
});
|
|
323
343
|
}
|
|
324
344
|
export function markSketchesHandedOff(entries, localBatchId) {
|
|
@@ -386,9 +406,14 @@ function updateHandoffs(handoffs, update) {
|
|
|
386
406
|
const expectedGeneration = generation;
|
|
387
407
|
if (!projectId)
|
|
388
408
|
return Promise.resolve(false);
|
|
389
|
-
const nextHandoffs = handoffs.map(update);
|
|
390
409
|
return enqueue(async () => {
|
|
391
410
|
assertWritable(projectId, expectedGeneration);
|
|
411
|
+
// Completion may delete a sketch before its dispatch response is handled.
|
|
412
|
+
const nextHandoffs = handoffs
|
|
413
|
+
.filter((handoff) => currentDocument(handoff.sketchId)?.revision === handoff.sketchRevision)
|
|
414
|
+
.map(update);
|
|
415
|
+
if (nextHandoffs.length === 0)
|
|
416
|
+
return false;
|
|
392
417
|
await writeSketchHandoffs(projectId, nextHandoffs);
|
|
393
418
|
assertWritable(projectId, expectedGeneration);
|
|
394
419
|
const changedIds = new Set(nextHandoffs.map((handoff) => handoff.id));
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState } from "react";
|
|
3
|
+
import { IconArrowsDiff, IconDroplet, IconMinus, IconPlus } from "@tabler/icons-react";
|
|
4
|
+
import { replaceColorPreservingOpacity, withColorOpacity } from "../../css/value-semantics/colorSemantics.js";
|
|
5
|
+
import { interpretTokenValue } from "../../css/value-semantics/tokenInterpretation.js";
|
|
6
|
+
import { getNudgeUiTokenEntries } from "../runtime/runtimeConfig.js";
|
|
7
|
+
import { colorValueToHex, TokenValueField } from "../tokens/TokenField.js";
|
|
8
|
+
import { setStyles } from "../tokens/editActions.js";
|
|
9
|
+
import { createChangeHistoryGroup, getActiveDraftChanges } from "../changes/draftChanges.js";
|
|
10
|
+
import { getStateStyleValue } from "../shell/stateValue.js";
|
|
11
|
+
import { ControlSurface } from "../ui/ControlSurface.js";
|
|
12
|
+
import { IconButton } from "../ui/IconButton.js";
|
|
13
|
+
import { InlineStyleWarning } from "../ui/InlineStyleWarning.js";
|
|
14
|
+
import { SegmentedControl } from "../ui/SegmentedControl.js";
|
|
15
|
+
import { Select } from "../ui/Select.js";
|
|
16
|
+
import { ImageBackgroundEditor } from "./ImageBackgroundEditor.js";
|
|
17
|
+
import { backgroundDeclarations, backgroundMode, BACKGROUND_BLEND_MODES, newBackgroundLayer, readBackgroundLayers } from "./backgroundLayers.js";
|
|
18
|
+
import { inlineBlockedBy } from "./inlineAuthored.js";
|
|
19
|
+
import { defaultGradient, parseGradient, serializeGradient } from "./gradientValue.js";
|
|
20
|
+
const MODES = [{ value: "solid", label: "Solid" }, { value: "gradient", label: "Gradient" }, { value: "image", label: "Image" }];
|
|
21
|
+
const TYPES = [{ value: "linear", label: "Linear" }, { value: "radial", label: "Radial" }, { value: "conic", label: "Conic" }];
|
|
22
|
+
/** Background layers are displayed in CSS paint order, with the top layer first. */
|
|
23
|
+
export function BackgroundEditor(props) {
|
|
24
|
+
const { element, selection, onAfterEdit } = props;
|
|
25
|
+
const el = element.domElement;
|
|
26
|
+
const elements = selection?.domElements ?? [el];
|
|
27
|
+
const source = readBackgroundLayers((property, fallback) => getStateStyleValue(el, property, fallback));
|
|
28
|
+
const base = source.at(-1);
|
|
29
|
+
const authoredColor = props.tokenRow?.property === "background-color"
|
|
30
|
+
? props.tokenRow.authored ?? props.tokenRow.declaredValue : undefined;
|
|
31
|
+
if (base?.baseColor && authoredColor) {
|
|
32
|
+
const table = Object.fromEntries((props.entries ?? getNudgeUiTokenEntries()).map((entry) => [entry.name, entry]));
|
|
33
|
+
const resolved = interpretTokenValue(authoredColor, { table }).resolvedValue;
|
|
34
|
+
if (colorValueToHex(resolved) && colorValueToHex(resolved) === colorValueToHex(base.value))
|
|
35
|
+
base.value = authoredColor;
|
|
36
|
+
}
|
|
37
|
+
const [draft, setDraft] = useState(null);
|
|
38
|
+
const activeDraft = draft?.element === el && draft.revision === getActiveDraftChanges().revision
|
|
39
|
+
&& draft.elements.length === elements.length && draft.elements.every((element, index) => element === elements[index]) ? draft : null;
|
|
40
|
+
const layers = activeDraft?.layers ?? source;
|
|
41
|
+
const properties = ["background-color", "background-image", "background-blend-mode", "background-size", "background-repeat", "background-position"];
|
|
42
|
+
const mixed = !activeDraft && elements.some((element) => properties.some((property) => selection?.getProperty(property)?.value.kind === "mixed"
|
|
43
|
+
|| getStateStyleValue(element, property) !== getStateStyleValue(el, property)));
|
|
44
|
+
const blockedBy = inlineBlockedBy(elements, ...properties);
|
|
45
|
+
function commit(next) {
|
|
46
|
+
const previous = backgroundDeclarations(layers);
|
|
47
|
+
const declarations = backgroundDeclarations(next).filter((declaration) => mixed || previous.find((current) => current.property === declaration.property)?.value !== declaration.value);
|
|
48
|
+
if (inlineBlockedBy(elements, ...declarations.map((declaration) => declaration.property)))
|
|
49
|
+
return false;
|
|
50
|
+
if (declarations.length && !setStyles(selection?.target ?? el, declarations).length)
|
|
51
|
+
return false;
|
|
52
|
+
setDraft({ element: el, elements: [...elements], revision: getActiveDraftChanges().revision, layers: next });
|
|
53
|
+
onAfterEdit?.();
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
return _jsxs("div", { className: "editor background-editor", "data-test": "background-editor", children: [_jsxs("div", { className: "editor__title-row", children: [_jsx("div", { className: "editor__title", children: "Background" }), _jsx(InlineStyleWarning, { blockedBy: blockedBy }), _jsx(IconButton, { variant: "quiet", label: "Add background layer", disabled: blockedBy !== null, onClick: () => commit([{ ...newBackgroundLayer(), baseColor: mixed || layers.length === 0 }, ...(mixed ? [] : layers)]), children: _jsx(IconPlus, { "aria-hidden": "true" }) })] }), mixed ? _jsx("div", { className: "background-editor__hint", children: "Mixed backgrounds. Add a layer to replace them." }) : layers.map((layer, index) => _jsx("div", { className: "background-editor__layer", role: "group", "aria-label": `Background layer ${index + 1}`, children: _jsx(BackgroundLayerEditor, { ...props, layer: layer, baseColor: layer.baseColor && layer.mode === "solid", bottomLayer: index === layers.length - 1, onChange: (next) => commit(layers.map((current, i) => i === index ? next : current)), onRemove: () => commit(layers.filter((_, i) => i !== index)) }) }, index))] });
|
|
57
|
+
}
|
|
58
|
+
function previewColor(color) {
|
|
59
|
+
const result = color.tokenName && color.opacity ? withColorOpacity(color.resolvedValue, color.opacity.value) : null;
|
|
60
|
+
return result?.ok ? result.value : color.resolvedValue;
|
|
61
|
+
}
|
|
62
|
+
function BackgroundLayerEditor(props) {
|
|
63
|
+
const { element, selection, entries, layer, baseColor, bottomLayer, onChange, onRemove } = props;
|
|
64
|
+
const allEntries = entries ?? getNudgeUiTokenEntries();
|
|
65
|
+
const tokenTable = Object.fromEntries(allEntries.map((entry) => [entry.name, entry]));
|
|
66
|
+
const el = element.domElement;
|
|
67
|
+
const elements = selection?.domElements ?? [el];
|
|
68
|
+
const { value, mode } = layer;
|
|
69
|
+
const gradient = layer.gradient ?? parseGradient(value);
|
|
70
|
+
const blockedBy = inlineBlockedBy(elements, baseColor ? "background-color" : "background-image");
|
|
71
|
+
const dragCommit = useRef(null);
|
|
72
|
+
function commit(nextValue, nextMode = backgroundMode(nextValue), nextGradient, declarations = []) {
|
|
73
|
+
if (blockedBy)
|
|
74
|
+
return false;
|
|
75
|
+
const next = {
|
|
76
|
+
...layer, value: nextValue, mode: nextMode, gradient: nextGradient,
|
|
77
|
+
baseColor: layer.baseColor || (nextMode === "solid" && mode !== "solid" && bottomLayer),
|
|
78
|
+
};
|
|
79
|
+
for (const declaration of declarations) {
|
|
80
|
+
if (declaration.property === "background-size")
|
|
81
|
+
next.size = declaration.value;
|
|
82
|
+
if (declaration.property === "background-repeat")
|
|
83
|
+
next.repeat = declaration.value;
|
|
84
|
+
if (declaration.property === "background-position")
|
|
85
|
+
next.position = declaration.value;
|
|
86
|
+
}
|
|
87
|
+
return onChange(next);
|
|
88
|
+
}
|
|
89
|
+
function commitGradient(next) { commit(serializeGradient(next), "gradient", next); }
|
|
90
|
+
function changeMode(next) {
|
|
91
|
+
if (next === mode)
|
|
92
|
+
return;
|
|
93
|
+
if (next === "solid")
|
|
94
|
+
commit("#ffffff", next);
|
|
95
|
+
else if (next === "gradient")
|
|
96
|
+
commitGradient(defaultGradient());
|
|
97
|
+
else
|
|
98
|
+
commit("none", next);
|
|
99
|
+
}
|
|
100
|
+
function updateStop(index, next) {
|
|
101
|
+
if (gradient)
|
|
102
|
+
commitGradient({ ...gradient, stops: gradient.stops.map((stop, i) => i === index ? { ...stop, ...next } : stop) });
|
|
103
|
+
}
|
|
104
|
+
function removeStop(index) {
|
|
105
|
+
if (!gradient || gradient.stops.length <= 2)
|
|
106
|
+
return;
|
|
107
|
+
commitGradient({ ...gradient, stops: gradient.stops.filter((_, i) => i !== index) });
|
|
108
|
+
}
|
|
109
|
+
function addStop(position = 50) {
|
|
110
|
+
if (!gradient)
|
|
111
|
+
return;
|
|
112
|
+
const color = gradient.stops[0].color;
|
|
113
|
+
commitGradient({ ...gradient, stops: [...gradient.stops, { color, position }] });
|
|
114
|
+
}
|
|
115
|
+
function commitColor(index, stop, color) {
|
|
116
|
+
const result = replaceColorPreservingOpacity(stop.color, color, { tokenTable });
|
|
117
|
+
if (result.ok)
|
|
118
|
+
updateStop(index, { color: result.value });
|
|
119
|
+
}
|
|
120
|
+
function percentAt(track, clientX) {
|
|
121
|
+
const rect = track.getBoundingClientRect();
|
|
122
|
+
return rect.width > 0 ? Math.round(Math.max(0, Math.min(100, (clientX - rect.left) / rect.width * 100))) : 50;
|
|
123
|
+
}
|
|
124
|
+
function rawField(label, current, onCommit, mixedValue = false) {
|
|
125
|
+
return _jsx(ControlSurface, { children: _jsx(TokenValueField, { property: "background-image", domElement: el, entries: [], committedValue: current, label: label, mixed: mixedValue, blockedBy: blockedBy, onCommitRaw: onCommit, onSelectToken: () => false, onUnlink: onCommit }) });
|
|
126
|
+
}
|
|
127
|
+
const toolbar = _jsxs("div", { className: "background-editor__type-row", children: [_jsx(SegmentedControl, { "aria-label": "Background type", value: mode, options: MODES, onChange: changeMode, disabled: blockedBy !== null }), !baseColor ? _jsx(Select, { iconOnly: true, "aria-label": "Background blending mode", title: `Blend mode: ${layer.blend}`, value: layer.blend, options: BACKGROUND_BLEND_MODES, popupPosition: { side: "top", align: "end" }, disabled: inlineBlockedBy(elements, "background-blend-mode") !== null, onValueChange: (blend) => onChange({ ...layer, blend }), children: _jsx(IconDroplet, { "aria-hidden": "true" }) }) : _jsx("span", { className: "background-editor__blend-space", "aria-hidden": "true" }), _jsx(IconButton, { variant: "quiet", label: "Remove background layer", disabled: blockedBy !== null, onClick: onRemove, children: _jsx(IconMinus, { "aria-hidden": "true" }) })] });
|
|
128
|
+
const color = interpretTokenValue(value, { table: tokenTable });
|
|
129
|
+
if (mode === "solid")
|
|
130
|
+
return _jsxs("div", { className: "background-editor__solid", "data-property": baseColor ? "background-color" : "background-image", children: [toolbar, _jsx(ControlSurface, { children: _jsx(TokenValueField, { property: baseColor ? "background-color" : "background-image", semanticSlot: "color", domElement: el, isColor: true, entries: allEntries, committedValue: color.tokenName ? value : colorValueToHex(value) ?? value, activeTokenName: color.tokenName, resolvedValue: previewColor(color), opacity: color.opacity, label: "Background Color", blockedBy: blockedBy, onCommitRaw: (raw) => {
|
|
131
|
+
const result = replaceColorPreservingOpacity(value, raw, { tokenTable });
|
|
132
|
+
if (result.ok)
|
|
133
|
+
commit(result.value, "solid");
|
|
134
|
+
}, onSelectToken: (token) => {
|
|
135
|
+
const result = replaceColorPreservingOpacity(value, `var(${token.name})`, { tokenTable });
|
|
136
|
+
return result.ok && commit(result.value, "solid");
|
|
137
|
+
}, onUnlink: (raw) => commit(raw, "solid"), onCommitOpacity: (opacity) => {
|
|
138
|
+
const result = withColorOpacity(value, opacity);
|
|
139
|
+
return result.ok && commit(result.value, "solid");
|
|
140
|
+
} }) })] });
|
|
141
|
+
return _jsxs("div", { className: "background-editor__content", children: [toolbar, mode === "image" ? _jsx(ImageBackgroundEditor, { element: el, elements: elements, value: value, mixed: false, sizingValues: { size: layer.size, repeat: layer.repeat }, onCss: (raw) => commit(raw, "image"), onUpload: (image) => commit(image, "image", undefined, [
|
|
142
|
+
{ property: "background-size", value: "cover" },
|
|
143
|
+
{ property: "background-repeat", value: "no-repeat" },
|
|
144
|
+
{ property: "background-position", value: "center" },
|
|
145
|
+
]), onSizing: (declarations) => commit(value, "image", undefined, declarations) }) : gradient && mode === "gradient" ? _jsxs("div", { className: "background-editor__gradient", children: [_jsx("div", { className: "background-editor__track", "data-test": "gradient-track", "aria-label": "Gradient stops", style: { backgroundImage: serializeGradient({ ...gradient, type: "linear", angle: 90, prelude: "", repeating: false,
|
|
146
|
+
stops: gradient.stops.map((stop) => ({ ...stop, color: previewColor(interpretTokenValue(stop.color, { table: tokenTable })) })) }) }, onPointerDown: (event) => { if (event.target === event.currentTarget && event.button === 0)
|
|
147
|
+
addStop(percentAt(event.currentTarget, event.clientX)); }, children: gradient.stops.map((stop, index) => _jsx("button", { type: "button", className: "background-editor__stop", "aria-label": `Gradient stop ${index + 1}`, disabled: blockedBy !== null, style: { left: `clamp(8px, ${stop.position}%, calc(100% - 8px))`, backgroundColor: previewColor(interpretTokenValue(stop.color, { table: tokenTable })) }, onPointerDown: (event) => {
|
|
148
|
+
if (event.button !== 0)
|
|
149
|
+
return;
|
|
150
|
+
event.stopPropagation();
|
|
151
|
+
dragCommit.current = createChangeHistoryGroup();
|
|
152
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
153
|
+
}, onPointerMove: (event) => {
|
|
154
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
|
155
|
+
const position = percentAt(event.currentTarget.parentElement, event.clientX);
|
|
156
|
+
if (position !== stop.position)
|
|
157
|
+
dragCommit.current?.(() => updateStop(index, { position }));
|
|
158
|
+
}
|
|
159
|
+
}, onPointerUp: (event) => {
|
|
160
|
+
dragCommit.current = null;
|
|
161
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId))
|
|
162
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
163
|
+
}, onPointerCancel: () => { dragCommit.current = null; }, onLostPointerCapture: () => { dragCommit.current = null; }, onDoubleClick: () => removeStop(index), onKeyDown: (event) => {
|
|
164
|
+
if (event.key === "Delete" || event.key === "Backspace") {
|
|
165
|
+
event.preventDefault();
|
|
166
|
+
event.stopPropagation();
|
|
167
|
+
removeStop(index);
|
|
168
|
+
}
|
|
169
|
+
if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
|
170
|
+
event.preventDefault();
|
|
171
|
+
event.stopPropagation();
|
|
172
|
+
updateStop(index, { position: Math.max(0, Math.min(100, stop.position + (event.key === "ArrowLeft" ? -1 : 1) * (event.shiftKey ? 10 : 1))) });
|
|
173
|
+
}
|
|
174
|
+
} }, index)) }), _jsxs("div", { className: "background-editor__toolbar", children: [_jsx(Select, { "aria-label": "Gradient type", value: gradient.type, options: TYPES, disabled: blockedBy !== null, onValueChange: (type) => commitGradient({ ...gradient, type: type, prelude: type === "radial" ? "circle at center" : "" }) }), gradient.type !== "radial" ? rawField("Gradient angle", `${gradient.angle}deg`, (raw) => {
|
|
175
|
+
const match = /^([+-]?(?:\d*\.)?\d+)(?:deg)?$/.exec(raw.trim());
|
|
176
|
+
if (match && Number.isFinite(Number(match[1])))
|
|
177
|
+
commitGradient({ ...gradient, angle: Number(match[1]) });
|
|
178
|
+
}) : null, _jsx(IconButton, { variant: "quiet", label: "Reverse gradient", disabled: blockedBy !== null, onClick: () => commitGradient({ ...gradient, stops: [...gradient.stops].reverse().map((stop) => ({ ...stop, position: 100 - stop.position })) }), children: _jsx(IconArrowsDiff, { "aria-hidden": "true" }) }), _jsx(IconButton, { variant: "quiet", label: "Add gradient stop", disabled: blockedBy !== null, onClick: () => addStop(), children: _jsx(IconPlus, { "aria-hidden": "true" }) })] }), gradient.stops.map((stop, index) => ({ stop, index })).sort((a, b) => a.stop.position - b.stop.position).map(({ stop, index }) => {
|
|
179
|
+
const color = interpretTokenValue(stop.color, { table: tokenTable });
|
|
180
|
+
return _jsxs("div", { className: "background-editor__stop-row", role: "group", "aria-label": `Stop ${index + 1}`, children: [rawField(`Stop ${index + 1} position`, `${Number(stop.position.toFixed(3))}%`, (raw) => {
|
|
181
|
+
const match = /^([+-]?(?:\d*\.)?\d+)%?$/.exec(raw.trim());
|
|
182
|
+
if (match)
|
|
183
|
+
updateStop(index, { position: Math.max(0, Math.min(100, Number(match[1]))) });
|
|
184
|
+
}), _jsx(ControlSurface, { children: _jsx(TokenValueField, { property: "background-image", semanticSlot: "color", domElement: el, isColor: true, entries: allEntries, activeTokenName: color.tokenName, committedValue: color.tokenName ? stop.color : colorValueToHex(stop.color) ?? stop.color, resolvedValue: previewColor(color), label: `Stop ${index + 1} color`, blockedBy: blockedBy, opacity: color.opacity, onCommitRaw: (color) => commitColor(index, stop, color), onSelectToken: (token) => commitColor(index, stop, `var(${token.name})`), onUnlink: (color) => updateStop(index, { color: previewColor(interpretTokenValue(color, { table: tokenTable })) }), onCommitOpacity: (opacity) => {
|
|
185
|
+
const result = withColorOpacity(stop.color, opacity);
|
|
186
|
+
if (!result.ok)
|
|
187
|
+
return false;
|
|
188
|
+
updateStop(index, { color: result.value });
|
|
189
|
+
} }) }), _jsx(IconButton, { variant: "quiet", label: `Remove stop ${index + 1}`, disabled: blockedBy !== null || gradient.stops.length <= 2, onClick: () => removeStop(index), children: _jsx(IconMinus, { "aria-hidden": "true" }) })] }, index);
|
|
190
|
+
})] }) : _jsxs("div", { className: "background-editor__raw", children: [mode === "gradient" ? _jsx("div", { className: "background-editor__hint", children: "Edit this background using CSS." }) : null, rawField("Background image CSS", value, (raw) => {
|
|
191
|
+
if (typeof CSS !== "undefined" && !CSS.supports("background-image", raw))
|
|
192
|
+
return;
|
|
193
|
+
commit(raw, mode);
|
|
194
|
+
}, false), mode === "gradient" ? _jsx(IconButton, { variant: "quiet", label: "Add linear gradient", disabled: blockedBy !== null, onClick: () => commitGradient(defaultGradient()), children: _jsx(IconPlus, { "aria-hidden": "true" }) }) : null] })] });
|
|
195
|
+
}
|