nudge-ui 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/README.md +164 -46
  2. package/dist/client.mjs +252 -54
  3. package/dist/css/value-semantics/colorSemantics.d.ts +4 -0
  4. package/dist/css/value-semantics/colorSemantics.js +17 -0
  5. package/dist/hosts/astro/astroRuntimeConfig.js +1 -0
  6. package/dist/hosts/astro/clientTransport.js +41 -32
  7. package/dist/hosts/astro/integration.d.ts +2 -2
  8. package/dist/hosts/astro/integration.js +9 -4
  9. package/dist/hosts/astro/markdownIdentity.d.ts +28 -0
  10. package/dist/hosts/astro/markdownIdentity.js +81 -0
  11. package/dist/hosts/environment.d.ts +15 -0
  12. package/dist/hosts/environment.js +16 -0
  13. package/dist/hosts/next/manifest.js +1 -1
  14. package/dist/hosts/next/sidecar.js +12 -2
  15. package/dist/hosts/next/wrapper.d.ts +2 -0
  16. package/dist/hosts/next/wrapper.js +2 -1
  17. package/dist/hosts/projectBridge.d.ts +1 -0
  18. package/dist/hosts/projectBridge.js +1 -0
  19. package/dist/hosts/static/manifest.js +3 -1
  20. package/dist/hosts/static/server.js +4 -0
  21. package/dist/hosts/vite/vite.d.ts +4 -0
  22. package/dist/hosts/vite/vite.js +28 -5
  23. package/dist/inspector/agent/McpConnectionDialog.d.ts +3 -2
  24. package/dist/inspector/agent/McpConnectionDialog.js +23 -22
  25. package/dist/inspector/agent/activity.d.ts +18 -0
  26. package/dist/inspector/agent/activity.js +70 -0
  27. package/dist/inspector/agent/client.d.ts +8 -0
  28. package/dist/inspector/agent/client.js +65 -5
  29. package/dist/inspector/agent/connectionStatus.d.ts +3 -3
  30. package/dist/inspector/agent/connectionStatus.js +9 -2
  31. package/dist/inspector/agent/httpTransport.d.ts +6 -0
  32. package/dist/inspector/agent/httpTransport.js +50 -3
  33. package/dist/inspector/agent/protocol.d.ts +9 -3
  34. package/dist/inspector/agent/protocol.js +1 -1
  35. package/dist/inspector/agent/verification.d.ts +11 -1
  36. package/dist/inspector/agent/verification.js +53 -25
  37. package/dist/inspector/canvas/AgentActivityOverlay.d.ts +13 -0
  38. package/dist/inspector/canvas/AgentActivityOverlay.js +111 -0
  39. package/dist/inspector/canvas/CanvasCard.d.ts +9 -2
  40. package/dist/inspector/canvas/CanvasCard.js +133 -38
  41. package/dist/inspector/canvas/CanvasElementOverlay.js +200 -22
  42. package/dist/inspector/canvas/CanvasToolIcons.d.ts +10 -0
  43. package/dist/inspector/canvas/CanvasToolIcons.js +20 -0
  44. package/dist/inspector/canvas/CanvasToolbar.js +8 -4
  45. package/dist/inspector/canvas/CanvasWorkspace.js +243 -56
  46. package/dist/inspector/canvas/StaleChangeIndicator.js +3 -13
  47. package/dist/inspector/canvas/agentPresentation.d.ts +8 -8
  48. package/dist/inspector/canvas/agentPresentation.js +64 -52
  49. package/dist/inspector/canvas/canvasGestures.d.ts +1 -1
  50. package/dist/inspector/canvas/canvasGestures.js +1 -1
  51. package/dist/inspector/canvas/canvasStore.d.ts +46 -82
  52. package/dist/inspector/canvas/canvasStore.js +251 -284
  53. package/dist/inspector/canvas/frameContent.d.ts +21 -0
  54. package/dist/inspector/canvas/frameContent.js +44 -0
  55. package/dist/inspector/canvas/frameProtocol.d.ts +34 -4
  56. package/dist/inspector/canvas/frameProtocol.js +9 -2
  57. package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -1
  58. package/dist/inspector/canvas/keyboardShortcuts.js +5 -3
  59. package/dist/inspector/canvas/layout.d.ts +52 -0
  60. package/dist/inspector/canvas/layout.js +106 -0
  61. package/dist/inspector/canvas/linkEligibility.d.ts +0 -5
  62. package/dist/inspector/canvas/linkEligibility.js +0 -29
  63. package/dist/inspector/canvas/projection.d.ts +15 -5
  64. package/dist/inspector/canvas/projection.js +69 -13
  65. package/dist/inspector/canvas/rendererBootstrap.js +45 -4
  66. package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
  67. package/dist/inspector/canvas/rendererElementSelector.js +102 -45
  68. package/dist/inspector/canvas/rendererStylesheet.js +2 -2
  69. package/dist/inspector/canvas/resizeHandleScale.js +1 -1
  70. package/dist/inspector/canvas/sessionStore.d.ts +11 -26
  71. package/dist/inspector/canvas/sessionStore.js +43 -183
  72. package/dist/inspector/canvas/staleChangeDetector.js +9 -3
  73. package/dist/inspector/canvas/useCanvasLayoutAnimation.d.ts +5 -0
  74. package/dist/inspector/canvas/useCanvasLayoutAnimation.js +107 -0
  75. package/dist/inspector/canvas/viewStore.d.ts +38 -0
  76. package/dist/inspector/canvas/viewStore.js +104 -0
  77. package/dist/inspector/canvas/wheelPan.d.ts +8 -0
  78. package/dist/inspector/canvas/wheelPan.js +8 -0
  79. package/dist/inspector/changes/changesLog.d.ts +6 -5
  80. package/dist/inspector/changes/changesLog.js +40 -32
  81. package/dist/inspector/changes/draftChanges.d.ts +56 -0
  82. package/dist/inspector/changes/draftChanges.js +300 -0
  83. package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
  84. package/dist/inspector/changes/previewDiagnostics.js +11 -25
  85. package/dist/inspector/clientManifest.d.ts +3 -1
  86. package/dist/inspector/clientManifest.js +1 -0
  87. package/dist/inspector/comments/CommentsOverlay.d.ts +2 -0
  88. package/dist/inspector/comments/CommentsOverlay.js +250 -0
  89. package/dist/inspector/comments/element.d.ts +6 -0
  90. package/dist/inspector/comments/element.js +47 -0
  91. package/dist/inspector/comments/store.d.ts +29 -0
  92. package/dist/inspector/comments/store.js +121 -0
  93. package/dist/inspector/comments/verification.d.ts +5 -0
  94. package/dist/inspector/comments/verification.js +52 -0
  95. package/dist/inspector/drafts/model.d.ts +35 -0
  96. package/dist/inspector/drafts/model.js +45 -0
  97. package/dist/inspector/drafts/store.d.ts +28 -0
  98. package/dist/inspector/drafts/store.js +231 -0
  99. package/dist/inspector/index.js +26 -35
  100. package/dist/inspector/inline-text/inlineTextEditor.js +7 -0
  101. package/dist/inspector/inspection/useBrowserCssInspection.js +15 -1
  102. package/dist/inspector/iterations/capture.d.ts +2 -0
  103. package/dist/inspector/iterations/capture.js +173 -0
  104. package/dist/inspector/iterations/client.d.ts +7 -0
  105. package/dist/inspector/iterations/client.js +52 -0
  106. package/dist/inspector/iterations/coordinator.d.ts +15 -0
  107. package/dist/inspector/iterations/coordinator.js +130 -0
  108. package/dist/inspector/iterations/prompt.d.ts +3 -0
  109. package/dist/inspector/iterations/prompt.js +26 -0
  110. package/dist/inspector/overlay/DomNavigator.d.ts +10 -0
  111. package/dist/inspector/overlay/DomNavigator.js +46 -0
  112. package/dist/inspector/overlay/InspectorOverlay.js +6 -2
  113. package/dist/inspector/overlay/clickPolicy.d.ts +18 -2
  114. package/dist/inspector/overlay/clickPolicy.js +34 -3
  115. package/dist/inspector/overlay/elementSelector.d.ts +1 -1
  116. package/dist/inspector/overlay/elementSelector.js +18 -7
  117. package/dist/inspector/overlay/interactionStyles.js +3 -1
  118. package/dist/inspector/overlay/spacingGestures.d.ts +15 -3
  119. package/dist/inspector/overlay/spacingGestures.js +88 -13
  120. package/dist/inspector/projection/{workspaceProjection.d.ts → draftProjection.d.ts} +6 -6
  121. package/dist/inspector/projection/{workspaceProjection.js → draftProjection.js} +4 -4
  122. package/dist/inspector/projection/renderedInstance.d.ts +2 -1
  123. package/dist/inspector/projection/renderedInstance.js +9 -5
  124. package/dist/inspector/projection/structuralProjection.d.ts +2 -1
  125. package/dist/inspector/projection/structuralProjection.js +19 -15
  126. package/dist/inspector/projection/textProjection.d.ts +2 -1
  127. package/dist/inspector/projection/textProjection.js +8 -4
  128. package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
  129. package/dist/inspector/prompt/clipboardHandoff.d.ts +10 -18
  130. package/dist/inspector/prompt/clipboardHandoff.js +92 -143
  131. package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
  132. package/dist/inspector/prompt/generatePrompt.js +17 -16
  133. package/dist/inspector/runtime/runtimeConfig.d.ts +10 -1
  134. package/dist/inspector/runtime/runtimeConfig.js +24 -0
  135. package/dist/inspector/selection/hierarchy.d.ts +6 -0
  136. package/dist/inspector/selection/hierarchy.js +8 -0
  137. package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
  138. package/dist/inspector/settings/SettingsDialog.js +22 -9
  139. package/dist/inspector/shell/AgentStatusButton.d.ts +11 -0
  140. package/dist/inspector/shell/AgentStatusButton.js +36 -0
  141. package/dist/inspector/shell/AppShell.js +4 -2
  142. package/dist/inspector/shell/ChangesLog.js +47 -27
  143. package/dist/inspector/shell/CopyPromptButton.js +146 -90
  144. package/dist/inspector/shell/EmptyState.js +9 -8
  145. package/dist/inspector/shell/InspectorShell.js +14 -25
  146. package/dist/inspector/shell/PeekOriginalButton.js +24 -23
  147. package/dist/inspector/shell/originalPreview.js +5 -5
  148. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  149. package/dist/inspector/shell/shortcuts.js +11 -0
  150. package/dist/inspector/sketch/SketchEditor.js +14 -14
  151. package/dist/inspector/sketch/SketchEntryButton.js +1 -1
  152. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +3 -1
  153. package/dist/inspector/sketch/SketchFrameOverlay.js +11 -8
  154. package/dist/inspector/sketch/SketchLayersPanel.js +13 -5
  155. package/dist/inspector/sketch/SketchOverlay.js +7 -7
  156. package/dist/inspector/sketch/SketchWorkspace.js +18 -12
  157. package/dist/inspector/sketch/interaction.d.ts +1 -0
  158. package/dist/inspector/sketch/interaction.js +8 -4
  159. package/dist/inspector/sketch/model.d.ts +5 -0
  160. package/dist/inspector/sketch/model.js +6 -2
  161. package/dist/inspector/sketch/raster.d.ts +1 -2
  162. package/dist/inspector/sketch/raster.js +68 -4
  163. package/dist/inspector/sketch/store.js +48 -23
  164. package/dist/inspector/styleEditors/BackgroundEditor.d.ts +4 -0
  165. package/dist/inspector/styleEditors/BackgroundEditor.js +195 -0
  166. package/dist/inspector/styleEditors/BorderEditor.js +49 -78
  167. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -1
  168. package/dist/inspector/styleEditors/BoxShadowEditor.d.ts +1 -2
  169. package/dist/inspector/styleEditors/BoxShadowEditor.js +132 -4
  170. package/dist/inspector/styleEditors/ColorPicker.d.ts +5 -1
  171. package/dist/inspector/styleEditors/ColorPicker.js +10 -4
  172. package/dist/inspector/styleEditors/GapField.js +3 -5
  173. package/dist/inspector/styleEditors/GridChildSection.d.ts +1 -1
  174. package/dist/inspector/styleEditors/GridChildSection.js +18 -9
  175. package/dist/inspector/styleEditors/GridPicker.d.ts +1 -1
  176. package/dist/inspector/styleEditors/GridPicker.js +8 -2
  177. package/dist/inspector/styleEditors/GridSection.js +1 -1
  178. package/dist/inspector/styleEditors/GridValueField.js +19 -14
  179. package/dist/inspector/styleEditors/ImageBackgroundEditor.d.ts +18 -0
  180. package/dist/inspector/styleEditors/ImageBackgroundEditor.js +118 -0
  181. package/dist/inspector/styleEditors/LayoutBlockedIndicator.d.ts +2 -2
  182. package/dist/inspector/styleEditors/LayoutBlockedIndicator.js +3 -3
  183. package/dist/inspector/styleEditors/LayoutComboField.js +6 -4
  184. package/dist/inspector/styleEditors/LayoutDropdown.js +6 -3
  185. package/dist/inspector/styleEditors/LayoutSection.js +32 -11
  186. package/dist/inspector/styleEditors/SpacingBox.js +16 -9
  187. package/dist/inspector/styleEditors/Typography.js +11 -3
  188. package/dist/inspector/styleEditors/backgroundLayers.d.ts +24 -0
  189. package/dist/inspector/styleEditors/backgroundLayers.js +51 -0
  190. package/dist/inspector/styleEditors/gradientValue.d.ts +17 -0
  191. package/dist/inspector/styleEditors/gradientValue.js +114 -0
  192. package/dist/inspector/styleEditors/inlineAuthored.d.ts +10 -0
  193. package/dist/inspector/styleEditors/inlineAuthored.js +114 -0
  194. package/dist/inspector/styleEditors/layoutValue.d.ts +0 -8
  195. package/dist/inspector/styleEditors/layoutValue.js +0 -42
  196. package/dist/inspector/styleEditors/nudgeValue.d.ts +4 -4
  197. package/dist/inspector/styleEditors/nudgeValue.js +41 -11
  198. package/dist/inspector/tokens/TokenField.d.ts +7 -0
  199. package/dist/inspector/tokens/TokenField.js +49 -22
  200. package/dist/inspector/tokens/TokenLabel.js +5 -14
  201. package/dist/inspector/tokens/TokensPanel.js +2 -1
  202. package/dist/inspector/tokens/dragPointerLock.d.ts +8 -0
  203. package/dist/inspector/tokens/dragPointerLock.js +70 -0
  204. package/dist/inspector/tokens/editActions.d.ts +1 -1
  205. package/dist/inspector/tokens/editActions.js +1 -1
  206. package/dist/inspector/ui/AtRuleContext.js +4 -6
  207. package/dist/inspector/ui/Button.d.ts +6 -2
  208. package/dist/inspector/ui/Button.js +4 -3
  209. package/dist/inspector/ui/ControlSurface.d.ts +2 -2
  210. package/dist/inspector/ui/ControlSurface.js +4 -3
  211. package/dist/inspector/ui/Disclosure.d.ts +22 -0
  212. package/dist/inspector/ui/Disclosure.js +13 -0
  213. package/dist/inspector/ui/InlineStyleWarning.d.ts +11 -0
  214. package/dist/inspector/ui/InlineStyleWarning.js +13 -0
  215. package/dist/inspector/ui/InspectorPopover.d.ts +2 -1
  216. package/dist/inspector/ui/InspectorPopover.js +4 -5
  217. package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
  218. package/dist/inspector/ui/PopoverListbox.js +8 -7
  219. package/dist/inspector/ui/SegmentedControl.d.ts +2 -1
  220. package/dist/inspector/ui/SegmentedControl.js +2 -2
  221. package/dist/inspector/ui/Select.d.ts +6 -1
  222. package/dist/inspector/ui/Select.js +7 -11
  223. package/dist/inspector/ui/SideValuesField.js +1 -1
  224. package/dist/inspector/ui/Tooltip.d.ts +26 -0
  225. package/dist/inspector/ui/Tooltip.js +32 -0
  226. package/dist/inspector/ui/classNames.d.ts +2 -0
  227. package/dist/inspector/ui/classNames.js +4 -0
  228. package/dist/inspector/ui/customCursors.d.ts +23 -0
  229. package/dist/inspector/ui/customCursors.js +122 -0
  230. package/dist/inspector/ui/dragCursor.d.ts +6 -0
  231. package/dist/inspector/ui/dragCursor.js +13 -0
  232. package/dist/inspector/ui/portalContainer.d.ts +2 -0
  233. package/dist/inspector/ui/portalContainer.js +6 -0
  234. package/dist/inspector/ui/styles.js +34 -24
  235. package/dist/inspector/workspace/commands.d.ts +9 -0
  236. package/dist/inspector/workspace/commands.js +82 -0
  237. package/dist/inspector/workspace/controller.d.ts +4 -0
  238. package/dist/inspector/workspace/controller.js +66 -0
  239. package/dist/inspector/workspace/handoff.d.ts +42 -0
  240. package/dist/inspector/workspace/handoff.js +115 -0
  241. package/dist/inspector/workspace/timeline.d.ts +30 -0
  242. package/dist/inspector/workspace/timeline.js +108 -0
  243. package/dist/internal/agent-protocol.d.ts +19 -2
  244. package/dist/internal/agent-protocol.js +13 -0
  245. package/dist/nudge-ui.mjs +560 -299
  246. package/dist/project/artifacts.d.ts +13 -0
  247. package/dist/project/artifacts.js +253 -0
  248. package/dist/transport/artifacts.d.ts +8 -0
  249. package/dist/transport/artifacts.js +9 -0
  250. package/dist/transport/editor.d.ts +20 -1
  251. package/dist/transport/editor.js +44 -11
  252. package/dist/transport/routes.d.ts +6 -1
  253. package/dist/transport/routes.js +6 -1
  254. package/package.json +6 -2
  255. package/dist/inspector/changes/workspaceChanges.d.ts +0 -43
  256. package/dist/inspector/changes/workspaceChanges.js +0 -239
  257. package/dist/inspector/shell/DomNavigation.d.ts +0 -8
  258. package/dist/inspector/shell/DomNavigation.js +0 -30
