nudge-ui 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/README.md +164 -46
  2. package/dist/client.mjs +252 -54
  3. package/dist/css/value-semantics/colorSemantics.d.ts +4 -0
  4. package/dist/css/value-semantics/colorSemantics.js +17 -0
  5. package/dist/hosts/astro/astroRuntimeConfig.js +1 -0
  6. package/dist/hosts/astro/clientTransport.js +41 -32
  7. package/dist/hosts/astro/integration.d.ts +2 -2
  8. package/dist/hosts/astro/integration.js +9 -4
  9. package/dist/hosts/astro/markdownIdentity.d.ts +28 -0
  10. package/dist/hosts/astro/markdownIdentity.js +81 -0
  11. package/dist/hosts/environment.d.ts +15 -0
  12. package/dist/hosts/environment.js +16 -0
  13. package/dist/hosts/next/manifest.js +1 -1
  14. package/dist/hosts/next/sidecar.js +12 -2
  15. package/dist/hosts/next/wrapper.d.ts +2 -0
  16. package/dist/hosts/next/wrapper.js +2 -1
  17. package/dist/hosts/projectBridge.d.ts +1 -0
  18. package/dist/hosts/projectBridge.js +1 -0
  19. package/dist/hosts/static/manifest.js +3 -1
  20. package/dist/hosts/static/server.js +4 -0
  21. package/dist/hosts/vite/vite.d.ts +4 -0
  22. package/dist/hosts/vite/vite.js +28 -5
  23. package/dist/inspector/agent/McpConnectionDialog.d.ts +3 -2
  24. package/dist/inspector/agent/McpConnectionDialog.js +23 -22
  25. package/dist/inspector/agent/activity.d.ts +18 -0
  26. package/dist/inspector/agent/activity.js +70 -0
  27. package/dist/inspector/agent/client.d.ts +8 -0
  28. package/dist/inspector/agent/client.js +65 -5
  29. package/dist/inspector/agent/connectionStatus.d.ts +3 -3
  30. package/dist/inspector/agent/connectionStatus.js +9 -2
  31. package/dist/inspector/agent/httpTransport.d.ts +6 -0
  32. package/dist/inspector/agent/httpTransport.js +50 -3
  33. package/dist/inspector/agent/protocol.d.ts +9 -3
  34. package/dist/inspector/agent/protocol.js +1 -1
  35. package/dist/inspector/agent/verification.d.ts +11 -1
  36. package/dist/inspector/agent/verification.js +53 -25
  37. package/dist/inspector/canvas/AgentActivityOverlay.d.ts +13 -0
  38. package/dist/inspector/canvas/AgentActivityOverlay.js +111 -0
  39. package/dist/inspector/canvas/CanvasCard.d.ts +9 -2
  40. package/dist/inspector/canvas/CanvasCard.js +133 -38
  41. package/dist/inspector/canvas/CanvasElementOverlay.js +200 -22
  42. package/dist/inspector/canvas/CanvasToolIcons.d.ts +10 -0
  43. package/dist/inspector/canvas/CanvasToolIcons.js +20 -0
  44. package/dist/inspector/canvas/CanvasToolbar.js +8 -4
  45. package/dist/inspector/canvas/CanvasWorkspace.js +243 -56
  46. package/dist/inspector/canvas/StaleChangeIndicator.js +3 -13
  47. package/dist/inspector/canvas/agentPresentation.d.ts +8 -8
  48. package/dist/inspector/canvas/agentPresentation.js +64 -52
  49. package/dist/inspector/canvas/canvasGestures.d.ts +1 -1
  50. package/dist/inspector/canvas/canvasGestures.js +1 -1
  51. package/dist/inspector/canvas/canvasStore.d.ts +46 -82
  52. package/dist/inspector/canvas/canvasStore.js +251 -284
  53. package/dist/inspector/canvas/frameContent.d.ts +21 -0
  54. package/dist/inspector/canvas/frameContent.js +44 -0
  55. package/dist/inspector/canvas/frameProtocol.d.ts +34 -4
  56. package/dist/inspector/canvas/frameProtocol.js +9 -2
  57. package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -1
  58. package/dist/inspector/canvas/keyboardShortcuts.js +5 -3
  59. package/dist/inspector/canvas/layout.d.ts +52 -0
  60. package/dist/inspector/canvas/layout.js +106 -0
  61. package/dist/inspector/canvas/linkEligibility.d.ts +0 -5
  62. package/dist/inspector/canvas/linkEligibility.js +0 -29
  63. package/dist/inspector/canvas/projection.d.ts +15 -5
  64. package/dist/inspector/canvas/projection.js +69 -13
  65. package/dist/inspector/canvas/rendererBootstrap.js +45 -4
  66. package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
  67. package/dist/inspector/canvas/rendererElementSelector.js +102 -45
  68. package/dist/inspector/canvas/rendererStylesheet.js +2 -2
  69. package/dist/inspector/canvas/resizeHandleScale.js +1 -1
  70. package/dist/inspector/canvas/sessionStore.d.ts +11 -26
  71. package/dist/inspector/canvas/sessionStore.js +43 -183
  72. package/dist/inspector/canvas/staleChangeDetector.js +9 -3
  73. package/dist/inspector/canvas/useCanvasLayoutAnimation.d.ts +5 -0
  74. package/dist/inspector/canvas/useCanvasLayoutAnimation.js +107 -0
  75. package/dist/inspector/canvas/viewStore.d.ts +38 -0
  76. package/dist/inspector/canvas/viewStore.js +104 -0
  77. package/dist/inspector/canvas/wheelPan.d.ts +8 -0
  78. package/dist/inspector/canvas/wheelPan.js +8 -0
  79. package/dist/inspector/changes/changesLog.d.ts +6 -5
  80. package/dist/inspector/changes/changesLog.js +40 -32
  81. package/dist/inspector/changes/draftChanges.d.ts +56 -0
  82. package/dist/inspector/changes/draftChanges.js +300 -0
  83. package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
  84. package/dist/inspector/changes/previewDiagnostics.js +11 -25
  85. package/dist/inspector/clientManifest.d.ts +3 -1
  86. package/dist/inspector/clientManifest.js +1 -0
  87. package/dist/inspector/comments/CommentsOverlay.d.ts +2 -0
  88. package/dist/inspector/comments/CommentsOverlay.js +250 -0
  89. package/dist/inspector/comments/element.d.ts +6 -0
  90. package/dist/inspector/comments/element.js +47 -0
  91. package/dist/inspector/comments/store.d.ts +29 -0
  92. package/dist/inspector/comments/store.js +121 -0
  93. package/dist/inspector/comments/verification.d.ts +5 -0
  94. package/dist/inspector/comments/verification.js +52 -0
  95. package/dist/inspector/drafts/model.d.ts +35 -0
  96. package/dist/inspector/drafts/model.js +45 -0
  97. package/dist/inspector/drafts/store.d.ts +28 -0
  98. package/dist/inspector/drafts/store.js +231 -0
  99. package/dist/inspector/index.js +26 -35
  100. package/dist/inspector/inline-text/inlineTextEditor.js +7 -0
  101. package/dist/inspector/inspection/useBrowserCssInspection.js +15 -1
  102. package/dist/inspector/iterations/capture.d.ts +2 -0
  103. package/dist/inspector/iterations/capture.js +173 -0
  104. package/dist/inspector/iterations/client.d.ts +7 -0
  105. package/dist/inspector/iterations/client.js +52 -0
  106. package/dist/inspector/iterations/coordinator.d.ts +15 -0
  107. package/dist/inspector/iterations/coordinator.js +130 -0
  108. package/dist/inspector/iterations/prompt.d.ts +3 -0
  109. package/dist/inspector/iterations/prompt.js +26 -0
  110. package/dist/inspector/overlay/DomNavigator.d.ts +10 -0
  111. package/dist/inspector/overlay/DomNavigator.js +46 -0
  112. package/dist/inspector/overlay/InspectorOverlay.js +6 -2
  113. package/dist/inspector/overlay/clickPolicy.d.ts +18 -2
  114. package/dist/inspector/overlay/clickPolicy.js +34 -3
  115. package/dist/inspector/overlay/elementSelector.d.ts +1 -1
  116. package/dist/inspector/overlay/elementSelector.js +18 -7
  117. package/dist/inspector/overlay/interactionStyles.js +3 -1
  118. package/dist/inspector/overlay/spacingGestures.d.ts +15 -3
  119. package/dist/inspector/overlay/spacingGestures.js +88 -13
  120. package/dist/inspector/projection/{workspaceProjection.d.ts → draftProjection.d.ts} +6 -6
  121. package/dist/inspector/projection/{workspaceProjection.js → draftProjection.js} +4 -4
  122. package/dist/inspector/projection/renderedInstance.d.ts +2 -1
  123. package/dist/inspector/projection/renderedInstance.js +9 -5
  124. package/dist/inspector/projection/structuralProjection.d.ts +2 -1
  125. package/dist/inspector/projection/structuralProjection.js +19 -15
  126. package/dist/inspector/projection/textProjection.d.ts +2 -1
  127. package/dist/inspector/projection/textProjection.js +8 -4
  128. package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
  129. package/dist/inspector/prompt/clipboardHandoff.d.ts +10 -18
  130. package/dist/inspector/prompt/clipboardHandoff.js +92 -143
  131. package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
  132. package/dist/inspector/prompt/generatePrompt.js +17 -16
  133. package/dist/inspector/runtime/runtimeConfig.d.ts +10 -1
  134. package/dist/inspector/runtime/runtimeConfig.js +24 -0
  135. package/dist/inspector/selection/hierarchy.d.ts +6 -0
  136. package/dist/inspector/selection/hierarchy.js +8 -0
  137. package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
  138. package/dist/inspector/settings/SettingsDialog.js +22 -9
  139. package/dist/inspector/shell/AgentStatusButton.d.ts +11 -0
  140. package/dist/inspector/shell/AgentStatusButton.js +36 -0
  141. package/dist/inspector/shell/AppShell.js +4 -2
  142. package/dist/inspector/shell/ChangesLog.js +47 -27
  143. package/dist/inspector/shell/CopyPromptButton.js +146 -90
  144. package/dist/inspector/shell/EmptyState.js +9 -8
  145. package/dist/inspector/shell/InspectorShell.js +14 -25
  146. package/dist/inspector/shell/PeekOriginalButton.js +24 -23
  147. package/dist/inspector/shell/originalPreview.js +5 -5
  148. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  149. package/dist/inspector/shell/shortcuts.js +11 -0
  150. package/dist/inspector/sketch/SketchEditor.js +14 -14
  151. package/dist/inspector/sketch/SketchEntryButton.js +1 -1
  152. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +3 -1
  153. package/dist/inspector/sketch/SketchFrameOverlay.js +11 -8
  154. package/dist/inspector/sketch/SketchLayersPanel.js +13 -5
  155. package/dist/inspector/sketch/SketchOverlay.js +7 -7
  156. package/dist/inspector/sketch/SketchWorkspace.js +18 -12
  157. package/dist/inspector/sketch/interaction.d.ts +1 -0
  158. package/dist/inspector/sketch/interaction.js +8 -4
  159. package/dist/inspector/sketch/model.d.ts +5 -0
  160. package/dist/inspector/sketch/model.js +6 -2
  161. package/dist/inspector/sketch/raster.d.ts +1 -2
  162. package/dist/inspector/sketch/raster.js +68 -4
  163. package/dist/inspector/sketch/store.js +48 -23
  164. package/dist/inspector/styleEditors/BackgroundEditor.d.ts +4 -0
  165. package/dist/inspector/styleEditors/BackgroundEditor.js +195 -0
  166. package/dist/inspector/styleEditors/BorderEditor.js +49 -78
  167. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -1
  168. package/dist/inspector/styleEditors/BoxShadowEditor.d.ts +1 -2
  169. package/dist/inspector/styleEditors/BoxShadowEditor.js +132 -4
  170. package/dist/inspector/styleEditors/ColorPicker.d.ts +5 -1
  171. package/dist/inspector/styleEditors/ColorPicker.js +10 -4
  172. package/dist/inspector/styleEditors/GapField.js +3 -5
  173. package/dist/inspector/styleEditors/GridChildSection.d.ts +1 -1
  174. package/dist/inspector/styleEditors/GridChildSection.js +18 -9
  175. package/dist/inspector/styleEditors/GridPicker.d.ts +1 -1
  176. package/dist/inspector/styleEditors/GridPicker.js +8 -2
  177. package/dist/inspector/styleEditors/GridSection.js +1 -1
  178. package/dist/inspector/styleEditors/GridValueField.js +19 -14
  179. package/dist/inspector/styleEditors/ImageBackgroundEditor.d.ts +18 -0
  180. package/dist/inspector/styleEditors/ImageBackgroundEditor.js +118 -0
  181. package/dist/inspector/styleEditors/LayoutBlockedIndicator.d.ts +2 -2
  182. package/dist/inspector/styleEditors/LayoutBlockedIndicator.js +3 -3
  183. package/dist/inspector/styleEditors/LayoutComboField.js +6 -4
  184. package/dist/inspector/styleEditors/LayoutDropdown.js +6 -3
  185. package/dist/inspector/styleEditors/LayoutSection.js +32 -11
  186. package/dist/inspector/styleEditors/SpacingBox.js +16 -9
  187. package/dist/inspector/styleEditors/Typography.js +11 -3
  188. package/dist/inspector/styleEditors/backgroundLayers.d.ts +24 -0
  189. package/dist/inspector/styleEditors/backgroundLayers.js +51 -0
  190. package/dist/inspector/styleEditors/gradientValue.d.ts +17 -0
  191. package/dist/inspector/styleEditors/gradientValue.js +114 -0
  192. package/dist/inspector/styleEditors/inlineAuthored.d.ts +10 -0
  193. package/dist/inspector/styleEditors/inlineAuthored.js +114 -0
  194. package/dist/inspector/styleEditors/layoutValue.d.ts +0 -8
  195. package/dist/inspector/styleEditors/layoutValue.js +0 -42
  196. package/dist/inspector/styleEditors/nudgeValue.d.ts +4 -4
  197. package/dist/inspector/styleEditors/nudgeValue.js +41 -11
  198. package/dist/inspector/tokens/TokenField.d.ts +7 -0
  199. package/dist/inspector/tokens/TokenField.js +49 -22
  200. package/dist/inspector/tokens/TokenLabel.js +5 -14
  201. package/dist/inspector/tokens/TokensPanel.js +2 -1
  202. package/dist/inspector/tokens/dragPointerLock.d.ts +8 -0
  203. package/dist/inspector/tokens/dragPointerLock.js +70 -0
  204. package/dist/inspector/tokens/editActions.d.ts +1 -1
  205. package/dist/inspector/tokens/editActions.js +1 -1
  206. package/dist/inspector/ui/AtRuleContext.js +4 -6
  207. package/dist/inspector/ui/Button.d.ts +6 -2
  208. package/dist/inspector/ui/Button.js +4 -3
  209. package/dist/inspector/ui/ControlSurface.d.ts +2 -2
  210. package/dist/inspector/ui/ControlSurface.js +4 -3
  211. package/dist/inspector/ui/Disclosure.d.ts +22 -0
  212. package/dist/inspector/ui/Disclosure.js +13 -0
  213. package/dist/inspector/ui/InlineStyleWarning.d.ts +11 -0
  214. package/dist/inspector/ui/InlineStyleWarning.js +13 -0
  215. package/dist/inspector/ui/InspectorPopover.d.ts +2 -1
  216. package/dist/inspector/ui/InspectorPopover.js +4 -5
  217. package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
  218. package/dist/inspector/ui/PopoverListbox.js +8 -7
  219. package/dist/inspector/ui/SegmentedControl.d.ts +2 -1
  220. package/dist/inspector/ui/SegmentedControl.js +2 -2
  221. package/dist/inspector/ui/Select.d.ts +6 -1
  222. package/dist/inspector/ui/Select.js +7 -11
  223. package/dist/inspector/ui/SideValuesField.js +1 -1
  224. package/dist/inspector/ui/Tooltip.d.ts +26 -0
  225. package/dist/inspector/ui/Tooltip.js +32 -0
  226. package/dist/inspector/ui/classNames.d.ts +2 -0
  227. package/dist/inspector/ui/classNames.js +4 -0
  228. package/dist/inspector/ui/customCursors.d.ts +23 -0
  229. package/dist/inspector/ui/customCursors.js +122 -0
  230. package/dist/inspector/ui/dragCursor.d.ts +6 -0
  231. package/dist/inspector/ui/dragCursor.js +13 -0
  232. package/dist/inspector/ui/portalContainer.d.ts +2 -0
  233. package/dist/inspector/ui/portalContainer.js +6 -0
  234. package/dist/inspector/ui/styles.js +34 -24
  235. package/dist/inspector/workspace/commands.d.ts +9 -0
  236. package/dist/inspector/workspace/commands.js +82 -0
  237. package/dist/inspector/workspace/controller.d.ts +4 -0
  238. package/dist/inspector/workspace/controller.js +66 -0
  239. package/dist/inspector/workspace/handoff.d.ts +42 -0
  240. package/dist/inspector/workspace/handoff.js +115 -0
  241. package/dist/inspector/workspace/timeline.d.ts +30 -0
  242. package/dist/inspector/workspace/timeline.js +108 -0
  243. package/dist/internal/agent-protocol.d.ts +19 -2
  244. package/dist/internal/agent-protocol.js +13 -0
  245. package/dist/nudge-ui.mjs +560 -299
  246. package/dist/project/artifacts.d.ts +13 -0
  247. package/dist/project/artifacts.js +253 -0
  248. package/dist/transport/artifacts.d.ts +8 -0
  249. package/dist/transport/artifacts.js +9 -0
  250. package/dist/transport/editor.d.ts +20 -1
  251. package/dist/transport/editor.js +44 -11
  252. package/dist/transport/routes.d.ts +6 -1
  253. package/dist/transport/routes.js +6 -1
  254. package/package.json +6 -2
  255. package/dist/inspector/changes/workspaceChanges.d.ts +0 -43
  256. package/dist/inspector/changes/workspaceChanges.js +0 -239
  257. package/dist/inspector/shell/DomNavigation.d.ts +0 -8
  258. package/dist/inspector/shell/DomNavigation.js +0 -30
