nudge-ui 0.2.2 → 0.2.4

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 (244) hide show
  1. package/README.md +164 -46
  2. package/dist/client.mjs +260 -61
  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 +38 -32
  7. package/dist/hosts/astro/integration.js +5 -1
  8. package/dist/hosts/astro/markdownIdentity.d.ts +28 -0
  9. package/dist/hosts/astro/markdownIdentity.js +81 -0
  10. package/dist/hosts/next/manifest.js +1 -1
  11. package/dist/hosts/next/sidecar.js +5 -1
  12. package/dist/hosts/static/manifest.js +1 -1
  13. package/dist/hosts/static/server.js +3 -0
  14. package/dist/hosts/vite/vite.d.ts +3 -0
  15. package/dist/hosts/vite/vite.js +24 -4
  16. package/dist/inspector/agent/McpConnectionDialog.js +13 -19
  17. package/dist/inspector/agent/activity.d.ts +18 -0
  18. package/dist/inspector/agent/activity.js +70 -0
  19. package/dist/inspector/agent/client.d.ts +2 -0
  20. package/dist/inspector/agent/client.js +29 -1
  21. package/dist/inspector/agent/connectionStatus.js +1 -1
  22. package/dist/inspector/agent/httpTransport.js +9 -2
  23. package/dist/inspector/agent/protocol.d.ts +6 -3
  24. package/dist/inspector/agent/protocol.js +1 -1
  25. package/dist/inspector/agent/verification.d.ts +15 -7
  26. package/dist/inspector/agent/verification.js +113 -41
  27. package/dist/inspector/canvas/AgentActivityOverlay.d.ts +13 -0
  28. package/dist/inspector/canvas/AgentActivityOverlay.js +111 -0
  29. package/dist/inspector/canvas/CanvasCard.d.ts +9 -2
  30. package/dist/inspector/canvas/CanvasCard.js +135 -38
  31. package/dist/inspector/canvas/CanvasElementOverlay.js +166 -16
  32. package/dist/inspector/canvas/CanvasToolIcons.d.ts +10 -0
  33. package/dist/inspector/canvas/CanvasToolIcons.js +20 -0
  34. package/dist/inspector/canvas/CanvasToolbar.js +8 -4
  35. package/dist/inspector/canvas/CanvasWorkspace.js +270 -122
  36. package/dist/inspector/canvas/StaleChangeIndicator.js +3 -13
  37. package/dist/inspector/canvas/agentPresentation.d.ts +8 -8
  38. package/dist/inspector/canvas/agentPresentation.js +64 -52
  39. package/dist/inspector/canvas/canvasGestures.d.ts +1 -1
  40. package/dist/inspector/canvas/canvasGestures.js +1 -1
  41. package/dist/inspector/canvas/canvasStore.d.ts +46 -82
  42. package/dist/inspector/canvas/canvasStore.js +251 -284
  43. package/dist/inspector/canvas/frameContent.d.ts +21 -0
  44. package/dist/inspector/canvas/frameContent.js +44 -0
  45. package/dist/inspector/canvas/frameProtocol.d.ts +21 -4
  46. package/dist/inspector/canvas/frameProtocol.js +3 -3
  47. package/dist/inspector/canvas/frameScrollbarStyles.d.ts +3 -0
  48. package/dist/inspector/canvas/frameScrollbarStyles.js +21 -0
  49. package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -1
  50. package/dist/inspector/canvas/keyboardShortcuts.js +4 -4
  51. package/dist/inspector/canvas/layout.d.ts +52 -0
  52. package/dist/inspector/canvas/layout.js +106 -0
  53. package/dist/inspector/canvas/projection.d.ts +15 -5
  54. package/dist/inspector/canvas/projection.js +69 -13
  55. package/dist/inspector/canvas/rendererBootstrap.js +32 -4
  56. package/dist/inspector/canvas/rendererElementSelector.js +47 -6
  57. package/dist/inspector/canvas/rendererStylesheet.js +2 -2
  58. package/dist/inspector/canvas/resizeHandleScale.js +1 -1
  59. package/dist/inspector/canvas/sessionStore.d.ts +11 -26
  60. package/dist/inspector/canvas/sessionStore.js +43 -183
  61. package/dist/inspector/canvas/staleChangeDetector.js +9 -3
  62. package/dist/inspector/canvas/useCanvasLayoutAnimation.d.ts +5 -0
  63. package/dist/inspector/canvas/useCanvasLayoutAnimation.js +107 -0
  64. package/dist/inspector/canvas/viewStore.d.ts +38 -0
  65. package/dist/inspector/canvas/viewStore.js +104 -0
  66. package/dist/inspector/canvas/wheelPan.d.ts +8 -0
  67. package/dist/inspector/canvas/wheelPan.js +8 -0
  68. package/dist/inspector/changes/changesLog.d.ts +9 -6
  69. package/dist/inspector/changes/changesLog.js +48 -32
  70. package/dist/inspector/changes/codecs.d.ts +1 -0
  71. package/dist/inspector/changes/codecs.js +10 -1
  72. package/dist/inspector/changes/draftChanges.d.ts +58 -0
  73. package/dist/inspector/changes/draftChanges.js +304 -0
  74. package/dist/inspector/changes/editModel.d.ts +10 -0
  75. package/dist/inspector/changes/editModel.js +9 -2
  76. package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
  77. package/dist/inspector/changes/previewDiagnostics.js +11 -25
  78. package/dist/inspector/changes/types.d.ts +7 -0
  79. package/dist/inspector/clientManifest.d.ts +1 -1
  80. package/dist/inspector/comments/CommentsOverlay.d.ts +2 -0
  81. package/dist/inspector/comments/CommentsOverlay.js +250 -0
  82. package/dist/inspector/comments/element.d.ts +6 -0
  83. package/dist/inspector/comments/element.js +54 -0
  84. package/dist/inspector/comments/store.d.ts +29 -0
  85. package/dist/inspector/comments/store.js +121 -0
  86. package/dist/inspector/comments/verification.d.ts +5 -0
  87. package/dist/inspector/comments/verification.js +52 -0
  88. package/dist/inspector/componentSemantics/adapterRegistry.d.ts +6 -1
  89. package/dist/inspector/componentSemantics/adapterRegistry.js +23 -1
  90. package/dist/inspector/componentSemantics/reactRuntime.d.ts +2 -1
  91. package/dist/inspector/componentSemantics/reactRuntime.js +38 -0
  92. package/dist/inspector/componentSemantics/runtimeBridge.d.ts +4 -2
  93. package/dist/inspector/componentSemantics/runtimeBridge.js +17 -0
  94. package/dist/inspector/componentSemantics/types.d.ts +11 -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 +20 -27
  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 +174 -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.js +21 -15
  111. package/dist/inspector/overlay/interactionStyles.js +3 -1
  112. package/dist/inspector/overlay/spacingGestures.d.ts +13 -1
  113. package/dist/inspector/overlay/spacingGestures.js +82 -5
  114. package/dist/inspector/projection/{workspaceProjection.d.ts → draftProjection.d.ts} +6 -6
  115. package/dist/inspector/projection/{workspaceProjection.js → draftProjection.js} +4 -4
  116. package/dist/inspector/projection/renderedInstance.d.ts +17 -3
  117. package/dist/inspector/projection/renderedInstance.js +62 -11
  118. package/dist/inspector/projection/structuralProjection.d.ts +2 -1
  119. package/dist/inspector/projection/structuralProjection.js +21 -17
  120. package/dist/inspector/projection/textProjection.d.ts +2 -1
  121. package/dist/inspector/projection/textProjection.js +64 -15
  122. package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
  123. package/dist/inspector/prompt/clipboardHandoff.d.ts +15 -18
  124. package/dist/inspector/prompt/clipboardHandoff.js +97 -143
  125. package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
  126. package/dist/inspector/prompt/generatePrompt.js +22 -17
  127. package/dist/inspector/runtime/runtimeConfig.d.ts +9 -0
  128. package/dist/inspector/runtime/runtimeConfig.js +24 -0
  129. package/dist/inspector/selection/sourceSite.d.ts +12 -0
  130. package/dist/inspector/selection/sourceSite.js +28 -0
  131. package/dist/inspector/settings/SettingsDialog.js +21 -8
  132. package/dist/inspector/shell/AgentStatusButton.d.ts +11 -0
  133. package/dist/inspector/shell/AgentStatusButton.js +36 -0
  134. package/dist/inspector/shell/AppShell.js +4 -2
  135. package/dist/inspector/shell/ChangesLog.js +46 -26
  136. package/dist/inspector/shell/CopyPromptButton.js +184 -93
  137. package/dist/inspector/shell/EmptyState.js +9 -8
  138. package/dist/inspector/shell/InspectorShell.js +13 -21
  139. package/dist/inspector/shell/PeekOriginalButton.js +24 -23
  140. package/dist/inspector/shell/originalPreview.js +5 -5
  141. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  142. package/dist/inspector/shell/shortcuts.js +11 -0
  143. package/dist/inspector/sketch/SketchEditor.js +14 -14
  144. package/dist/inspector/sketch/SketchEntryButton.js +1 -1
  145. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +3 -1
  146. package/dist/inspector/sketch/SketchFrameOverlay.js +11 -8
  147. package/dist/inspector/sketch/SketchLayersPanel.js +13 -5
  148. package/dist/inspector/sketch/SketchOverlay.js +7 -7
  149. package/dist/inspector/sketch/SketchWorkspace.js +18 -12
  150. package/dist/inspector/sketch/interaction.d.ts +1 -0
  151. package/dist/inspector/sketch/interaction.js +8 -4
  152. package/dist/inspector/sketch/model.d.ts +5 -0
  153. package/dist/inspector/sketch/model.js +6 -2
  154. package/dist/inspector/sketch/raster.d.ts +1 -2
  155. package/dist/inspector/sketch/raster.js +68 -4
  156. package/dist/inspector/styleEditors/BackgroundEditor.d.ts +4 -0
  157. package/dist/inspector/styleEditors/BackgroundEditor.js +195 -0
  158. package/dist/inspector/styleEditors/BorderEditor.js +49 -78
  159. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -1
  160. package/dist/inspector/styleEditors/BoxShadowEditor.d.ts +1 -2
  161. package/dist/inspector/styleEditors/BoxShadowEditor.js +132 -4
  162. package/dist/inspector/styleEditors/ColorPicker.d.ts +5 -1
  163. package/dist/inspector/styleEditors/ColorPicker.js +10 -4
  164. package/dist/inspector/styleEditors/GapField.js +3 -5
  165. package/dist/inspector/styleEditors/GridChildSection.d.ts +1 -1
  166. package/dist/inspector/styleEditors/GridChildSection.js +18 -9
  167. package/dist/inspector/styleEditors/GridPicker.d.ts +1 -1
  168. package/dist/inspector/styleEditors/GridPicker.js +8 -2
  169. package/dist/inspector/styleEditors/GridSection.js +1 -1
  170. package/dist/inspector/styleEditors/GridValueField.js +19 -14
  171. package/dist/inspector/styleEditors/ImageBackgroundEditor.d.ts +18 -0
  172. package/dist/inspector/styleEditors/ImageBackgroundEditor.js +118 -0
  173. package/dist/inspector/styleEditors/LayoutBlockedIndicator.d.ts +2 -2
  174. package/dist/inspector/styleEditors/LayoutBlockedIndicator.js +3 -3
  175. package/dist/inspector/styleEditors/LayoutComboField.js +6 -4
  176. package/dist/inspector/styleEditors/LayoutDropdown.js +6 -3
  177. package/dist/inspector/styleEditors/LayoutSection.js +32 -11
  178. package/dist/inspector/styleEditors/SpacingBox.js +7 -5
  179. package/dist/inspector/styleEditors/Typography.js +11 -3
  180. package/dist/inspector/styleEditors/backgroundLayers.d.ts +24 -0
  181. package/dist/inspector/styleEditors/backgroundLayers.js +51 -0
  182. package/dist/inspector/styleEditors/gradientValue.d.ts +17 -0
  183. package/dist/inspector/styleEditors/gradientValue.js +114 -0
  184. package/dist/inspector/styleEditors/inlineAuthored.d.ts +10 -0
  185. package/dist/inspector/styleEditors/inlineAuthored.js +114 -0
  186. package/dist/inspector/styleEditors/layoutValue.d.ts +0 -8
  187. package/dist/inspector/styleEditors/layoutValue.js +0 -42
  188. package/dist/inspector/styleEditors/nudgeValue.d.ts +2 -2
  189. package/dist/inspector/styleEditors/nudgeValue.js +36 -8
  190. package/dist/inspector/tokens/TokenField.d.ts +7 -0
  191. package/dist/inspector/tokens/TokenField.js +35 -25
  192. package/dist/inspector/tokens/TokenLabel.js +5 -14
  193. package/dist/inspector/tokens/editActions.d.ts +1 -1
  194. package/dist/inspector/tokens/editActions.js +7 -1
  195. package/dist/inspector/ui/AtRuleContext.js +4 -6
  196. package/dist/inspector/ui/Button.d.ts +6 -2
  197. package/dist/inspector/ui/Button.js +4 -3
  198. package/dist/inspector/ui/ControlSurface.d.ts +2 -2
  199. package/dist/inspector/ui/ControlSurface.js +4 -3
  200. package/dist/inspector/ui/InlineStyleWarning.d.ts +11 -0
  201. package/dist/inspector/ui/InlineStyleWarning.js +13 -0
  202. package/dist/inspector/ui/InspectorPopover.d.ts +2 -1
  203. package/dist/inspector/ui/InspectorPopover.js +4 -5
  204. package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
  205. package/dist/inspector/ui/PopoverListbox.js +8 -7
  206. package/dist/inspector/ui/SegmentedControl.d.ts +2 -1
  207. package/dist/inspector/ui/SegmentedControl.js +2 -2
  208. package/dist/inspector/ui/Select.d.ts +6 -1
  209. package/dist/inspector/ui/Select.js +7 -11
  210. package/dist/inspector/ui/SideValuesField.js +1 -1
  211. package/dist/inspector/ui/Tooltip.d.ts +26 -0
  212. package/dist/inspector/ui/Tooltip.js +32 -0
  213. package/dist/inspector/ui/classNames.d.ts +2 -0
  214. package/dist/inspector/ui/classNames.js +4 -0
  215. package/dist/inspector/ui/customCursors.d.ts +23 -0
  216. package/dist/inspector/ui/customCursors.js +122 -0
  217. package/dist/inspector/ui/dragCursor.d.ts +0 -2
  218. package/dist/inspector/ui/dragCursor.js +2 -10
  219. package/dist/inspector/ui/portalContainer.d.ts +2 -0
  220. package/dist/inspector/ui/portalContainer.js +6 -0
  221. package/dist/inspector/ui/styles.js +29 -21
  222. package/dist/inspector/workspace/commands.d.ts +9 -0
  223. package/dist/inspector/workspace/commands.js +82 -0
  224. package/dist/inspector/workspace/controller.d.ts +4 -0
  225. package/dist/inspector/workspace/controller.js +66 -0
  226. package/dist/inspector/workspace/handoff.d.ts +42 -0
  227. package/dist/inspector/workspace/handoff.js +115 -0
  228. package/dist/inspector/workspace/sentItems.d.ts +19 -0
  229. package/dist/inspector/workspace/sentItems.js +51 -0
  230. package/dist/inspector/workspace/timeline.d.ts +30 -0
  231. package/dist/inspector/workspace/timeline.js +108 -0
  232. package/dist/internal/agent-protocol.d.ts +19 -2
  233. package/dist/internal/agent-protocol.js +13 -0
  234. package/dist/nudge-ui.mjs +558 -311
  235. package/dist/project/artifacts.d.ts +13 -0
  236. package/dist/project/artifacts.js +253 -0
  237. package/dist/transport/artifacts.d.ts +8 -0
  238. package/dist/transport/artifacts.js +9 -0
  239. package/dist/transport/editor.js +3 -1
  240. package/dist/transport/routes.d.ts +2 -0
  241. package/dist/transport/routes.js +2 -0
  242. package/package.json +7 -3
  243. package/dist/inspector/changes/workspaceChanges.d.ts +0 -47
  244. package/dist/inspector/changes/workspaceChanges.js +0 -265
