nudge-ui 0.2.0 → 0.2.2

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 (231) 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 +878 -11
  5. package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
  6. package/dist/hosts/astro/clientTransport.js +63 -11
  7. package/dist/hosts/astro/integration.d.ts +2 -2
  8. package/dist/hosts/astro/integration.js +4 -3
  9. package/dist/hosts/environment.d.ts +15 -0
  10. package/dist/hosts/environment.js +16 -0
  11. package/dist/hosts/next/sidecar.d.ts +4 -0
  12. package/dist/hosts/next/sidecar.js +56 -7
  13. package/dist/hosts/next/wrapper.d.ts +2 -0
  14. package/dist/hosts/next/wrapper.js +32 -3
  15. package/dist/hosts/projectBridge.d.ts +21 -0
  16. package/dist/hosts/projectBridge.js +98 -0
  17. package/dist/hosts/static/cli.d.ts +1 -1
  18. package/dist/hosts/static/cli.js +6 -1
  19. package/dist/hosts/static/manifest.d.ts +1 -1
  20. package/dist/hosts/static/manifest.js +3 -1
  21. package/dist/hosts/static/server.js +45 -3
  22. package/dist/hosts/vite/vite.d.ts +6 -1
  23. package/dist/hosts/vite/vite.js +82 -15
  24. package/dist/inspector/agent/McpConnectionDialog.d.ts +5 -4
  25. package/dist/inspector/agent/McpConnectionDialog.js +20 -37
  26. package/dist/inspector/agent/client.d.ts +16 -3
  27. package/dist/inspector/agent/client.js +114 -11
  28. package/dist/inspector/agent/connectionStatus.d.ts +3 -3
  29. package/dist/inspector/agent/connectionStatus.js +11 -4
  30. package/dist/inspector/agent/httpTransport.d.ts +8 -0
  31. package/dist/inspector/agent/httpTransport.js +78 -18
  32. package/dist/inspector/agent/protocol.d.ts +11 -3
  33. package/dist/inspector/agent/protocol.js +1 -1
  34. package/dist/inspector/agent/verification.d.ts +1 -1
  35. package/dist/inspector/agent/verification.js +69 -12
  36. package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
  37. package/dist/inspector/canvas/CanvasCard.js +181 -91
  38. package/dist/inspector/canvas/CanvasElementOverlay.js +288 -52
  39. package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
  40. package/dist/inspector/canvas/CanvasToolbar.js +24 -0
  41. package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
  42. package/dist/inspector/canvas/CanvasWorkspace.js +410 -72
  43. package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
  44. package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
  45. package/dist/inspector/canvas/canvasResize.d.ts +30 -0
  46. package/dist/inspector/canvas/canvasResize.js +52 -0
  47. package/dist/inspector/canvas/canvasStore.d.ts +30 -6
  48. package/dist/inspector/canvas/canvasStore.js +128 -61
  49. package/dist/inspector/canvas/frameProtocol.d.ts +82 -5
  50. package/dist/inspector/canvas/frameProtocol.js +63 -2
  51. package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -0
  52. package/dist/inspector/canvas/keyboardShortcuts.js +16 -0
  53. package/dist/inspector/canvas/linkEligibility.d.ts +0 -5
  54. package/dist/inspector/canvas/linkEligibility.js +0 -29
  55. package/dist/inspector/canvas/projection.d.ts +7 -0
  56. package/dist/inspector/canvas/projection.js +118 -25
  57. package/dist/inspector/canvas/rendererBootstrap.js +42 -29
  58. package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
  59. package/dist/inspector/canvas/rendererElementSelector.js +385 -58
  60. package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
  61. package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
  62. package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
  63. package/dist/inspector/canvas/sessionStore.d.ts +1 -0
  64. package/dist/inspector/canvas/sessionStore.js +12 -4
  65. package/dist/inspector/canvas/staleChangeDetector.js +16 -14
  66. package/dist/inspector/changes/changesLog.js +31 -1
  67. package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
  68. package/dist/inspector/changes/managedStyleProjection.js +3 -3
  69. package/dist/inspector/changes/structuralTypes.d.ts +4 -0
  70. package/dist/inspector/changes/workspaceChanges.d.ts +4 -0
  71. package/dist/inspector/changes/workspaceChanges.js +29 -3
  72. package/dist/inspector/clientManifest.d.ts +7 -0
  73. package/dist/inspector/clientManifest.js +12 -0
  74. package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
  75. package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
  76. package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
  77. package/dist/inspector/index.d.ts +2 -0
  78. package/dist/inspector/index.js +80 -32
  79. package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
  80. package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
  81. package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
  82. package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
  83. package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
  84. package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
  85. package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
  86. package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
  87. package/dist/inspector/inline-text/textEditChange.js +165 -0
  88. package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
  89. package/dist/inspector/inspection/selectionProperty.js +1 -0
  90. package/dist/inspector/overlay/DomNavigator.d.ts +10 -0
  91. package/dist/inspector/overlay/DomNavigator.js +40 -0
  92. package/dist/inspector/overlay/InspectorOverlay.js +6 -2
  93. package/dist/inspector/overlay/clickPolicy.d.ts +18 -2
  94. package/dist/inspector/overlay/clickPolicy.js +34 -3
  95. package/dist/inspector/overlay/elementSelector.d.ts +1 -1
  96. package/dist/inspector/overlay/elementSelector.js +18 -7
  97. package/dist/inspector/overlay/selectedGeometry.js +21 -2
  98. package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
  99. package/dist/inspector/overlay/spacingGestures.js +351 -0
  100. package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
  101. package/dist/inspector/overlay/structuralGestures.js +12 -10
  102. package/dist/inspector/projection/managedStylesheet.js +4 -1
  103. package/dist/inspector/projection/renderedInstance.d.ts +2 -0
  104. package/dist/inspector/projection/renderedInstance.js +5 -1
  105. package/dist/inspector/projection/structuralProjection.d.ts +2 -0
  106. package/dist/inspector/projection/structuralProjection.js +85 -1
  107. package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
  108. package/dist/inspector/projection/textProjection.d.ts +2 -0
  109. package/dist/inspector/projection/textProjection.js +10 -4
  110. package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
  111. package/dist/inspector/prompt/clipboardHandoff.js +3 -3
  112. package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
  113. package/dist/inspector/prompt/generatePrompt.js +19 -2
  114. package/dist/inspector/runtime/editorShell.d.ts +2 -0
  115. package/dist/inspector/runtime/editorShell.js +4 -0
  116. package/dist/inspector/runtime/runtimeConfig.d.ts +5 -1
  117. package/dist/inspector/runtime/runtimeConfig.js +4 -0
  118. package/dist/inspector/selection/hierarchy.d.ts +6 -0
  119. package/dist/inspector/selection/hierarchy.js +8 -0
  120. package/dist/inspector/selection/selectionStore.js +2 -0
  121. package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
  122. package/dist/inspector/settings/SettingsDialog.js +12 -2
  123. package/dist/inspector/shell/AppShell.js +5 -1
  124. package/dist/inspector/shell/ChangesLog.js +26 -22
  125. package/dist/inspector/shell/CopyPromptButton.js +102 -19
  126. package/dist/inspector/shell/EmptyState.js +1 -1
  127. package/dist/inspector/shell/InspectorShell.d.ts +0 -1
  128. package/dist/inspector/shell/InspectorShell.js +38 -42
  129. package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
  130. package/dist/inspector/shell/PeekOriginalButton.js +57 -0
  131. package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
  132. package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
  133. package/dist/inspector/shell/originalPreview.d.ts +9 -0
  134. package/dist/inspector/shell/originalPreview.js +61 -0
  135. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  136. package/dist/inspector/shell/shortcuts.js +15 -0
  137. package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
  138. package/dist/inspector/sketch/SketchChanges.js +72 -0
  139. package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
  140. package/dist/inspector/sketch/SketchEditor.js +402 -0
  141. package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
  142. package/dist/inspector/sketch/SketchEntryButton.js +11 -0
  143. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
  144. package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
  145. package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
  146. package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
  147. package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
  148. package/dist/inspector/sketch/SketchOverlay.js +420 -0
  149. package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
  150. package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
  151. package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
  152. package/dist/inspector/sketch/SketchWorkspace.js +152 -0
  153. package/dist/inspector/sketch/capture.d.ts +55 -0
  154. package/dist/inspector/sketch/capture.js +350 -0
  155. package/dist/inspector/sketch/freehand.d.ts +13 -0
  156. package/dist/inspector/sketch/freehand.js +64 -0
  157. package/dist/inspector/sketch/geometry.d.ts +17 -0
  158. package/dist/inspector/sketch/geometry.js +98 -0
  159. package/dist/inspector/sketch/handoff.d.ts +21 -0
  160. package/dist/inspector/sketch/handoff.js +72 -0
  161. package/dist/inspector/sketch/interaction.d.ts +21 -0
  162. package/dist/inspector/sketch/interaction.js +98 -0
  163. package/dist/inspector/sketch/model.d.ts +104 -0
  164. package/dist/inspector/sketch/model.js +282 -0
  165. package/dist/inspector/sketch/persistence.d.ts +21 -0
  166. package/dist/inspector/sketch/persistence.js +205 -0
  167. package/dist/inspector/sketch/raster.d.ts +22 -0
  168. package/dist/inspector/sketch/raster.js +203 -0
  169. package/dist/inspector/sketch/sketchNote.d.ts +10 -0
  170. package/dist/inspector/sketch/sketchNote.js +30 -0
  171. package/dist/inspector/sketch/store.d.ts +47 -0
  172. package/dist/inspector/sketch/store.js +434 -0
  173. package/dist/inspector/styleEditors/BorderEditor.js +15 -19
  174. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
  175. package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
  176. package/dist/inspector/styleEditors/ColorPicker.js +11 -36
  177. package/dist/inspector/styleEditors/GapField.d.ts +3 -2
  178. package/dist/inspector/styleEditors/GapField.js +2 -2
  179. package/dist/inspector/styleEditors/GridSection.js +1 -1
  180. package/dist/inspector/styleEditors/LayoutSection.js +11 -8
  181. package/dist/inspector/styleEditors/SpacingBox.js +31 -33
  182. package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
  183. package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
  184. package/dist/inspector/styleEditors/layoutValue.js +3 -0
  185. package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
  186. package/dist/inspector/styleEditors/nudgeValue.js +93 -5
  187. package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
  188. package/dist/inspector/styleEditors/stylePresence.js +22 -0
  189. package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
  190. package/dist/inspector/styleEditors/textRelevance.js +35 -0
  191. package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
  192. package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
  193. package/dist/inspector/tokens/TokenField.js +130 -7
  194. package/dist/inspector/tokens/TokensPanel.js +2 -1
  195. package/dist/inspector/tokens/dragPointerLock.d.ts +8 -0
  196. package/dist/inspector/tokens/dragPointerLock.js +70 -0
  197. package/dist/inspector/tokens/editActions.d.ts +2 -0
  198. package/dist/inspector/tokens/editActions.js +2 -2
  199. package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
  200. package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
  201. package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
  202. package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
  203. package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
  204. package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
  205. package/dist/inspector/tokens/resolution.d.ts +3 -26
  206. package/dist/inspector/tokens/resolution.js +44 -735
  207. package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
  208. package/dist/inspector/tokens/tokenAvailability.js +204 -0
  209. package/dist/inspector/ui/Button.d.ts +1 -1
  210. package/dist/inspector/ui/Disclosure.d.ts +22 -0
  211. package/dist/inspector/ui/Disclosure.js +13 -0
  212. package/dist/inspector/ui/SideValuesField.js +2 -2
  213. package/dist/inspector/ui/StatusCallout.d.ts +4 -1
  214. package/dist/inspector/ui/StatusCallout.js +2 -2
  215. package/dist/inspector/ui/dragCursor.d.ts +8 -0
  216. package/dist/inspector/ui/dragCursor.js +21 -0
  217. package/dist/inspector/ui/styles.js +21 -17
  218. package/dist/internal/agent-protocol.d.ts +65 -1
  219. package/dist/internal/agent-protocol.js +183 -3
  220. package/dist/nudge-ui.mjs +276 -58
  221. package/dist/transport/editor.d.ts +47 -0
  222. package/dist/transport/editor.js +163 -0
  223. package/dist/transport/index.d.ts +1 -0
  224. package/dist/transport/index.js +1 -0
  225. package/dist/transport/routes.d.ts +10 -0
  226. package/dist/transport/routes.js +10 -0
  227. package/package.json +3 -1
  228. package/dist/inspector/shell/DomNavigation.d.ts +0 -8
  229. package/dist/inspector/shell/DomNavigation.js +0 -30
  230. package/dist/inspector/shell/panelLayout.d.ts +0 -2
  231. package/dist/inspector/shell/panelLayout.js +0 -29