@@ -2,12 +2,16 @@ import { completeCssValue, CSS_NUMBER_SOURCE } from "./completeCssValue.js";
2
2
  import { valuePolicyFor } from "./valuePolicy.js";
3
3
  const CSS_NUMBER = new RegExp(`^(?:${CSS_NUMBER_SOURCE})$`, "i");
4
4
  const CSS_NUMERIC_LITERAL = new RegExp(`^(${CSS_NUMBER_SOURCE})(px|rem|em|%)?$`, "i");
5
- const DRAG_NUDGE_PROPERTY = /^(?:(?:padding|margin)-(?:top|right|bottom|left|horizontal|vertical)|(?:top|right|bottom|left|inset|inset-(?:horizontal|vertical))|(?:row|column)-gap|border-radius|border-(?:top-left|top-right|bottom-right|bottom-left)-radius)$/;
5
+ const DRAG_PIXELS_PER_STEP = 16;
6
+ const LARGE_DRAG_PIXELS_PER_STEP = 8;
7
+ const DRAG_NUDGE_PROPERTY = /^(?:(?:padding|margin)-(?:top|right|bottom|left|horizontal|vertical)|(?:top|right|bottom|left|inset|inset-(?:horizontal|vertical))|(?:row|column)-gap|(?:font-size|letter-spacing|line-height|opacity)|border-radius|border-(?:top-left|top-right|bottom-right|bottom-left)-radius)$/;
6
8
  /**
7
9
  * Returns a nudged CSS numeric literal, or null when the value is not a
8
10
  * standalone number that the inspector can safely edit.
9
11
  */
