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.
Files changed (201) hide show
  1. package/bin/nudge-ui.mjs +3 -1
  2. package/dist/cli/agentCommand.d.ts +8 -0
  3. package/dist/cli/agentCommand.js +59 -0
  4. package/dist/client.mjs +872 -12
  5. package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
  6. package/dist/hosts/astro/clientTransport.js +60 -11
  7. package/dist/hosts/next/sidecar.d.ts +4 -0
  8. package/dist/hosts/next/sidecar.js +49 -6
  9. package/dist/hosts/next/wrapper.js +30 -2
  10. package/dist/hosts/projectBridge.d.ts +20 -0
  11. package/dist/hosts/projectBridge.js +97 -0
  12. package/dist/hosts/static/cli.d.ts +1 -1
  13. package/dist/hosts/static/cli.js +6 -1
  14. package/dist/hosts/static/manifest.d.ts +1 -1
  15. package/dist/hosts/static/manifest.js +1 -1
  16. package/dist/hosts/static/server.js +44 -3
  17. package/dist/hosts/vite/vite.d.ts +5 -1
  18. package/dist/hosts/vite/vite.js +78 -14
  19. package/dist/inspector/agent/McpConnectionDialog.d.ts +2 -2
  20. package/dist/inspector/agent/McpConnectionDialog.js +11 -35
  21. package/dist/inspector/agent/client.d.ts +10 -3
  22. package/dist/inspector/agent/client.js +80 -9
  23. package/dist/inspector/agent/connectionStatus.js +3 -3
  24. package/dist/inspector/agent/httpTransport.d.ts +2 -0
  25. package/dist/inspector/agent/httpTransport.js +37 -17
  26. package/dist/inspector/agent/protocol.d.ts +8 -3
  27. package/dist/inspector/agent/protocol.js +1 -1
  28. package/dist/inspector/agent/verification.d.ts +1 -1
  29. package/dist/inspector/agent/verification.js +69 -12
  30. package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
  31. package/dist/inspector/canvas/CanvasCard.js +181 -91
  32. package/dist/inspector/canvas/CanvasElementOverlay.js +246 -52
  33. package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
  34. package/dist/inspector/canvas/CanvasToolbar.js +24 -0
  35. package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
  36. package/dist/inspector/canvas/CanvasWorkspace.js +392 -73
  37. package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
  38. package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
  39. package/dist/inspector/canvas/canvasResize.d.ts +30 -0
  40. package/dist/inspector/canvas/canvasResize.js +52 -0
  41. package/dist/inspector/canvas/canvasStore.d.ts +30 -6
  42. package/dist/inspector/canvas/canvasStore.js +128 -61
  43. package/dist/inspector/canvas/frameProtocol.d.ts +69 -5
  44. package/dist/inspector/canvas/frameProtocol.js +56 -2
  45. package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -0
  46. package/dist/inspector/canvas/keyboardShortcuts.js +14 -0
  47. package/dist/inspector/canvas/projection.d.ts +7 -0
  48. package/dist/inspector/canvas/projection.js +118 -25
  49. package/dist/inspector/canvas/rendererBootstrap.js +29 -29
  50. package/dist/inspector/canvas/rendererElementSelector.js +341 -30
  51. package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
  52. package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
  53. package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
  54. package/dist/inspector/canvas/sessionStore.d.ts +1 -0
  55. package/dist/inspector/canvas/sessionStore.js +12 -4
  56. package/dist/inspector/canvas/staleChangeDetector.js +16 -14
  57. package/dist/inspector/changes/changesLog.js +31 -1
  58. package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
  59. package/dist/inspector/changes/managedStyleProjection.js +3 -3
  60. package/dist/inspector/changes/structuralTypes.d.ts +4 -0
  61. package/dist/inspector/clientManifest.d.ts +5 -0
  62. package/dist/inspector/clientManifest.js +11 -0
  63. package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
  64. package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
  65. package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
  66. package/dist/inspector/index.d.ts +2 -0
  67. package/dist/inspector/index.js +74 -24
  68. package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
  69. package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
  70. package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
  71. package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
  72. package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
  73. package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
  74. package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
  75. package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
  76. package/dist/inspector/inline-text/textEditChange.js +165 -0
  77. package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
  78. package/dist/inspector/inspection/selectionProperty.js +1 -0
  79. package/dist/inspector/overlay/selectedGeometry.js +21 -2
  80. package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
  81. package/dist/inspector/overlay/spacingGestures.js +353 -0
  82. package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
  83. package/dist/inspector/overlay/structuralGestures.js +12 -10
  84. package/dist/inspector/projection/managedStylesheet.js +4 -1
  85. package/dist/inspector/projection/renderedInstance.d.ts +2 -0
  86. package/dist/inspector/projection/renderedInstance.js +5 -1
  87. package/dist/inspector/projection/structuralProjection.d.ts +2 -0
  88. package/dist/inspector/projection/structuralProjection.js +85 -1
  89. package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
  90. package/dist/inspector/projection/textProjection.d.ts +2 -0
  91. package/dist/inspector/projection/textProjection.js +10 -4
  92. package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
  93. package/dist/inspector/prompt/clipboardHandoff.js +3 -3
  94. package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
  95. package/dist/inspector/prompt/generatePrompt.js +19 -2
  96. package/dist/inspector/runtime/editorShell.d.ts +2 -0
  97. package/dist/inspector/runtime/editorShell.js +4 -0
  98. package/dist/inspector/runtime/runtimeConfig.d.ts +4 -0
  99. package/dist/inspector/runtime/runtimeConfig.js +4 -0
  100. package/dist/inspector/selection/selectionStore.js +2 -0
  101. package/dist/inspector/settings/SettingsDialog.js +10 -0
  102. package/dist/inspector/shell/AppShell.js +5 -1
  103. package/dist/inspector/shell/ChangesLog.js +8 -4
  104. package/dist/inspector/shell/CopyPromptButton.js +95 -19
  105. package/dist/inspector/shell/EmptyState.js +1 -1
  106. package/dist/inspector/shell/InspectorShell.d.ts +0 -1
  107. package/dist/inspector/shell/InspectorShell.js +37 -38
  108. package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
  109. package/dist/inspector/shell/PeekOriginalButton.js +57 -0
  110. package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
  111. package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
  112. package/dist/inspector/shell/originalPreview.d.ts +9 -0
  113. package/dist/inspector/shell/originalPreview.js +61 -0
  114. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  115. package/dist/inspector/shell/shortcuts.js +15 -0
  116. package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
  117. package/dist/inspector/sketch/SketchChanges.js +72 -0
  118. package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
  119. package/dist/inspector/sketch/SketchEditor.js +402 -0
  120. package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
  121. package/dist/inspector/sketch/SketchEntryButton.js +11 -0
  122. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
  123. package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
  124. package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
  125. package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
  126. package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
  127. package/dist/inspector/sketch/SketchOverlay.js +420 -0
  128. package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
  129. package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
  130. package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
  131. package/dist/inspector/sketch/SketchWorkspace.js +152 -0
  132. package/dist/inspector/sketch/capture.d.ts +55 -0
  133. package/dist/inspector/sketch/capture.js +350 -0
  134. package/dist/inspector/sketch/freehand.d.ts +13 -0
  135. package/dist/inspector/sketch/freehand.js +64 -0
  136. package/dist/inspector/sketch/geometry.d.ts +17 -0
  137. package/dist/inspector/sketch/geometry.js +98 -0
  138. package/dist/inspector/sketch/handoff.d.ts +21 -0
  139. package/dist/inspector/sketch/handoff.js +72 -0
  140. package/dist/inspector/sketch/interaction.d.ts +21 -0
  141. package/dist/inspector/sketch/interaction.js +98 -0
  142. package/dist/inspector/sketch/model.d.ts +104 -0
  143. package/dist/inspector/sketch/model.js +282 -0
  144. package/dist/inspector/sketch/persistence.d.ts +21 -0
  145. package/dist/inspector/sketch/persistence.js +205 -0
  146. package/dist/inspector/sketch/raster.d.ts +22 -0
  147. package/dist/inspector/sketch/raster.js +203 -0
  148. package/dist/inspector/sketch/sketchNote.d.ts +10 -0
  149. package/dist/inspector/sketch/sketchNote.js +30 -0
  150. package/dist/inspector/sketch/store.d.ts +47 -0
  151. package/dist/inspector/sketch/store.js +409 -0
  152. package/dist/inspector/styleEditors/BorderEditor.js +15 -19
  153. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
  154. package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
  155. package/dist/inspector/styleEditors/ColorPicker.js +11 -36
  156. package/dist/inspector/styleEditors/GapField.d.ts +3 -2
  157. package/dist/inspector/styleEditors/GapField.js +2 -2
  158. package/dist/inspector/styleEditors/GridSection.js +1 -1
  159. package/dist/inspector/styleEditors/LayoutSection.js +11 -8
  160. package/dist/inspector/styleEditors/SpacingBox.js +23 -30
  161. package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
  162. package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
  163. package/dist/inspector/styleEditors/layoutValue.js +3 -0
  164. package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
  165. package/dist/inspector/styleEditors/nudgeValue.js +91 -5
  166. package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
  167. package/dist/inspector/styleEditors/stylePresence.js +22 -0
  168. package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
  169. package/dist/inspector/styleEditors/textRelevance.js +35 -0
  170. package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
  171. package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
  172. package/dist/inspector/tokens/TokenField.js +111 -7
  173. package/dist/inspector/tokens/editActions.d.ts +2 -0
  174. package/dist/inspector/tokens/editActions.js +2 -2
  175. package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
  176. package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
  177. package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
  178. package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
  179. package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
  180. package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
  181. package/dist/inspector/tokens/resolution.d.ts +3 -26
  182. package/dist/inspector/tokens/resolution.js +44 -735
  183. package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
  184. package/dist/inspector/tokens/tokenAvailability.js +204 -0
  185. package/dist/inspector/ui/Button.d.ts +1 -1
  186. package/dist/inspector/ui/SideValuesField.js +2 -2
  187. package/dist/inspector/ui/StatusCallout.d.ts +4 -1
  188. package/dist/inspector/ui/StatusCallout.js +2 -2
  189. package/dist/inspector/ui/styles.js +13 -11
  190. package/dist/internal/agent-protocol.d.ts +65 -1
  191. package/dist/internal/agent-protocol.js +183 -3
  192. package/dist/nudge-ui.mjs +261 -57
  193. package/dist/transport/editor.d.ts +28 -0
  194. package/dist/transport/editor.js +132 -0
  195. package/dist/transport/index.d.ts +1 -0
  196. package/dist/transport/index.js +1 -0
  197. package/dist/transport/routes.d.ts +7 -0
  198. package/dist/transport/routes.js +7 -0
  199. package/package.json +3 -1
  200. package/dist/inspector/shell/panelLayout.d.ts +0 -2
  201. 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 (ZERO_WIDTH.test(trimmed))
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 ZERO_WIDTH.test(first);
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
- * - explicit `none` / `hidden`
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 && !isZeroWidthValue(structuredWidth))
147
+ if (structuredWidth)
146
148
  return true;