@@ -1,11 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useMemo, useRef, useState } from "react";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { IconLinkOff } from "@tabler/icons-react";
4
4
  import { applyValueEdit, normalizeOpacityPercent, selectTokens, } from "../../css/value-semantics/index.js";
5
5
  import { promoteToToken, swapToken } from "./editActions.js";
6
6
  import { setStyle } from "./editActions.js";
7
7
  import { completeCssValue } from "../styleEditors/completeCssValue.js";
8
- import { nudgeCssValue, nudgeOpacityValue } from "../styleEditors/nudgeValue.js";
8
+ import { canNudgeCssValueByDrag, nudgeCssValue, nudgeCssValueByDrag, nudgeOpacityValue, supportsDragNudge, } from "../styleEditors/nudgeValue.js";
9
9
  import { valuePolicyFor } from "../styleEditors/valuePolicy.js";
10
10
  import { IconButton } from "../ui/IconButton.js";
11
11
  import { PopoverListbox } from "../ui/PopoverListbox.js";
@@ -13,6 +13,8 @@ import { ColorSwatch } from "../ui/ColorSwatch.js";
13
13
  import { getStateStyleValue } from "../shell/stateValue.js";
14
14
  import { AtRuleIndicator, useFieldAtRules } from "../ui/AtRuleContext.js";