@@ -0,0 +1,195 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef, useState } from "react";
3
+ import { IconArrowsDiff, IconDroplet, IconMinus, IconPlus } from "@tabler/icons-react";
4
+ import { replaceColorPreservingOpacity, withColorOpacity } from "../../css/value-semantics/colorSemantics.js";
5
+ import { interpretTokenValue } from "../../css/value-semantics/tokenInterpretation.js";
6
+ import { getNudgeUiTokenEntries } from "../runtime/runtimeConfig.js";
7
+ import { colorValueToHex, TokenValueField } from "../tokens/TokenField.js";
8
+ import { setStyles } from "../tokens/editActions.js";
9
+ import { createChangeHistoryGroup, getActiveDraftChanges } from "../changes/draftChanges.js";
10
+ import { getStateStyleValue } from "../shell/stateValue.js";
11
+ import { ControlSurface } from "../ui/ControlSurface.js";
12
+ import { IconButton } from "../ui/IconButton.js";
13
+ import { InlineStyleWarning } from "../ui/InlineStyleWarning.js";
14
+ import { SegmentedControl } from "../ui/SegmentedControl.js";
15
+ import { Select } from "../ui/Select.js";
16
+ import { ImageBackgroundEditor } from "./ImageBackgroundEditor.js";
17
+ import { backgroundDeclarations, backgroundMode, BACKGROUND_BLEND_MODES, newBackgroundLayer, readBackgroundLayers } from "./backgroundLayers.js";
18
+ import { inlineBlockedBy } from "./inlineAuthored.js";
19
+ import { defaultGradient, parseGradient, serializeGradient } from "./gradientValue.js";
20
+ const MODES = [{ value: "solid", label: "Solid" }, { value: "gradient", label: "Gradient" }, { value: "image", label: "Image" }];
21
+ const TYPES = [{ value: "linear", label: "Linear" }, { value: "radial", label: "Radial" }, { value: "conic", label: "Conic" }];
22
+ /** Background layers are displayed in CSS paint order, with the top layer first. */
23
+ export function BackgroundEditor(props) {
24
+ const { element, selection, onAfterEdit } = props;
25
+ const el = element.domElement;
26
+ const elements = selection?.domElements ?? [el];
27
+ const source = readBackgroundLayers((property, fallback) => getStateStyleValue(el, property, fallback));
28
+ const base = source.at(-1);
29
+ const authoredColor = props.tokenRow?.property === "background-color" || props.tokenRow?.property === "background"
30
+ ? props.tokenRow.authored ?? props.tokenRow.declaredValue : undefined;
31
+ if (base?.baseColor && authoredColor) {
32
+ const table = Object.fromEntries((props.entries ?? getNudgeUiTokenEntries()).map((entry) => [entry.name, entry]));
33
+ const resolved = interpretTokenValue(authoredColor, { table }).resolvedValue;
34
+ if (colorValueToHex(resolved) && colorValueToHex(resolved) === colorValueToHex(base.value))
35
+ base.value = authoredColor;
36
+ }
37
+ const [draft, setDraft] = useState(null);
38
+ const activeDraft = draft?.element === el && draft.revision === getActiveDraftChanges().revision
39
+ && draft.elements.length === elements.length && draft.elements.every((element, index) => element === elements[index]) ? draft : null;
40
+ const layers = activeDraft?.layers ?? source;
41
+ const properties = ["background-color", "background-image", "background-blend-mode", "background-size", "background-repeat", "background-position"];
42
+ const mixed = !activeDraft && elements.some((element) => properties.some((property) => selection?.getProperty(property)?.value.kind === "mixed"
43
+ || getStateStyleValue(element, property) !== getStateStyleValue(el, property)));
44
+ const blockedBy = inlineBlockedBy(elements, ...properties);
45
+ function commit(next) {
46
+ const previous = backgroundDeclarations(layers);
47
+ const declarations = backgroundDeclarations(next).filter((declaration) => mixed || previous.find((current) => current.property === declaration.property)?.value !== declaration.value);
48
+ if (inlineBlockedBy(elements, ...declarations.map((declaration) => declaration.property)))
49
+ return false;
50
+ if (declarations.length && !setStyles(selection?.target ?? el, declarations).length)
51
+ return false;
52
+ setDraft({ element: el, elements: [...elements], revision: getActiveDraftChanges().revision, layers: next });
53
+ onAfterEdit?.();
54
+ return true;
55
+ }
56
+ return _jsxs("div", { className: "editor background-editor", "data-test": "background-editor", children: [_jsxs("div", { className: "editor__title-row", children: [_jsx("div", { className: "editor__title", children: "Background" }), _jsx(InlineStyleWarning, { blockedBy: blockedBy }), _jsx(IconButton, { variant: "quiet", label: "Add background layer", disabled: blockedBy !== null, onClick: () => commit([{ ...newBackgroundLayer(), baseColor: mixed || layers.length === 0 }, ...(mixed ? [] : layers)]), children: _jsx(IconPlus, { "aria-hidden": "true" }) })] }), mixed ? _jsx("div", { className: "background-editor__hint", children: "Mixed backgrounds. Add a layer to replace them." }) : layers.map((layer, index) => _jsx("div", { className: "background-editor__layer", role: "group", "aria-label": `Background layer ${index + 1}`, children: _jsx(BackgroundLayerEditor, { ...props, layer: layer, baseColor: layer.baseColor && layer.mode === "solid", bottomLayer: index === layers.length - 1, onChange: (next) => commit(layers.map((current, i) => i === index ? next : current)), onRemove: () => commit(layers.filter((_, i) => i !== index)) }) }, index))] });
57
+ }
58
+ function previewColor(color) {
59
+ const result = color.tokenName && color.opacity ? withColorOpacity(color.resolvedValue, color.opacity.value) : null;
60
+ return result?.ok ? result.value : color.resolvedValue;
61
+ }
62
+ function BackgroundLayerEditor(props) {
63
+ const { element, selection, entries, layer, baseColor, bottomLayer, onChange, onRemove } = props;
64
+ const allEntries = entries ?? getNudgeUiTokenEntries();
65
+ const tokenTable = Object.fromEntries(allEntries.map((entry) => [entry.name, entry]));
66
+ const el = element.domElement;
67
+ const elements = selection?.domElements ?? [el];
68
+ const { value, mode } = layer;
69
+ const gradient = layer.gradient ?? parseGradient(value);
70
+ const blockedBy = inlineBlockedBy(elements, baseColor ? "background-color" : "background-image");
71
+ const dragCommit = useRef(null);
72
+ function commit(nextValue, nextMode = backgroundMode(nextValue), nextGradient, declarations = []) {
73
+ if (blockedBy)
74
+ return false;
75
+ const next = {
76
+ ...layer, value: nextValue, mode: nextMode, gradient: nextGradient,
77
+ baseColor: layer.baseColor || (nextMode === "solid" && mode !== "solid" && bottomLayer),
78
+ };
79
+ for (const declaration of declarations) {
80
+ if (declaration.property === "background-size")
81
+ next.size = declaration.value;
82
+ if (declaration.property === "background-repeat")
83
+ next.repeat = declaration.value;
84
+ if (declaration.property === "background-position")
85
+ next.position = declaration.value;
86
+ }
87
+ return onChange(next);
88
+ }
89
+ function commitGradient(next) { commit(serializeGradient(next), "gradient", next); }
90
+ function changeMode(next) {
91
+ if (next === mode)
92
+ return;
93
+ if (next === "solid")
94
+ commit("#ffffff", next);
95
+ else if (next === "gradient")
96
+ commitGradient(defaultGradient());
97
+ else
98
+ commit("none", next);
99
+ }
100
+ function updateStop(index, next) {
101
+ if (gradient)
102
+ commitGradient({ ...gradient, stops: gradient.stops.map((stop, i) => i === index ? { ...stop, ...next } : stop) });
103
+ }
104
+ function removeStop(index) {
105
+ if (!gradient || gradient.stops.length <= 2)
106
+ return;
107
+ commitGradient({ ...gradient, stops: gradient.stops.filter((_, i) => i !== index) });
108
+ }
109
+ function addStop(position = 50) {
110
+ if (!gradient)
111
+ return;
112
+ const color = gradient.stops[0].color;
113
+ commitGradient({ ...gradient, stops: [...gradient.stops, { color, position }] });
114
+ }
115
+ function commitColor(index, stop, color) {
116
+ const result = replaceColorPreservingOpacity(stop.color, color, { tokenTable });
117
+ if (result.ok)
118
+ updateStop(index, { color: result.value });
119
+ }
120
+ function percentAt(track, clientX) {
121
+ const rect = track.getBoundingClientRect();
122
+ return rect.width > 0 ? Math.round(Math.max(0, Math.min(100, (clientX - rect.left) / rect.width * 100))) : 50;
123
+ }
124
+ function rawField(label, current, onCommit, mixedValue = false) {
125
+ return _jsx(ControlSurface, { children: _jsx(TokenValueField, { property: "background-image", domElement: el, entries: [], committedValue: current, label: label, mixed: mixedValue, blockedBy: blockedBy, onCommitRaw: onCommit, onSelectToken: () => false, onUnlink: onCommit }) });
126
+ }
127
+ const toolbar = _jsxs("div", { className: "background-editor__type-row", children: [_jsx(SegmentedControl, { "aria-label": "Background type", value: mode, options: MODES, onChange: changeMode, disabled: blockedBy !== null }), !baseColor ? _jsx(Select, { iconOnly: true, "aria-label": "Background blending mode", title: `Blend mode: ${layer.blend}`, value: layer.blend, options: BACKGROUND_BLEND_MODES, popupPosition: { side: "top", align: "end" }, disabled: inlineBlockedBy(elements, "background-blend-mode") !== null, onValueChange: (blend) => onChange({ ...layer, blend }), children: _jsx(IconDroplet, { "aria-hidden": "true" }) }) : _jsx("span", { className: "background-editor__blend-space", "aria-hidden": "true" }), _jsx(IconButton, { variant: "quiet", label: "Remove background layer", disabled: blockedBy !== null, onClick: onRemove, children: _jsx(IconMinus, { "aria-hidden": "true" }) })] });
128
+ const color = interpretTokenValue(value, { table: tokenTable });
129
+ if (mode === "solid")
130
+ return _jsxs("div", { className: "background-editor__solid", "data-property": baseColor ? "background-color" : "background-image", children: [toolbar, _jsx(ControlSurface, { children: _jsx(TokenValueField, { property: baseColor ? "background-color" : "background-image", semanticSlot: "color", domElement: el, isColor: true, entries: allEntries, committedValue: color.tokenName ? value : colorValueToHex(value) ?? value, activeTokenName: color.tokenName, resolvedValue: previewColor(color), opacity: color.opacity, label: "Background Color", blockedBy: blockedBy, onCommitRaw: (raw) => {
131
+ const result = replaceColorPreservingOpacity(value, raw, { tokenTable });
132
+ if (result.ok)
133
+ commit(result.value, "solid");
134
+ }, onSelectToken: (token) => {
135
+ const result = replaceColorPreservingOpacity(value, `var(${token.name})`, { tokenTable });
136
+ return result.ok && commit(result.value, "solid");
137
+ }, onUnlink: (raw) => commit(raw, "solid"), onCommitOpacity: (opacity) => {
138
+ const result = withColorOpacity(value, opacity);
139
+ return result.ok && commit(result.value, "solid");
140
+ } }) })] });
141
+ return _jsxs("div", { className: "background-editor__content", children: [toolbar, mode === "image" ? _jsx(ImageBackgroundEditor, { element: el, elements: elements, value: value, mixed: false, sizingValues: { size: layer.size, repeat: layer.repeat }, onCss: (raw) => commit(raw, "image"), onUpload: (image) => commit(image, "image", undefined, [
142
+ { property: "background-size", value: "cover" },
143
+ { property: "background-repeat", value: "no-repeat" },
144
+ { property: "background-position", value: "center" },
145
+ ]), onSizing: (declarations) => commit(value, "image", undefined, declarations) }) : gradient && mode === "gradient" ? _jsxs("div", { className: "background-editor__gradient", children: [_jsx("div", { className: "background-editor__track", "data-test": "gradient-track", "aria-label": "Gradient stops", style: { backgroundImage: serializeGradient({ ...gradient, type: "linear", angle: 90, prelude: "", repeating: false,
146
+ stops: gradient.stops.map((stop) => ({ ...stop, color: previewColor(interpretTokenValue(stop.color, { table: tokenTable })) })) }) }, onPointerDown: (event) => { if (event.target === event.currentTarget && event.button === 0)
147
+ addStop(percentAt(event.currentTarget, event.clientX)); }, children: gradient.stops.map((stop, index) => _jsx("button", { type: "button", className: "background-editor__stop", "aria-label": `Gradient stop ${index + 1}`, disabled: blockedBy !== null, style: { left: `clamp(8px, ${stop.position}%, calc(100% - 8px))`, backgroundColor: previewColor(interpretTokenValue(stop.color, { table: tokenTable })) }, onPointerDown: (event) => {
148
+ if (event.button !== 0)
149
+ return;
150
+ event.stopPropagation();
151
+ dragCommit.current = createChangeHistoryGroup();
152
+ event.currentTarget.setPointerCapture(event.pointerId);
153
+ }, onPointerMove: (event) => {
154
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
155
+ const position = percentAt(event.currentTarget.parentElement, event.clientX);
156
+ if (position !== stop.position)
157
+ dragCommit.current?.(() => updateStop(index, { position }));
158
+ }
159
+ }, onPointerUp: (event) => {
160
+ dragCommit.current = null;
161
+ if (event.currentTarget.hasPointerCapture(event.pointerId))
162
+ event.currentTarget.releasePointerCapture(event.pointerId);
163
+ }, onPointerCancel: () => { dragCommit.current = null; }, onLostPointerCapture: () => { dragCommit.current = null; }, onDoubleClick: () => removeStop(index), onKeyDown: (event) => {
164
+ if (event.key === "Delete" || event.key === "Backspace") {
165
+ event.preventDefault();
166
+ event.stopPropagation();
167
+ removeStop(index);
168
+ }
169
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
170
+ event.preventDefault();
171
+ event.stopPropagation();
172
+ updateStop(index, { position: Math.max(0, Math.min(100, stop.position + (event.key === "ArrowLeft" ? -1 : 1) * (event.shiftKey ? 10 : 1))) });
173
+ }
174
+ } }, index)) }), _jsxs("div", { className: "background-editor__toolbar", children: [_jsx(Select, { "aria-label": "Gradient type", value: gradient.type, options: TYPES, disabled: blockedBy !== null, onValueChange: (type) => commitGradient({ ...gradient, type: type, prelude: type === "radial" ? "circle at center" : "" }) }), gradient.type !== "radial" ? rawField("Gradient angle", `${gradient.angle}deg`, (raw) => {
175
+ const match = /^([+-]?(?:\d*\.)?\d+)(?:deg)?$/.exec(raw.trim());
176
+ if (match && Number.isFinite(Number(match[1])))
177
+ commitGradient({ ...gradient, angle: Number(match[1]) });
178
+ }) : null, _jsx(IconButton, { variant: "quiet", label: "Reverse gradient", disabled: blockedBy !== null, onClick: () => commitGradient({ ...gradient, stops: [...gradient.stops].reverse().map((stop) => ({ ...stop, position: 100 - stop.position })) }), children: _jsx(IconArrowsDiff, { "aria-hidden": "true" }) }), _jsx(IconButton, { variant: "quiet", label: "Add gradient stop", disabled: blockedBy !== null, onClick: () => addStop(), children: _jsx(IconPlus, { "aria-hidden": "true" }) })] }), gradient.stops.map((stop, index) => ({ stop, index })).sort((a, b) => a.stop.position - b.stop.position).map(({ stop, index }) => {
179
+ const color = interpretTokenValue(stop.color, { table: tokenTable });
180
+ return _jsxs("div", { className: "background-editor__stop-row", role: "group", "aria-label": `Stop ${index + 1}`, children: [rawField(`Stop ${index + 1} position`, `${Number(stop.position.toFixed(3))}%`, (raw) => {
181
+ const match = /^([+-]?(?:\d*\.)?\d+)%?$/.exec(raw.trim());
182
+ if (match)
183
+ updateStop(index, { position: Math.max(0, Math.min(100, Number(match[1]))) });
184
+ }), _jsx(ControlSurface, { children: _jsx(TokenValueField, { property: "background-image", semanticSlot: "color", domElement: el, isColor: true, entries: allEntries, activeTokenName: color.tokenName, committedValue: color.tokenName ? stop.color : colorValueToHex(stop.color) ?? stop.color, resolvedValue: previewColor(color), label: `Stop ${index + 1} color`, blockedBy: blockedBy, opacity: color.opacity, onCommitRaw: (color) => commitColor(index, stop, color), onSelectToken: (token) => commitColor(index, stop, `var(${token.name})`), onUnlink: (color) => updateStop(index, { color: previewColor(interpretTokenValue(color, { table: tokenTable })) }), onCommitOpacity: (opacity) => {
185
+ const result = withColorOpacity(stop.color, opacity);
186
+ if (!result.ok)
187
+ return false;
188
+ updateStop(index, { color: result.value });
189
+ } }) }), _jsx(IconButton, { variant: "quiet", label: `Remove stop ${index + 1}`, disabled: blockedBy !== null || gradient.stops.length <= 2, onClick: () => removeStop(index), children: _jsx(IconMinus, { "aria-hidden": "true" }) })] }, index);
190
+ })] }) : _jsxs("div", { className: "background-editor__raw", children: [mode === "gradient" ? _jsx("div", { className: "background-editor__hint", children: "Edit this background using CSS." }) : null, rawField("Background image CSS", value, (raw) => {
191
+ if (typeof CSS !== "undefined" && !CSS.supports("background-image", raw))
192
+ return;
193
+ commit(raw, mode);
194
+ }, false), mode === "gradient" ? _jsx(IconButton, { variant: "quiet", label: "Add linear gradient", disabled: blockedBy !== null, onClick: () => commitGradient(defaultGradient()), children: _jsx(IconPlus, { "aria-hidden": "true" }) }) : null] })] });
195
+ }
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
- import { IconBorderBottom, IconBorderLeft, IconBorderRight, IconBorderSides, IconBorderStyle2, IconBorderTop, IconCheck, IconMinus, IconPlus, } from "@tabler/icons-react";
3
+ import { IconBorderSides, IconBorderStyle2, IconCheck, IconMinus, IconPlus, } from "@tabler/icons-react";
4
4
  import { findTokenRow } from "./rowLookup.js";