147
- if ((property === "border-width" || property.endsWith("-width")) && authored && !isZeroWidthValue(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 [borderSessionOpen, setBorderSessionOpen] = useState(false);
213
- useEffect(() => {
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
- setBorderSessionOpen(true);
219
+ visibility.show();
224
220
  onAfterEdit?.();
225
221
  }
226
222
  function handleRemoveBorder() {
227
223
  setStyle(editTarget, "border", "0 solid");
228
- setBorderSessionOpen(false);
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
- const Icon = BORDER_RADIUS_ICONS[index];
82
- return _jsx(Icon, { className: "side-values__icon side-values__side-icon", size: 16, "aria-hidden": "true" });
83
- })(),
84
- 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" })),
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
- return (_jsxs("div", { className: "editor", "data-test": "box-shadow-editor", children: [_jsx("div", { className: "editor__title", children: "Box Shadow" }), _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 }) }) }) })] }));
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 (/^#(?:0000|00000000)$/.test(normalized))
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.slice(0, 3).every(isZeroColorComponent)
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 = useMemo(() => selection?.elements ?? [element], [element, selection]);
35
+ const selectedElements = selection?.domElements ?? [el];
39
36
  const target = selection?.target ?? el;
40
37
  const allEntries = entries ?? getNudgeUiTokenEntries();
41
- const declaredValue = tokenRow?.declaredValue?.trim() ?? "";
42
- const paintedValue = getStateStyleValue(el, property);
43
- const resolvedValue = tokenRow?.resolvedValue ?? paintedValue;
44
- // An explicit declaration remains meaningful even when it paints as empty
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
- setFieldAdded(false);
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: () => setFieldAdded(true), 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: isEmpty ? null : tokenRow, selection: selection, initialValue: isEmpty ? "" : undefined, domElement: el, editTarget: target, entries: allEntries, onAfterEdit: handleAfterEdit }) }) }))] }));
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;