15
15
  import { TokenChip } from "./TokenChip.js";
16
+ import { startDragPointerLock } from "./dragPointerLock.js";
17
+ import { createChangeHistoryGroup } from "../changes/workspaceChanges.js";
16
18
  import { isMultiTarget } from "../selection/editTarget.js";
17
19
  const NON_COLOR_FACTS = { hasEmbeddedAlpha: false, isExpression: false, opacityEditable: false };
18
20
  function colorFacts(value) {
@@ -61,6 +63,16 @@ function formatNumber(value) {
61
63
  function stripCssUnit(value) {
62
64
  return value.replace(/^(-?\d+(?:\.\d+)?)(px|rem|em|vh|vw|vmin|vmax|%|ch|ex|cm|mm|in|pt|pc)$/i, "$1");
63
65
  }
66
+ function releasePointerCapture(state) {
67
+ try {
68
+ if (state.handle.hasPointerCapture?.(state.pointerId)) {
69
+ state.handle.releasePointerCapture(state.pointerId);
70
+ }
71
+ }
72
+ catch {
73
+ // The browser may release capture before pointerup/pointercancel arrives.
74
+ }
75
+ }
64
76
  /** Returns the first family in a CSS family list without splitting var() fallbacks. */
65
77
  function primaryFontFamily(value) {
66
78
  let depth = 0;
@@ -138,8 +150,9 @@ function browserRecognizesColor(value) {
138
150
  function NativeColorSwatch({ value, onChange, disabled, }) {
139
151
  const resolvedHex = colorValueToHex(value);
140
152
  const hex = resolvedHex ?? "#000000";
141
- const hasRenderableColor = resolvedHex !== null || browserRecognizesColor(value);
142
- return (_jsxs("label", { className: "token-color-control", "data-resolved": hasRenderableColor ? "true" : "false", children: [_jsx(ColorSwatch, { color: resolvedHex ?? (value.trim() || "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) })] }));
153
+ const cssColor = browserRecognizesColor(value) ? value.trim() : resolvedHex;
154
+ const hasRenderableColor = cssColor !== null;
155
+ 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) })] }));
143
156
  }
144
157
  export function TokenValueField(props) {
145
158
  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;
@@ -164,6 +177,8 @@ export function TokenValueField(props) {
164
177
  const selectedFromPopover = useRef(false);
165
178
  const cancelOnBlur = useRef(false);
166
179
  const isNavigatingSuggestions = useRef(false);
180
+ const dragNudgeRef = useRef(null);
181
+ const [isDraggingNudge, setIsDraggingNudge] = useState(false);
167
182
  useEffect(() => {
168
183
  setRawValue(committedValue);
169
184
  setActiveTokenName(controlledTokenName);
@@ -301,6 +316,106 @@ export function TokenValueField(props) {
301
316
  inputRef.current?.blur();
302
317
  }
303
318
  }
319
+ const stopDragNudge = useCallback(() => {
320
+ const drag = dragNudgeRef.current;
321
+ if (!drag)
322
+ return;
323
+ dragNudgeRef.current = null;
324
+ drag.pointerLock?.stop();
325
+ releasePointerCapture(drag);
326
+ setIsDraggingNudge(false);
327
+ }, []);
328
+ useEffect(() => {
329
+ const stopOnWindowBlur = () => stopDragNudge();
330
+ window.addEventListener("blur", stopOnWindowBlur);
331
+ return () => {
332
+ window.removeEventListener("blur", stopOnWindowBlur);
333
+ stopDragNudge();
334
+ };
335
+ }, [stopDragNudge]);
336
+ function handleDragNudgePointerDown(event) {
337
+ if (event.button !== 0)
338
+ return;
339
+ if (disabled || !supportsDragNudge(property))
340
+ return;
341
+ if (!canNudgeCssValueByDrag(property, rawValue))
342
+ return;
343
+ stopDragNudge();
344
+ const handle = event.currentTarget;
345
+ const drag = {
346
+ commit: createChangeHistoryGroup(),
347
+ handle,
348
+ pointerId: event.pointerId,
349
+ lastClientX: event.clientX,
350
+ deltaX: 0,
351
+ startValue: rawValue,
352
+ lastValue: rawValue,
353
+ shiftActive: event.shiftKey,
354
+ shiftSnapPending: event.shiftKey,
355
+ };
356
+ dragNudgeRef.current = drag;
357
+ if (typeof handle.setPointerCapture === "function")
358
+ handle.setPointerCapture(event.pointerId);
359
+ if (event.pointerType === "mouse") {
360
+ drag.pointerLock = startDragPointerLock(handle, { x: event.clientX, y: event.clientY }, (mouseEvent) => {
361
+ updateDragNudge(mouseEvent, mouseEvent.movementX);
362
+ }, stopDragNudge);
363
+ }
364
+ setIsDraggingNudge(true);
365
+ event.preventDefault();
366
+ event.stopPropagation();
367
+ }
368
+ function handleDragNudgePointerMove(event) {
369
+ const drag = dragNudgeRef.current;
370
+ if (!drag || drag.pointerId !== event.pointerId)
371
+ return;
372
+ if (drag.pointerLock?.isLocked())
373
+ return;
374
+ const movementX = event.clientX - drag.lastClientX;
375
+ drag.lastClientX = event.clientX;
376
+ updateDragNudge(event, movementX);
377
+ }
378
+ function updateDragNudge(event, movementX) {
379
+ const drag = dragNudgeRef.current;
380
+ if (!drag)
381
+ return;
382
+ if (event.shiftKey !== drag.shiftActive) {
383
+ drag.shiftActive = event.shiftKey;
384
+ drag.startValue = drag.lastValue;
385
+ drag.deltaX = 0;
386
+ drag.shiftSnapPending = event.shiftKey;
387
+ }
388
+ drag.deltaX += movementX;
389
+ const next = nudgeCssValueByDrag(property, drag.startValue, drag.deltaX, drag.shiftActive, drag.shiftActive && drag.shiftSnapPending);
390
+ if (next === null || next === drag.lastValue)
391
+ return;
392
+ drag.lastValue = next;
393
+ if (drag.shiftActive && drag.shiftSnapPending) {
394
+ drag.startValue = next;
395
+ drag.deltaX = 0;
396
+ drag.shiftSnapPending = false;
397
+ }
398
+ event.preventDefault();
399
+ event.stopPropagation();
400
+ setRawValue(next);
401
+ setActiveTokenName(null);
402
+ drag.commit(() => onCommitRaw(next));
403
+ }
404
+ function handleDragNudgePointerEnd(event) {
405
+ if (dragNudgeRef.current?.pointerId !== event.pointerId)
406
+ return;
407
+ stopDragNudge();
408
+ }
409
+ function renderLeading() {
410
+ if (!leading)
411
+ return null;
412
+ const canDrag = !disabled
413
+ && supportsDragNudge(property)
414
+ && canNudgeCssValueByDrag(property, rawValue);
415
+ if (!canDrag)
416
+ return _jsx("span", { className: "token-field__leading", children: leading });
417
+ 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 }));
418
+ }
304
419
  function commitOpacityValue(value = opacityValue) {
305
420
  if (!showOpacity || !onCommitOpacity)
306
421
  return;
@@ -343,12 +458,12 @@ export function TokenValueField(props) {
343
458
  if (activeToken) {
344
459
  const chipValue = chipVariant === "small" ? stripCssUnit(activeToken.value) : activeToken.name;
345
460
  const embedColorSwatch = isColor && chipVariant !== "small";
346
- 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: [leading ? _jsx("span", { className: "token-field__leading", children: leading }) : null, 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) => {
461
+ 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) => {
347
462
  handleSuggestionSelect(value);
348
463
  } }), _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] }));
349
464
  }