5
5
  import { TokenField } from "../tokens/TokenField.js";
6
6
  import { setStyle, setStyles } from "../tokens/editActions.js";
@@ -13,8 +13,11 @@ import { getStateStyleValue } from "../shell/stateValue.js";
13
13
  import { PopoverListbox } from "../ui/PopoverListbox.js";
14
14
  import { ControlSurface } from "../ui/ControlSurface.js";
15
15
  import { getNudgeUiTokenEntries } from "../runtime/runtimeConfig.js";
16
- import { hasAuthoredProperty, hasAuthoredStyle, isZeroCssValue } from "./stylePresence.js";
16
+ import { isZeroCssValue } from "./stylePresence.js";
17
17
  import { useFieldVisibility } from "./useFieldVisibility.js";
18
+ import { Tooltip } from "../ui/Tooltip.js";
19
+ import { inlineStyleWarningContent } from "../ui/InlineStyleWarning.js";
20
+ import { inlineBlockedBy } from "./inlineAuthored.js";
18
21
  const BORDER_STYLES = ["none", "hidden", "solid", "dashed", "dotted", "double", "groove", "ridge", "inset", "outset"];
19
22
  const INVISIBLE_BORDER_STYLES = new Set(["none", "hidden"]);
20
23
  const BORDER_SIDES = SIDE_NAMES.map((side) => `border-${side}`);