@@ -1,5 +1,5 @@
1
1
  import type { ReactElement } from "react";
2
- import type { EditTarget } from "../selection/editTarget.js";
2
+ import { type EditTarget } from "../selection/editTarget.js";
3
3
  export declare const GRID_PICKER_MAX_COLUMNS = 12;
4
4
  export declare const GRID_PICKER_MAX_ROWS = 8;
5
5
  export interface GridPickerProps {
@@ -1,7 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useLayoutEffect, useRef, useState } from "react";
3
3
  import { getLayoutValue } from "./layoutValue.js";
4
+ import { inlineBlockedBy } from "./inlineAuthored.js";
4
5
  import { setStyles } from "../tokens/editActions.js";
6
+ import { targetElements } from "../selection/editTarget.js";
7
+ import { InlineStyleWarning } from "../ui/InlineStyleWarning.js";
5
8
  export const GRID_PICKER_MAX_COLUMNS = 12;
6
9
  export const GRID_PICKER_MAX_ROWS = 8;
7
10
  function clampDimension(value, max) {
@@ -96,6 +99,7 @@ export function GridPicker({ domElement: el, editTarget, revision = 0, onAfterEd
96
99
  const [popoverPosition, setPopoverPosition] = useState(null);
97
100
  const pickerRef = useRef(null);
98
101
  const triggerRef = useRef(null);
102
+ const blockedBy = inlineBlockedBy(targetElements(editTarget ?? el), "grid-template-columns", "grid-template-rows");
99
103
  useEffect(() => {
100
104
  setDimensions(readDimensions(el));
101
105
  setHovered(null);
@@ -143,6 +147,8 @@ export function GridPicker({ domElement: el, editTarget, revision = 0, onAfterEd
143
147
  };
144
148
  }, [open]);
145
149
  function choose(columns, rows) {
150
+ if (blockedBy)
151
+ return;
146
152
  const next = {
147
153
  columns: clampDimension(columns, GRID_PICKER_MAX_COLUMNS),
148
154
  rows: clampDimension(rows, GRID_PICKER_MAX_ROWS),
@@ -165,10 +171,10 @@ export function GridPicker({ domElement: el, editTarget, revision = 0, onAfterEd
165
171
  setOpen(false);
166
172
  setHovered(null);
167
173
  }
168
- }, children: [_jsx("button", { type: "button", className: "grid-picker__trigger", "data-test": "layout-grid-picker-trigger", ref: triggerRef, "aria-label": `Grid ${dimensions.columns} by ${dimensions.rows}`, "aria-expanded": open, "aria-haspopup": "dialog", onClick: () => {
174
+ }, children: [_jsx("button", { type: "button", className: "grid-picker__trigger", "data-test": "layout-grid-picker-trigger", ref: triggerRef, "aria-label": `Grid ${dimensions.columns} by ${dimensions.rows}`, "aria-expanded": open, "aria-haspopup": "dialog", disabled: blockedBy !== null, onClick: () => {
169
175
  setPopoverPosition(null);
170
176
  setOpen((current) => !current);
171
- }, children: _jsxs("span", { className: "grid-picker__preview", style: gridStyle(dimensions.columns, dimensions.rows), "aria-hidden": "true", children: [previewCells.map(({ column, row }) => (_jsx("span", { className: "grid-picker__preview-cell" }, `${column}-${row}`))), _jsxs("span", { className: "grid-picker__preview-label", children: [dimensions.columns, " \u00D7 ", dimensions.rows] })] }) }), open ? (_jsxs("div", { className: "grid-picker__popover", "data-test": "layout-grid-picker-popover", role: "dialog", "aria-label": "Choose grid size", style: popoverPosition ? {
177
+ }, children: _jsxs("span", { className: "grid-picker__preview", style: gridStyle(dimensions.columns, dimensions.rows), "aria-hidden": "true", children: [previewCells.map(({ column, row }) => (_jsx("span", { className: "grid-picker__preview-cell" }, `${column}-${row}`))), _jsxs("span", { className: "grid-picker__preview-label", children: [dimensions.columns, " \u00D7 ", dimensions.rows] })] }) }), _jsx(InlineStyleWarning, { blockedBy: blockedBy }), open ? (_jsxs("div", { className: "grid-picker__popover", "data-test": "layout-grid-picker-popover", role: "dialog", "aria-label": "Choose grid size", style: popoverPosition ? {
172
178
  left: popoverPosition.left,
173
179
  top: popoverPosition.top,
174
180
  visibility: "visible",
@@ -23,5 +23,5 @@ const GRID_CONTENT_ALIGNMENT_OPTIONS = [
23
23
  const GRID_ITEM_ALIGNMENT_OPTIONS = ["normal", "stretch", "start", "end", "center", "self-start", "self-end"];
24
24
  export function GridSection({ domElement: el, editTarget, selection, entries, tokenRows, showContainer, showChild, revision = 0, onAfterEdit, }) {
25
25
  const [advancedOpen, setAdvancedOpen] = useState(false);
26
- return (_jsxs(_Fragment, { children: [showContainer ? _jsx("div", { className: "layout__group layout__grid", "data-test": "layout-grid-container", children: _jsxs("div", { className: "layout__grid-primary", children: [_jsxs("div", { className: "layout__grid-picker-column", children: [_jsxs("div", { className: "layout__grid-heading", children: [_jsx("div", { className: "editor__title", children: "Grid" }), _jsx(InspectorPopover, { "data-test": "layout-grid-settings", side: "bottom", align: "end", triggerElement: (_jsx(IconButton, { variant: "quiet", size: "default", label: "Grid settings", title: "Grid settings", "data-active": advancedOpen, "data-test": "layout-grid-settings", children: _jsx(IconSettings, { size: 16, stroke: 1.8, "aria-hidden": "true" }) })), open: advancedOpen, onOpenChange: setAdvancedOpen, children: _jsxs("div", { className: "layout__grid-advanced", "data-test": "layout-grid-advanced", children: [_jsx("div", { className: "editor__title", children: "Advanced grid CSS" }), _jsxs("div", { className: "layout__grid-fields", children: [_jsx(GridValueField, { property: "grid-template-columns", domElement: el, editTarget: editTarget, selection: selection, revision: revision, onAfterEdit: onAfterEdit }), _jsx(GridValueField, { property: "grid-template-rows", domElement: el, editTarget: editTarget, selection: selection, revision: revision, onAfterEdit: onAfterEdit }), _jsx(LayoutDropdown, { property: "grid-auto-flow", options: GRID_AUTO_FLOW_OPTIONS, domElement: el, editTarget: editTarget, selection: selection, revision: revision, onAfterEdit: onAfterEdit }), _jsx(GridValueField, { property: "grid-auto-columns", domElement: el, editTarget: editTarget, selection: selection, revision: revision, onAfterEdit: onAfterEdit }), _jsx(GridValueField, { property: "grid-auto-rows", domElement: el, editTarget: editTarget, selection: selection, revision: revision, onAfterEdit: onAfterEdit })] }), _jsxs("div", { className: "layout__grid-alignment", "data-test": "layout-grid-alignment", children: [_jsx(LayoutDropdown, { property: "justify-content", options: GRID_CONTENT_ALIGNMENT_OPTIONS, domElement: el, editTarget: editTarget, selection: selection, stacked: true, revision: revision, onAfterEdit: onAfterEdit }), _jsx(LayoutDropdown, { property: "align-content", options: GRID_CONTENT_ALIGNMENT_OPTIONS, domElement: el, editTarget: editTarget, selection: selection, stacked: true, revision: revision, onAfterEdit: onAfterEdit }), _jsx(LayoutDropdown, { property: "justify-items", options: GRID_ITEM_ALIGNMENT_OPTIONS, domElement: el, editTarget: editTarget, selection: selection, stacked: true, revision: revision, onAfterEdit: onAfterEdit }), _jsx(LayoutDropdown, { property: "align-items", options: GRID_ITEM_ALIGNMENT_OPTIONS, domElement: el, editTarget: editTarget, selection: selection, stacked: true, revision: revision, onAfterEdit: onAfterEdit })] })] }) })] }), _jsx(GridPicker, { domElement: el, editTarget: editTarget, revision: revision, onAfterEdit: onAfterEdit })] }), _jsxs("div", { className: "layout__grid-gap", "data-test": "layout-grid-gap", children: [_jsx("div", { className: "editor__title", children: "Gap" }), _jsxs("div", { className: "layout__grid-gap-fields", children: [_jsx(ControlSurface, { className: "layout__spacing-field", "data-test": "layout-grid-row-gap", children: _jsx(GapField, { property: "row-gap", domElement: el, editTarget: editTarget, selection: selection, entries: entries, tokenRows: tokenRows, leading: (_jsx(IconSpacingVertical, { className: "layout__spacing-icon", size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true", "data-test": "layout-spacing-icon-row-gap" })), onAfterEdit: onAfterEdit }) }), _jsx(ControlSurface, { className: "layout__spacing-field", "data-test": "layout-grid-column-gap", children: _jsx(GapField, { property: "column-gap", domElement: el, editTarget: editTarget, selection: selection, entries: entries, tokenRows: tokenRows, leading: (_jsx(IconSpacingHorizontal, { className: "layout__spacing-icon", size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true", "data-test": "layout-spacing-icon-column-gap" })), onAfterEdit: onAfterEdit }) })] })] })] }) }) : null, showChild ? _jsx(GridChildSection, { domElement: el, editTarget: editTarget, revision: revision, onAfterEdit: onAfterEdit }) : null] }));
26
+ return (_jsxs(_Fragment, { children: [showContainer ? _jsx("div", { className: "layout__group layout__grid", "data-test": "layout-grid-container", children: _jsxs("div", { className: "layout__grid-primary", children: [_jsxs("div", { className: "layout__grid-picker-column", children: [_jsxs("div", { className: "layout__grid-heading", children: [_jsx("div", { className: "editor__title", children: "Grid" }), _jsx(InspectorPopover, { "data-test": "layout-grid-settings", side: "bottom", align: "end", triggerTooltip: "Grid settings", triggerElement: (_jsx(IconButton, { variant: "quiet", size: "default", label: "Grid settings", "data-active": advancedOpen, "data-test": "layout-grid-settings", children: _jsx(IconSettings, { size: 16, stroke: 1.8, "aria-hidden": "true" }) })), open: advancedOpen, onOpenChange: setAdvancedOpen, children: _jsxs("div", { className: "layout__grid-advanced", "data-test": "layout-grid-advanced", children: [_jsx("div", { className: "editor__title", children: "Advanced grid CSS" }), _jsxs("div", { className: "layout__grid-fields", children: [_jsx(GridValueField, { property: "grid-template-columns", domElement: el, editTarget: editTarget, selection: selection, revision: revision, onAfterEdit: onAfterEdit }), _jsx(GridValueField, { property: "grid-template-rows", domElement: el, editTarget: editTarget, selection: selection, revision: revision, onAfterEdit: onAfterEdit }), _jsx(LayoutDropdown, { property: "grid-auto-flow", options: GRID_AUTO_FLOW_OPTIONS, domElement: el, editTarget: editTarget, selection: selection, revision: revision, onAfterEdit: onAfterEdit }), _jsx(GridValueField, { property: "grid-auto-columns", domElement: el, editTarget: editTarget, selection: selection, revision: revision, onAfterEdit: onAfterEdit }), _jsx(GridValueField, { property: "grid-auto-rows", domElement: el, editTarget: editTarget, selection: selection, revision: revision, onAfterEdit: onAfterEdit })] }), _jsxs("div", { className: "layout__grid-alignment", "data-test": "layout-grid-alignment", children: [_jsx(LayoutDropdown, { property: "justify-content", options: GRID_CONTENT_ALIGNMENT_OPTIONS, domElement: el, editTarget: editTarget, selection: selection, stacked: true, revision: revision, onAfterEdit: onAfterEdit }), _jsx(LayoutDropdown, { property: "align-content", options: GRID_CONTENT_ALIGNMENT_OPTIONS, domElement: el, editTarget: editTarget, selection: selection, stacked: true, revision: revision, onAfterEdit: onAfterEdit }), _jsx(LayoutDropdown, { property: "justify-items", options: GRID_ITEM_ALIGNMENT_OPTIONS, domElement: el, editTarget: editTarget, selection: selection, stacked: true, revision: revision, onAfterEdit: onAfterEdit }), _jsx(LayoutDropdown, { property: "align-items", options: GRID_ITEM_ALIGNMENT_OPTIONS, domElement: el, editTarget: editTarget, selection: selection, stacked: true, revision: revision, onAfterEdit: onAfterEdit })] })] }) })] }), _jsx(GridPicker, { domElement: el, editTarget: editTarget, revision: revision, onAfterEdit: onAfterEdit })] }), _jsxs("div", { className: "layout__grid-gap", "data-test": "layout-grid-gap", children: [_jsx("div", { className: "layout__grid-heading", "data-test": "layout-grid-gap-heading", "aria-hidden": "true", children: _jsx("div", { className: "editor__title" }) }), _jsxs("div", { className: "layout__grid-gap-fields", children: [_jsx(ControlSurface, { className: "layout__spacing-field", "data-test": "layout-grid-row-gap", children: _jsx(GapField, { property: "row-gap", domElement: el, editTarget: editTarget, selection: selection, entries: entries, tokenRows: tokenRows, leading: (_jsx(IconSpacingVertical, { className: "layout__spacing-icon", size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true", "data-test": "layout-spacing-icon-row-gap" })), onAfterEdit: onAfterEdit }) }), _jsx(ControlSurface, { className: "layout__spacing-field", "data-test": "layout-grid-column-gap", children: _jsx(GapField, { property: "column-gap", domElement: el, editTarget: editTarget, selection: selection, entries: entries, tokenRows: tokenRows, leading: (_jsx(IconSpacingHorizontal, { className: "layout__spacing-icon", size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true", "data-test": "layout-spacing-icon-column-gap" })), onAfterEdit: onAfterEdit }) })] })] })] }) }) : null, showChild ? _jsx(GridChildSection, { domElement: el, editTarget: editTarget, revision: revision, onAfterEdit: onAfterEdit }) : null] }));
27
27
  }
@@ -1,10 +1,12 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from "react";
3
3
  import { FieldRow } from "../ui/FieldRow.js";
4
4
  import { TextInput } from "../ui/TextInput.js";
5
5
  import { getLayoutValue } from "./layoutValue.js";
6
6
  import { setStyle } from "../tokens/editActions.js";
7
7
  import { useFieldAtRules } from "../ui/AtRuleContext.js";
8
+ import { InlineStyleWarning } from "../ui/InlineStyleWarning.js";
9
+ import { inlineBlockedBy } from "./inlineAuthored.js";
8
10
  const DEFAULT_GRID_VALUES = {
9
11
  "grid-template-columns": "none",
10
12
  "grid-template-rows": "none",
@@ -21,6 +23,7 @@ const DEFAULT_GRID_VALUES = {
21
23
  export function GridValueField({ property, domElement: el, editTarget, selection, revision = 0, onAfterEdit, }) {
22
24
  const atRules = useFieldAtRules(property);
23
25
  const mixed = selection?.getProperty(property)?.value.kind === "mixed";
26
+ const blockedBy = inlineBlockedBy(selection?.domElements ?? [el], property);
24
27
  const readValue = () => {
25
28
  const value = getLayoutValue(el, property);
26
29
  return value.authored || value.computed || DEFAULT_GRID_VALUES[property] || "";
@@ -41,6 +44,8 @@ export function GridValueField({ property, domElement: el, editTarget, selection
41
44
  setDraft(next);
42
45
  }
43
46
  function commit() {
47
+ if (blockedBy)
48
+ return;
44
49
  const next = draftRef.current.trim();
45
50
  if (!next) {
46
51
  updateDraft(mixed ? "" : value);
@@ -54,17 +59,17 @@ export function GridValueField({ property, domElement: el, editTarget, selection
54
59
  function cancel() {
55
60
  updateDraft(mixed ? "" : value);
56
61
  }
57
- return (_jsx(FieldRow, { label: property, property: property, atRules: atRules, "data-test": `layout-grid-${property}`, children: _jsx(TextInput, { value: draft, placeholder: mixed ? "Mixed" : undefined, "data-test": `layout-grid-input-${property}`, "aria-label": property, onChange: (event) => updateDraft(event.target.value), onBlur: () => {
58
- commit();
59
- }, onKeyDown: (event) => {
60
- if (event.key === "Escape") {
61
- event.preventDefault();
62
- cancel();
63
- event.currentTarget.blur();
64
- }
65
- else if (event.key === "Enter") {
66
- event.preventDefault();
67
- event.currentTarget.blur();
68
- }
69
- } }) }));
62
+ return (_jsxs(FieldRow, { label: property, property: property, atRules: atRules, "data-test": `layout-grid-${property}`, children: [_jsx(TextInput, { value: draft, placeholder: mixed ? "Mixed" : undefined, "data-test": `layout-grid-input-${property}`, "aria-label": property, disabled: blockedBy !== null, onChange: (event) => updateDraft(event.target.value), onBlur: () => {
63
+ commit();
64
+ }, onKeyDown: (event) => {
65
+ if (event.key === "Escape") {
66
+ event.preventDefault();
67
+ cancel();
68
+ event.currentTarget.blur();
69
+ }
70
+ else if (event.key === "Enter") {
71
+ event.preventDefault();
72
+ event.currentTarget.blur();
73
+ }
74
+ } }), _jsx(InlineStyleWarning, { blockedBy: blockedBy })] }));
70
75
  }
@@ -0,0 +1,18 @@
1
+ import type { ReactElement } from "react";
2
+ import type { StyleDeclaration } from "../tokens/editActions.js";
3
+ interface ImageBackgroundEditorProps {
4
+ element: HTMLElement;
5
+ elements: readonly HTMLElement[];
6
+ value: string;
7
+ mixed: boolean;
8
+ sizingValues?: {
9
+ size: string;
10
+ repeat: string;
11
+ };
12
+ onUpload(value: string): boolean;
13
+ onCss(value: string): boolean;
14
+ onSizing(declarations: StyleDeclaration[]): boolean;
15
+ }
16
+ /** Preview uploaded images through the existing workspace style edits. */
17
+ export declare function ImageBackgroundEditor({ element, elements, value, mixed, sizingValues, onUpload, onCss, onSizing }: ImageBackgroundEditorProps): ReactElement;
18
+ export {};
@@ -0,0 +1,118 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { IconPhoto, IconUpload } from "@tabler/icons-react";
4
+ import { TokenValueField } from "../tokens/TokenField.js";
5
+ import { ControlSurface } from "../ui/ControlSurface.js";
6
+ import { getActiveDraftChanges } from "../changes/draftChanges.js";
7
+ import { getStateStyleValue } from "../shell/stateValue.js";
8
+ import { Button } from "../ui/Button.js";
9
+ import { InlineStyleWarning } from "../ui/InlineStyleWarning.js";
10
+ import { Select } from "../ui/Select.js";
11
+ import { StatusCallout } from "../ui/StatusCallout.js";
12
+ import { inlineBlockedBy } from "./inlineAuthored.js";
13
+ const SIZING = [
14
+ { value: "cover", label: "Cover" },
15
+ { value: "contain", label: "Fit" },
16
+ { value: "100% 100%", label: "Stretch" },
17
+ { value: "auto", label: "Original size" },
18
+ { value: "tile", label: "Tile" },
19
+ ];
20
+ function imageUrl(value) {
21
+ const match = /^url\(\s*(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'|([^\s"']+))\s*\)$/s.exec(value);
22
+ const url = match?.[1] ?? match?.[2] ?? match?.[3];
23
+ return url?.replace(/\\(["'\\])/g, "$1") ?? null;
24
+ }
25
+ function imageName(src, base) {
26
+ if (src.startsWith("data:"))
27
+ return "Uploaded image";
28
+ try {
29
+ return decodeURIComponent(new URL(src, base).pathname.split("/").pop() || "Background image");
30
+ }
31
+ catch {
32
+ return "Background image";
33
+ }
34
+ }
35
+ /** Preview uploaded images through the existing workspace style edits. */
36
+ export function ImageBackgroundEditor({ element, elements, value, mixed, sizingValues, onUpload, onCss, onSizing }) {
37
+ const input = useRef(null);
38
+ const reader = useRef(null);
39
+ const request = useRef(0);
40
+ const currentElements = useRef(elements);
41
+ currentElements.current = elements;
42
+ const [pending, setPending] = useState(false);
43
+ const [error, setError] = useState(null);
44
+ const [image, setImage] = useState(null);
45
+ const [sizingDraft, setSizingDraft] = useState(null);
46
+ const src = mixed ? null : imageUrl(value);
47
+ const title = src ? image?.src === src ? image.name : imageName(src, element.ownerDocument.baseURI) : null;
48
+ const computedSize = sizingValues?.size ?? getStateStyleValue(element, "background-size", "auto");
49
+ const computedRepeat = sizingValues?.repeat ?? getStateStyleValue(element, "background-repeat", "repeat");
50
+ const computedSizing = computedRepeat === "repeat" && /^(?:auto|auto auto)$/.test(computedSize) ? "tile"
51
+ : computedSize === "auto auto" ? "auto" : computedSize;
52
+ const sizing = sizingDraft?.element === element && sizingDraft.revision === getActiveDraftChanges().revision ? sizingDraft.value : computedSizing;
53
+ const uploadBlocked = inlineBlockedBy(elements, "background-image", "background-size", "background-repeat", "background-position");
54
+ const sizingBlocked = inlineBlockedBy(elements, "background-size", "background-repeat", "background-position");
55
+ useEffect(() => {
56
+ setPending(false);
57
+ setError(null);
58
+ return () => { request.current++; reader.current?.abort(); };
59
+ }, [element, value]);
60
+ async function upload(file) {
61
+ const id = ++request.current;
62
+ const targets = [...elements];
63
+ const isCurrent = () => request.current === id && currentElements.current.length === targets.length
64
+ && currentElements.current.every((element, index) => element === targets[index]);
65
+ if (!file.type.startsWith("image/")) {
66
+ setError("Choose an image file.");
67
+ return;
68
+ }
69
+ if (uploadBlocked)
70
+ return;
71
+ setPending(true);
72
+ setError(null);
73
+ try {
74
+ const data = await new Promise((resolve, reject) => {
75
+ const fileReader = new FileReader();
76
+ reader.current = fileReader;
77
+ fileReader.onload = () => typeof fileReader.result === "string" ? resolve(fileReader.result) : reject(new Error("No image data"));
78
+ fileReader.onerror = fileReader.onabort = () => reject(new Error("Cannot read image"));
79
+ fileReader.readAsDataURL(file);
80
+ });
81
+ const probe = new Image();
82
+ probe.src = data;
83
+ await probe.decode();
84
+ if (!isCurrent())
85
+ return;
86
+ if (!onUpload(`url("${data}")`))
87
+ return;
88
+ setImage({ src: data, name: file.name });
89
+ setSizingDraft({ element, revision: getActiveDraftChanges().revision, value: "cover" });
90
+ }
91
+ catch {
92
+ if (isCurrent())
93
+ setError("This image could not be loaded. Choose another file.");
94
+ }
95
+ finally {
96
+ if (isCurrent())
97
+ setPending(false);
98
+ }
99
+ }
100
+ function changeSizing(next) {
101
+ if (sizingBlocked)
102
+ return;
103
+ if (!onSizing([
104
+ { property: "background-size", value: next === "tile" ? "auto" : next },
105
+ { property: "background-repeat", value: next === "tile" ? "repeat" : "no-repeat" },
106
+ { property: "background-position", value: "center" },
107
+ ]))
108
+ return;
109
+ setSizingDraft({ element, revision: getActiveDraftChanges().revision, value: next });
110
+ }
111
+ return _jsxs("div", { className: "image-background", "data-test": "image-background-editor", children: [_jsx("input", { ref: input, type: "file", accept: "image/*", hidden: true, "data-test": "background-image-upload", "aria-label": "Upload background image", disabled: uploadBlocked !== null || pending, onChange: (event) => {
112
+ const file = event.currentTarget.files?.[0];
113
+ event.currentTarget.value = "";
114
+ if (file)
115
+ void upload(file);
116
+ } }), src ? _jsxs("div", { className: "image-background__grid", children: [_jsxs(Button, { variant: "quiet", className: "image-background__tile", "aria-label": "Replace background image", disabled: uploadBlocked !== null || pending, onClick: () => input.current?.click(), children: [_jsx("img", { className: "image-background__thumb", src: src, alt: "Background image thumbnail", onLoad: () => setImage((previous) => previous?.src === src ? previous : { src, name: imageName(src, element.ownerDocument.baseURI) }), onError: () => setError("The background image could not be loaded.") }), _jsx("span", { className: "image-background__title", title: title ?? undefined, children: title })] }), _jsx(Select, { "aria-label": "Background image sizing", value: sizing, options: SIZING.some((option) => option.value === sizing) ? SIZING : [...SIZING, { value: sizing, label: `Custom (${sizing})` }], disabled: sizingBlocked !== null, onValueChange: changeSizing })] }) : _jsxs(Button, { variant: "secondary", disabled: uploadBlocked !== null || pending, onClick: () => input.current?.click(), children: [pending ? _jsx(IconPhoto, { size: 16, "aria-hidden": "true" }) : _jsx(IconUpload, { size: 16, "aria-hidden": "true" }), pending ? "Loading image…" : mixed ? "Upload image to replace backgrounds" : "Upload image"] }), !src && !mixed && value !== "none" ? _jsx(ControlSurface, { children: _jsx(TokenValueField, { property: "background-image", domElement: element, entries: [], committedValue: value, label: "Background image CSS", blockedBy: inlineBlockedBy(elements, "background-image"), onCommitRaw: (raw) => { if (typeof CSS === "undefined" || CSS.supports("background-image", raw))
117
+ onCss(raw); }, onSelectToken: () => false, onUnlink: onCss }) }) : null, _jsx(InlineStyleWarning, { blockedBy: uploadBlocked ?? sizingBlocked }), error ? _jsx(StatusCallout, { tone: "warning", role: "alert", children: error }) : null] });
118
+ }
@@ -1,6 +1,6 @@
1
1
  import type { ReactElement } from "react";
2
2
  export interface LayoutBlockedIndicatorProps {
3
- blockedBy: string;
3
+ blockedBy: string | null;
4
4
  }
5
5
  /** Explains why a layout preview cannot override an inline-authored value. */
6
- export declare function LayoutBlockedIndicator({ blockedBy }: LayoutBlockedIndicatorProps): ReactElement;
6
+ export declare function LayoutBlockedIndicator({ blockedBy }: LayoutBlockedIndicatorProps): ReactElement | null;
@@ -1,6 +1,6 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Tooltip } from "@base-ui/react/tooltip";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { InlineStyleWarning } from "../ui/InlineStyleWarning.js";
3
3
  /** Explains why a layout preview cannot override an inline-authored value. */
4
4
  export function LayoutBlockedIndicator({ blockedBy }) {
5
- return (_jsx(Tooltip.Provider, { children: _jsxs(Tooltip.Root, { disableHoverablePopup: true, children: [_jsx(Tooltip.Trigger, { type: "button", delay: 0, className: "layout-combo-blocked", "data-test": "layout-combo-blocked", "aria-label": `Blocked by inline style: ${blockedBy}`, children: _jsx("span", { className: "layout-combo-blocked__symbol", "aria-hidden": "true", children: "!" }) }), _jsx(Tooltip.Portal, { container: document.getElementById("nudge-ui-root")?.shadowRoot ?? document.body, children: _jsx(Tooltip.Positioner, { className: "at-rule-tooltip-positioner", side: "top", align: "end", sideOffset: 7, children: _jsx(Tooltip.Popup, { className: "at-rule-tooltip", "data-test": "layout-combo-blocked-tooltip", children: _jsx("div", { className: "at-rule-tooltip__rules", children: _jsxs("div", { className: "at-rule-tooltip__rule at-rule-tooltip__rule--active", children: ["Set inline (", _jsx("code", { children: blockedBy }), "). Previews can't beat inline styles \u2014 move it to a stylesheet to edit it here."] }) }) }) }) })] }) }));
5
+ return (_jsx(InlineStyleWarning, { blockedBy: blockedBy, dataTest: "layout-combo-blocked", tooltipDataTest: "layout-combo-blocked-tooltip", className: "layout-combo-blocked", symbolClassName: "layout-combo-blocked__symbol" }));
6
6
  }
@@ -4,10 +4,12 @@ import { setStyle } from "../tokens/editActions.js";
4
4
  import { completeCssValue } from "./completeCssValue.js";
5
5
  import { nudgeCssValue } from "./nudgeValue.js";
6
6
  import { valuePolicyFor } from "./valuePolicy.js";
7
- import { inlineAuthoredValue, meaningfulLayoutValue } from "./layoutValue.js";
7
+ import { meaningfulLayoutValue } from "./layoutValue.js";
8
+ import { inlineBlockedBy } from "./inlineAuthored.js";
8
9
  import { LayoutBlockedIndicator } from "./LayoutBlockedIndicator.js";
9
10
  import { Select } from "../ui/Select.js";
10
11
  import { TextInput } from "../ui/TextInput.js";
12
+ import { cx } from "../ui/classNames.js";
11
13
  import { formatInspectorLabel } from "../ui/labels.js";
12
14
  import { AtRuleIndicator, useFieldAtRules } from "../ui/AtRuleContext.js";
13
15
  import { getCanvasProjectionAcknowledgementVersion, getCanvasProjectionStatus, subscribeCanvasProjectionAcknowledgements, } from "../canvas/projection.js";
@@ -58,7 +60,7 @@ export function LayoutComboField(props) {
58
60
  // blocked (with the reason) instead of accepting edits that silently snap
59
61
  // back to the computed value on the next revision. Recomputed every render
60
62
  // so external style-attribute edits update the field without a remount.
61
- const blockedBy = inlineAuthoredValue(el, property);
63
+ const blockedBy = inlineBlockedBy(selection?.domElements ?? [el], property);
62
64
  function commit(value) {
63
65
  if (blockedBy)
64
66
  return;
@@ -124,10 +126,10 @@ export function LayoutComboField(props) {
124
126
  draftDirtyRef.current = true;
125
127
  setCustomValue(e.target.value);
126
128
  }, onBlur: handleCustomApply, onKeyDown: handleCustomKeyDown }));
127
- return (_jsxs("span", { className: `layout-combo${compact ? " layout-combo--compact" : ""}${blockedBy ? " layout-combo--blocked" : ""}`, "data-test": "layout-combo", "data-property": property, children: [inputOnly ? customInput : (_jsxs(_Fragment, { children: [_jsx(Select, { compact: compact, appearance: appearance, "data-test": `layout-combo-select-${property}`, value: selectValue, options: [
129
+ return (_jsxs("span", { className: cx("layout-combo", compact && "layout-combo--compact", blockedBy && "layout-combo--blocked"), "data-test": "layout-combo", "data-property": property, children: [inputOnly ? customInput : (_jsxs(_Fragment, { children: [_jsx(Select, { compact: compact, appearance: appearance, "data-test": `layout-combo-select-${property}`, value: selectValue, options: [
128
130
  ...presets.map((p) => ({ value: p, label: formatInspectorLabel(p) })),
129
131
  { value: CUSTOM_KEY, label: "Custom…" },
130
- ], onValueChange: handleSelectChange, disabled: blockedBy !== null }), showCustom || alwaysShowInput ? (_jsx("span", { className: "layout-combo__custom", children: customInput })) : null] })), _jsx(AtRuleIndicator, { atRules: atRules }), blockedBy ? _jsx(LayoutBlockedIndicator, { blockedBy: blockedBy }) : null] }));
132
+ ], onValueChange: handleSelectChange, disabled: blockedBy !== null }), showCustom || alwaysShowInput ? (_jsx("span", { className: "layout-combo__custom", children: customInput })) : null] })), _jsx(AtRuleIndicator, { atRules: atRules }), _jsx(LayoutBlockedIndicator, { blockedBy: blockedBy })] }));
131
133
  }
132
134
  function arrowDirection(key) {
133
135
  if (key === "ArrowUp" || key === "ArrowRight")
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
3
  import { setStyle } from "../tokens/editActions.js";
4
4
  import { FieldRow } from "../ui/FieldRow.js";
@@ -6,11 +6,14 @@ import { Select } from "../ui/Select.js";
6
6
  import { formatInspectorLabel } from "../ui/labels.js";
7
7
  import { getStateStyleValue } from "../shell/stateValue.js";
8
8
  import { useFieldAtRules } from "../ui/AtRuleContext.js";
9
+ import { InlineStyleWarning } from "../ui/InlineStyleWarning.js";
10
+ import { inlineBlockedBy } from "./inlineAuthored.js";
9
11
  export function LayoutDropdown(props) {
10
12
  const { property, options, domElement: el, editTarget, selection, stacked, revision = 0, onAfterEdit } = props;
11
13
  const atRules = useFieldAtRules(property);
12
14
  const selectedProperty = selection?.getProperty(property);
13
15
  const mixed = selectedProperty?.value.kind === "mixed";
16
+ const blockedBy = inlineBlockedBy(selection?.domElements ?? [el], property);
14
17
  const [value, setValue] = useState(() => getStateStyleValue(el, property, options[0]));
15
18
  useEffect(() => {
16
19
  try {
@@ -21,7 +24,7 @@ export function LayoutDropdown(props) {
21
24
  }
22
25
  }, [el, property, options, revision]);
23
26
  function handleChange(next) {
24
- if (next === "mixed")
27
+ if (next === "mixed" || blockedBy)
25
28
  return;
26
29
  setValue(next);
27
30
  setStyle(editTarget ?? el, property, next);
@@ -30,5 +33,5 @@ export function LayoutDropdown(props) {
30
33
  const allOptions = mixed
31
34
  ? ["mixed", ...options]
32
35
  : value && !options.includes(value) ? [...options, value] : options;
33
- return (_jsx(FieldRow, { label: property, property: property, atRules: atRules, className: stacked ? "field-row--stacked" : undefined, "data-test": "layout-dropdown", children: _jsx(Select, { "data-test": `layout-select-${property}`, value: mixed ? "mixed" : value, options: allOptions.map((opt) => ({ value: opt, label: formatInspectorLabel(opt) })), onValueChange: handleChange }) }));
36
+ return (_jsxs(FieldRow, { label: property, property: property, atRules: atRules, className: stacked ? "field-row--stacked" : undefined, "data-test": "layout-dropdown", children: [_jsx(Select, { "data-test": `layout-select-${property}`, value: mixed ? "mixed" : value, options: allOptions.map((opt) => ({ value: opt, label: formatInspectorLabel(opt) })), onValueChange: handleChange, disabled: blockedBy !== null }), _jsx(InlineStyleWarning, { blockedBy: blockedBy })] }));
34
37
  }
@@ -9,6 +9,7 @@ import { AspectRatioField } from "./AspectRatioField.js";
9
9
  import { PositionInsets } from "./PositionInsets.js";
10
10
  import { GridSection } from "./GridSection.js";
11
11
  import { meaningfulLayoutValue } from "./layoutValue.js";
12
+ import { inlineBlockedBy } from "./inlineAuthored.js";
12
13
  import { setStyle, setStyles } from "../tokens/editActions.js";
13
14
  import { IconButton } from "../ui/IconButton.js";
14
15
  import { InspectorPopover } from "../ui/InspectorPopover.js";
@@ -19,6 +20,8 @@ import { formatInspectorLabel } from "../ui/labels.js";
19
20
  import { getElementComputedStyle } from "../runtime/domRealm.js";
20
21
  import { FieldRow } from "../ui/FieldRow.js";
21
22
  import { ControlSurface } from "../ui/ControlSurface.js";
23
+ import { Tooltip } from "../ui/Tooltip.js";
24
+ import { inlineStyleWarningContent } from "../ui/InlineStyleWarning.js";
22
25
  import { getNudgeUiTokenEntries } from "../runtime/runtimeConfig.js";
23
26
  import { getCanvasProjectionAcknowledgementVersion, subscribeCanvasProjectionAcknowledgements, } from "../canvas/projection.js";
24
27
  const DISPLAY_OPTIONS = ["block", "inline", "inline-block", "flex", "inline-flex", "grid", "inline-grid", "none", "contents"];
@@ -106,7 +109,7 @@ export function LayoutSection(props) {
106
109
  const showPositionInsets = selection
107
110
  ? selection.supportsRole("inset-position")
108
111
  : position === "absolute" || position === "fixed";
109
- return (_jsxs("div", { className: "editor", "data-test": "layout-section", children: [_jsx("div", { className: "editor__title", children: "Layout" }), _jsxs("div", { className: "layout", children: [_jsxs("div", { className: "layout__basic", children: [_jsx(SizeSection, { domElement: el, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsxs("div", { className: "layout__tool-row", children: [_jsx(LayoutDropdown, { property: "display", options: DISPLAY_OPTIONS, domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(LayoutDropdown, { property: "position", options: POSITION_OPTIONS, domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })] })] }), isFlexContainer ? (_jsxs("div", { className: "layout__group", "data-test": "layout-flex-container", children: [_jsx("div", { className: "editor__title", children: "Flex" }), _jsx("div", { className: "layout__flex-toolbar", children: _jsxs("div", { className: "layout__direction-tools", children: [_jsx(FlexDirectionControl, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(FlexWrapToggle, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(FlexSettingsMenu, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })] }) }), _jsxs("div", { className: "layout__flex-lower", children: [mixedFlexDirection ? (_jsx("div", { className: "layout__alignment-grid", "data-test": "layout-flex-axis-mixed", children: "Choose a shared flex direction to edit physical alignment." })) : (_jsx(FlexAlignmentGrid, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })), _jsx("div", { className: "layout__gap-column", "data-test": "layout-gap", children: _jsxs("div", { className: "layout__gap-fields", children: [_jsxs("div", { className: "layout__spacing-primary", children: [_jsx(FlexGapField, { property: relevantGap, domElement: el, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, onAfterEdit: notifyAfterEdit }), _jsx(FlexDistributionControl, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })] }), isFlexWrapped ? (_jsx(FlexGapField, { property: lineGap, domElement: el, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, onAfterEdit: notifyAfterEdit })) : null] }) })] })] })) : null, isFlexChild ? (_jsxs("div", { className: "layout__group", "data-test": "layout-flex-child", children: [_jsx("div", { className: "editor__title", children: "Flex Child" }), _jsxs("div", { className: "layout__flex-child-fields", children: [_jsx(FlexChildValueField, { label: "Grow", property: "flex-grow", presets: FLEX_GROW_PRESETS, domElement: el, editTarget: editTarget, selection: selection, inputOnly: true, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(FlexChildValueField, { label: "Shrink", property: "flex-shrink", presets: FLEX_SHRINK_PRESETS, domElement: el, editTarget: editTarget, selection: selection, inputOnly: true, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(FlexChildValueField, { label: "Basis", property: "flex-basis", presets: FLEX_BASIS_PRESETS, domElement: el, editTarget: editTarget, selection: selection, inputOnly: true, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(FlexChildSettingsMenu, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })] })] })) : null, isGridContainer || isGridChild ? (_jsx(GridSection, { domElement: el, showContainer: isGridContainer, showChild: isGridChild, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, revision: computedRevision, onAfterEdit: notifyAfterEdit })) : null, showPositionInsets ? (_jsx(PositionInsets, { domElement: el, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, revision: computedRevision, onAfterEdit: notifyAfterEdit })) : null] })] }));
112
+ return (_jsxs("div", { className: "editor", "data-test": "layout-section", children: [_jsx("div", { className: "editor__title", children: "Layout" }), _jsxs("div", { className: "layout", children: [_jsxs("div", { className: "layout__basic", children: [_jsx(SizeSection, { domElement: el, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsxs("div", { className: "layout__tool-row", children: [_jsx(LayoutDropdown, { property: "display", options: DISPLAY_OPTIONS, domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(LayoutDropdown, { property: "position", options: POSITION_OPTIONS, domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })] })] }), isFlexContainer ? (_jsxs("div", { className: "layout__group", "data-test": "layout-flex-container", children: [_jsxs("div", { className: "layout__flex-heading", children: [_jsx("div", { className: "editor__title", children: "Flex" }), _jsx(FlexSettingsMenu, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })] }), _jsxs("div", { className: "layout__flex-lower", children: [mixedFlexDirection ? (_jsx("div", { className: "layout__alignment-grid", "data-test": "layout-flex-axis-mixed", children: "Choose a shared flex direction to edit physical alignment." })) : (_jsx(FlexAlignmentGrid, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })), _jsxs("div", { className: "layout__gap-column", "data-test": "layout-gap", children: [_jsxs("div", { className: "layout__direction-tools", children: [_jsx(FlexDirectionControl, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(FlexWrapToggle, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })] }), _jsxs("div", { className: "layout__gap-fields", children: [_jsxs("div", { className: "layout__spacing-primary", children: [_jsx(FlexGapField, { property: relevantGap, domElement: el, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, onAfterEdit: notifyAfterEdit }), _jsx(FlexDistributionControl, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })] }), isFlexWrapped ? (_jsx(FlexGapField, { property: lineGap, domElement: el, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, onAfterEdit: notifyAfterEdit })) : null] })] })] })] })) : null, isFlexChild ? (_jsxs("div", { className: "layout__group", "data-test": "layout-flex-child", children: [_jsx("div", { className: "editor__title", children: "Flex Child" }), _jsxs("div", { className: "layout__flex-child-fields", children: [_jsx(FlexChildValueField, { label: "Grow", property: "flex-grow", presets: FLEX_GROW_PRESETS, domElement: el, editTarget: editTarget, selection: selection, inputOnly: true, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(FlexChildValueField, { label: "Shrink", property: "flex-shrink", presets: FLEX_SHRINK_PRESETS, domElement: el, editTarget: editTarget, selection: selection, inputOnly: true, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(FlexChildValueField, { label: "Basis", property: "flex-basis", presets: FLEX_BASIS_PRESETS, domElement: el, editTarget: editTarget, selection: selection, inputOnly: true, revision: computedRevision, onAfterEdit: notifyAfterEdit }), _jsx(FlexChildSettingsMenu, { domElement: el, editTarget: editTarget, selection: selection, revision: computedRevision, onAfterEdit: notifyAfterEdit })] })] })) : null, isGridContainer || isGridChild ? (_jsx(GridSection, { domElement: el, showContainer: isGridContainer, showChild: isGridChild, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, revision: computedRevision, onAfterEdit: notifyAfterEdit })) : null, showPositionInsets ? (_jsx(PositionInsets, { domElement: el, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, revision: computedRevision, onAfterEdit: notifyAfterEdit })) : null] })] }));
110
113
  }
111
114
  function SizeSection({ domElement: el, editTarget, selection, entries, tokenRows, revision, onAfterEdit }) {
112
115
  const [expanded, setExpanded] = useState(false);
@@ -125,14 +128,14 @@ function SizeSection({ domElement: el, editTarget, selection, entries, tokenRows
125
128
  ? _jsx(IconLetterH, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" })
126
129
  : undefined, onAfterEdit: onAfterEdit }) }));
127
130
  }
128
- return (_jsx("div", { className: "layout__size", "data-test": "layout-size", "data-expanded": expanded ? "true" : "false", children: _jsxs("div", { className: "layout__size-grid", children: [_jsx(FieldRow, { label: "Width", hideLabel: true, "data-test": "layout-size-width", className: "layout__size-cell layout__size-cell--width", children: renderTokenField("width", SIZE_PRESETS) }), _jsx(FieldRow, { label: "Height", hideLabel: true, "data-test": "layout-size-height", className: "layout__size-cell layout__size-cell--height", children: renderTokenField("height", SIZE_PRESETS) }), _jsx(IconButton, { className: "layout__size-cell layout__size-cell--toggle", variant: "quiet", size: "default", "data-test": expanded ? "layout-size-collapse" : "layout-size-expand", "aria-label": expanded ? "Collapse Size Fields" : "Expand Size Fields", label: expanded ? "Collapse Size Fields" : "Expand Size Fields", title: expanded ? "Collapse Size Fields" : "Expand Size Fields", onClick: () => setExpanded(!expanded), children: expanded ? (_jsx(IconArrowsMinimize, { size: 16, stroke: 1.8, "aria-hidden": "true" })) : (_jsx(IconArrowsMaximize, { size: 16, stroke: 1.8, "aria-hidden": "true" })) }), expanded && fields.slice(2).map(({ property, presets }) => (_jsx(FieldRow, { label: property, "data-test": `layout-size-${property}`, className: `layout__size-cell layout__size-cell--${property}`, children: renderTokenField(property, presets) }, property))), expanded && (_jsx(AspectRatioField, { className: "layout__size-cell layout__size-cell--aspect-ratio", domElement: el, editTarget: editTarget, selection: selection, entries: entries, tokenRow: tokenRows.find((row) => row.property === "aspect-ratio") ?? null, revision: revision, onAfterEdit: onAfterEdit }))] }) }));
131
+ return (_jsx("div", { className: "layout__size", "data-test": "layout-size", "data-expanded": expanded ? "true" : "false", children: _jsxs("div", { className: "layout__size-grid", children: [_jsx(FieldRow, { label: "Width", hideLabel: true, "data-test": "layout-size-width", className: "layout__size-cell layout__size-cell--width", children: renderTokenField("width", SIZE_PRESETS) }), _jsx(FieldRow, { label: "Height", hideLabel: true, "data-test": "layout-size-height", className: "layout__size-cell layout__size-cell--height", children: renderTokenField("height", SIZE_PRESETS) }), _jsx(Tooltip, { children: _jsx(IconButton, { className: "layout__size-cell layout__size-cell--toggle", variant: "quiet", size: "default", "data-test": expanded ? "layout-size-collapse" : "layout-size-expand", "aria-label": expanded ? "Collapse Size Fields" : "Expand Size Fields", label: expanded ? "Collapse Size Fields" : "Expand Size Fields", onClick: () => setExpanded(!expanded), children: expanded ? (_jsx(IconArrowsMinimize, { size: 16, stroke: 1.8, "aria-hidden": "true" })) : (_jsx(IconArrowsMaximize, { size: 16, stroke: 1.8, "aria-hidden": "true" })) }) }), expanded && fields.slice(2).map(({ property, presets }) => (_jsx(FieldRow, { label: property, "data-test": `layout-size-${property}`, className: `layout__size-cell layout__size-cell--${property}`, children: renderTokenField(property, presets) }, property))), expanded && (_jsx(AspectRatioField, { className: "layout__size-cell layout__size-cell--aspect-ratio", domElement: el, editTarget: editTarget, selection: selection, entries: entries, tokenRow: tokenRows.find((row) => row.property === "aspect-ratio") ?? null, revision: revision, onAfterEdit: onAfterEdit }))] }) }));
129
132
  }
130
133
  function FlexChildValueField({ label, property, presets, inputOnly = false, domElement, editTarget, selection, revision = 0, onAfterEdit }) {
131
134
  return (_jsx(FieldRow, { label: label, className: "layout__flex-child-field", children: _jsx(LayoutComboField, { property: property, presets: presets, domElement: domElement, editTarget: editTarget, selection: selection, inputOnly: inputOnly, revision: revision, onAfterEdit: onAfterEdit }) }));
132
135
  }
133
136
  function FlexChildSettingsMenu({ domElement, editTarget, selection, revision = 0, onAfterEdit }) {
134
137
  const [open, setOpen] = useState(false);
135
- return (_jsx(InspectorPopover, { "data-test": "layout-flex-child-settings", side: "left", align: "start", triggerElement: (_jsx(IconButton, { variant: "quiet", size: "default", label: "Flex child settings", title: "Flex child settings", "data-test": "layout-flex-child-settings", children: _jsx(IconSettings, { size: 16, stroke: 1.8, "aria-hidden": "true" }) })), open: open, onOpenChange: setOpen, children: _jsxs("div", { className: "layout__flex-child-settings", "data-test": "layout-flex-child-settings-content", children: [_jsx("div", { className: "layout__flex-child-settings-title", children: "Flex Child Settings" }), _jsx(LayoutDropdown, { property: "align-self", options: ALIGN_SELF_OPTIONS, domElement: domElement, editTarget: editTarget, selection: selection, stacked: true, revision: revision, onAfterEdit: onAfterEdit }), _jsx(FlexChildValueField, { label: "Order", property: "order", presets: ORDER_PRESETS, domElement: domElement, editTarget: editTarget, selection: selection, revision: revision, onAfterEdit: onAfterEdit })] }) }));
138
+ return (_jsx(InspectorPopover, { "data-test": "layout-flex-child-settings", side: "left", align: "start", triggerTooltip: "Flex child settings", triggerElement: (_jsx(IconButton, { variant: "quiet", size: "default", label: "Flex child settings", "data-test": "layout-flex-child-settings", children: _jsx(IconSettings, { size: 16, stroke: 1.8, "aria-hidden": "true" }) })), open: open, onOpenChange: setOpen, children: _jsxs("div", { className: "layout__flex-child-settings", "data-test": "layout-flex-child-settings-content", children: [_jsx("div", { className: "layout__flex-child-settings-title", children: "Flex Child Settings" }), _jsx(LayoutDropdown, { property: "align-self", options: ALIGN_SELF_OPTIONS, domElement: domElement, editTarget: editTarget, selection: selection, stacked: true, revision: revision, onAfterEdit: onAfterEdit }), _jsx(FlexChildValueField, { label: "Order", property: "order", presets: ORDER_PRESETS, domElement: domElement, editTarget: editTarget, selection: selection, revision: revision, onAfterEdit: onAfterEdit })] }) }));
136
139
  }
137
140
  function FlexGapField({ property, domElement, editTarget, selection, entries, tokenRows, onAfterEdit }) {
138
141
  const GapIcon = property === "column-gap" ? IconSpacingHorizontal : IconSpacingVertical;
@@ -145,14 +148,17 @@ function FlexDirectionControl({ domElement, editTarget, selection, revision = 0,
145
148
  const sharedDirection = selectedDirection?.kind === "common" ? selectedDirection.value : direction;
146
149
  const orientation = sharedDirection.startsWith("column") ? "column" : "row";
147
150
  const reverse = sharedDirection.endsWith("-reverse");
151
+ const blockedBy = inlineBlockedBy(selection?.domElements ?? [domElement], "flex-direction");
148
152
  function selectDirection(next) {
153
+ if (blockedBy)
154
+ return;
149
155
  if (!FLEX_DIRECTION_OPTIONS.includes(next))
150
156
  return;
151
157
  setDirection(next);
152
158
  setStyle(editTarget ?? domElement, "flex-direction", next);
153
159
  onAfterEdit?.();
154
160
  }
155
- return (_jsx(SegmentedControl, { value: mixed ? null : orientation, "aria-label": "Flex direction", options: [
161
+ const control = (_jsx(SegmentedControl, { value: mixed ? null : orientation, "aria-label": "Flex direction", disabled: blockedBy !== null, options: [
156
162
  {
157
163
  value: "row",
158
164
  label: "Set Flex Direction To Row",
@@ -166,6 +172,7 @@ function FlexDirectionControl({ domElement, editTarget, selection, revision = 0,
166
172
  icon: (_jsx("svg", { viewBox: "0 0 16 16", width: "16", height: "16", "aria-hidden": "true", children: _jsx("path", { d: "M8 2v11M4 9l4 4 4-4", fill: "none", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "var(--icon-stroke-width)" }) })),
167
173
  },
168
174
  ], onChange: (next) => selectDirection(`${next}${!mixed && reverse ? "-reverse" : ""}`) }));
175
+ return _jsx(Tooltip, { content: inlineStyleWarningContent(blockedBy), disabled: blockedBy === null, children: control });
169
176
  }
170
177
  function FlexWrapToggle({ domElement, editTarget, selection, revision = 0, onAfterEdit }) {
171
178
  const [wrap] = useComputedLayoutValue(domElement, "flex-wrap", "nowrap", revision);
@@ -173,11 +180,14 @@ function FlexWrapToggle({ domElement, editTarget, selection, revision = 0, onAft
173
180
  const mixed = selectedWrap?.kind === "mixed";
174
181
  const sharedWrap = selectedWrap?.kind === "common" ? selectedWrap.value : wrap;
175
182
  const isWrapped = !mixed && sharedWrap !== "nowrap";
183
+ const blockedBy = inlineBlockedBy(selection?.domElements ?? [domElement], "flex-wrap");
176
184
  function toggleWrap() {
185
+ if (blockedBy)
186
+ return;
177
187
  setStyle(editTarget ?? domElement, "flex-wrap", isWrapped ? "nowrap" : "wrap");
178
188
  onAfterEdit?.();
179
189
  }
180
- return (_jsx(IconButton, { variant: "secondary", size: "default", "data-active": isWrapped, "data-test": "layout-flex-wrap-toggle", label: mixed ? "Set Flex Wrap" : isWrapped ? "Disable Flex Wrap" : "Enable Flex Wrap", "aria-pressed": mixed ? "mixed" : isWrapped, onClick: toggleWrap, children: _jsx(IconTextWrap, { size: 16, stroke: 1.8, "aria-hidden": "true" }) }));
190
+ return (_jsx(Tooltip, { content: inlineStyleWarningContent(blockedBy), children: _jsx(IconButton, { variant: "secondary", size: "default", "data-active": isWrapped, "data-test": "layout-flex-wrap-toggle", label: mixed ? "Set Flex Wrap" : isWrapped ? "Disable Flex Wrap" : "Enable Flex Wrap", "aria-pressed": mixed ? "mixed" : isWrapped, disabled: blockedBy !== null, onClick: toggleWrap, children: _jsx(IconTextWrap, { size: 16, stroke: 1.8, "aria-hidden": "true" }) }) }));
181
191
  }
182
192
  function FlexSettingsMenu({ domElement, editTarget, selection, revision = 0, onAfterEdit }) {
183
193
  const [direction] = useComputedLayoutValue(domElement, "flex-direction", "row", revision);
@@ -199,6 +209,7 @@ function FlexSettingsMenu({ domElement, editTarget, selection, revision = 0, onA
199
209
  const reverseDirection = sharedDirection.endsWith("-reverse")
200
210
  ? orientation
201
211
  : `${orientation}-reverse`;
212
+ const blockedBy = inlineBlockedBy(selection?.domElements ?? [domElement], "flex-direction", "flex-wrap", "align-content", "align-items");
202
213
  const items = [
203
214
  ...(mixedDirection ? [] : [{
204
215
  value: `flex-direction:${reverseDirection}`,
@@ -237,6 +248,8 @@ function FlexSettingsMenu({ domElement, editTarget, selection, revision = 0, onA
237
248
  },
238
249
  ];
239
250
  function selectSetting(setting) {
251
+ if (blockedBy)
252
+ return;
240
253
  const separator = setting.indexOf(":");
241
254
  if (separator < 0)
242
255
  return;
@@ -246,7 +259,7 @@ function FlexSettingsMenu({ domElement, editTarget, selection, revision = 0, onA
246
259
  setOpen(false);
247
260
  onAfterEdit?.();
248
261
  }
249
- return (_jsx(PopoverListbox, { query: "", value: null, open: open, triggerElement: (_jsx(IconButton, { variant: "quiet", size: "default", label: "Flex settings", "data-test": "layout-flex-settings", children: _jsx(IconSettings, { size: 16, stroke: 1.8, "aria-hidden": "true" }) })), triggerDataTest: "layout-flex-settings", triggerAriaLabel: "Flex settings", items: items.map((item) => ({
262
+ return (_jsx(PopoverListbox, { query: "", value: null, open: open, triggerElement: (_jsx(IconButton, { variant: "quiet", size: "default", label: "Flex settings", "data-test": "layout-flex-settings", children: _jsx(IconSettings, { size: 16, stroke: 1.8, "aria-hidden": "true" }) })), triggerDataTest: "layout-flex-settings", triggerAriaLabel: "Flex settings", triggerTooltip: inlineStyleWarningContent(blockedBy), disabled: blockedBy !== null, items: items.map((item) => ({
250
263
  value: item.value,
251
264
  label: item.label,
252
265
  trailing: item.trailing,
@@ -254,22 +267,25 @@ function FlexSettingsMenu({ domElement, editTarget, selection, revision = 0, onA
254
267
  "data-test": item["data-test"],
255
268
  })), onQueryChange: () => undefined, onOpenChange: setOpen, onSelect: selectSetting }));
256
269
  }
257
- function FlexDistributionControl({ domElement, editTarget, revision = 0, onAfterEdit }) {
270
+ function FlexDistributionControl({ domElement, editTarget, selection, revision = 0, onAfterEdit }) {
258
271
  const [computedJustify] = useComputedLayoutValue(domElement, "justify-content", "flex-start", revision);
259
272
  const justify = normalizeFlexJustify(computedJustify);
260
273
  const lastPlacement = useRef("flex-start");
261
274
  const distributed = FLEX_DISTRIBUTION_OPTIONS.includes(justify);
262
275
  const [open, setOpen] = useState(false);
276
+ const blockedBy = inlineBlockedBy(selection?.domElements ?? [domElement], "justify-content");
263
277
  useEffect(() => {
264
278
  if (FLEX_ALIGNMENT_OPTIONS.includes(justify))
265
279
  lastPlacement.current = justify;
266
280
  }, [justify]);
267
281
  function selectDistribution(next) {
282
+ if (blockedBy)
283
+ return;
268
284
  const value = next === "packed" ? lastPlacement.current : next;
269
285
  setStyle(editTarget ?? domElement, "justify-content", value);
270
286
  onAfterEdit?.();
271
287
  }
272
- return (_jsx(PopoverListbox, { className: "layout__distribution-control", query: "", value: distributed ? justify : "packed", open: open, triggerElement: (_jsx(IconButton, { variant: "quiet", size: "default", label: "Item distribution", title: "Item distribution", "data-active": distributed, "data-test": "layout-flex-distribution", children: _jsx(IconAdjustmentsHorizontal, { size: 16, stroke: 1.8, "aria-hidden": "true" }) })), triggerDataTest: "layout-flex-distribution", triggerAriaLabel: "Item distribution", items: [
288
+ return (_jsx(PopoverListbox, { className: "layout__distribution-control", query: "", value: distributed ? justify : "packed", open: open, triggerElement: (_jsx(IconButton, { variant: "quiet", size: "default", label: "Item distribution", "data-active": distributed, "data-test": "layout-flex-distribution", children: _jsx(IconAdjustmentsHorizontal, { size: 16, stroke: 1.8, "aria-hidden": "true" }) })), triggerDataTest: "layout-flex-distribution", triggerAriaLabel: "Item distribution", triggerTooltip: inlineStyleWarningContent(blockedBy), disabled: blockedBy !== null, items: [
273
289
  { value: "packed", label: "Keep grouped" },
274
290
  { value: "space-between", label: "Spread between" },
275
291
  { value: "space-around", label: "Spread around" },
@@ -285,7 +301,7 @@ function FlexDistributionControl({ domElement, editTarget, revision = 0, onAfter
285
301
  setOpen(false);
286
302
  } }));
287
303
  }
288
- function FlexAlignmentGrid({ domElement, editTarget, revision = 0, onAfterEdit }) {
304
+ function FlexAlignmentGrid({ domElement, editTarget, selection, revision = 0, onAfterEdit }) {
289
305
  const [direction] = useComputedLayoutValue(domElement, "flex-direction", "row", revision);
290
306
  const [computedJustify, setJustify] = useComputedLayoutValue(domElement, "justify-content", "flex-start", revision);
291
307
  const [computedAlign, setAlign] = useComputedLayoutValue(domElement, "align-items", "stretch", revision);
@@ -294,6 +310,7 @@ function FlexAlignmentGrid({ domElement, editTarget, revision = 0, onAfterEdit }
294
310
  const isColumn = direction.startsWith("column");
295
311
  const isReverse = direction.endsWith("-reverse");
296
312
  const distributed = FLEX_DISTRIBUTION_OPTIONS.includes(justify);
313
+ const blockedBy = inlineBlockedBy(selection?.domElements ?? [domElement], "justify-content", "align-items");
297
314
  // The grid represents physical positions in the parent. For row flex
298
315
  // containers, justify-content runs horizontally; for column flex
299
316
  // containers, it runs vertically, so the two grid axes need to transpose.
@@ -304,6 +321,8 @@ function FlexAlignmentGrid({ domElement, editTarget, revision = 0, onAfterEdit }
304
321
  ? [...FLEX_ALIGNMENT_OPTIONS].reverse()
305
322
  : FLEX_ALIGNMENT_OPTIONS;
306
323
  function selectAlignment(nextJustify, nextAlign) {
324
+ if (blockedBy)
325
+ return;
307
326
  // Keep an explicit distribution choice when the grid is used to change
308
327
  // the cross-axis alignment. The distribution control owns values such as
309
328
  // `space-between`; a grid click should not silently replace them with the
@@ -317,12 +336,14 @@ function FlexAlignmentGrid({ domElement, editTarget, revision = 0, onAfterEdit }
317
336
  ]);
318
337
  onAfterEdit?.();
319
338
  }
320
- return (_jsxs("div", { className: "layout__alignment-grid", role: "group", "aria-label": "Place flex items", "data-direction": isColumn ? "column" : "row", "data-justify": justify, "data-align": align, "data-distributed": distributed, children: [distributed ? (_jsxs("span", { className: "layout__distribution-preview", "aria-hidden": "true", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {})] })) : null, rowValues.flatMap((rowValue) => columnValues.map((columnValue) => {
339
+ const grid = (_jsxs("div", { className: "layout__alignment-grid", role: "group", "aria-label": "Place flex items", "data-direction": isColumn ? "column" : "row", "data-justify": justify, "data-align": align, "data-distributed": distributed, children: [distributed ? (_jsxs("span", { className: "layout__distribution-preview", "aria-hidden": "true", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {})] })) : null, rowValues.flatMap((rowValue) => columnValues.map((columnValue) => {
321
340
  const justifyValue = isColumn ? rowValue : columnValue;
322
341
  const alignValue = isColumn ? columnValue : rowValue;
323
342
  const active = justify === justifyValue && align === alignValue;
324
- return (_jsx(IconButton, { size: "default", variant: "quiet", className: "layout__alignment-button", "data-active": active, "data-test": `layout-align-${alignValue}-${justifyValue}`, label: formatInspectorLabel(`Align ${alignValue.replace("flex-", "")} And Distribute ${justifyValue.replace("flex-", "")}`), "aria-pressed": active, onClick: () => selectAlignment(justifyValue, alignValue), children: _jsx("span", {}) }, `${alignValue}-${justifyValue}`));
343
+ const label = formatInspectorLabel(`Align ${alignValue.replace("flex-", "")} And Distribute ${justifyValue.replace("flex-", "")}`);
344
+ return (_jsx(Tooltip, { children: _jsx(IconButton, { size: "default", variant: "quiet", className: "layout__alignment-button", "data-active": active, "data-test": `layout-align-${alignValue}-${justifyValue}`, label: label, "aria-pressed": active, disabled: blockedBy !== null, onClick: () => selectAlignment(justifyValue, alignValue), children: _jsx("span", {}) }) }, `${alignValue}-${justifyValue}`));
325
345
  }))] }));
346
+ return _jsx(Tooltip, { content: inlineStyleWarningContent(blockedBy), disabled: blockedBy === null, children: grid });
326
347
  }
327
348
  function normalizeFlexJustify(value) {
328
349
  // `normal` is the computed initial value, but behaves as `flex-start` for