350
465
  const showPopover = isFocused && (filteredTokens.length > 0 || availableSuggestions.length > 0);
351
- 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: [leading ? _jsx("span", { className: "token-field__leading", children: leading }) : null, colorControlEl, _jsx(PopoverListbox, { query: rawValue, value: null, open: showPopover, placeholder: property, inputRef: inputRef, inputAppearance: "embedded", inputDataTest: inputDataTest, inputOnFocus: () => {
466
+ 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: () => {
352
467
  setIsFocused(true);
353
468
  if (mixed && rawValue.trim().toLowerCase() === "mixed")
354
469
  setRawValue("");
@@ -417,7 +532,15 @@ export function TokenField(props) {
417
532
  const currentToken = activeTokenName
418
533
  ? entries.find((entry) => entry.name === activeTokenName) ?? null
419
534
  : null;
420
- return (_jsx(TokenValueField, { property: property, domElement: el, semanticSlot: semanticSlot, committedValue: committedValue, resolvedValue: tokenRow?.propertyOpacity?.value ?? tokenRow?.resolvedValue ?? committedValue, activeTokenName: activeTokenName, attributionTokens: attributionTokens
535
+ let resolvedValue = tokenRow?.propertyOpacity?.value ?? tokenRow?.resolvedValue ?? committedValue;
536
+ if (property === "background-color") {
537
+ // Background rows can contain the complete computed shorthand. Extract
538
+ // its color before passing it to the color control, preserving alpha.
539
+ const style = el.ownerDocument.createElement("span").style;
540
+ style.background = resolvedValue;
541
+ resolvedValue = style.backgroundColor || resolvedValue;
542
+ }
543
+ return (_jsx(TokenValueField, { property: property, domElement: el, semanticSlot: semanticSlot, committedValue: committedValue, resolvedValue: resolvedValue, activeTokenName: activeTokenName, attributionTokens: attributionTokens
421
544
  ?? (selectedProperty?.token.kind === "mixed" ? ["Mixed tokens"] : undefined)
422
545
  ?? (expression
423
546
  ? tokenRow?.tokens?.filter((token) => token.name !== tokenRow.opacity?.tokenName).map((token) => token.name)
@@ -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
+ }
@@ -22,6 +22,8 @@ export declare function swapTokens(target: EditTarget, changes: readonly TokenSw
22
22
  export interface StyleDeclaration {
23
23
  property: string;
24
24
  value: string;
25
+ /** Optional baseline captured before a transient preview changed the DOM. */
26
+ oldRawValue?: string;
25
27
  metadata?: StyleEditMetadata;
26
28
  }
27
29
  export interface ElementStyleEdit {
@@ -135,7 +135,7 @@ export function setStyles(target, declarations, metadata) {
135
135
  export function setElementStyles(edits) {
136
136
  return commitElementStyles(edits);
137
137
  }
138
- function buildStyleRecord(el, scopePlan, { property, value, metadata }) {
138
+ function buildStyleRecord(el, scopePlan, { property, value, oldRawValue, metadata }) {
139
139
  const cid = el.getAttribute("data-cid") ?? "";
140
140
  const { selector, state } = stateFields(el);
141
141
  if (!selector)
@@ -152,7 +152,7 @@ function buildStyleRecord(el, scopePlan, { property, value, metadata }) {
152
152
  oldToken: null,
153
153
  newToken: null,
154
154
  rawValue: value,
155
- oldRawValue: getStateStyleValue(el, property) || undefined,
155
+ oldRawValue: (oldRawValue ?? getStateStyleValue(el, property)) || undefined,
156
156
  source: { file: source.file, line: source.line, component: cid },
157
157
  runtimeEvidence: source.runtimeEvidence,
158
158
  state,
@@ -15,9 +15,9 @@ export declare function subscribeDocumentRevision(doc: Document, cb: (revisions:
15
15
  * the element revision. Registration is idempotent.
16
16
  */
17
17
  export declare function registerResolutionElement(el: Element): void;
18
+ export declare function registerResolutionLineage(element: HTMLElement): void;
18
19
  /** Releases the observer, listener, and cached state owned by one document. */
19
20
  export declare function disposeDocumentResolution(doc: Document): void;
20
- /** Keeps one document's CSSOM observer alive for a browser inspection owner. */
21
21
  export declare function retainDocumentResolution(doc: Document): () => void;
22
22
  export declare function documentRevisions(doc: Document): DocumentRevisions;
23
23
  /** Invalidates CSSOM-derived snapshots after programmatic stylesheet edits. */
@@ -39,6 +39,13 @@ export function subscribeDocumentRevision(doc, cb) {
39
39
  export function registerResolutionElement(el) {
40
40
  registeredElements.add(el);
41
41
  }
42
+ export function registerResolutionLineage(element) {
43
+ let current = element;
44
+ while (current) {
45
+ registerResolutionElement(current);
46
+ current = current.parentElement;
47
+ }
48
+ }
42
49
  /** Releases the observer, listener, and cached state owned by one document. */
43
50
  export function disposeDocumentResolution(doc) {
44
51
  const session = documentResolutionSessions.get(doc);
@@ -54,9 +61,8 @@ export function disposeDocumentResolution(doc) {
54
61
  }
55
62
  documentResolutionReferences.delete(doc);
56
63
  // Keep revision counters monotonic per Document. Resetting them to (0,0)
57
- // resurrects stale revision-keyed caches in tokens/resolution.ts
58
- // (stableTokenCache, stateResolutionSnapshots, tokenEntriesCache,
59
- // concreteElementMatchCaches, sourceSiteMatchCaches): an entry written at
64
+ // resurrects stale revision-keyed caches in property resolution, token
65
+ // availability, and rule matching: an entry written at
60
66
  // (0,0) before disposal matches immediately after disposal at (0,0), even
61
67
  // though the CSSOM changed while no observer was attached. Bump instead so
62
68
  // every pre-disposal cache entry misses, then let the next
@@ -73,7 +79,6 @@ export function disposeDocumentResolution(doc) {
73
79
  documentRevisionListeners.get(doc)?.clear();
74
80
  documentRevisionListeners.delete(doc);
75
81
  }
76
- /** Keeps one document's CSSOM observer alive for a browser inspection owner. */
77
82
  export function retainDocumentResolution(doc) {
78
83
  documentRevisions(doc);
79
84
  const session = documentResolutionSessions.get(doc);
@@ -113,7 +118,6 @@ function changesStylesheet(record) {
113
118
  || Array.from(record.addedNodes).some(isStylesheetNode)
114
119
  || Array.from(record.removedNodes).some(isStylesheetNode);
115
120
  }
116
- /** A node that is, or lives inside, one of the tool's own probe elements. */
117
121
  function isProbeNode(node) {
118
122
  let current = node;
119
123
  while (current && current.nodeType !== current.DOCUMENT_NODE) {
@@ -0,0 +1,40 @@
1
+ import type { MatchedRule } from "../../../css/model/index.js";
2
+ import { type InteractionState } from "../../shell/styleState.js";
3
+ export interface ElementMatch {
4
+ rule: MatchedRule;
5
+ /** The selector actually used for matching (state-stripped for interaction states). */
6
+ selectorText: string;
7
+ branch: string;
8
+ specificity: number;
9
+ }
10
+ /**
11
+ * The selector set used by a resolution. Interaction states strip or drop
12
+ * pseudo-class rules, `live` keeps the raw CSSOM rules (used by
13
+ * `getResolvedProperties`), and `stable` drops transient rules (used by
14
+ * `getResolvedPropertiesStable`).
15
+ */
16
+ type CascadeTransform = InteractionState | "live" | "stable";
17
+ /** Test-only cache reset. */
18
+ export declare function resetSourceSiteMatchCache(): void;
19
+ /** Test-only cache size. */
20
+ export declare function sourceSiteMatchCacheSize(): number;
21
+ export declare function matchRuleForElement(el: HTMLElement, entry: {
22
+ rule: MatchedRule;
23
+ selectorText: string;
24
+ }): ElementMatch | null;
25
+ /**
26
+ * Shares dynamic matches across cache validation and ancestry within one sweep.
27
+ * Element-relative conditions, such as container queries, remain the resolver's
28
+ * responsibility; these results describe selector matches and stylesheet activity.
29
+ */
30
+ export interface RuleMatcher {
31
+ dynamicMatchKey(element: HTMLElement): string;
32
+ matchesForElement(element: HTMLElement): {
33
+ matched: ElementMatch[];
34
+ allMatched: ElementMatch[];
35
+ };
36
+ }
37
+ /** Creates a matcher for one CSSOM snapshot. */
38
+ export declare function createRuleMatcher(rules: MatchedRule[], transform: CascadeTransform): RuleMatcher;
39
+ export declare function getAvailableInteractionStates(el: HTMLElement): InteractionState[];
40
+ export {};