@@ -96,67 +99,15 @@ function valuesAreLinked(el, rows, shorthand, sideProperties) {
96
99
  const signatures = sideProperties.map((property) => sideComponentSignature(el, rows, property));
97
100
  return new Set(signatures).size === 1;
98
101
  }
99
- function isBorderFaceProperty(property) {
100
- const p = property.toLowerCase();
101
- return p === "border"
102
- || p === "border-width"
103
- || p === "border-style"
104
- || p === "border-color"
105
- || /^border-(top|right|bottom|left)(?:-(?:width|style|color))?$/.test(p);
106
- }
107
- function isZeroWidthValue(value) {
108
- const trimmed = value.trim().toLowerCase();
109
- if (isZeroCssValue(trimmed))
110
- return true;
111
- // Structured/preflight authored forms like `0 solid` / `0px solid currentcolor`.
112
- const first = trimmed.split(/\s+/)[0] ?? "";
113
- return isZeroCssValue(first);
114
- }
115
- /** True when a face paints: drawn style and non-zero width. */
116
- function sidePaints(el, rows, side) {
117
- const style = borderStyleValue(el, rows, `border-${side}-style`);
118
- if (INVISIBLE_BORDER_STYLES.has(style))
119
- return false;
120
- return !isZeroWidthValue(borderWidthValue(el, rows, `border-${side}-width`));
121
- }
122
- /**
123
- * Show border controls for intentional / painted borders only.
124
- *
125
- * Tailwind preflight authors `border: 0 solid` on `*`. That is cascade
126
- * evidence but not a visible border — hide controls and show + instead.
127
- *
128
- * Counts as presence:
129
- * - any painted face (drawn style + non-zero width)
130
- * - a direct width or disabling style, including zero
131
- */
132
- function hasBorderPresence(el, rows) {
133
- if (SIDE_NAMES.some((side) => sidePaints(el, rows, side)))
134
- return true;
135
- for (const row of rows) {
136
- if (!isBorderFaceProperty(row.property) && !row.structure)
137
- continue;
138
- if (!hasAuthoredStyle(row))
139
- continue;
140
- const property = row.property.toLowerCase();
141
- const authored = (row.authored ?? row.declaredValue ?? "").trim().toLowerCase();
142
- const structuredStyle = row.structure?.style?.trim().toLowerCase() ?? "";
143
- const structuredWidth = row.structure?.width?.trim() ?? "";
144
- if (INVISIBLE_BORDER_STYLES.has(structuredStyle) || INVISIBLE_BORDER_STYLES.has(authored)) {
145
- return true;
146
- }
147
- if (structuredWidth)
148
- return true;
149
- if ((property === "border-width" || property.endsWith("-width")) && authored) {
150
- return true;
151
- }
152
- }
153
- // Computed non-zero width without cascade rows (inline / inaccessible sheets).
154
- if (!isZeroWidthValue(borderWidthValue(el, rows, "border-width")))
155
- return true;
156
- if (SIDE_NAMES.some((side) => !isZeroWidthValue(borderWidthValue(el, rows, `border-${side}-width`)))) {
157
- return true;
158
- }
159
- return false;
102
+ /** Initial visibility follows the effective border, including stylesheet resets. */
103
+ function hasBorderPresence(el) {
104
+ return SIDE_NAMES.some((side) => {
105
+ const style = getStateStyleValue(el, `border-${side}-style`, "none").toLowerCase();
106
+ if (INVISIBLE_BORDER_STYLES.has(style))
107
+ return false;
108
+ const width = getStateStyleValue(el, `border-${side}-width`, "0px");
109
+ return !isZeroCssValue(width);
110
+ });
160
111
  }
161
112
  /**
162
113
  * Linked presentation from cascade data + optional user toggle.
@@ -192,12 +143,14 @@ function useBorderLinkedState(dataLinked, resetKey) {
192
143
  export function BorderEditor(props) {
193
144
  const { element, selection, entries, tokenRows = [], onAfterEdit } = props;
194
145
  const el = element.domElement;
146
+ const selectedElements = selection?.domElements ?? [el];
195
147
  const editTarget = selection?.target ?? el;
196
148
  const isGroup = Boolean(selection && selection.elements.length > 1);
197
149
  const allEntries = entries ?? getNudgeUiTokenEntries();
198
150
  const borderWidthProperties = BORDER_SIDES.map((side) => `${side}-width`);
199
151
  const borderStyleProperties = BORDER_SIDES.map((side) => `${side}-style`);
200
152
  const borderColorProperties = BORDER_SIDES.map((side) => `${side}-color`);
153
+ const borderBlockedBy = inlineBlockedBy(selectedElements, "border", "border-width", "border-style", "border-color");
201
154
  const borderWidthDataLinked = valuesAreLinked(el, tokenRows, "border-width", borderWidthProperties);
202
155
  const borderStyleDataLinked = valuesAreLinked(el, tokenRows, "border-style", borderStyleProperties);
203
156
  const borderColorDataLinked = valuesAreLinked(el, tokenRows, "border-color", borderColorProperties);
@@ -208,18 +161,19 @@ export function BorderEditor(props) {
208
161
  const rawBorderFallback = !isGroup && Boolean(borderRow && !borderRow.structure && !hasStructuredBorderRows);
209
162
  const linkedBorderStyle = borderStyleValue(el, tokenRows, "border-style");
210
163
  const showWidthAndColor = !(borderLinked && INVISIBLE_BORDER_STYLES.has(linkedBorderStyle));
211
- const hasBorder = isGroup
212
- ? (selection?.domElements.some((candidate) => hasBorderPresence(candidate, [])) ?? false)
213
- || borderWidthProperties.some((property) => hasAuthoredProperty(selection, property))
214
- : hasBorderPresence(el, tokenRows);
215
- const visibility = useFieldVisibility(selection?.domElements ?? [el], "border", hasBorder || rawBorderFallback);
164
+ const hasBorder = selectedElements.some(hasBorderPresence);
165
+ const visibility = useFieldVisibility(selectedElements, "border", hasBorder);
216
166
  const showBorderControls = visibility.visible;
217
167
  function handleAddBorder() {
168
+ if (borderBlockedBy)
169
+ return;
218
170
  setStyle(editTarget, "border", "1px solid");
219
171
  visibility.show();
220
172
  onAfterEdit?.();
221
173
  }
222
174
  function handleRemoveBorder() {
175
+ if (borderBlockedBy)
176
+ return;
223
177
  setStyle(editTarget, "border", "0 solid");
224
178
  visibility.hide();
225
179
  onAfterEdit?.();
@@ -228,6 +182,8 @@ export function BorderEditor(props) {
228
182
  setBorderLinked(false);
229
183
  }
230
184
  function handleCollapse() {
185
+ if (borderBlockedBy)
186
+ return;
231
187
  setBorderLinked(true);
232
188
  const declarations = [
233
189
  borderLinkDeclaration(el, tokenRows, "border-style", borderStyleProperties),
@@ -245,29 +201,42 @@ export function BorderEditor(props) {
245
201
  width: (_jsx(ControlSurface, { children: _jsx(TokenField, { property: borderWidthProperties[index], selection: selection, tokenRow: findTokenRow(tokenRows, borderWidthProperties[index]), domElement: el, editTarget: editTarget, entries: allEntries, onAfterEdit: onAfterEdit, chipVariant: "small" }) })),
246
202
  style: (_jsx(BorderStyleSettingsMenu, { property: borderStyleProperties[index], tokenRow: findTokenRow(tokenRows, borderStyleProperties[index]), domElement: el, editTarget: editTarget, selection: selection, dataTest: `border-style-${side}`, onAfterEdit: onAfterEdit })),
247
203
  }));
248
- return (_jsxs("div", { className: `editor`, "data-test": "border-editor", children: [_jsxs("div", { className: "editor__title-row", children: [_jsx("div", { className: "editor__title", children: "Border" }), showBorderControls ? (_jsx(IconButton, { variant: "quiet", label: "Remove Border", "data-test": "remove-border", className: "border__remove", onClick: handleRemoveBorder, children: _jsx(IconMinus, { size: 16, "aria-hidden": "true" }) })) : (_jsx(IconButton, { variant: "quiet", label: "Add Border", "data-test": "add-border", className: "border__add", onClick: handleAddBorder, children: _jsx(IconPlus, { size: 16, "aria-hidden": "true" }) }))] }), showBorderControls && (_jsx("div", { className: "border", "data-expanded": borderLinked ? "false" : "true", children: rawBorderFallback ? (_jsx(FieldRow, { label: "Border", children: _jsx(ControlSurface, { children: _jsx(TokenField, { property: "border", selection: selection, tokenRow: borderRow, domElement: el, editTarget: editTarget, entries: allEntries, onAfterEdit: onAfterEdit }) }) })) : borderLinked ? (_jsxs("div", { className: "border__linked-row", children: [showWidthAndColor ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "border__linked-control border__linked-control--color", children: _jsx(ControlSurface, { children: _jsx(TokenField, { property: "border-color", selection: selection, tokenRow: linkedTokenRow(tokenRows, "border-color", borderColorProperties), domElement: el, editTarget: editTarget, entries: allEntries, onAfterEdit: onAfterEdit }) }) }), _jsx("div", { className: "border__linked-control border__linked-control--width", children: _jsx(ControlSurface, { children: _jsx(TokenField, { property: "border-width", selection: selection, tokenRow: linkedTokenRow(tokenRows, "border-width", borderWidthProperties), domElement: el, editTarget: editTarget, entries: allEntries, onAfterEdit: onAfterEdit, chipVariant: "small" }) }) })] })) : null, _jsx(BorderStyleSettingsMenu, { property: "border-style", tokenRow: linkedTokenRow(tokenRows, "border-style", borderStyleProperties), domElement: el, editTarget: editTarget, selection: selection, onAfterEdit: onAfterEdit }), _jsx(ToggleButton, { variant: "quiet", size: "default", "data-test": "border-expand", label: "Edit Individual Border Sides", title: "Edit Individual Border Sides", pressed: !borderLinked, onPressedChange: (pressed) => {
204
+ return (_jsxs("div", { className: `editor`, "data-test": "border-editor", children: [_jsxs("div", { className: "editor__title-row", children: [_jsx("div", { className: "editor__title", children: "Border" }), showBorderControls ? (_jsx(Tooltip, { content: inlineStyleWarningContent(borderBlockedBy), children: _jsx(IconButton, { variant: "quiet", label: "Remove Border", "data-test": "remove-border", className: "border__remove", disabled: borderBlockedBy !== null, onClick: handleRemoveBorder, children: _jsx(IconMinus, { size: 16, "aria-hidden": "true" }) }) })) : (_jsx(IconButton, { variant: "quiet", label: "Add Border", "data-test": "add-border", className: "border__add", disabled: borderBlockedBy !== null, onClick: handleAddBorder, children: _jsx(IconPlus, { size: 16, "aria-hidden": "true" }) }))] }), showBorderControls && (_jsx("div", { className: "border", "data-expanded": borderLinked ? "false" : "true", children: rawBorderFallback ? (_jsx(FieldRow, { label: "Border", children: _jsx(ControlSurface, { children: _jsx(TokenField, { property: "border", selection: selection, tokenRow: borderRow, domElement: el, editTarget: editTarget, entries: allEntries, onAfterEdit: onAfterEdit }) }) })) : borderLinked ? (_jsxs("div", { className: "border__linked-row", children: [showWidthAndColor ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "border__linked-control border__linked-control--color", children: _jsx(ControlSurface, { children: _jsx(TokenField, { property: "border-color", selection: selection, tokenRow: linkedTokenRow(tokenRows, "border-color", borderColorProperties), domElement: el, editTarget: editTarget, entries: allEntries, onAfterEdit: onAfterEdit }) }) }), _jsx("div", { className: "border__linked-control border__linked-control--width", children: _jsx(ControlSurface, { children: _jsx(TokenField, { property: "border-width", selection: selection, tokenRow: linkedTokenRow(tokenRows, "border-width", borderWidthProperties), domElement: el, editTarget: editTarget, entries: allEntries, onAfterEdit: onAfterEdit, chipVariant: "small" }) }) })] })) : null, _jsx(BorderStyleSettingsMenu, { property: "border-style", tokenRow: linkedTokenRow(tokenRows, "border-style", borderStyleProperties), domElement: el, editTarget: editTarget, selection: selection, onAfterEdit: onAfterEdit }), _jsx(ToggleButton, { variant: "quiet", size: "default", "data-test": "border-expand", label: "Edit Individual Border Sides", pressed: !borderLinked, onPressedChange: (pressed) => {
249
205
  if (pressed)
250
206
  handleExpand();
251
- }, children: _jsx(IconBorderSides, { size: 16, "aria-hidden": "true" }) })] })) : (_jsxs("div", { className: "border__expanded", children: [_jsxs("div", { className: "border__expanded-header", children: [_jsx("span", { className: "side-values__label", children: formatInspectorLabel("Individual Sides") }), _jsx(ToggleButton, { variant: "quiet", size: "default", "data-test": "border-collapse", label: "Link All Border Sides", title: "Link All Border Sides", pressed: !borderLinked, onPressedChange: (pressed) => {
207
+ }, children: _jsx(IconBorderSides, { size: 16, "aria-hidden": "true" }) })] })) : (_jsxs("div", { className: "border__expanded", children: [_jsxs("div", { className: "border__expanded-header", children: [_jsx("span", { className: "side-values__label", children: formatInspectorLabel("Individual Sides") }), _jsx(ToggleButton, { variant: "quiet", size: "default", "data-test": "border-collapse", label: "Link All Border Sides", pressed: !borderLinked, disabled: borderBlockedBy !== null, onPressedChange: (pressed) => {
252
208
  if (!pressed)
253
209
  handleCollapse();
254
210
  }, children: _jsx(IconBorderSides, { size: 16, "aria-hidden": "true" }) })] }), showWidthAndColor ? (_jsx("div", { className: "border__side-rows", "data-test": "border-side-rows", children: sideRows.map(({ side, color, width, style }) => (_jsxs("div", { className: "border__side-row", "data-side": side, "aria-label": `Border ${formatInspectorLabel(side)}`, children: [_jsx(BorderSideIndicator, { side: side }), _jsx("div", { className: "border__side-control border__side-control--color", children: color }), _jsx("div", { className: "border__side-control border__side-control--width", children: width }), style] }, side))) })) : null] })) }))] }));
255
211
  }
256
- const BORDER_SIDE_ICONS = {
257
- top: IconBorderTop,
258
- right: IconBorderRight,
259
- bottom: IconBorderBottom,
260
- left: IconBorderLeft,
212
+ const BORDER_SIDE_ICON_PATHS = {
213
+ left: {
214
+ outline: "M20 3H4v18h16a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1Z",
215
+ emphasis: { x1: "3.5", y1: "3.5", x2: "3.5", y2: "20.5" },
216
+ },
217
+ right: {
218
+ outline: "M4 3a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h16V3H4Z",
219
+ emphasis: { x1: "20.5", y1: "3.5", x2: "20.5", y2: "20.5" },
220
+ },
221
+ top: {
222
+ outline: "M21 20a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4h18v16Z",
223
+ emphasis: { x1: "3.5", y1: "3.5", x2: "20.5", y2: "3.5" },
224
+ },
225
+ bottom: {
226
+ outline: "M3 4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v17H3V4Z",
227
+ emphasis: { x1: "3.5", y1: "20.5", x2: "20.5", y2: "20.5" },
228
+ },
261
229
  };
262
230
  function BorderSideIndicator({ side }) {
263
- const Icon = BORDER_SIDE_ICONS[side];
264
- return _jsx(Icon, { className: "side-values__icon side-values__side-icon", size: 16, "aria-hidden": "true" });
231
+ const { outline, emphasis } = BORDER_SIDE_ICON_PATHS[side];
232
+ return (_jsxs("svg", { className: "side-values__icon side-values__side-icon", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [_jsx("path", { d: outline, stroke: "currentColor", strokeWidth: "var(--icon-stroke-width)", strokeLinecap: "square", strokeOpacity: "0.2" }), _jsx("line", { ...emphasis, stroke: "currentColor", strokeWidth: "var(--icon-stroke-width)", strokeLinecap: "round" })] }));
265
233
  }
266
234
  function BorderStyleSettingsMenu({ property, tokenRow, domElement: el, editTarget, selection, dataTest = "border-style-settings", onAfterEdit }) {
267
235
  const structured = tokenRow?.structure?.style?.trim().toLowerCase() ?? "";
268
236
  const selectedValue = selection?.getProperty(property)?.value;
269
237
  const mixed = selectedValue?.kind === "mixed";
270
238
  const commonValue = selectedValue?.kind === "common" ? selectedValue.value : "";
239
+ const blockedBy = inlineBlockedBy(selection?.domElements ?? [el], property);
271
240
  const initial = mixed ? "Mixed" : commonValue || structured || getStateStyleValue(el, property, "none") || "none";
272
241
  const [value, setValue] = useState(initial);
273
242
  const [open, setOpen] = useState(false);
@@ -275,6 +244,8 @@ function BorderStyleSettingsMenu({ property, tokenRow, domElement: el, editTarge
275
244
  setValue(mixed ? "Mixed" : commonValue || structured || getStateStyleValue(el, property, "none") || "none");
276
245
  }, [commonValue, el, mixed, property, structured]);
277
246
  function handleChange(next) {
247
+ if (blockedBy)
248
+ return;
278
249
  if (!BORDER_STYLES.includes(next))
279
250
  return;
280
251
  setValue(next);
@@ -282,7 +253,7 @@ function BorderStyleSettingsMenu({ property, tokenRow, domElement: el, editTarge
282
253
  setOpen(false);
283
254
  onAfterEdit?.();
284
255
  }
285
- return (_jsx(PopoverListbox, { query: "", value: null, open: open, triggerElement: (_jsx(IconButton, { variant: "quiet", size: "default", label: `Border style: ${formatInspectorLabel(value)}`, "data-test": dataTest, "data-current-style": value, children: _jsx(IconBorderStyle2, { size: 16, "aria-hidden": "true" }) })), triggerDataTest: dataTest, triggerAriaLabel: "Border style settings", items: BORDER_STYLES.map((style) => ({
256
+ return (_jsx(PopoverListbox, { query: "", value: null, open: open, triggerElement: (_jsx(IconButton, { variant: "quiet", size: "default", label: `Border style: ${formatInspectorLabel(value)}`, "data-test": dataTest, "data-current-style": value, children: _jsx(IconBorderStyle2, { size: 16, "aria-hidden": "true" }) })), triggerDataTest: dataTest, triggerAriaLabel: "Border style settings", triggerTooltip: inlineStyleWarningContent(blockedBy), disabled: blockedBy !== null, items: BORDER_STYLES.map((style) => ({
286
257
  value: style,
287
258
  label: formatInspectorLabel(style),
288
259
  leading: style === value ? _jsx(IconCheck, { size: 14, stroke: 2, "aria-hidden": "true" }) : undefined,
@@ -10,6 +10,7 @@ import { ControlSurface } from "../ui/ControlSurface.js";
10
10
  import { getNudgeUiTokenEntries } from "../runtime/runtimeConfig.js";
11
11
  import { completeCssValue } from "./completeCssValue.js";
12
12
  import { valuePolicyFor } from "./valuePolicy.js";
13
+ import { inlineBlockedBy } from "./inlineAuthored.js";
13
14
  const BORDER_RADIUS_CORNERS = [
14
15
  "border-top-left-radius",
15
16
  "border-top-right-radius",
@@ -52,6 +53,7 @@ export function BorderRadiusEditor(props) {
52
53
  const isGroup = Boolean(selection && selection.elements.length > 1);
53
54
  const allEntries = entries ?? getNudgeUiTokenEntries();
54
55
  const dataLinked = !isGroup && cornersAreLinked(tokenRows);
56
+ const blockedBy = inlineBlockedBy(selection?.domElements ?? [el], "border-radius");
55
57
  const [userUnlinked, setUserUnlinked] = useState(false);
56
58
  const [userLinked, setUserLinked] = useState(false);
57
59
  useEffect(() => {
@@ -70,6 +72,8 @@ export function BorderRadiusEditor(props) {
70
72
  setUserLinked(false);
71
73
  }
72
74
  function handleCollapse() {
75
+ if (blockedBy)
76
+ return;
73
77
  setUserLinked(true);
74
78
  setUserUnlinked(false);
75
79
  linkAllCorners(editTarget, tokenRows, selection, onAfterEdit);
@@ -94,7 +98,8 @@ export function BorderRadiusEditor(props) {
94
98
  const groupedControl = (_jsx(ControlSurface, { children: _jsx(TokenField, { property: "border-radius", selection: selection, tokenRow: mixed ? null : borderRadiusRow, initialValue: mixed ? "Mix" : undefined, displayValue: mixed ? "Mix" : undefined, domElement: el, editTarget: editTarget, entries: allEntries, editMetadata: metadataFor(borderRadiusRow), mixed: mixed, formatRawValue: (value) => mixed && value.trim().toLowerCase() === "mix"
95
99
  ? ""
96
100
  : completeCssValue(value.trim(), valuePolicyFor("border-radius")), onAfterEdit: onAfterEdit, chipVariant: "small", leading: _jsx(IconBorderCorners, { className: "side-values__icon side-values__side-icon", size: 16, "aria-hidden": "true" }) }) }));
97
- const toggleButton = (_jsx(ToggleButton, { className: embedded ? "border-radius-editor__toggle" : undefined, variant: "quiet", size: "default", "data-test": isLinked ? "border-radius-expand" : "border-radius-collapse", label: isLinked ? "Edit Individual Corners" : "Link All Corners", title: isLinked ? "Edit Individual Corners" : "Link All Corners", pressed: !isLinked, onPressedChange: (pressed) => {
101
+ const toggleBlocked = !isLinked && blockedBy !== null;
102
+ const toggleButton = (_jsx(ToggleButton, { className: embedded ? "border-radius-editor__toggle" : undefined, variant: "quiet", size: "default", "data-test": isLinked ? "border-radius-expand" : "border-radius-collapse", label: isLinked ? "Edit Individual Corners" : "Link All Corners", pressed: !isLinked, disabled: toggleBlocked, onPressedChange: (pressed) => {
98
103
  if (pressed)
99
104
  handleExpand();
100
105
  else
@@ -1,6 +1,5 @@
1
1
  import type { ReactElement } from "react";
2
- import type { TokenEntry } from "../../css/model/index.js";
3
- import type { ResolvedProperty } from "../../css/model/index.js";
2
+ import type { TokenEntry, ResolvedProperty } from "../../css/model/index.js";
4
3
  import type { SelectedElement } from "../selection/selectionStore.js";
5
4
  import type { StyleSelection } from "../selection/styleSelection.js";
6
5
  export interface BoxShadowEditorProps {