10
12
  export function nudgeCssValue(property, rawValue, direction, large = false) {
13
+ if (property === "opacity")
14
+ return nudgeOpacityValue(rawValue, direction, large);
11
15
  const normalised = normaliseForNudge(property, rawValue);
12
16
  const parsed = parseNumericLiteral(normalised);
13
17
  if (!parsed)
@@ -23,13 +27,13 @@ export function supportsDragNudge(property) {
23
27
  return DRAG_NUDGE_PROPERTY.test(property);
24
28
  }
25
29
  /**
26
- * Applies one nudge per two horizontal pointer pixels. Large nudges still use
27
- * one 8px step per pointer pixel. When a large drag starts from an off-grid
28
- * value, the first step snaps to the next large-step boundary in the drag
29
- * direction.
30
+ * Applies one regular nudge per sixteen horizontal pointer pixels and one large
31
+ * nudge per eight horizontal pointer pixels. When a large drag starts from an
32
+ * off-grid value, the first step snaps to the next large-step boundary in the
33
+ * drag direction.
30
34
  */
31
35
  export function nudgeCssValueByDrag(property, rawValue, deltaX, large = false, snapToLargeStep = false) {
32
- const steps = Math.trunc(Math.abs(deltaX) / (large ? 1 : 2));
36
+ const steps = Math.trunc(Math.abs(deltaX) / (large ? LARGE_DRAG_PIXELS_PER_STEP : DRAG_PIXELS_PER_STEP));
33
37
  if (steps === 0)
34
38
  return null;
35
39
  const direction = deltaX < 0 ? -1 : 1;
@@ -52,16 +56,16 @@ export function nudgeCssValueByDrag(property, rawValue, deltaX, large = false, s
52
56
  }
53
57
  /** Returns whether a field has a numeric value that can respond to dragging. */
54
58
  export function canNudgeCssValueByDrag(property, rawValue) {
55
- return nudgeCssValueByDrag(property, rawValue, 2) !== null
56
- || nudgeCssValueByDrag(property, rawValue, -2) !== null;
59
+ return nudgeCssValueByDrag(property, rawValue, DRAG_PIXELS_PER_STEP) !== null
60
+ || nudgeCssValueByDrag(property, rawValue, -DRAG_PIXELS_PER_STEP) !== null;
57
61
  }
58
62
  /** Returns a clamped opacity percentage nudged by 1%, or 10% with Shift. */
59
63
  export function nudgeOpacityValue(rawValue, direction, large = false) {
60
- const parsed = parseNumericLiteral(rawValue);
61
- if (!parsed || parsed.unit !== "%")
64
+ const percent = parseOpacityPercent(rawValue);
65
+ if (percent === null)
62
66
  return null;
63
67
  const step = large ? 10 : 1;
64
- const next = Math.min(100, Math.max(0, parsed.number + direction * step));
68
+ const next = Math.min(100, Math.max(0, percent + direction * step));
65
69
  return `${formatNumber(next)}%`;
66
70
  }
67
71
  function normaliseForNudge(property, rawValue) {
@@ -116,6 +120,8 @@ function snapCssValueToLargeStep(property, rawValue, direction) {
116
120
  return snappedParts.join(", ");
117
121
  }
118
122
  function snapNumericValueToLargeStep(property, rawValue, direction) {
123
+ if (property === "opacity")
124
+ return snapOpacityValueToLargeStep(rawValue, direction);
119
125
  const normalised = normaliseForNudge(property, rawValue);
120
126
  const parsed = parseNumericLiteral(normalised);
121
127
  if (!parsed)
@@ -132,6 +138,30 @@ function snapNumericValueToLargeStep(property, rawValue, direction) {
132
138
  next = clampNudgeNumber(property, next);
133
139
  return `${formatNumber(next)}${parsed.unit}`;
134
140
  }
141
+ function snapOpacityValueToLargeStep(rawValue, direction) {
142
+ const percent = parseOpacityPercent(rawValue);
143
+ if (percent === null)
144
+ return null;
145
+ const step = 10;
146
+ let next = direction > 0
147
+ ? Math.ceil(percent / step) * step
148
+ : Math.floor(percent / step) * step;
149
+ if ((direction > 0 && next <= percent) || (direction < 0 && next >= percent)) {
150
+ next += direction * step;
151
+ }
152
+ next = Math.min(100, Math.max(0, next));
153
+ return `${formatNumber(next)}%`;
154
+ }
155
+ function parseOpacityPercent(rawValue) {
156
+ const parsed = parseNumericLiteral(rawValue);
157
+ if (!parsed)
158
+ return null;
159
+ if (parsed.unit === "%")
160
+ return parsed.number;
161
+ if (parsed.unit === "" && parsed.number >= 0 && parsed.number <= 1)
162
+ return parsed.number * 100;
163
+ return null;
164
+ }
135
165
  function isLineHeight(property) {
136
166
  return property === "line-height" || property.includes("line-height");
137
167
  }
@@ -7,6 +7,8 @@ import { type EditTarget } from "../selection/editTarget.js";
7
7
  import type { StyleSelection } from "../selection/styleSelection.js";
8
8
  export interface TokenValueFieldProps {
9
9
  property: string;
10
+ /** Numeric editing policy for a scalar slot inside a composite property. */
11
+ nudgeProperty?: string;
10
12
  domElement?: HTMLElement;
11
13
  semanticSlot?: TokenSemanticSlot;
12
14
  committedValue: string;
@@ -34,6 +36,9 @@ export interface TokenValueFieldProps {
34
36
  onCommitOpacity?(value: string): boolean | void;
35
37
  atRules?: readonly AtRuleContext[];
36
38
  chipVariant?: "default" | "small";
39
+ blockedBy?: string | null;
40
+ inlineWarningDataTest?: string;
41
+ inlineWarningTooltipDataTest?: string;
37
42
  }
38
43
  export interface TokenFieldProps {
39
44
  property: string;
@@ -62,6 +67,8 @@ export interface TokenFieldProps {
62
67
  mixed?: boolean;
63
68
  /** Referenced tokens used for raw color-field behavior; not rendered inline. */
64
69
  attributionTokens?: string[];
70
+ inlineWarningDataTest?: string;
71
+ inlineWarningTooltipDataTest?: string;
65
72
  }
66
73
  export declare function colorValueToHex(value: string): string | null;
67
74
  export declare function TokenValueField(props: TokenValueFieldProps): ReactElement;
@@ -12,8 +12,14 @@ import { PopoverListbox } from "../ui/PopoverListbox.js";
12
12
  import { ColorSwatch } from "../ui/ColorSwatch.js";
13
13
  import { getStateStyleValue } from "../shell/stateValue.js";
14
14
  import { AtRuleIndicator, useFieldAtRules } from "../ui/AtRuleContext.js";
15
+ import { Tooltip } from "../ui/Tooltip.js";
16
+ import { cx } from "../ui/classNames.js";
15
17
  import { TokenChip } from "./TokenChip.js";
18
+ import { startDragPointerLock } from "./dragPointerLock.js";
19
+ import { createChangeHistoryGroup } from "../changes/draftChanges.js";
16
20
  import { isMultiTarget } from "../selection/editTarget.js";
21
+ import { inlineBlockedBy } from "../styleEditors/inlineAuthored.js";
22
+ import { InlineStyleWarning } from "../ui/InlineStyleWarning.js";
17
23
  const NON_COLOR_FACTS = { hasEmbeddedAlpha: false, isExpression: false, opacityEditable: false };
18
24
  function colorFacts(value) {
19
25
  return selectTokens({
@@ -153,7 +159,8 @@ function NativeColorSwatch({ value, onChange, disabled, }) {
153
159
  return (_jsxs("label", { className: "token-color-control", "data-resolved": hasRenderableColor ? "true" : "false", children: [_jsx(ColorSwatch, { color: cssColor ?? "transparent", "data-test": "token-color-swatch" }), _jsx("input", { className: "token-color-control__input", "data-test": "token-color-input", type: "color", value: hex, disabled: disabled, "aria-label": "Choose color", onChange: (event) => onChange(event.target.value) })] }));
154
160
  }
155
161
  export function TokenValueField(props) {
156
- const { property, domElement, semanticSlot, committedValue, resolvedValue = committedValue, activeTokenName: controlledTokenName = null, entries, suggestions = [], inputDataTest = "raw-input", allowedTokenNames, isColor = false, disabled = false, mixed = false, formatRawValue = (value) => value.trim(), onCommitRaw, onSelectToken, onUnlink, attributionTokens = [], leading, trailing, className, label, opacity, color, onCommitOpacity, atRules, chipVariant = "default", } = props;
162
+ const { property, nudgeProperty = property, domElement, semanticSlot, committedValue, resolvedValue = committedValue, activeTokenName: controlledTokenName = null, entries, suggestions = [], inputDataTest = "raw-input", allowedTokenNames, isColor = false, disabled = false, mixed = false, formatRawValue = (value) => value.trim(), onCommitRaw, onSelectToken, onUnlink, attributionTokens = [], leading, trailing, className, label, opacity, color, onCommitOpacity, atRules, chipVariant = "default", blockedBy = null, inlineWarningDataTest, inlineWarningTooltipDataTest, } = props;
163
+ const fieldDisabled = disabled || blockedBy !== null;
157
164
  const inheritedAtRules = useFieldAtRules(property);
158
165
  const fieldAtRules = atRules ?? inheritedAtRules;
159
166
  const controlledToken = controlledTokenName ? entries.find((entry) => entry.name === controlledTokenName) : null;
@@ -241,6 +248,8 @@ export function TokenValueField(props) {
241
248
  handleTokenSelect(chosen);
242
249
  }
243
250
  function commitRawValue(value = rawValue) {
251
+ if (fieldDisabled)
252
+ return;
244
253
  if (mixed && value.trim().toLowerCase() === "mixed")
245
254
  return;
246
255
  const formatted = formatRawValue(value);
@@ -284,7 +293,7 @@ export function TokenValueField(props) {
284
293
  if (direction && !event.altKey && !event.ctrlKey && !event.metaKey) {
285
294
  const next = property === "opacity"
286
295
  ? nudgeOpacityValue(rawValue, direction, event.shiftKey)
287
- : nudgeCssValue(property, rawValue, direction, event.shiftKey);
296
+ : nudgeCssValue(nudgeProperty, rawValue, direction, event.shiftKey);
288
297
  if (next) {
289
298
  event.preventDefault();
290
299
  event.stopPropagation();
@@ -318,8 +327,9 @@ export function TokenValueField(props) {
318
327
  const drag = dragNudgeRef.current;
319
328
  if (!drag)
320
329
  return;
321
- releasePointerCapture(drag);
322
330
  dragNudgeRef.current = null;
331
+ drag.pointerLock?.stop();
332
+ releasePointerCapture(drag);
323
333
  setIsDraggingNudge(false);
324
334
  }, []);
325
335
  useEffect(() => {
@@ -331,13 +341,16 @@ export function TokenValueField(props) {
331
341
  };
332
342
  }, [stopDragNudge]);
333
343
  function handleDragNudgePointerDown(event) {
334
- if (disabled || !supportsDragNudge(property))
344
+ if (event.button !== 0)
335
345
  return;
336
- if (!canNudgeCssValueByDrag(property, rawValue))
346
+ if (fieldDisabled || !supportsDragNudge(nudgeProperty))
347
+ return;
348
+ if (!canNudgeCssValueByDrag(nudgeProperty, rawValue))
337
349
  return;
338
350
  stopDragNudge();
339
351
  const handle = event.currentTarget;
340
352
  const drag = {
353
+ commit: createChangeHistoryGroup(),
341
354
  handle,
342
355
  pointerId: event.pointerId,
343
356
  lastClientX: event.clientX,
@@ -350,6 +363,11 @@ export function TokenValueField(props) {
350
363
  dragNudgeRef.current = drag;
351
364
  if (typeof handle.setPointerCapture === "function")
352
365
  handle.setPointerCapture(event.pointerId);
366
+ if (event.pointerType === "mouse") {
367
+ drag.pointerLock = startDragPointerLock(handle, { x: event.clientX, y: event.clientY }, (mouseEvent) => {
368
+ updateDragNudge(mouseEvent, mouseEvent.movementX);
369
+ }, stopDragNudge);
370
+ }
353
371
  setIsDraggingNudge(true);
354
372
  event.preventDefault();
355
373
  event.stopPropagation();
@@ -358,8 +376,16 @@ export function TokenValueField(props) {
358
376
  const drag = dragNudgeRef.current;
359
377
  if (!drag || drag.pointerId !== event.pointerId)
360
378
  return;
379
+ if (drag.pointerLock?.isLocked())
380
+ return;
361
381
  const movementX = event.clientX - drag.lastClientX;
362
382
  drag.lastClientX = event.clientX;
383
+ updateDragNudge(event, movementX);
384
+ }
385
+ function updateDragNudge(event, movementX) {
386
+ const drag = dragNudgeRef.current;
387
+ if (!drag)
388
+ return;
363
389
  if (event.shiftKey !== drag.shiftActive) {
364
390
  drag.shiftActive = event.shiftKey;
365
391
  drag.startValue = drag.lastValue;
@@ -367,7 +393,7 @@ export function TokenValueField(props) {
367
393
  drag.shiftSnapPending = event.shiftKey;
368
394
  }
369
395
  drag.deltaX += movementX;
370
- const next = nudgeCssValueByDrag(property, drag.startValue, drag.deltaX, drag.shiftActive, drag.shiftActive && drag.shiftSnapPending);
396
+ const next = nudgeCssValueByDrag(nudgeProperty, drag.startValue, drag.deltaX, drag.shiftActive, drag.shiftActive && drag.shiftSnapPending);
371
397
  if (next === null || next === drag.lastValue)
372
398
  return;
373
399
  drag.lastValue = next;
@@ -380,7 +406,7 @@ export function TokenValueField(props) {
380
406
  event.stopPropagation();
381
407
  setRawValue(next);
382
408
  setActiveTokenName(null);
383
- onCommitRaw(next);
409
+ drag.commit(() => onCommitRaw(next));
384
410
  }
385
411
  function handleDragNudgePointerEnd(event) {
386
412
  if (dragNudgeRef.current?.pointerId !== event.pointerId)
@@ -390,15 +416,15 @@ export function TokenValueField(props) {
390
416
  function renderLeading() {
391
417
  if (!leading)
392
418
  return null;
393
- const canDrag = !disabled
394
- && supportsDragNudge(property)
395
- && canNudgeCssValueByDrag(property, rawValue);
419
+ const canDrag = !fieldDisabled
420
+ && supportsDragNudge(nudgeProperty)
421
+ && canNudgeCssValueByDrag(nudgeProperty, rawValue);
396
422
  if (!canDrag)
397
423
  return _jsx("span", { className: "token-field__leading", children: leading });
398
- return (_jsx("span", { className: "token-field__leading token-field__leading--nudge", "data-test": "nudge-handle", "data-dragging": isDraggingNudge ? "true" : undefined, title: `Drag to adjust ${label ?? property}`, onPointerDown: handleDragNudgePointerDown, onPointerMove: handleDragNudgePointerMove, onPointerUp: handleDragNudgePointerEnd, onPointerCancel: handleDragNudgePointerEnd, onBlur: stopDragNudge, children: leading }));
424
+ return (_jsx("span", { className: "token-field__leading token-field__leading--nudge", "data-test": "nudge-handle", "data-dragging": isDraggingNudge ? "true" : undefined, onPointerDown: handleDragNudgePointerDown, onPointerMove: handleDragNudgePointerMove, onPointerUp: handleDragNudgePointerEnd, onPointerCancel: handleDragNudgePointerEnd, onBlur: stopDragNudge, children: leading }));
399
425
  }
400
426
  function commitOpacityValue(value = opacityValue) {
401
- if (!showOpacity || !onCommitOpacity)
427
+ if (fieldDisabled || !showOpacity || !onCommitOpacity)
402
428
  return;
403
429
  const normalized = normalizeOpacityPercent(value);
404
430
  if (normalized === null) {
@@ -411,7 +437,7 @@ export function TokenValueField(props) {
411
437
  }
412
438
  setOpacityValue(normalized);
413
439
  }
414
- const opacityControl = showOpacity ? (_jsx("input", { className: "token-opacity-input", "data-test": "color-opacity-input", type: "text", inputMode: "decimal", value: opacityValue, disabled: disabled || !onCommitOpacity, "aria-label": `Opacity for ${property}`, onChange: (event) => setOpacityValue(event.target.value), onBlur: () => commitOpacityValue(), onKeyDown: (event) => {
440
+ const opacityControl = showOpacity ? (_jsx("input", { className: "token-opacity-input", "data-test": "color-opacity-input", type: "text", inputMode: "decimal", value: opacityValue, disabled: fieldDisabled || !onCommitOpacity, "aria-label": `Opacity for ${property}`, onChange: (event) => setOpacityValue(event.target.value), onBlur: () => commitOpacityValue(), onKeyDown: (event) => {
415
441
  const direction = arrowDirection(event.key);
416
442
  if (direction && !event.altKey && !event.ctrlKey && !event.metaKey) {
417
443
  const next = nudgeOpacityValue(opacityValue, direction, event.shiftKey);
@@ -434,17 +460,17 @@ export function TokenValueField(props) {
434
460
  }
435
461
  } })) : null;
436
462
  const colorPreview = activeToken ? resolvedValue || activeToken.value : resolvedValue || rawValue;
437
- const colorControlEl = isColor ? (_jsx(NativeColorSwatch, { value: colorPreview, disabled: disabled, onChange: (value) => commitRawValue(value) })) : null;
463
+ const colorControlEl = isColor ? (_jsx(NativeColorSwatch, { value: colorPreview, disabled: fieldDisabled, onChange: (value) => commitRawValue(value) })) : null;
438
464
  const rawSuggestionItems = availableSuggestions.map(rawSuggestion);
439
465
  if (activeToken) {
440
466
  const chipValue = chipVariant === "small" ? stripCssUnit(activeToken.value) : activeToken.name;
441
467
  const embedColorSwatch = isColor && chipVariant !== "small";
442
- return (_jsxs("span", { className: `token-field${isColor ? " token-field--color" : ""}${embedColorSwatch ? " token-field--color-chip" : ""}${className ? ` ${className}` : ""}`, "data-test": "token-field", "data-property": property, "aria-label": label, title: label, children: [renderLeading(), embedColorSwatch ? null : colorControlEl, _jsxs(TokenChip, { size: chipVariant, "data-group": groupByTokenName.get(activeToken.name) ?? tokenGroup(activeToken), children: [_jsx(PopoverListbox, { query: "", value: activeToken.name, open: isTokenPickerOpen, trigger: (_jsxs(TokenChip.Picker, { children: [embedColorSwatch ? colorControlEl : null, _jsx(TokenChip.Label, { title: activeToken.name, children: chipValue })] })), triggerClassName: "token-chip__trigger", triggerDataTest: "token-chip", triggerAriaLabel: `Change ${property} token`, searchable: true, searchPlaceholder: "Search tokens\u2026", searchAriaLabel: "Search tokens", items: [...rawSuggestionItems, ...relevantTokens.map((entry) => tokenSuggestion(entry, groupByTokenName))], onQueryChange: () => undefined, onOpenChange: setTokenPickerOpen, disabled: disabled, onSelect: (value) => {
468
+ return (_jsxs("span", { className: cx("token-field", isColor && "token-field--color", embedColorSwatch && "token-field--color-chip", blockedBy !== null && "token-field--blocked", className), "data-test": "token-field", "data-property": property, "aria-disabled": fieldDisabled || undefined, "aria-label": label, children: [renderLeading(), embedColorSwatch ? null : colorControlEl, _jsxs(TokenChip, { size: chipVariant, "data-group": groupByTokenName.get(activeToken.name) ?? tokenGroup(activeToken), children: [_jsx(PopoverListbox, { query: "", value: activeToken.name, open: isTokenPickerOpen, trigger: (_jsxs(TokenChip.Picker, { children: [embedColorSwatch ? colorControlEl : null, _jsx(TokenChip.Label, { title: chipVariant === "small" ? activeToken.name : undefined, children: chipValue })] })), triggerClassName: "token-chip__trigger", triggerDataTest: "token-chip", triggerAriaLabel: `Change ${property} token`, searchable: true, searchPlaceholder: "Search tokens\u2026", searchAriaLabel: "Search tokens", items: [...rawSuggestionItems, ...relevantTokens.map((entry) => tokenSuggestion(entry, groupByTokenName))], onQueryChange: () => undefined, onOpenChange: setTokenPickerOpen, disabled: fieldDisabled, onSelect: (value) => {
443
469
  handleSuggestionSelect(value);
444
- } }), _jsx(TokenChip.Action, { children: _jsx(IconButton, { variant: "quiet", size: "compact", style: { borderRadius: "var(--radius-4)" }, label: "Replace with raw value", "data-test": "delink-btn", disabled: disabled, onClick: handleDelink, children: _jsx(IconLinkOff, { size: chipVariant === "small" ? 12 : 14, stroke: 1.75, "aria-hidden": "true" }) }) })] }), opacityControl, _jsx(AtRuleIndicator, { atRules: fieldAtRules }), trailing ? _jsx("span", { className: "token-field__trailing", children: trailing }) : null] }));
470
+ } }), _jsx(TokenChip.Action, { children: _jsx(Tooltip, { stableTrigger: true, children: _jsx(IconButton, { variant: "quiet", size: "compact", style: { borderRadius: "var(--radius-4)" }, label: "Replace with raw value", "data-test": "delink-btn", disabled: fieldDisabled, onClick: handleDelink, children: _jsx(IconLinkOff, { size: chipVariant === "small" ? 12 : 14, stroke: 1.75, "aria-hidden": "true" }) }) }) })] }), opacityControl, _jsx(AtRuleIndicator, { atRules: fieldAtRules }), trailing ? _jsx("span", { className: "token-field__trailing", children: trailing }) : null, _jsx(InlineStyleWarning, { blockedBy: blockedBy, dataTest: inlineWarningDataTest, tooltipDataTest: inlineWarningTooltipDataTest })] }));
445
471
  }
446
472
  const showPopover = isFocused && (filteredTokens.length > 0 || availableSuggestions.length > 0);
447
- return (_jsxs("span", { className: `token-field token-field--raw${isColor ? " token-field--color" : ""}${className ? ` ${className}` : ""}`, "data-test": "token-field", "data-property": property, "aria-label": label, title: label, children: [renderLeading(), colorControlEl, _jsx(PopoverListbox, { query: rawValue, value: null, open: showPopover, placeholder: property, inputRef: inputRef, inputAppearance: "embedded", inputDataTest: inputDataTest, inputOnFocus: () => {
473
+ return (_jsxs("span", { className: cx("token-field", "token-field--raw", isColor && "token-field--color", blockedBy !== null && "token-field--blocked", className), "data-test": "token-field", "data-property": property, "aria-disabled": fieldDisabled || undefined, "aria-label": label, children: [renderLeading(), colorControlEl, _jsx(PopoverListbox, { query: rawValue, value: null, open: showPopover, placeholder: property, inputRef: inputRef, inputAppearance: "embedded", inputDataTest: inputDataTest, inputOnFocus: () => {
448
474
  setIsFocused(true);
449
475
  if (mixed && rawValue.trim().toLowerCase() === "mixed")
450
476
  setRawValue("");
@@ -454,9 +480,9 @@ export function TokenValueField(props) {
454
480
  ], onQueryChange: (value) => {
455
481
  isNavigatingSuggestions.current = false;
456
482
  setRawValue(value);
457
- }, onOpenChange: setIsFocused, disabled: disabled, onSelect: (value) => {
483
+ }, onOpenChange: setIsFocused, disabled: fieldDisabled, onSelect: (value) => {
458
484
  handleSuggestionSelect(value);
459
- } }), opacityControl, _jsx(AtRuleIndicator, { atRules: fieldAtRules }), trailing ? _jsx("span", { className: "token-field__trailing", children: trailing }) : null] }));
485
+ } }), opacityControl, _jsx(AtRuleIndicator, { atRules: fieldAtRules }), trailing ? _jsx("span", { className: "token-field__trailing", children: trailing }) : null, _jsx(InlineStyleWarning, { blockedBy: blockedBy, dataTest: inlineWarningDataTest, tooltipDataTest: inlineWarningTooltipDataTest })] }));
460
486
  }
461
487
  function arrowDirection(key) {
462
488
  if (key === "ArrowUp")
@@ -466,7 +492,7 @@ function arrowDirection(key) {
466
492
  return null;
467
493
  }
468
494
  export function TokenField(props) {
469
- const { property, semanticSlot, tokenRow, selection, initialValue, displayValue, domElement: el, editTarget, entries, suggestions, inputDataTest, onAfterEdit, editMetadata, leading, trailing, className, label, chipVariant, formatRawValue: formatRawValueProp, disabled = false, mixed = false, attributionTokens, } = props;
495
+ const { property, semanticSlot, tokenRow, selection, initialValue, displayValue, domElement: el, editTarget, entries, suggestions, inputDataTest, onAfterEdit, editMetadata, leading, trailing, className, label, chipVariant, formatRawValue: formatRawValueProp, disabled = false, mixed = false, attributionTokens, inlineWarningDataTest, inlineWarningTooltipDataTest, } = props;
470
496
  const selectedProperty = selection && selection.elements.length > 1
471
497
  ? selection.getProperty(property)
472
498
  : null;
@@ -476,6 +502,7 @@ export function TokenField(props) {
476
502
  ? selectedProperty.value.value
477
503
  : undefined;
478
504
  const effectiveMetadata = editTarget && isMultiTarget(editTarget) ? undefined : editMetadata;
505
+ const blockedBy = inlineBlockedBy(selection?.domElements ?? [el], property);
479
506
  const tokenBackedOpacityName = tokenRow?.tokenName
480
507
  && tokenRow.opacity
481
508
  && tokenRow.opacity.tokenName !== tokenRow.tokenName
@@ -525,7 +552,7 @@ export function TokenField(props) {
525
552
  ?? (selectedProperty?.token.kind === "mixed" ? ["Mixed tokens"] : undefined)
526
553
  ?? (expression
527
554
  ? tokenRow?.tokens?.filter((token) => token.name !== tokenRow.opacity?.tokenName).map((token) => token.name)
528
- : undefined), opacity: tokenRow?.opacity, color: tokenRow?.color, entries: entries, suggestions: suggestions, inputDataTest: inputDataTest, disabled: disabled, mixed: effectiveMixed, isColor: selectTokens({ property, slot: semanticSlot, entries: [] }).preferredGroup === "color", formatRawValue: (value) => {
555
+ : undefined), opacity: tokenRow?.opacity, color: tokenRow?.color, entries: entries, suggestions: suggestions, inputDataTest: inputDataTest, disabled: disabled, blockedBy: blockedBy, mixed: effectiveMixed, isColor: selectTokens({ property, slot: semanticSlot, entries: [] }).preferredGroup === "color", formatRawValue: (value) => {
529
556
  if (formatRawValueProp)
530
557
  return formatRawValueProp(value);
531
558
  return completeCssValue(value.trim(), valuePolicyFor(property));
@@ -563,7 +590,7 @@ export function TokenField(props) {
563
590
  }, onUnlink: (value) => {
564
591
  setStyle(editTarget ?? el, property, value, effectiveMetadata);
565
592
  onAfterEdit?.();
566
- }, leading: leading, trailing: trailing, className: className, label: label, chipVariant: chipVariant }));
593
+ }, leading: leading, trailing: trailing, className: className, label: label, chipVariant: chipVariant, inlineWarningDataTest: inlineWarningDataTest, inlineWarningTooltipDataTest: inlineWarningTooltipDataTest }));
567
594
  }
568
595
  function tokenSuggestion(entry, groups) {
569
596
  return {
@@ -1,23 +1,14 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- function textContent(children) {
3
- if (children === null || children === undefined || typeof children === "boolean")
4
- return "";
5
- if (typeof children === "string" || typeof children === "number" || typeof children === "bigint") {
6
- return String(children);
7
- }
8
- if (Array.isArray(children))
9
- return children.map(textContent).join("");
10
- return "";
11
- }
2
+ import { cx } from "../ui/classNames.js";
3
+ import { Tooltip } from "../ui/Tooltip.js";
12
4
  export function TokenLabel({ as = "span", className, children, title, ...props }) {
13
- const childText = textContent(children);
14
5
  const labelProps = {
15
6
  ...props,
16
- className: `token-label${className ? ` ${className}` : ""}`,
17
- title: title ?? childText,
7
+ className: cx("token-label", className),
18
8
  };
19
9
  const content = _jsx("span", { className: "token-label__content", children: children });
20
- return as === "code"
10
+ const label = as === "code"
21
11
  ? _jsx("code", { ...labelProps, children: content })
22
12
  : _jsx("span", { ...labelProps, children: content });
13
+ return title === undefined ? label : _jsx(Tooltip, { content: title, children: label });
23
14
  }
@@ -3,6 +3,7 @@ import { useMemo, useState } from "react";
3
3
  import { isTokenChange, useChanges } from "../changes/changesLog.js";
4
4
  import { TextInput } from "../ui/TextInput.js";
5
5
  import { ControlSurface } from "../ui/ControlSurface.js";
6
+ import { Disclosure } from "../ui/Disclosure.js";
6
7
  import { TokenValueField } from "./TokenField.js";
7
8
  import { TOKEN_GROUP_LABELS, TOKEN_GROUP_ORDER, aliasName, compatibleTokenNames, contextLabel, filterTokenRows, } from "./catalog.js";
8
9
  import { setGlobalTokenValue } from "./tokenEdits.js";
@@ -43,5 +44,5 @@ function TokenCatalogItem({ row, rows, entries, }) {
43
44
  const allowed = compatibleTokenNames(effectiveRow, effectiveRows);
44
45
  const variants = row.definition.declarations.filter((declaration) => declaration !== row.activeDeclaration);
45
46
  const tokenName = row.definition.name || row.definition.cssName;
46
- return (_jsxs("article", { className: "token-row", "data-test": "global-token-row", "data-token-name": row.definition.cssName, "data-active": row.activeDeclaration ? "true" : "false", children: [_jsxs("div", { className: "token-row__inline", children: [_jsx(TokenLabel, { as: "code", className: "token-row__name", title: tokenName, children: tokenName }), row.activeDeclaration ? (_jsx(ControlSurface, { children: _jsx(TokenValueField, { property: row.definition.cssName, committedValue: committedValue, resolvedValue: row.resolvedValue || committedValue, activeTokenName: activeAlias, entries: entries, allowedTokenNames: allowed, isColor: row.group === "color", onCommitRaw: (value) => setGlobalTokenValue(row, value), onSelectToken: (token) => { setGlobalTokenValue(row, `var(${token.name})`); }, onUnlink: (value) => setGlobalTokenValue(row, value) }) })) : (_jsx(TextInput, { disabled: true, value: "Inactive in current theme", "aria-label": `${row.definition.cssName} inactive` }))] }), variants.length > 0 ? (_jsxs("details", { className: "token-variants", "data-test": "token-variants", children: [_jsxs("summary", { children: ["Variants (", variants.length, ")"] }), _jsx("div", { className: "token-variants__list", children: variants.map((declaration) => (_jsxs("div", { className: "token-variant", children: [_jsx("span", { className: "token-variant__context", children: contextLabel(declaration.context) }), _jsx("code", { children: declaration.value }), _jsx("span", { children: declaration.source })] }, `${declaration.source}-${contextLabel(declaration.context)}`))) })] })) : null] }));
47
+ return (_jsxs("article", { className: "token-row", "data-test": "global-token-row", "data-token-name": row.definition.cssName, "data-active": row.activeDeclaration ? "true" : "false", children: [_jsxs("div", { className: "token-row__inline", children: [_jsx(TokenLabel, { as: "code", className: "token-row__name", title: tokenName, children: tokenName }), row.activeDeclaration ? (_jsx(ControlSurface, { children: _jsx(TokenValueField, { property: row.definition.cssName, committedValue: committedValue, resolvedValue: row.resolvedValue || committedValue, activeTokenName: activeAlias, entries: entries, allowedTokenNames: allowed, isColor: row.group === "color", onCommitRaw: (value) => setGlobalTokenValue(row, value), onSelectToken: (token) => { setGlobalTokenValue(row, `var(${token.name})`); }, onUnlink: (value) => setGlobalTokenValue(row, value) }) })) : (_jsx(TextInput, { disabled: true, value: "Inactive in current theme", "aria-label": `${row.definition.cssName} inactive` }))] }), variants.length > 0 ? (_jsx(Disclosure, { className: "token-variants", "data-test": "token-variants", title: `Variants (${variants.length})`, panelClassName: "token-variants__list", children: variants.map((declaration) => (_jsxs("div", { className: "token-variant", children: [_jsx("span", { className: "token-variant__context", children: contextLabel(declaration.context) }), _jsx("code", { children: declaration.value }), _jsx("span", { children: declaration.source })] }, `${declaration.source}-${contextLabel(declaration.context)}`))) })) : null] }));
47
48
  }
@@ -0,0 +1,8 @@
1
+ /** Keeps mouse drags relative, including when the handle is inside a shadow root. */
2
+ export declare function startDragPointerLock(handle: HTMLElement, point: {
3
+ x: number;
4
+ y: number;
5
+ }, onMove: (event: MouseEvent) => void, onEnd: () => void): {
6
+ isLocked: () => boolean;
7
+ stop: () => void;
8
+ };
@@ -0,0 +1,70 @@
1
+ import { showLockedDragCursor } from "../ui/dragCursor.js";
2
+ /** Keeps mouse drags relative, including when the handle is inside a shadow root. */
3
+ export function startDragPointerLock(handle, point, onMove, onEnd) {
4
+ const doc = handle.ownerDocument;
5
+ const root = handle.getRootNode();
6
+ const isLocked = () => root.pointerLockElement === handle;
7
+ let stopped = false;
8
+ let pending = true;
9
+ let acquired = false;
10
+ let removeCursor;
11
+ function removeLockListeners() {
12
+ doc.removeEventListener("pointerlockchange", onLockChange);
13
+ doc.removeEventListener("pointerlockerror", onLockError);
14
+ }
15
+ function onLockChange() {
16
+ if (isLocked()) {
17
+ pending = false;
18
+ acquired = true;
19
+ if (stopped) {
20
+ doc.exitPointerLock();
21
+ removeLockListeners();
22
+ }
23
+ else if (!removeCursor) {
24
+ removeCursor = showLockedDragCursor(doc, point);
25
+ }
26
+ }
27
+ else if (acquired) {
28
+ onEnd();
29
+ }
30
+ }
31
+ function onLockError() {
32
+ pending = false;
33
+ removeLockListeners();
34
+ }
35
+ function onMouseMove(event) {
36
+ if (!stopped && isLocked())
37
+ onMove(event);
38
+ }
39
+ function onMouseUp(event) {
40
+ if (event.button === 0)
41
+ onEnd();
42
+ }
43
+ doc.addEventListener("mousemove", onMouseMove);
44
+ doc.addEventListener("mouseup", onMouseUp);
45
+ doc.addEventListener("pointerlockchange", onLockChange);
46
+ doc.addEventListener("pointerlockerror", onLockError);
47
+ try {
48
+ // Older implementations return void. Rejections retain pointer-capture dragging.
49
+ const request = handle.requestPointerLock();
50
+ if (request)
51
+ void request.catch(onLockError);
52
+ }
53
+ catch {
54
+ onLockError();
55
+ }
56
+ return {
57
+ isLocked,
58
+ stop() {
59
+ stopped = true;
60
+ removeCursor?.();
61
+ doc.removeEventListener("mousemove", onMouseMove);
62
+ doc.removeEventListener("mouseup", onMouseUp);
63
+ if (isLocked())
64
+ doc.exitPointerLock();
65
+ // A request can finish after mouse-up or unmount. Release that late lock too.
66
+ if (!pending)
67
+ removeLockListeners();
68
+ },
69
+ };
70
+ }
@@ -2,7 +2,7 @@ import type { TokenEntry } from "../../css/model/index.js";
2
2
  import type { ElementChangeRecord } from "../changes/changesLog.js";
3
3
  import { type EditTarget } from "../selection/editTarget.js";
4
4
  export type { ChangeRecord } from "../changes/changesLog.js";
5
- export { getPendingRules, getChangesList as getChangeRecords, clearWorkspace as resetPendingRules } from "../changes/changesLog.js";
5
+ export { getPendingRules, getChangesList as getChangeRecords, clearActiveDraft as resetPendingRules } from "../changes/changesLog.js";
6
6
  export interface StyleEditMetadata {
7
7
  sourceProperty?: string;
8
8
  sourceAuthoredValue?: string;
@@ -44,7 +44,7 @@ function sourceFields(el) {
44
44
  column: parsed.column,
45
45
  };
46
46
  }
47
- export { getPendingRules, getChangesList as getChangeRecords, clearWorkspace as resetPendingRules } from "../changes/changesLog.js";
47
+ export { getPendingRules, getChangesList as getChangeRecords, clearActiveDraft as resetPendingRules } from "../changes/changesLog.js";
48
48
  /** Returns whether a style write can address every element in the target safely. */
49
49
  export function canEditStyles(target) {
50
50
  return canPlanBatchEditScopes(targetElements(target));
@@ -1,6 +1,7 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useContext, useMemo } from "react";
3
- import { Tooltip } from "@base-ui/react/tooltip";
3
+ import { Tooltip } from "./Tooltip.js";
4
+ import { cx } from "./classNames.js";
4
5
  const EMPTY_AT_RULES = [];
5
6
  const FieldAtRuleContext = createContext(new Map());
6
7
  /**
@@ -40,9 +41,6 @@ function countLabel(label, count) {
40
41
  const noun = label.toLowerCase().replace(/query$/, count === 1 ? "query" : "queries");
41
42
  return `${count} ${noun}`;
42
43
  }
43
- function portalContainer() {
44
- return document.getElementById("nudge-ui-root")?.shadowRoot ?? document.body;
45
- }
46
44
  /** Compact, field-level attribution for the conditional rule that currently wins. */
47
45
  export function AtRuleIndicator({ atRules = EMPTY_AT_RULES, className }) {
48
46
  if (atRules.length === 0)
@@ -50,5 +48,5 @@ export function AtRuleIndicator({ atRules = EMPTY_AT_RULES, className }) {
50
48
  const label = labelFor(atRules);
51
49
  const activeCount = atRules.filter((atRule) => !("active" in atRule) || atRule.active).length;
52
50
  const count = countLabel(label, atRules.length);
53
- return (_jsx(Tooltip.Provider, { children: _jsxs(Tooltip.Root, { disableHoverablePopup: true, children: [_jsx(Tooltip.Trigger, { type: "button", delay: 0, className: `at-rule-indicator${className ? ` ${className}` : ""}`, "data-test": "at-rule-indicator", "aria-label": activeCount > 0 ? `Active ${count}` : count, children: _jsx("span", { className: "at-rule-indicator__symbol", "aria-hidden": "true", children: atRules.length }) }), _jsx(Tooltip.Portal, { container: portalContainer(), children: _jsx(Tooltip.Positioner, { className: "at-rule-tooltip-positioner", side: "top", align: "end", sideOffset: 7, children: _jsx(Tooltip.Popup, { className: "at-rule-tooltip", "data-test": "at-rule-tooltip", children: _jsx("div", { className: "at-rule-tooltip__rules", children: atRules.map((atRule, index) => (_jsx("div", { className: `at-rule-tooltip__rule${!("active" in atRule) || atRule.active ? " at-rule-tooltip__rule--active" : ""}`, "data-active": !("active" in atRule) || atRule.active ? "true" : "false", children: _jsx("code", { children: atRule.params }) }, `${atRule.kind}-${atRule.params}-${index}`))) }) }) }) })] }) }));
51
+ return (_jsx(Tooltip, { content: (_jsx("div", { className: "at-rule-tooltip__rules", children: atRules.map((atRule, index) => (_jsx("div", { className: cx("at-rule-tooltip__rule", (!("active" in atRule) || atRule.active) && "at-rule-tooltip__rule--active"), "data-active": !("active" in atRule) || atRule.active ? "true" : "false", children: _jsx("code", { children: atRule.params }) }, `${atRule.kind}-${atRule.params}-${index}`))) })), delay: 0, positionerClassName: "at-rule-tooltip-positioner", popupClassName: "at-rule-tooltip", "data-test": "at-rule-tooltip", children: _jsx("button", { type: "button", className: cx("at-rule-indicator", className), "data-test": "at-rule-indicator", "aria-label": activeCount > 0 ? `Active ${count}` : count, children: _jsx("span", { className: "at-rule-indicator__symbol", "aria-hidden": "true", children: atRules.length }) }) }));
54
52
  }
@@ -1,4 +1,4 @@
1
- import type { ButtonHTMLAttributes, ReactElement } from "react";
1
+ import type { ButtonHTMLAttributes } from "react";
2
2
  export type ButtonVariant = "primary" | "secondary" | "quiet" | "danger" | "disabled";
3
3
  export type ButtonSize = "default" | "compact" | "large";
4
4
  export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
@@ -6,4 +6,8 @@ export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
6
6
  size?: ButtonSize;
7
7
  "data-test"?: string;
8
8
  };
9
- export declare function Button({ variant, size, className, ...props }: ButtonProps): ReactElement;
9
+ export declare const Button: import("react").ForwardRefExoticComponent<ButtonHTMLAttributes<HTMLButtonElement> & {
10
+ variant?: ButtonVariant;
11
+ size?: ButtonSize;
12
+ "data-test"?: string;
13
+ } & import("react").RefAttributes<HTMLButtonElement>>;
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- export function Button({ variant = "secondary", size = "default", className, ...props }) {
3
- return (_jsx("button", { ...props, className: `button button--${variant} button--${size}${className ? ` ${className}` : ""}` }));
4
- }
2
+ import { forwardRef } from "react";
3
+ export const Button = forwardRef(function Button({ variant = "secondary", size = "default", className, ...props }, ref) {
4
+ return (_jsx("button", { ref: ref, ...props, className: `button button--${variant} button--${size}${className ? ` ${className}` : ""}` }));
5
+ });
@@ -1,4 +1,4 @@
1
- import type { HTMLAttributes, ReactElement, ReactNode } from "react";
1
+ import type { HTMLAttributes, ReactNode } from "react";
2
2
  /**
3
3
  * Visual boundary for an inspector control. It owns the shared control chrome
4
4
  * while the child owns its particular input or selection behaviour.
@@ -11,4 +11,4 @@ export interface ControlSurfaceProps extends HTMLAttributes<HTMLSpanElement> {
11
11
  "data-test"?: string;
12
12
  children?: ReactNode;
13
13
  }
14
- export declare function ControlSurface({ appearance, density, className, children, ...props }: ControlSurfaceProps): ReactElement;
14
+ export declare const ControlSurface: import("react").ForwardRefExoticComponent<ControlSurfaceProps & import("react").RefAttributes<HTMLSpanElement>>;
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- export function ControlSurface({ appearance = "default", density = "default", className, children, ...props }) {
2
+ import { forwardRef } from "react";
3
+ export const ControlSurface = forwardRef(function ControlSurface({ appearance = "default", density = "default", className, children, ...props }, ref) {
3
4
  const classes = [
4
5
  "control-surface",
5
6
  appearance === "embedded" ? "control-surface--embedded" : "",
6
7
  density === "compact" ? "control-surface--compact" : "",
7
8
  className ?? "",
8
9
  ].filter(Boolean).join(" ");
9
- return _jsx("span", { ...props, className: classes, children: children });
10
- }
10
+ return _jsx("span", { ref: ref, ...props, className: classes, children: children });
11
+ });
@@ -0,0 +1,22 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ export interface DisclosureProps {
3
+ readonly title: ReactNode;
4
+ /** Optional accessory rendered between the label and the chevron (e.g. a count badge). */
5
+ readonly badge?: ReactNode;
6
+ readonly children: ReactNode;
7
+ readonly className?: string;
8
+ readonly triggerClassName?: string;
9
+ readonly panelClassName?: string;
10
+ readonly defaultOpen?: boolean;
11
+ readonly open?: boolean;
12
+ readonly onOpenChange?: (open: boolean) => void;
13
+ /** Keeps panel content in the DOM while collapsed, matching native details behavior. */
14
+ readonly keepMounted?: boolean;
15
+ readonly "data-test"?: string;
16
+ readonly triggerDataTest?: string;
17
+ }
18
+ /**
19
+ * A single collapsible disclosure built on Base UI Collapsible.
20
+ * Replaces native details/summary for consistent 12px label styling and chevron.
21
+ */
22
+ export declare function Disclosure({ title, badge, children, className, triggerClassName, panelClassName, defaultOpen, open, onOpenChange, keepMounted, "data-test": dataTest, triggerDataTest, }: DisclosureProps): ReactElement;