nudge-ui 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/nudge-ui.mjs +3 -1
- package/dist/cli/agentCommand.d.ts +8 -0
- package/dist/cli/agentCommand.js +59 -0
- package/dist/client.mjs +872 -12
- package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
- package/dist/hosts/astro/clientTransport.js +60 -11
- package/dist/hosts/next/sidecar.d.ts +4 -0
- package/dist/hosts/next/sidecar.js +49 -6
- package/dist/hosts/next/wrapper.js +30 -2
- package/dist/hosts/projectBridge.d.ts +20 -0
- package/dist/hosts/projectBridge.js +97 -0
- package/dist/hosts/static/cli.d.ts +1 -1
- package/dist/hosts/static/cli.js +6 -1
- package/dist/hosts/static/manifest.d.ts +1 -1
- package/dist/hosts/static/manifest.js +1 -1
- package/dist/hosts/static/server.js +44 -3
- package/dist/hosts/vite/vite.d.ts +5 -1
- package/dist/hosts/vite/vite.js +78 -14
- package/dist/inspector/agent/McpConnectionDialog.d.ts +2 -2
- package/dist/inspector/agent/McpConnectionDialog.js +11 -35
- package/dist/inspector/agent/client.d.ts +10 -3
- package/dist/inspector/agent/client.js +80 -9
- package/dist/inspector/agent/connectionStatus.js +3 -3
- package/dist/inspector/agent/httpTransport.d.ts +2 -0
- package/dist/inspector/agent/httpTransport.js +37 -17
- package/dist/inspector/agent/protocol.d.ts +8 -3
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +1 -1
- package/dist/inspector/agent/verification.js +69 -12
- package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
- package/dist/inspector/canvas/CanvasCard.js +181 -91
- package/dist/inspector/canvas/CanvasElementOverlay.js +246 -52
- package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
- package/dist/inspector/canvas/CanvasToolbar.js +24 -0
- package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
- package/dist/inspector/canvas/CanvasWorkspace.js +392 -73
- package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
- package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
- package/dist/inspector/canvas/canvasResize.d.ts +30 -0
- package/dist/inspector/canvas/canvasResize.js +52 -0
- package/dist/inspector/canvas/canvasStore.d.ts +30 -6
- package/dist/inspector/canvas/canvasStore.js +128 -61
- package/dist/inspector/canvas/frameProtocol.d.ts +69 -5
- package/dist/inspector/canvas/frameProtocol.js +56 -2
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -0
- package/dist/inspector/canvas/keyboardShortcuts.js +14 -0
- package/dist/inspector/canvas/projection.d.ts +7 -0
- package/dist/inspector/canvas/projection.js +118 -25
- package/dist/inspector/canvas/rendererBootstrap.js +29 -29
- package/dist/inspector/canvas/rendererElementSelector.js +341 -30
- package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
- package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
- package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
- package/dist/inspector/canvas/sessionStore.d.ts +1 -0
- package/dist/inspector/canvas/sessionStore.js +12 -4
- package/dist/inspector/canvas/staleChangeDetector.js +16 -14
- package/dist/inspector/changes/changesLog.js +31 -1
- package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
- package/dist/inspector/changes/managedStyleProjection.js +3 -3
- package/dist/inspector/changes/structuralTypes.d.ts +4 -0
- package/dist/inspector/clientManifest.d.ts +5 -0
- package/dist/inspector/clientManifest.js +11 -0
- package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
- package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
- package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
- package/dist/inspector/index.d.ts +2 -0
- package/dist/inspector/index.js +74 -24
- package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
- package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
- package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
- package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
- package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
- package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
- package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
- package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
- package/dist/inspector/inline-text/textEditChange.js +165 -0
- package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
- package/dist/inspector/inspection/selectionProperty.js +1 -0
- package/dist/inspector/overlay/selectedGeometry.js +21 -2
- package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
- package/dist/inspector/overlay/spacingGestures.js +353 -0
- package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
- package/dist/inspector/overlay/structuralGestures.js +12 -10
- package/dist/inspector/projection/managedStylesheet.js +4 -1
- package/dist/inspector/projection/renderedInstance.d.ts +2 -0
- package/dist/inspector/projection/renderedInstance.js +5 -1
- package/dist/inspector/projection/structuralProjection.d.ts +2 -0
- package/dist/inspector/projection/structuralProjection.js +85 -1
- package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
- package/dist/inspector/projection/textProjection.d.ts +2 -0
- package/dist/inspector/projection/textProjection.js +10 -4
- package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
- package/dist/inspector/prompt/clipboardHandoff.js +3 -3
- package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
- package/dist/inspector/prompt/generatePrompt.js +19 -2
- package/dist/inspector/runtime/editorShell.d.ts +2 -0
- package/dist/inspector/runtime/editorShell.js +4 -0
- package/dist/inspector/runtime/runtimeConfig.d.ts +4 -0
- package/dist/inspector/runtime/runtimeConfig.js +4 -0
- package/dist/inspector/selection/selectionStore.js +2 -0
- package/dist/inspector/settings/SettingsDialog.js +10 -0
- package/dist/inspector/shell/AppShell.js +5 -1
- package/dist/inspector/shell/ChangesLog.js +8 -4
- package/dist/inspector/shell/CopyPromptButton.js +95 -19
- package/dist/inspector/shell/EmptyState.js +1 -1
- package/dist/inspector/shell/InspectorShell.d.ts +0 -1
- package/dist/inspector/shell/InspectorShell.js +37 -38
- package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
- package/dist/inspector/shell/PeekOriginalButton.js +57 -0
- package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
- package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
- package/dist/inspector/shell/originalPreview.d.ts +9 -0
- package/dist/inspector/shell/originalPreview.js +61 -0
- package/dist/inspector/shell/shortcuts.d.ts +3 -0
- package/dist/inspector/shell/shortcuts.js +15 -0
- package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
- package/dist/inspector/sketch/SketchChanges.js +72 -0
- package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
- package/dist/inspector/sketch/SketchEditor.js +402 -0
- package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
- package/dist/inspector/sketch/SketchEntryButton.js +11 -0
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
- package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
- package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
- package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
- package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
- package/dist/inspector/sketch/SketchOverlay.js +420 -0
- package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
- package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
- package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
- package/dist/inspector/sketch/SketchWorkspace.js +152 -0
- package/dist/inspector/sketch/capture.d.ts +55 -0
- package/dist/inspector/sketch/capture.js +350 -0
- package/dist/inspector/sketch/freehand.d.ts +13 -0
- package/dist/inspector/sketch/freehand.js +64 -0
- package/dist/inspector/sketch/geometry.d.ts +17 -0
- package/dist/inspector/sketch/geometry.js +98 -0
- package/dist/inspector/sketch/handoff.d.ts +21 -0
- package/dist/inspector/sketch/handoff.js +72 -0
- package/dist/inspector/sketch/interaction.d.ts +21 -0
- package/dist/inspector/sketch/interaction.js +98 -0
- package/dist/inspector/sketch/model.d.ts +104 -0
- package/dist/inspector/sketch/model.js +282 -0
- package/dist/inspector/sketch/persistence.d.ts +21 -0
- package/dist/inspector/sketch/persistence.js +205 -0
- package/dist/inspector/sketch/raster.d.ts +22 -0
- package/dist/inspector/sketch/raster.js +203 -0
- package/dist/inspector/sketch/sketchNote.d.ts +10 -0
- package/dist/inspector/sketch/sketchNote.js +30 -0
- package/dist/inspector/sketch/store.d.ts +47 -0
- package/dist/inspector/sketch/store.js +409 -0
- package/dist/inspector/styleEditors/BorderEditor.js +15 -19
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
- package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
- package/dist/inspector/styleEditors/ColorPicker.js +11 -36
- package/dist/inspector/styleEditors/GapField.d.ts +3 -2
- package/dist/inspector/styleEditors/GapField.js +2 -2
- package/dist/inspector/styleEditors/GridSection.js +1 -1
- package/dist/inspector/styleEditors/LayoutSection.js +11 -8
- package/dist/inspector/styleEditors/SpacingBox.js +23 -30
- package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
- package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
- package/dist/inspector/styleEditors/layoutValue.js +3 -0
- package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
- package/dist/inspector/styleEditors/nudgeValue.js +91 -5
- package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
- package/dist/inspector/styleEditors/stylePresence.js +22 -0
- package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
- package/dist/inspector/styleEditors/textRelevance.js +35 -0
- package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
- package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
- package/dist/inspector/tokens/TokenField.js +111 -7
- package/dist/inspector/tokens/editActions.d.ts +2 -0
- package/dist/inspector/tokens/editActions.js +2 -2
- package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
- package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
- package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
- package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
- package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
- package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
- package/dist/inspector/tokens/resolution.d.ts +3 -26
- package/dist/inspector/tokens/resolution.js +44 -735
- package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
- package/dist/inspector/tokens/tokenAvailability.js +204 -0
- package/dist/inspector/ui/Button.d.ts +1 -1
- package/dist/inspector/ui/SideValuesField.js +2 -2
- package/dist/inspector/ui/StatusCallout.d.ts +4 -1
- package/dist/inspector/ui/StatusCallout.js +2 -2
- package/dist/inspector/ui/styles.js +13 -11
- package/dist/internal/agent-protocol.d.ts +65 -1
- package/dist/internal/agent-protocol.js +183 -3
- package/dist/nudge-ui.mjs +261 -57
- package/dist/transport/editor.d.ts +28 -0
- package/dist/transport/editor.js +132 -0
- package/dist/transport/index.d.ts +1 -0
- package/dist/transport/index.js +1 -0
- package/dist/transport/routes.d.ts +7 -0
- package/dist/transport/routes.js +7 -0
- package/package.json +3 -1
- package/dist/inspector/shell/panelLayout.d.ts +0 -2
- package/dist/inspector/shell/panelLayout.js +0 -29
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
import { canWriteWorkspace } from "../canvas/workspaceLease.js";
|
|
3
|
+
import { getNudgeUiRuntimeConfig } from "../runtime/runtimeConfig.js";
|
|
4
|
+
import { clearSketchPersistence, deleteSketchPersistence, readSketchPersistence, SketchStorageError, writeSketchHandoffs, writeSketchPersistence, } from "./persistence.js";
|
|
5
|
+
import { SKETCH_LIMITS, SKETCH_SCHEMA_VERSION, createSketchFilename, isSketchDocument, isSketchHandoffRecord, sameSketchContent, toAgentSketchMetadata, } from "./model.js";
|
|
6
|
+
const EMPTY_SNAPSHOT = {
|
|
7
|
+
projectId: null,
|
|
8
|
+
ready: false,
|
|
9
|
+
documents: [],
|
|
10
|
+
handoffs: [],
|
|
11
|
+
items: [],
|
|
12
|
+
error: null,
|
|
13
|
+
};
|
|
14
|
+
let snapshot = EMPTY_SNAPSHOT;
|
|
15
|
+
let initialization = null;
|
|
16
|
+
let activeProjectId = null;
|
|
17
|
+
let generation = 0;
|
|
18
|
+
let writeQueue = Promise.resolve();
|
|
19
|
+
const listeners = new Set();
|
|
20
|
+
function notify() {
|
|
21
|
+
for (const listener of listeners) {
|
|
22
|
+
try {
|
|
23
|
+
listener();
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// A subscriber must not prevent the store from notifying the others.
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function setSnapshot(next) {
|
|
31
|
+
snapshot = next;
|
|
32
|
+
notify();
|
|
33
|
+
}
|
|
34
|
+
function sortDocuments(documents) {
|
|
35
|
+
return [...documents].sort((left, right) => right.updatedAt - left.updatedAt || left.id.localeCompare(right.id));
|
|
36
|
+
}
|
|
37
|
+
function sortHandoffs(handoffs) {
|
|
38
|
+
return [...handoffs].sort((left, right) => right.timestamp - left.timestamp || left.id.localeCompare(right.id));
|
|
39
|
+
}
|
|
40
|
+
function currentHandoff(document, handoffs) {
|
|
41
|
+
return handoffs
|
|
42
|
+
.filter((handoff) => handoff.sketchId === document.id && handoff.sketchRevision === document.revision)
|
|
43
|
+
.sort((left, right) => right.timestamp - left.timestamp || right.id.localeCompare(left.id))[0] ?? null;
|
|
44
|
+
}
|
|
45
|
+
function deriveItems(documents, handoffs) {
|
|
46
|
+
return sortDocuments(documents).map((document) => {
|
|
47
|
+
const handoff = currentHandoff(document, handoffs);
|
|
48
|
+
return {
|
|
49
|
+
document,
|
|
50
|
+
handoff,
|
|
51
|
+
status: handoff?.state ?? "pending",
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function publish(projectId, documents, handoffs, error = snapshot.error) {
|
|
56
|
+
setSnapshot({
|
|
57
|
+
projectId,
|
|
58
|
+
ready: true,
|
|
59
|
+
documents: sortDocuments(documents),
|
|
60
|
+
handoffs: sortHandoffs(handoffs),
|
|
61
|
+
items: deriveItems(documents, handoffs),
|
|
62
|
+
error,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function messageForError(error) {
|
|
66
|
+
if (error instanceof Error && error.message)
|
|
67
|
+
return error.message;
|
|
68
|
+
return "Sketch storage is unavailable. Your current sketch remains open; try again.";
|
|
69
|
+
}
|
|
70
|
+
function randomId(prefix) {
|
|
71
|
+
try {
|
|
72
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
73
|
+
return `${prefix}-${crypto.randomUUID()}`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Fall through to the deterministic-enough browser fallback.
|
|
78
|
+
}
|
|
79
|
+
return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
80
|
+
}
|
|
81
|
+
function enqueue(operation) {
|
|
82
|
+
const next = writeQueue.then(operation, operation);
|
|
83
|
+
writeQueue = next.then(() => undefined, () => undefined);
|
|
84
|
+
return next;
|
|
85
|
+
}
|
|
86
|
+
function assertWritable(projectId, expectedGeneration) {
|
|
87
|
+
if (!canWriteWorkspace()) {
|
|
88
|
+
throw new SketchStorageError("This inspector tab no longer owns the sketch workspace.");
|
|
89
|
+
}
|
|
90
|
+
if (activeProjectId !== projectId || generation !== expectedGeneration) {
|
|
91
|
+
throw new SketchStorageError("This sketch workspace has changed; reload the inspector and try again.");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function validateSaveInput(input) {
|
|
95
|
+
if (input.id !== undefined && (input.id.length === 0 || input.id.length > 256)) {
|
|
96
|
+
throw new SketchStorageError("The sketch identifier is invalid.");
|
|
97
|
+
}
|
|
98
|
+
if (!isSketchDocument({
|
|
99
|
+
schemaVersion: SKETCH_SCHEMA_VERSION,
|
|
100
|
+
projectId: "placeholder",
|
|
101
|
+
id: input.id ?? "placeholder",
|
|
102
|
+
revision: 1,
|
|
103
|
+
createdAt: Date.now(),
|
|
104
|
+
updatedAt: Date.now(),
|
|
105
|
+
capture: input.capture,
|
|
106
|
+
description: input.description,
|
|
107
|
+
strokes: input.strokes,
|
|
108
|
+
annotations: input.annotations ?? [],
|
|
109
|
+
imageWidth: input.imageWidth,
|
|
110
|
+
imageHeight: input.imageHeight,
|
|
111
|
+
originalByteSize: input.originalImage.size,
|
|
112
|
+
annotatedByteSize: input.annotatedImage.size,
|
|
113
|
+
filename: "sketch.png",
|
|
114
|
+
originalImage: input.originalImage,
|
|
115
|
+
annotatedImage: input.annotatedImage,
|
|
116
|
+
})) {
|
|
117
|
+
throw new SketchStorageError("The sketch exceeds one of the supported size or format limits.");
|
|
118
|
+
}
|
|
119
|
+
if (input.originalImage.type !== "image/png" || input.annotatedImage.type !== "image/png") {
|
|
120
|
+
throw new SketchStorageError("Sketch images must be PNG files.");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function projectBytes(documents) {
|
|
124
|
+
return documents.reduce((total, document) => total + document.originalByteSize + document.annotatedByteSize, 0);
|
|
125
|
+
}
|
|
126
|
+
function currentDocument(sketchId) {
|
|
127
|
+
return snapshot.documents.find((document) => document.id === sketchId) ?? null;
|
|
128
|
+
}
|
|
129
|
+
function updateHandoff(handoff, patch) {
|
|
130
|
+
const next = {
|
|
131
|
+
...handoff,
|
|
132
|
+
state: patch.state,
|
|
133
|
+
timestamp: Date.now(),
|
|
134
|
+
...(patch.transport === undefined ? {} : { transport: patch.transport }),
|
|
135
|
+
...(patch.localBatchId === undefined ? {} : { localBatchId: patch.localBatchId }),
|
|
136
|
+
};
|
|
137
|
+
if (patch.clearDelivery) {
|
|
138
|
+
delete next.batchRevision;
|
|
139
|
+
delete next.agentRequestId;
|
|
140
|
+
}
|
|
141
|
+
if (patch.batchRevision !== undefined)
|
|
142
|
+
next.batchRevision = patch.batchRevision;
|
|
143
|
+
if (patch.agentRequestId !== undefined)
|
|
144
|
+
next.agentRequestId = patch.agentRequestId;
|
|
145
|
+
return next;
|
|
146
|
+
}
|
|
147
|
+
function matchingCurrentHandoffs(entries) {
|
|
148
|
+
const expected = new Map(entries.map((entry) => [entry.id, entry.revision]));
|
|
149
|
+
return snapshot.handoffs.filter((handoff) => expected.get(handoff.sketchId) === handoff.sketchRevision
|
|
150
|
+
&& currentDocument(handoff.sketchId)?.revision === handoff.sketchRevision);
|
|
151
|
+
}
|
|
152
|
+
export function getSketchStoreSnapshot() {
|
|
153
|
+
return snapshot;
|
|
154
|
+
}
|
|
155
|
+
export function subscribeSketchStore(listener) {
|
|
156
|
+
listeners.add(listener);
|
|
157
|
+
return () => listeners.delete(listener);
|
|
158
|
+
}
|
|
159
|
+
export function useSketchStore() {
|
|
160
|
+
return useSyncExternalStore(subscribeSketchStore, getSketchStoreSnapshot, getSketchStoreSnapshot);
|
|
161
|
+
}
|
|
162
|
+
export function useSketches() {
|
|
163
|
+
return useSketchStore().items;
|
|
164
|
+
}
|
|
165
|
+
export function getSketches() {
|
|
166
|
+
return snapshot.items;
|
|
167
|
+
}
|
|
168
|
+
export function getPendingSketches() {
|
|
169
|
+
return snapshot.items.filter((item) => item.status === "pending");
|
|
170
|
+
}
|
|
171
|
+
export function getSketch(sketchId) {
|
|
172
|
+
return snapshot.items.find((item) => item.document.id === sketchId) ?? null;
|
|
173
|
+
}
|
|
174
|
+
export function initializeSketchStore(projectId = getNudgeUiRuntimeConfig().projectId) {
|
|
175
|
+
if (activeProjectId === projectId && initialization)
|
|
176
|
+
return initialization;
|
|
177
|
+
generation += 1;
|
|
178
|
+
activeProjectId = projectId;
|
|
179
|
+
setSnapshot({ ...EMPTY_SNAPSHOT, projectId });
|
|
180
|
+
const expectedGeneration = generation;
|
|
181
|
+
initialization = readSketchPersistence(projectId)
|
|
182
|
+
.then((stored) => {
|
|
183
|
+
if (activeProjectId !== projectId || generation !== expectedGeneration)
|
|
184
|
+
return;
|
|
185
|
+
const validDocuments = stored.documents.filter((document) => isSketchDocument(document) && document.projectId === projectId);
|
|
186
|
+
const validDocumentIds = new Set(validDocuments.map((document) => document.id));
|
|
187
|
+
const validHandoffs = stored.handoffs
|
|
188
|
+
.filter((handoff) => isSketchHandoffRecord(handoff)
|
|
189
|
+
&& handoff.projectId === projectId && validDocumentIds.has(handoff.sketchId))
|
|
190
|
+
.map((handoff) => handoff.state === "dispatching" || handoff.state === "handing-off"
|
|
191
|
+
? { ...handoff, state: "unknown", timestamp: Date.now() }
|
|
192
|
+
: handoff);
|
|
193
|
+
const invalidStoredData = validDocuments.length !== stored.documents.length
|
|
194
|
+
|| validHandoffs.length !== stored.handoffs.length;
|
|
195
|
+
publish(projectId, validDocuments, validHandoffs, invalidStoredData ? "Some saved sketches could not be restored." : null);
|
|
196
|
+
})
|
|
197
|
+
.catch((error) => {
|
|
198
|
+
if (activeProjectId !== projectId || generation !== expectedGeneration)
|
|
199
|
+
return;
|
|
200
|
+
publish(projectId, [], [], messageForError(error));
|
|
201
|
+
initialization = null;
|
|
202
|
+
throw error;
|
|
203
|
+
});
|
|
204
|
+
return initialization;
|
|
205
|
+
}
|
|
206
|
+
export function saveSketch(input) {
|
|
207
|
+
validateSaveInput(input);
|
|
208
|
+
const projectId = activeProjectId ?? getNudgeUiRuntimeConfig().projectId;
|
|
209
|
+
const expectedGeneration = generation;
|
|
210
|
+
const existing = input.id ? currentDocument(input.id) : null;
|
|
211
|
+
if (existing && sameSketchContent(existing, input.description, input.strokes, input.annotations ?? [])) {
|
|
212
|
+
return Promise.resolve(existing);
|
|
213
|
+
}
|
|
214
|
+
const hydration = initialization;
|
|
215
|
+
return enqueue(async () => {
|
|
216
|
+
if (hydration)
|
|
217
|
+
await hydration;
|
|
218
|
+
assertWritable(projectId, expectedGeneration);
|
|
219
|
+
const documents = [...snapshot.documents];
|
|
220
|
+
const current = input.id ? documents.find((document) => document.id === input.id) : undefined;
|
|
221
|
+
const now = Date.now();
|
|
222
|
+
const id = current?.id ?? input.id ?? randomId("sketch");
|
|
223
|
+
const revision = (current?.revision ?? 0) + 1;
|
|
224
|
+
const document = {
|
|
225
|
+
schemaVersion: SKETCH_SCHEMA_VERSION,
|
|
226
|
+
projectId,
|
|
227
|
+
id,
|
|
228
|
+
revision,
|
|
229
|
+
createdAt: current?.createdAt ?? now,
|
|
230
|
+
updatedAt: now,
|
|
231
|
+
capture: { ...input.capture },
|
|
232
|
+
description: input.description,
|
|
233
|
+
strokes: input.strokes.map((stroke) => ({ ...stroke, points: stroke.points.map((point) => ({ ...point })) })),
|
|
234
|
+
annotations: (input.annotations ?? []).map((annotation) => ({
|
|
235
|
+
...annotation,
|
|
236
|
+
point: { ...annotation.point },
|
|
237
|
+
})),
|
|
238
|
+
imageWidth: input.imageWidth,
|
|
239
|
+
imageHeight: input.imageHeight,
|
|
240
|
+
originalByteSize: input.originalImage.size,
|
|
241
|
+
annotatedByteSize: input.annotatedImage.size,
|
|
242
|
+
filename: createSketchFilename(id, revision),
|
|
243
|
+
originalImage: input.originalImage,
|
|
244
|
+
annotatedImage: input.annotatedImage,
|
|
245
|
+
};
|
|
246
|
+
const nextDocuments = documents.filter((candidate) => candidate.id !== document.id);
|
|
247
|
+
nextDocuments.push(document);
|
|
248
|
+
if (nextDocuments.length > SKETCH_LIMITS.completedSketches) {
|
|
249
|
+
throw new SketchStorageError(`You can keep up to ${SKETCH_LIMITS.completedSketches} sketches at a time.`);
|
|
250
|
+
}
|
|
251
|
+
if (projectBytes(nextDocuments) > SKETCH_LIMITS.projectBytes) {
|
|
252
|
+
throw new SketchStorageError("The saved sketches use more than the project storage limit.");
|
|
253
|
+
}
|
|
254
|
+
const handoff = {
|
|
255
|
+
id: randomId("handoff"),
|
|
256
|
+
projectId,
|
|
257
|
+
sketchId: document.id,
|
|
258
|
+
sketchRevision: document.revision,
|
|
259
|
+
localBatchId: randomId("batch"),
|
|
260
|
+
transport: "local",
|
|
261
|
+
timestamp: now,
|
|
262
|
+
state: "pending",
|
|
263
|
+
};
|
|
264
|
+
await writeSketchPersistence(projectId, document, handoff);
|
|
265
|
+
assertWritable(projectId, expectedGeneration);
|
|
266
|
+
const nextHandoffs = snapshot.handoffs.filter((candidate) => candidate.sketchId !== document.id
|
|
267
|
+
|| candidate.sketchRevision !== document.revision);
|
|
268
|
+
nextHandoffs.push(handoff);
|
|
269
|
+
publish(projectId, nextDocuments, nextHandoffs, null);
|
|
270
|
+
return document;
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
export function markSketchesDispatching(entries, batchRevision, localBatchId) {
|
|
274
|
+
return updateMatchingHandoffs(entries, (handoff) => updateHandoff(handoff, {
|
|
275
|
+
state: "dispatching",
|
|
276
|
+
transport: "direct",
|
|
277
|
+
batchRevision,
|
|
278
|
+
localBatchId,
|
|
279
|
+
clearDelivery: true,
|
|
280
|
+
}));
|
|
281
|
+
}
|
|
282
|
+
export function markSketchesHandingOff(entries, batchRevision, localBatchId, agentRequestId) {
|
|
283
|
+
const matching = matchingCurrentHandoffs(entries)
|
|
284
|
+
.filter((handoff) => handoff.state === "dispatching" || handoff.state === "handing-off");
|
|
285
|
+
return updateHandoffs(matching, (handoff) => updateHandoff(handoff, {
|
|
286
|
+
state: "handing-off",
|
|
287
|
+
transport: "direct",
|
|
288
|
+
batchRevision,
|
|
289
|
+
localBatchId,
|
|
290
|
+
...(agentRequestId ? { agentRequestId } : {}),
|
|
291
|
+
}));
|
|
292
|
+
}
|
|
293
|
+
export function settleSketchDispatch(batchRevision, state, agentRequestId, localBatchId) {
|
|
294
|
+
if (batchRevision === undefined)
|
|
295
|
+
return Promise.resolve(false);
|
|
296
|
+
const changes = snapshot.handoffs
|
|
297
|
+
.filter((handoff) => handoff.batchRevision === batchRevision
|
|
298
|
+
&& (localBatchId === undefined || handoff.localBatchId === localBatchId)
|
|
299
|
+
&& (handoff.state === "dispatching" || handoff.state === "handing-off"));
|
|
300
|
+
if (changes.length === 0)
|
|
301
|
+
return Promise.resolve(false);
|
|
302
|
+
return updateHandoffs(changes, (handoff) => {
|
|
303
|
+
if (state === "working") {
|
|
304
|
+
return updateHandoff(handoff, {
|
|
305
|
+
state: "handing-off",
|
|
306
|
+
transport: "direct",
|
|
307
|
+
...(agentRequestId ? { agentRequestId } : {}),
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
if (state === "completed") {
|
|
311
|
+
return updateHandoff(handoff, {
|
|
312
|
+
state: "needs-review",
|
|
313
|
+
transport: "direct",
|
|
314
|
+
...(agentRequestId ? { agentRequestId } : {}),
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
return updateHandoff(handoff, {
|
|
318
|
+
state: "pending",
|
|
319
|
+
transport: "direct",
|
|
320
|
+
clearDelivery: true,
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
export function markSketchesHandedOff(entries, localBatchId) {
|
|
325
|
+
return updateMatchingHandoffs(entries, (handoff) => updateHandoff(handoff, {
|
|
326
|
+
state: "needs-review",
|
|
327
|
+
transport: "clipboard",
|
|
328
|
+
...(localBatchId ? { localBatchId } : {}),
|
|
329
|
+
}));
|
|
330
|
+
}
|
|
331
|
+
export function requeueSketch(sketchId) {
|
|
332
|
+
const item = getSketch(sketchId);
|
|
333
|
+
if (!item)
|
|
334
|
+
return Promise.resolve(false);
|
|
335
|
+
return updateMatchingHandoffs([{ id: sketchId, revision: item.document.revision }], (handoff) => updateHandoff(handoff, {
|
|
336
|
+
state: "pending",
|
|
337
|
+
transport: "local",
|
|
338
|
+
clearDelivery: true,
|
|
339
|
+
localBatchId: randomId("batch"),
|
|
340
|
+
}));
|
|
341
|
+
}
|
|
342
|
+
export function removeSketch(sketchId) {
|
|
343
|
+
const item = getSketch(sketchId);
|
|
344
|
+
if (!item || item.status === "dispatching" || item.status === "handing-off")
|
|
345
|
+
return Promise.resolve(false);
|
|
346
|
+
const projectId = activeProjectId;
|
|
347
|
+
const expectedGeneration = generation;
|
|
348
|
+
if (!projectId)
|
|
349
|
+
return Promise.resolve(false);
|
|
350
|
+
return enqueue(async () => {
|
|
351
|
+
assertWritable(projectId, expectedGeneration);
|
|
352
|
+
await deleteSketchPersistence(projectId, sketchId);
|
|
353
|
+
assertWritable(projectId, expectedGeneration);
|
|
354
|
+
publish(projectId, snapshot.documents.filter((document) => document.id !== sketchId), snapshot.handoffs.filter((handoff) => handoff.sketchId !== sketchId), null);
|
|
355
|
+
return true;
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
export function clearSketchesForProject(projectId = activeProjectId ?? getNudgeUiRuntimeConfig().projectId) {
|
|
359
|
+
if (activeProjectId === projectId) {
|
|
360
|
+
generation += 1;
|
|
361
|
+
const expectedGeneration = generation;
|
|
362
|
+
publish(projectId, [], [], null);
|
|
363
|
+
return enqueue(async () => {
|
|
364
|
+
if (!canWriteWorkspace())
|
|
365
|
+
throw new SketchStorageError("This inspector tab no longer owns the sketch workspace.");
|
|
366
|
+
await clearSketchPersistence(projectId);
|
|
367
|
+
if (activeProjectId !== projectId || generation !== expectedGeneration)
|
|
368
|
+
return;
|
|
369
|
+
}).catch((error) => {
|
|
370
|
+
if (activeProjectId === projectId && generation === expectedGeneration) {
|
|
371
|
+
setSnapshot({ ...snapshot, error: messageForError(error) });
|
|
372
|
+
}
|
|
373
|
+
throw error;
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
return enqueue(() => clearSketchPersistence(projectId));
|
|
377
|
+
}
|
|
378
|
+
function updateMatchingHandoffs(entries, update) {
|
|
379
|
+
const matching = matchingCurrentHandoffs(entries);
|
|
380
|
+
if (matching.length === 0)
|
|
381
|
+
return Promise.resolve(false);
|
|
382
|
+
return updateHandoffs(matching, update);
|
|
383
|
+
}
|
|
384
|
+
function updateHandoffs(handoffs, update) {
|
|
385
|
+
const projectId = activeProjectId;
|
|
386
|
+
const expectedGeneration = generation;
|
|
387
|
+
if (!projectId)
|
|
388
|
+
return Promise.resolve(false);
|
|
389
|
+
const nextHandoffs = handoffs.map(update);
|
|
390
|
+
return enqueue(async () => {
|
|
391
|
+
assertWritable(projectId, expectedGeneration);
|
|
392
|
+
await writeSketchHandoffs(projectId, nextHandoffs);
|
|
393
|
+
assertWritable(projectId, expectedGeneration);
|
|
394
|
+
const changedIds = new Set(nextHandoffs.map((handoff) => handoff.id));
|
|
395
|
+
publish(projectId, snapshot.documents, [...snapshot.handoffs.filter((handoff) => !changedIds.has(handoff.id)), ...nextHandoffs], null);
|
|
396
|
+
return true;
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
export function sketchMetadataFor(items) {
|
|
400
|
+
return items.map((item) => toAgentSketchMetadata(item.document));
|
|
401
|
+
}
|
|
402
|
+
export function resetSketchStore() {
|
|
403
|
+
generation += 1;
|
|
404
|
+
activeProjectId = null;
|
|
405
|
+
initialization = null;
|
|
406
|
+
snapshot = EMPTY_SNAPSHOT;
|
|
407
|
+
writeQueue = Promise.resolve();
|
|
408
|
+
notify();
|
|
409
|
+
}
|
|
@@ -13,9 +13,10 @@ import { getStateStyleValue } from "../shell/stateValue.js";
|
|
|
13
13
|
import { PopoverListbox } from "../ui/PopoverListbox.js";
|
|
14
14
|
import { ControlSurface } from "../ui/ControlSurface.js";
|
|
15
15
|
import { getNudgeUiTokenEntries } from "../runtime/runtimeConfig.js";
|
|
16
|
+
import { hasAuthoredProperty, hasAuthoredStyle, isZeroCssValue } from "./stylePresence.js";
|
|
17
|
+
import { useFieldVisibility } from "./useFieldVisibility.js";
|
|
16
18
|
const BORDER_STYLES = ["none", "hidden", "solid", "dashed", "dotted", "double", "groove", "ridge", "inset", "outset"];
|
|
17
19
|
const INVISIBLE_BORDER_STYLES = new Set(["none", "hidden"]);
|
|
18
|
-
const ZERO_WIDTH = /^(?:0|0px|0rem|0em|0%)$/i;
|
|
19
20
|
const BORDER_SIDES = SIDE_NAMES.map((side) => `border-${side}`);
|
|
20
21
|
function borderStyleValue(el, rows, property) {
|
|
21
22
|
const row = findTokenRow(rows, property);
|
|
@@ -105,11 +106,11 @@ function isBorderFaceProperty(property) {
|
|
|
105
106
|
}
|
|
106
107
|
function isZeroWidthValue(value) {
|
|
107
108
|
const trimmed = value.trim().toLowerCase();
|
|
108
|
-
if (
|
|
109
|
+
if (isZeroCssValue(trimmed))
|
|
109
110
|
return true;
|
|
110
111
|
// Structured/preflight authored forms like `0 solid` / `0px solid currentcolor`.
|
|
111
112
|
const first = trimmed.split(/\s+/)[0] ?? "";
|
|
112
|
-
return
|
|
113
|
+
return isZeroCssValue(first);
|
|
113
114
|
}
|
|
114
115
|
/** True when a face paints: drawn style and non-zero width. */
|
|
115
116
|
function sidePaints(el, rows, side) {
|
|
@@ -126,8 +127,7 @@ function sidePaints(el, rows, side) {
|
|
|
126
127
|
*
|
|
127
128
|
* Counts as presence:
|
|
128
129
|
* - any painted face (drawn style + non-zero width)
|
|
129
|
-
* -
|
|
130
|
-
* - authored non-zero width
|
|
130
|
+
* - a direct width or disabling style, including zero
|
|
131
131
|
*/
|
|
132
132
|
function hasBorderPresence(el, rows) {
|
|
133
133
|
if (SIDE_NAMES.some((side) => sidePaints(el, rows, side)))
|
|
@@ -135,6 +135,8 @@ function hasBorderPresence(el, rows) {
|
|
|
135
135
|
for (const row of rows) {
|
|
136
136
|
if (!isBorderFaceProperty(row.property) && !row.structure)
|
|
137
137
|
continue;
|
|
138
|
+
if (!hasAuthoredStyle(row))
|
|
139
|
+
continue;
|
|
138
140
|
const property = row.property.toLowerCase();
|
|
139
141
|
const authored = (row.authored ?? row.declaredValue ?? "").trim().toLowerCase();
|
|
140
142
|
const structuredStyle = row.structure?.style?.trim().toLowerCase() ?? "";
|
|
@@ -142,9 +144,9 @@ function hasBorderPresence(el, rows) {
|
|
|
142
144
|
if (INVISIBLE_BORDER_STYLES.has(structuredStyle) || INVISIBLE_BORDER_STYLES.has(authored)) {
|
|
143
145
|
return true;
|
|
144
146
|
}
|
|
145
|
-
if (structuredWidth
|
|
147
|
+
if (structuredWidth)
|
|
146
148
|
return true;
|
|
147
|
-
if ((property === "border-width" || property.endsWith("-width")) && authored
|
|
149
|
+
if ((property === "border-width" || property.endsWith("-width")) && authored) {
|
|
148
150
|
return true;
|
|
149
151
|
}
|
|
150
152
|
}
|
|
@@ -207,25 +209,19 @@ export function BorderEditor(props) {
|
|
|
207
209
|
const linkedBorderStyle = borderStyleValue(el, tokenRows, "border-style");
|
|
208
210
|
const showWidthAndColor = !(borderLinked && INVISIBLE_BORDER_STYLES.has(linkedBorderStyle));
|
|
209
211
|
const hasBorder = isGroup
|
|
210
|
-
? selection?.domElements.some((candidate) => hasBorderPresence(candidate, [])) ?? false
|
|
212
|
+
? (selection?.domElements.some((candidate) => hasBorderPresence(candidate, [])) ?? false)
|
|
213
|
+
|| borderWidthProperties.some((property) => hasAuthoredProperty(selection, property))
|
|
211
214
|
: hasBorderPresence(el, tokenRows);
|
|
212
|
-
const [
|
|
213
|
-
|
|
214
|
-
setBorderSessionOpen(false);
|
|
215
|
-
}, [el]);
|
|
216
|
-
useEffect(() => {
|
|
217
|
-
if (hasBorder || rawBorderFallback)
|
|
218
|
-
setBorderSessionOpen(true);
|
|
219
|
-
}, [hasBorder, rawBorderFallback]);
|
|
220
|
-
const showBorderControls = hasBorder || borderSessionOpen || rawBorderFallback;
|
|
215
|
+
const visibility = useFieldVisibility(selection?.domElements ?? [el], "border", hasBorder || rawBorderFallback);
|
|
216
|
+
const showBorderControls = visibility.visible;
|
|
221
217
|
function handleAddBorder() {
|
|
222
218
|
setStyle(editTarget, "border", "1px solid");
|
|
223
|
-
|
|
219
|
+
visibility.show();
|
|
224
220
|
onAfterEdit?.();
|
|
225
221
|
}
|
|
226
222
|
function handleRemoveBorder() {
|
|
227
223
|
setStyle(editTarget, "border", "0 solid");
|
|
228
|
-
|
|
224
|
+
visibility.hide();
|
|
229
225
|
onAfterEdit?.();
|
|
230
226
|
}
|
|
231
227
|
function handleExpand() {
|
|
@@ -77,11 +77,11 @@ export function BorderRadiusEditor(props) {
|
|
|
77
77
|
const cornerSides = BORDER_RADIUS_CORNERS.map((corner, index) => ({
|
|
78
78
|
side: SIDE_NAMES[index],
|
|
79
79
|
sideLabel: BORDER_RADIUS_LABELS[index],
|
|
80
|
-
icon:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
icon: null,
|
|
81
|
+
control: (_jsx(TokenField, { property: corner, selection: selection, tokenRow: findTokenRow(tokenRows, corner), domElement: el, editTarget: editTarget, entries: allEntries, editMetadata: metadataFor(findTokenRow(tokenRows, corner)), onAfterEdit: onAfterEdit, chipVariant: "small", leading: (() => {
|
|
82
|
+
const Icon = BORDER_RADIUS_ICONS[index];
|
|
83
|
+
return _jsx(Icon, { className: "side-values__icon side-values__side-icon", size: 16, "aria-hidden": "true" });
|
|
84
|
+
})() })),
|
|
85
85
|
}));
|
|
86
86
|
const linkedTokenRow = () => {
|
|
87
87
|
const first = findTokenRow(tokenRows, BORDER_RADIUS_CORNERS[0]);
|
|
@@ -93,7 +93,7 @@ export function BorderRadiusEditor(props) {
|
|
|
93
93
|
const mixed = !dataLinked;
|
|
94
94
|
const groupedControl = (_jsx(ControlSurface, { children: _jsx(TokenField, { property: "border-radius", selection: selection, tokenRow: mixed ? null : borderRadiusRow, initialValue: mixed ? "Mix" : undefined, displayValue: mixed ? "Mix" : undefined, domElement: el, editTarget: editTarget, entries: allEntries, editMetadata: metadataFor(borderRadiusRow), mixed: mixed, formatRawValue: (value) => mixed && value.trim().toLowerCase() === "mix"
|
|
95
95
|
? ""
|
|
96
|
-
: completeCssValue(value.trim(), valuePolicyFor("border-radius")), onAfterEdit: onAfterEdit, chipVariant: "small" }) }));
|
|
96
|
+
: completeCssValue(value.trim(), valuePolicyFor("border-radius")), onAfterEdit: onAfterEdit, chipVariant: "small", leading: _jsx(IconBorderCorners, { className: "side-values__icon side-values__side-icon", size: 16, "aria-hidden": "true" }) }) }));
|
|
97
97
|
const toggleButton = (_jsx(ToggleButton, { className: embedded ? "border-radius-editor__toggle" : undefined, variant: "quiet", size: "default", "data-test": isLinked ? "border-radius-expand" : "border-radius-collapse", label: isLinked ? "Edit Individual Corners" : "Link All Corners", title: isLinked ? "Edit Individual Corners" : "Link All Corners", pressed: !isLinked, onPressedChange: (pressed) => {
|
|
98
98
|
if (pressed)
|
|
99
99
|
handleExpand();
|
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { IconPlus } from "@tabler/icons-react";
|
|
2
3
|
import { TokenField } from "../tokens/TokenField.js";
|
|
3
4
|
import { FieldRow } from "../ui/FieldRow.js";
|
|
4
5
|
import { ControlSurface } from "../ui/ControlSurface.js";
|
|
5
6
|
import { getNudgeUiTokenEntries } from "../runtime/runtimeConfig.js";
|
|
7
|
+
import { IconButton } from "../ui/IconButton.js";
|
|
8
|
+
import { getStateStyleValue } from "../shell/stateValue.js";
|
|
9
|
+
import { hasAuthoredProperty } from "./stylePresence.js";
|
|
10
|
+
import { useFieldVisibility } from "./useFieldVisibility.js";
|
|
6
11
|
export function BoxShadowEditor(props) {
|
|
7
12
|
const { element, selection, entries, tokenRows = [], onAfterEdit } = props;
|
|
8
13
|
const el = element.domElement;
|
|
9
14
|
const editTarget = selection?.target ?? el;
|
|
10
15
|
const allEntries = entries ?? getNudgeUiTokenEntries();
|
|
11
16
|
const tokenRow = tokenRows.find((row) => row.property === "box-shadow") ?? null;
|
|
12
|
-
|
|
17
|
+
const elements = selection?.domElements ?? [el];
|
|
18
|
+
const hasShadow = hasAuthoredProperty(selection, "box-shadow", tokenRow) || elements.some((element) => {
|
|
19
|
+
const value = getStateStyleValue(element, "box-shadow");
|
|
20
|
+
return value !== "" && value !== "none";
|
|
21
|
+
});
|
|
22
|
+
const visibility = useFieldVisibility(elements, "box-shadow", hasShadow);
|
|
23
|
+
return (_jsxs("div", { className: "editor", "data-test": "box-shadow-editor", children: [_jsxs("div", { className: "editor__title-row", children: [_jsx("div", { className: "editor__title", children: "Box Shadow" }), !visibility.visible ? (_jsx(IconButton, { variant: "quiet", label: "Add Box Shadow", "data-test": "add-shadow", onClick: visibility.show, children: _jsx(IconPlus, { size: 16, "aria-hidden": "true" }) })) : null] }), visibility.visible ? _jsx("div", { className: "property", children: _jsx(FieldRow, { label: "Box Shadow", children: _jsx(ControlSurface, { children: _jsx(TokenField, { property: "box-shadow", selection: selection, tokenRow: tokenRow, domElement: el, editTarget: editTarget, entries: allEntries, onAfterEdit: onAfterEdit }) }) }) }) : null] }));
|
|
13
24
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useMemo, useState } from "react";
|
|
3
2
|
import { IconMinus, IconPlus } from "@tabler/icons-react";
|
|
4
3
|
import { getStateStyleValue } from "../shell/stateValue.js";
|
|
5
4
|
import { TokenField } from "../tokens/TokenField.js";
|
|
@@ -8,6 +7,8 @@ import { IconButton } from "../ui/IconButton.js";
|
|
|
8
7
|
import { ControlSurface } from "../ui/ControlSurface.js";
|
|
9
8
|
import { setStyle } from "../tokens/editActions.js";
|
|
10
9
|
import { getNudgeUiTokenEntries } from "../runtime/runtimeConfig.js";
|
|
10
|
+
import { hasAuthoredProperty, isZeroCssValue } from "./stylePresence.js";
|
|
11
|
+
import { useFieldVisibility } from "./useFieldVisibility.js";
|
|
11
12
|
function colorSectionTitle(property) {
|
|
12
13
|
if (property === "background-color")
|
|
13
14
|
return "Background Color";
|
|
@@ -15,59 +16,33 @@ function colorSectionTitle(property) {
|
|
|
15
16
|
return "Color";
|
|
16
17
|
return formatInspectorLabel(property);
|
|
17
18
|
}
|
|
18
|
-
function isZeroColorComponent(value) {
|
|
19
|
-
return /^0(?:\.0+)?%?$/.test(value.trim());
|
|
20
|
-
}
|
|
21
19
|
export function isEmptyColorValue(value) {
|
|
22
20
|
const normalized = value.trim().toLowerCase();
|
|
23
21
|
if (!normalized || normalized === "none" || normalized === "transparent")
|
|
24
22
|
return true;
|
|
25
|
-
if (/^#(?:
|
|
23
|
+
if (/^#(?:[0-9a-f]{3}0|[0-9a-f]{6}00)$/.test(normalized))
|
|
26
24
|
return true;
|
|
27
25
|
const match = normalized.match(/^rgba?\((.*)\)$/);
|
|
28
26
|
if (!match)
|
|
29
27
|
return false;
|
|
30
28
|
const components = match[1].split(/[\s,/]+/).filter(Boolean);
|
|
31
29
|
return components.length === 4
|
|
32
|
-
&& components
|
|
33
|
-
&& isZeroColorComponent(components[3]);
|
|
30
|
+
&& isZeroCssValue(components[3]);
|
|
34
31
|
}
|
|
35
32
|
export function ColorPicker(props) {
|
|
36
33
|
const { element, selection, property = "color", entries, tokenRow, onAfterEdit } = props;
|
|
37
34
|
const el = element.domElement;
|
|
38
|
-
const selectedElements =
|
|
35
|
+
const selectedElements = selection?.domElements ?? [el];
|
|
39
36
|
const target = selection?.target ?? el;
|
|
40
37
|
const allEntries = entries ?? getNudgeUiTokenEntries();
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
// for text color (for example `transparent` or `var(--missing, transparent)`).
|
|
46
|
-
// A transparent background, however, is the empty state represented by the
|
|
47
|
-
// remove action and should not remain visible as an authored value.
|
|
48
|
-
const emptyValues = selectedElements.map((selected) => getStateStyleValue(selected.domElement, property));
|
|
49
|
-
const isEmpty = selectedElements.length > 1
|
|
50
|
-
? emptyValues.every((value) => isEmptyColorValue(value))
|
|
51
|
-
: property === "background-color"
|
|
52
|
-
? isEmptyColorValue(declaredValue) || isEmptyColorValue(resolvedValue) || isEmptyColorValue(paintedValue)
|
|
53
|
-
: declaredValue.length === 0 && isEmptyColorValue(resolvedValue);
|
|
54
|
-
const [fieldAdded, setFieldAdded] = useState(false);
|
|
55
|
-
const [backgroundRemoved, setBackgroundRemoved] = useState(false);
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
setFieldAdded(false);
|
|
58
|
-
setBackgroundRemoved(false);
|
|
59
|
-
}, [el, property, selectedElements]);
|
|
60
|
-
function handleAfterEdit() {
|
|
61
|
-
setFieldAdded(false);
|
|
62
|
-
setBackgroundRemoved(false);
|
|
63
|
-
onAfterEdit?.();
|
|
64
|
-
}
|
|
65
|
-
const showTokenField = (!isEmpty && !backgroundRemoved) || fieldAdded;
|
|
38
|
+
const hasColor = hasAuthoredProperty(selection, property, tokenRow)
|
|
39
|
+
|| selectedElements.some((selected) => !isEmptyColorValue(getStateStyleValue(selected, property)));
|
|
40
|
+
const visibility = useFieldVisibility(selectedElements, property, hasColor);
|
|
41
|
+
const showTokenField = visibility.visible;
|
|
66
42
|
function handleRemoveColor() {
|
|
67
43
|
setStyle(target, property, "transparent");
|
|
68
|
-
|
|
69
|
-
setBackgroundRemoved(property === "background-color");
|
|
44
|
+
visibility.hide();
|
|
70
45
|
onAfterEdit?.();
|
|
71
46
|
}
|
|
72
|
-
return (_jsxs("div", { className: `editor`, "data-test": "color-picker", "data-property": property, children: [_jsxs("div", { className: "editor__title-row", children: [_jsx("div", { className: "editor__title", children: colorSectionTitle(property) }), showTokenField ? (_jsx(IconButton, { variant: "quiet", label: `Remove ${colorSectionTitle(property)}`, "data-test": "remove-color", className: "color__remove", onClick: handleRemoveColor, children: _jsx(IconMinus, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) })) : (_jsx(IconButton, { variant: "quiet", label: `Add ${colorSectionTitle(property)}`, "data-test": "add-color", className: "color__add", onClick:
|
|
47
|
+
return (_jsxs("div", { className: `editor`, "data-test": "color-picker", "data-property": property, children: [_jsxs("div", { className: "editor__title-row", children: [_jsx("div", { className: "editor__title", children: colorSectionTitle(property) }), showTokenField ? (_jsx(IconButton, { variant: "quiet", label: `Remove ${colorSectionTitle(property)}`, "data-test": "remove-color", className: "color__remove", onClick: handleRemoveColor, children: _jsx(IconMinus, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) })) : (_jsx(IconButton, { variant: "quiet", label: `Add ${colorSectionTitle(property)}`, "data-test": "add-color", className: "color__add", onClick: visibility.show, children: _jsx(IconPlus, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) }))] }), showTokenField && (_jsx("div", { className: "color", children: _jsx(ControlSurface, { children: _jsx(TokenField, { property: property, tokenRow: tokenRow, selection: selection, domElement: el, editTarget: target, entries: allEntries, onAfterEdit: onAfterEdit }) }) }))] }));
|
|
73
48
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ReactElement } from "react";
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
2
|
import type { TokenEntry } from "../../css/model/index.js";
|
|
3
3
|
import type { ResolvedProperty } from "../../css/model/index.js";
|
|
4
4
|
import type { EditTarget } from "../selection/editTarget.js";
|
|
@@ -11,7 +11,8 @@ export interface GapFieldProps {
|
|
|
11
11
|
selection?: StyleSelection | null;
|
|
12
12
|
entries: TokenEntry[];
|
|
13
13
|
tokenRows: ResolvedProperty[];
|
|
14
|
+
leading?: ReactNode;
|
|
14
15
|
onAfterEdit?: () => void;
|
|
15
16
|
}
|
|
16
17
|
/** Renders a gap value with a token chip or the legacy raw-value fallback. */
|
|
17
|
-
export declare function GapField({ property, domElement: el, editTarget, selection, entries, tokenRows, onAfterEdit, }: GapFieldProps): ReactElement;
|
|
18
|
+
export declare function GapField({ property, domElement: el, editTarget, selection, entries, tokenRows, leading, onAfterEdit, }: GapFieldProps): ReactElement;
|