nudge-ui 0.2.2 → 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.
- package/README.md +164 -46
- package/dist/client.mjs +252 -61
- package/dist/css/value-semantics/colorSemantics.d.ts +4 -0
- package/dist/css/value-semantics/colorSemantics.js +17 -0
- package/dist/hosts/astro/astroRuntimeConfig.js +1 -0
- package/dist/hosts/astro/clientTransport.js +38 -32
- package/dist/hosts/astro/integration.js +5 -1
- package/dist/hosts/astro/markdownIdentity.d.ts +28 -0
- package/dist/hosts/astro/markdownIdentity.js +81 -0
- package/dist/hosts/next/manifest.js +1 -1
- package/dist/hosts/next/sidecar.js +5 -1
- package/dist/hosts/static/manifest.js +1 -1
- package/dist/hosts/static/server.js +3 -0
- package/dist/hosts/vite/vite.d.ts +3 -0
- package/dist/hosts/vite/vite.js +24 -4
- package/dist/inspector/agent/McpConnectionDialog.js +13 -19
- package/dist/inspector/agent/activity.d.ts +18 -0
- package/dist/inspector/agent/activity.js +70 -0
- package/dist/inspector/agent/client.d.ts +2 -0
- package/dist/inspector/agent/client.js +29 -1
- package/dist/inspector/agent/connectionStatus.js +1 -1
- package/dist/inspector/agent/httpTransport.js +9 -2
- package/dist/inspector/agent/protocol.d.ts +6 -3
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +11 -1
- package/dist/inspector/agent/verification.js +53 -25
- package/dist/inspector/canvas/AgentActivityOverlay.d.ts +13 -0
- package/dist/inspector/canvas/AgentActivityOverlay.js +111 -0
- package/dist/inspector/canvas/CanvasCard.d.ts +9 -2
- package/dist/inspector/canvas/CanvasCard.js +133 -38
- package/dist/inspector/canvas/CanvasElementOverlay.js +152 -16
- package/dist/inspector/canvas/CanvasToolIcons.d.ts +10 -0
- package/dist/inspector/canvas/CanvasToolIcons.js +20 -0
- package/dist/inspector/canvas/CanvasToolbar.js +8 -4
- package/dist/inspector/canvas/CanvasWorkspace.js +225 -57
- package/dist/inspector/canvas/StaleChangeIndicator.js +3 -13
- package/dist/inspector/canvas/agentPresentation.d.ts +8 -8
- package/dist/inspector/canvas/agentPresentation.js +64 -52
- package/dist/inspector/canvas/canvasGestures.d.ts +1 -1
- package/dist/inspector/canvas/canvasGestures.js +1 -1
- package/dist/inspector/canvas/canvasStore.d.ts +46 -82
- package/dist/inspector/canvas/canvasStore.js +251 -284
- package/dist/inspector/canvas/frameContent.d.ts +21 -0
- package/dist/inspector/canvas/frameContent.js +44 -0
- package/dist/inspector/canvas/frameProtocol.d.ts +21 -4
- package/dist/inspector/canvas/frameProtocol.js +3 -3
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -1
- package/dist/inspector/canvas/keyboardShortcuts.js +4 -4
- package/dist/inspector/canvas/layout.d.ts +52 -0
- package/dist/inspector/canvas/layout.js +106 -0
- package/dist/inspector/canvas/projection.d.ts +15 -5
- package/dist/inspector/canvas/projection.js +69 -13
- package/dist/inspector/canvas/rendererBootstrap.js +32 -4
- package/dist/inspector/canvas/rendererElementSelector.js +47 -6
- package/dist/inspector/canvas/rendererStylesheet.js +2 -2
- package/dist/inspector/canvas/resizeHandleScale.js +1 -1
- package/dist/inspector/canvas/sessionStore.d.ts +11 -26
- package/dist/inspector/canvas/sessionStore.js +43 -183
- package/dist/inspector/canvas/staleChangeDetector.js +9 -3
- package/dist/inspector/canvas/useCanvasLayoutAnimation.d.ts +5 -0
- package/dist/inspector/canvas/useCanvasLayoutAnimation.js +107 -0
- package/dist/inspector/canvas/viewStore.d.ts +38 -0
- package/dist/inspector/canvas/viewStore.js +104 -0
- package/dist/inspector/canvas/wheelPan.d.ts +8 -0
- package/dist/inspector/canvas/wheelPan.js +8 -0
- package/dist/inspector/changes/changesLog.d.ts +6 -5
- package/dist/inspector/changes/changesLog.js +40 -32
- package/dist/inspector/changes/draftChanges.d.ts +56 -0
- package/dist/inspector/changes/draftChanges.js +300 -0
- package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
- package/dist/inspector/changes/previewDiagnostics.js +11 -25
- package/dist/inspector/clientManifest.d.ts +1 -1
- package/dist/inspector/comments/CommentsOverlay.d.ts +2 -0
- package/dist/inspector/comments/CommentsOverlay.js +250 -0
- package/dist/inspector/comments/element.d.ts +6 -0
- package/dist/inspector/comments/element.js +47 -0
- package/dist/inspector/comments/store.d.ts +29 -0
- package/dist/inspector/comments/store.js +121 -0
- package/dist/inspector/comments/verification.d.ts +5 -0
- package/dist/inspector/comments/verification.js +52 -0
- package/dist/inspector/drafts/model.d.ts +35 -0
- package/dist/inspector/drafts/model.js +45 -0
- package/dist/inspector/drafts/store.d.ts +28 -0
- package/dist/inspector/drafts/store.js +231 -0
- package/dist/inspector/index.js +20 -27
- package/dist/inspector/inline-text/inlineTextEditor.js +7 -0
- package/dist/inspector/inspection/useBrowserCssInspection.js +15 -1
- package/dist/inspector/iterations/capture.d.ts +2 -0
- package/dist/inspector/iterations/capture.js +173 -0
- package/dist/inspector/iterations/client.d.ts +7 -0
- package/dist/inspector/iterations/client.js +52 -0
- package/dist/inspector/iterations/coordinator.d.ts +15 -0
- package/dist/inspector/iterations/coordinator.js +130 -0
- package/dist/inspector/iterations/prompt.d.ts +3 -0
- package/dist/inspector/iterations/prompt.js +26 -0
- package/dist/inspector/overlay/DomNavigator.js +21 -15
- package/dist/inspector/overlay/interactionStyles.js +3 -1
- package/dist/inspector/overlay/spacingGestures.d.ts +13 -1
- package/dist/inspector/overlay/spacingGestures.js +82 -5
- package/dist/inspector/projection/{workspaceProjection.d.ts → draftProjection.d.ts} +6 -6
- package/dist/inspector/projection/{workspaceProjection.js → draftProjection.js} +4 -4
- package/dist/inspector/projection/renderedInstance.d.ts +2 -1
- package/dist/inspector/projection/renderedInstance.js +9 -5
- package/dist/inspector/projection/structuralProjection.d.ts +2 -1
- package/dist/inspector/projection/structuralProjection.js +19 -15
- package/dist/inspector/projection/textProjection.d.ts +2 -1
- package/dist/inspector/projection/textProjection.js +8 -4
- package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
- package/dist/inspector/prompt/clipboardHandoff.d.ts +10 -18
- package/dist/inspector/prompt/clipboardHandoff.js +92 -143
- package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
- package/dist/inspector/prompt/generatePrompt.js +17 -16
- package/dist/inspector/runtime/runtimeConfig.d.ts +9 -0
- package/dist/inspector/runtime/runtimeConfig.js +24 -0
- package/dist/inspector/settings/SettingsDialog.js +21 -8
- package/dist/inspector/shell/AgentStatusButton.d.ts +11 -0
- package/dist/inspector/shell/AgentStatusButton.js +36 -0
- package/dist/inspector/shell/AppShell.js +4 -2
- package/dist/inspector/shell/ChangesLog.js +46 -26
- package/dist/inspector/shell/CopyPromptButton.js +138 -89
- package/dist/inspector/shell/EmptyState.js +9 -8
- package/dist/inspector/shell/InspectorShell.js +13 -21
- package/dist/inspector/shell/PeekOriginalButton.js +24 -23
- package/dist/inspector/shell/originalPreview.js +5 -5
- package/dist/inspector/shell/shortcuts.d.ts +3 -0
- package/dist/inspector/shell/shortcuts.js +11 -0
- package/dist/inspector/sketch/SketchEditor.js +14 -14
- package/dist/inspector/sketch/SketchEntryButton.js +1 -1
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +3 -1
- package/dist/inspector/sketch/SketchFrameOverlay.js +11 -8
- package/dist/inspector/sketch/SketchLayersPanel.js +13 -5
- package/dist/inspector/sketch/SketchOverlay.js +7 -7
- package/dist/inspector/sketch/SketchWorkspace.js +18 -12
- package/dist/inspector/sketch/interaction.d.ts +1 -0
- package/dist/inspector/sketch/interaction.js +8 -4
- package/dist/inspector/sketch/model.d.ts +5 -0
- package/dist/inspector/sketch/model.js +6 -2
- package/dist/inspector/sketch/raster.d.ts +1 -2
- package/dist/inspector/sketch/raster.js +68 -4
- package/dist/inspector/styleEditors/BackgroundEditor.d.ts +4 -0
- package/dist/inspector/styleEditors/BackgroundEditor.js +195 -0
- package/dist/inspector/styleEditors/BorderEditor.js +49 -78
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -1
- package/dist/inspector/styleEditors/BoxShadowEditor.d.ts +1 -2
- package/dist/inspector/styleEditors/BoxShadowEditor.js +132 -4
- package/dist/inspector/styleEditors/ColorPicker.d.ts +5 -1
- package/dist/inspector/styleEditors/ColorPicker.js +10 -4
- package/dist/inspector/styleEditors/GapField.js +3 -5
- package/dist/inspector/styleEditors/GridChildSection.d.ts +1 -1
- package/dist/inspector/styleEditors/GridChildSection.js +18 -9
- package/dist/inspector/styleEditors/GridPicker.d.ts +1 -1
- package/dist/inspector/styleEditors/GridPicker.js +8 -2
- package/dist/inspector/styleEditors/GridSection.js +1 -1
- package/dist/inspector/styleEditors/GridValueField.js +19 -14
- package/dist/inspector/styleEditors/ImageBackgroundEditor.d.ts +18 -0
- package/dist/inspector/styleEditors/ImageBackgroundEditor.js +118 -0
- package/dist/inspector/styleEditors/LayoutBlockedIndicator.d.ts +2 -2
- package/dist/inspector/styleEditors/LayoutBlockedIndicator.js +3 -3
- package/dist/inspector/styleEditors/LayoutComboField.js +6 -4
- package/dist/inspector/styleEditors/LayoutDropdown.js +6 -3
- package/dist/inspector/styleEditors/LayoutSection.js +32 -11
- package/dist/inspector/styleEditors/SpacingBox.js +7 -5
- package/dist/inspector/styleEditors/Typography.js +11 -3
- package/dist/inspector/styleEditors/backgroundLayers.d.ts +24 -0
- package/dist/inspector/styleEditors/backgroundLayers.js +51 -0
- package/dist/inspector/styleEditors/gradientValue.d.ts +17 -0
- package/dist/inspector/styleEditors/gradientValue.js +114 -0
- package/dist/inspector/styleEditors/inlineAuthored.d.ts +10 -0
- package/dist/inspector/styleEditors/inlineAuthored.js +114 -0
- package/dist/inspector/styleEditors/layoutValue.d.ts +0 -8
- package/dist/inspector/styleEditors/layoutValue.js +0 -42
- package/dist/inspector/styleEditors/nudgeValue.d.ts +2 -2
- package/dist/inspector/styleEditors/nudgeValue.js +36 -8
- package/dist/inspector/tokens/TokenField.d.ts +7 -0
- package/dist/inspector/tokens/TokenField.js +29 -21
- package/dist/inspector/tokens/TokenLabel.js +5 -14
- package/dist/inspector/tokens/editActions.d.ts +1 -1
- package/dist/inspector/tokens/editActions.js +1 -1
- package/dist/inspector/ui/AtRuleContext.js +4 -6
- package/dist/inspector/ui/Button.d.ts +6 -2
- package/dist/inspector/ui/Button.js +4 -3
- package/dist/inspector/ui/ControlSurface.d.ts +2 -2
- package/dist/inspector/ui/ControlSurface.js +4 -3
- package/dist/inspector/ui/InlineStyleWarning.d.ts +11 -0
- package/dist/inspector/ui/InlineStyleWarning.js +13 -0
- package/dist/inspector/ui/InspectorPopover.d.ts +2 -1
- package/dist/inspector/ui/InspectorPopover.js +4 -5
- package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
- package/dist/inspector/ui/PopoverListbox.js +8 -7
- package/dist/inspector/ui/SegmentedControl.d.ts +2 -1
- package/dist/inspector/ui/SegmentedControl.js +2 -2
- package/dist/inspector/ui/Select.d.ts +6 -1
- package/dist/inspector/ui/Select.js +7 -11
- package/dist/inspector/ui/SideValuesField.js +1 -1
- package/dist/inspector/ui/Tooltip.d.ts +26 -0
- package/dist/inspector/ui/Tooltip.js +32 -0
- package/dist/inspector/ui/classNames.d.ts +2 -0
- package/dist/inspector/ui/classNames.js +4 -0
- package/dist/inspector/ui/customCursors.d.ts +23 -0
- package/dist/inspector/ui/customCursors.js +122 -0
- package/dist/inspector/ui/dragCursor.d.ts +0 -2
- package/dist/inspector/ui/dragCursor.js +2 -10
- package/dist/inspector/ui/portalContainer.d.ts +2 -0
- package/dist/inspector/ui/portalContainer.js +6 -0
- package/dist/inspector/ui/styles.js +29 -21
- package/dist/inspector/workspace/commands.d.ts +9 -0
- package/dist/inspector/workspace/commands.js +82 -0
- package/dist/inspector/workspace/controller.d.ts +4 -0
- package/dist/inspector/workspace/controller.js +66 -0
- package/dist/inspector/workspace/handoff.d.ts +42 -0
- package/dist/inspector/workspace/handoff.js +115 -0
- package/dist/inspector/workspace/timeline.d.ts +30 -0
- package/dist/inspector/workspace/timeline.js +108 -0
- package/dist/internal/agent-protocol.d.ts +19 -2
- package/dist/internal/agent-protocol.js +13 -0
- package/dist/nudge-ui.mjs +558 -311
- package/dist/project/artifacts.d.ts +13 -0
- package/dist/project/artifacts.js +253 -0
- package/dist/transport/artifacts.d.ts +8 -0
- package/dist/transport/artifacts.js +9 -0
- package/dist/transport/editor.js +3 -1
- package/dist/transport/routes.d.ts +2 -0
- package/dist/transport/routes.js +2 -0
- package/package.json +6 -2
- package/dist/inspector/changes/workspaceChanges.d.ts +0 -47
- package/dist/inspector/changes/workspaceChanges.js +0 -265
|
@@ -11,6 +11,7 @@ import { projectInspectorValues, projectInspectorValuesForSelection, } from "../
|
|
|
11
11
|
import { getStateStyleValue } from "../shell/stateValue.js";
|
|
12
12
|
import { isZeroCssValue } from "./stylePresence.js";
|
|
13
13
|
import { useFieldVisibility } from "./useFieldVisibility.js";
|
|
14
|
+
import { inlineBlockedBy } from "./inlineAuthored.js";
|
|
14
15
|
export function SpacingBox(props) {
|
|
15
16
|
const { element, selection, entries, tokenRows = [], onAfterEdit } = props;
|
|
16
17
|
const el = element.domElement;
|
|
@@ -52,9 +53,9 @@ export function SpacingField({ property, projection: spacingProjection, domEleme
|
|
|
52
53
|
const pairSlots = pairDefinitions.map(({ axis, sideProperties }) => ({
|
|
53
54
|
axis,
|
|
54
55
|
icon: null,
|
|
55
|
-
control: (_jsx(PairedTokenField, { displayProperty: `${property}-${axis}`, sideProperties: sideProperties, domElement: el, editTarget: editTarget, axisProjection: spacingProjection.axes[axis], entries: entries, suggestions: suggestions, onAfterEdit: onAfterEdit, chipVariant: "small", leading: _jsx(SpacingAxisIndicator, { property: property, axis: axis }) })),
|
|
56
|
+
control: (_jsx(PairedTokenField, { displayProperty: `${property}-${axis}`, sideProperties: sideProperties, domElement: el, editTarget: editTarget, selection: selection, axisProjection: spacingProjection.axes[axis], entries: entries, suggestions: suggestions, onAfterEdit: onAfterEdit, chipVariant: "small", leading: _jsx(SpacingAxisIndicator, { property: property, axis: axis }) })),
|
|
56
57
|
}));
|
|
57
|
-
return (_jsx(SideValuesField, { label: property, "data-test": property === "inset" ? "layout-inset" : `spacing-${property}`, "data-property": property, resetKey: el, pairedControls: pairSlots, showLabel: showLabel, empty: showEmptyState && !visibility.visible, onAdd: visibility.show, emptyAction: property === "inset" ? (_jsx(IconButton, { variant: "quiet", size: "default", "data-test": "add-inset", label: "Add Inset Values",
|
|
58
|
+
return (_jsx(SideValuesField, { label: property, "data-test": property === "inset" ? "layout-inset" : `spacing-${property}`, "data-property": property, resetKey: el, pairedControls: pairSlots, showLabel: showLabel, empty: showEmptyState && !visibility.visible, onAdd: visibility.show, emptyAction: property === "inset" ? (_jsx(IconButton, { variant: "quiet", size: "default", "data-test": "add-inset", label: "Add Inset Values", onClick: visibility.show, children: _jsx(IconPlus, { size: 16, "aria-hidden": "true" }) })) : undefined, sides: sideSlots }));
|
|
58
59
|
}
|
|
59
60
|
function sideProperty(property, side) {
|
|
60
61
|
return property === "inset" ? side : `${property}-${side}`;
|
|
@@ -83,7 +84,7 @@ function SpacingAxisIndicator({ property, axis, }) {
|
|
|
83
84
|
}
|
|
84
85
|
return (_jsxs("svg", { className: "side-values__icon side-values__axis-icon", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [_jsx("rect", { x: "19", y: "6", width: "12", height: "14", rx: "2", transform: "rotate(90 19 6)", stroke: "currentColor", strokeWidth: "var(--icon-stroke-width)" }), _jsx("line", { x1: "19", y1: "2", x2: "5", y2: "2", stroke: "currentColor", strokeWidth: "var(--icon-stroke-width)", strokeLinecap: "round" }), _jsx("line", { x1: "19", y1: "22", x2: "5", y2: "22", stroke: "currentColor", strokeWidth: "var(--icon-stroke-width)", strokeLinecap: "round" })] }));
|
|
85
86
|
}
|
|
86
|
-
function PairedTokenField({ displayProperty, sideProperties, domElement: el, editTarget, axisProjection, entries, suggestions, onAfterEdit, chipVariant, leading, }) {
|
|
87
|
+
function PairedTokenField({ displayProperty, sideProperties, domElement: el, editTarget, selection, axisProjection, entries, suggestions, onAfterEdit, chipVariant, leading, }) {
|
|
87
88
|
const row = pairTokenRow(displayProperty, axisProjection);
|
|
88
89
|
const groupMixed = axisProjection.fields.some((field) => field.value === "Mixed");
|
|
89
90
|
const valuesMatch = axisProjection.state === "shared" && !groupMixed;
|
|
@@ -108,6 +109,7 @@ function PairedTokenField({ displayProperty, sideProperties, domElement: el, edi
|
|
|
108
109
|
const currentToken = activeTokenName
|
|
109
110
|
? entries.find((entry) => entry.name === activeTokenName) ?? null
|
|
110
111
|
: null;
|
|
112
|
+
const blockedBy = inlineBlockedBy(selection?.domElements ?? [el], ...sideProperties);
|
|
111
113
|
function commitAxisValue(value) {
|
|
112
114
|
const pair = splitAxisValue(value);
|
|
113
115
|
const records = setStyles(editTarget ?? el, sideProperties.map((property, index) => ({
|
|
@@ -117,7 +119,7 @@ function PairedTokenField({ displayProperty, sideProperties, domElement: el, edi
|
|
|
117
119
|
if (records.some(Boolean))
|
|
118
120
|
onAfterEdit?.();
|
|
119
121
|
}
|
|
120
|
-
return (_jsx(TokenValueField, { property: displayProperty, committedValue: committedValue, resolvedValue: resolvedValue, activeTokenName: activeTokenName, mixed: groupMixed, atRules: row.atRuleCandidates ?? row.atRules, entries: entries, suggestions: suggestions, chipVariant: chipVariant, formatRawValue: (value) => formatAxisValue(value, displayProperty), onCommitRaw: commitAxisValue, onSelectToken: (chosen) => {
|
|
122
|
+
return (_jsx(TokenValueField, { property: displayProperty, domElement: el, committedValue: committedValue, resolvedValue: resolvedValue, activeTokenName: activeTokenName, mixed: groupMixed, atRules: row.atRuleCandidates ?? row.atRules, entries: entries, suggestions: suggestions, chipVariant: chipVariant, formatRawValue: (value) => formatAxisValue(value, displayProperty), onCommitRaw: commitAxisValue, onSelectToken: (chosen) => {
|
|
121
123
|
const records = swapTokens(editTarget ?? el, sideProperties.map((property) => ({
|
|
122
124
|
property,
|
|
123
125
|
newToken: chosen,
|
|
@@ -125,7 +127,7 @@ function PairedTokenField({ displayProperty, sideProperties, domElement: el, edi
|
|
|
125
127
|
})));
|
|
126
128
|
if (records.some(Boolean))
|
|
127
129
|
onAfterEdit?.();
|
|
128
|
-
}, onUnlink: commitAxisValue, leading: leading }));
|
|
130
|
+
}, onUnlink: commitAxisValue, leading: leading, blockedBy: blockedBy }));
|
|
129
131
|
}
|
|
130
132
|
function fieldDisplayValue(field) {
|
|
131
133
|
return isCalcValue(field.authoredValue) ? field.value : field.authoredValue.trim() || field.value;
|
|
@@ -9,6 +9,8 @@ import { getStateStyleValue } from "../shell/stateValue.js";
|
|
|
9
9
|
import { setStyle, setStyles } from "../tokens/editActions.js";
|
|
10
10
|
import { AtRuleIndicator, useFieldAtRules } from "../ui/AtRuleContext.js";
|
|
11
11
|
import { ControlSurface } from "../ui/ControlSurface.js";
|
|
12
|
+
import { InlineStyleWarning } from "../ui/InlineStyleWarning.js";
|
|
13
|
+
import { inlineBlockedBy } from "./inlineAuthored.js";
|
|
12
14
|
export function Typography(props) {
|
|
13
15
|
const { element, selection, entries, tokenRows = [], onAfterEdit } = props;
|
|
14
16
|
const el = element.domElement;
|
|
@@ -56,6 +58,7 @@ function FontStyleField({ element, elements, editTarget, fontStyleRow, fontWeigh
|
|
|
56
58
|
const atRules = fontStyleRow?.atRuleCandidates ?? fontStyleRow?.atRules
|
|
57
59
|
?? fontWeightRow?.atRuleCandidates ?? fontWeightRow?.atRules
|
|
58
60
|
?? (fontStyleAtRules.length > 0 ? fontStyleAtRules : fontWeightAtRules);
|
|
61
|
+
const blockedBy = inlineBlockedBy(elements, "font-style", "font-weight");
|
|
59
62
|
const readValue = () => readFontStyle(element);
|
|
60
63
|
const values = elements.map(readFontStyle);
|
|
61
64
|
const isMixed = values.some((value) => value.weight !== values[0]?.weight || value.style !== values[0]?.style);
|
|
@@ -72,6 +75,8 @@ function FontStyleField({ element, elements, editTarget, fontStyleRow, fontWeigh
|
|
|
72
75
|
? FONT_STYLE_OPTIONS
|
|
73
76
|
: [...FONT_STYLE_OPTIONS, { value: currentKey, label: currentLabel, ...current }];
|
|
74
77
|
function handleChange(value) {
|
|
78
|
+
if (blockedBy)
|
|
79
|
+
return;
|
|
75
80
|
const option = options.find((candidate) => candidate.value === value);
|
|
76
81
|
if (!option || option.value === "mixed" || !("weight" in option) || !("style" in option))
|
|
77
82
|
return;
|
|
@@ -82,7 +87,7 @@ function FontStyleField({ element, elements, editTarget, fontStyleRow, fontWeigh
|
|
|
82
87
|
]);
|
|
83
88
|
onAfterEdit?.();
|
|
84
89
|
}
|
|
85
|
-
return (_jsxs(ControlSurface, { className: "typography__field typography__field--font-style", "data-test": "typography-field", "data-property": "font-style", "aria-label": "Font style",
|
|
90
|
+
return (_jsxs(ControlSurface, { className: "typography__field typography__field--font-style", "data-test": "typography-field", "data-property": "font-style", "aria-label": "Font style", children: [_jsx("span", { className: "typography__field-icon", "aria-hidden": "true", children: _jsx(IconTypography, { size: "var(--icon-size-small)", stroke: 1.45 }) }), _jsx(Select, { appearance: "embedded", value: isMixed ? "mixed" : currentKey, options: options.map(({ value, label }) => ({ value, label })), onValueChange: handleChange, disabled: blockedBy !== null, "data-test": "font-style-field" }), _jsx(AtRuleIndicator, { atRules: atRules }), _jsx(InlineStyleWarning, { blockedBy: blockedBy })] }));
|
|
86
91
|
}
|
|
87
92
|
function readFontStyle(element) {
|
|
88
93
|
return {
|
|
@@ -104,6 +109,7 @@ function formatCustomFontStyle(weight, style) {
|
|
|
104
109
|
}
|
|
105
110
|
function AlignmentField({ property, label, element, elements, editTarget, defaultValue, options, onAfterEdit }) {
|
|
106
111
|
const atRules = useFieldAtRules(property);
|
|
112
|
+
const blockedBy = inlineBlockedBy(elements, property);
|
|
107
113
|
const readValues = () => elements.map((target) => getStateStyleValue(target, property, defaultValue));
|
|
108
114
|
const initialValues = readValues();
|
|
109
115
|
const isMixed = initialValues.some((value) => value !== initialValues[0]);
|
|
@@ -115,16 +121,18 @@ function AlignmentField({ property, label, element, elements, editTarget, defaul
|
|
|
115
121
|
setCurrent(values.some((value) => value !== values[0]) ? null : normalizeAlignment(property, next));
|
|
116
122
|
}, [defaultValue, element, elements, property]);
|
|
117
123
|
function handleChange(value) {
|
|
124
|
+
if (blockedBy)
|
|
125
|
+
return;
|
|
118
126
|
setCurrent(value);
|
|
119
127
|
setStyle(editTarget, property, value);
|
|
120
128
|
onAfterEdit?.();
|
|
121
129
|
}
|
|
122
|
-
return (_jsxs("div", { className: "typography__alignment-field", children: [isMixed || current === null && elements.length > 1 ? (_jsx("span", { className: "typography__mixed", "data-test": `typography-mixed-${property}`, children: "Mixed" })) : null, _jsx(SegmentedControl, { value: current, "aria-label": label, "data-property": property, options: options.map((option) => ({
|
|
130
|
+
return (_jsxs("div", { className: "typography__alignment-field", children: [isMixed || current === null && elements.length > 1 ? (_jsx("span", { className: "typography__mixed", "data-test": `typography-mixed-${property}`, children: "Mixed" })) : null, _jsx(SegmentedControl, { value: current, "aria-label": label, "data-property": property, disabled: blockedBy !== null, options: options.map((option) => ({
|
|
123
131
|
value: option.value,
|
|
124
132
|
label: option.label,
|
|
125
133
|
icon: option.icon,
|
|
126
134
|
testId: `typography-align-${property}-${option.value}`,
|
|
127
|
-
})), onChange: handleChange }), _jsx(AtRuleIndicator, { atRules: atRules })] }));
|
|
135
|
+
})), onChange: handleChange }), _jsx(AtRuleIndicator, { atRules: atRules }), _jsx(InlineStyleWarning, { blockedBy: blockedBy })] }));
|
|
128
136
|
}
|
|
129
137
|
function normalizeAlignment(property, value) {
|
|
130
138
|
if (property !== "text-align")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { GradientValue } from "./gradientValue.js";
|
|
2
|
+
export type BackgroundMode = "solid" | "gradient" | "image";
|
|
3
|
+
export interface BackgroundLayer {
|
|
4
|
+
mode: BackgroundMode;
|
|
5
|
+
baseColor: boolean;
|
|
6
|
+
gradient?: GradientValue;
|
|
7
|
+
value: string;
|
|
8
|
+
blend: string;
|
|
9
|
+
size: string;
|
|
10
|
+
repeat: string;
|
|
11
|
+
position: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const BACKGROUND_BLEND_MODES: {
|
|
14
|
+
value: string;
|
|
15
|
+
label: string;
|
|
16
|
+
}[];
|
|
17
|
+
export declare function backgroundMode(value: string): BackgroundMode;
|
|
18
|
+
export declare function newBackgroundLayer(): BackgroundLayer;
|
|
19
|
+
/** CSS repeats shorter background property lists to match the image count. */
|
|
20
|
+
export declare function readBackgroundLayers(read: (property: string, fallback: string) => string): BackgroundLayer[];
|
|
21
|
+
export declare function backgroundDeclarations(layers: BackgroundLayer[]): {
|
|
22
|
+
property: string;
|
|
23
|
+
value: string;
|
|
24
|
+
}[];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { splitTopLevel } from "../../css/value-semantics/cssSyntax.js";
|
|
2
|
+
import { isEmptyColorValue } from "./ColorPicker.js";
|
|
3
|
+
export const BACKGROUND_BLEND_MODES = ["normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"]
|
|
4
|
+
.map((value) => ({ value, label: value.replace(/-/g, " ").replace(/^./, (letter) => letter.toUpperCase()) }));
|
|
5
|
+
export function backgroundMode(value) {
|
|
6
|
+
if (!value || value === "none")
|
|
7
|
+
return "solid";
|
|
8
|
+
return /(?:repeating-)?(?:linear|radial|conic)-gradient\(/.test(value) ? "gradient" : "image";
|
|
9
|
+
}
|
|
10
|
+
export function newBackgroundLayer() {
|
|
11
|
+
return { mode: "solid", baseColor: true, value: "#ffffff", blend: "normal", size: "auto", repeat: "repeat", position: "0% 0%" };
|
|
12
|
+
}
|
|
13
|
+
/** CSS repeats shorter background property lists to match the image count. */
|
|
14
|
+
export function readBackgroundLayers(read) {
|
|
15
|
+
const image = read("background-image", "none");
|
|
16
|
+
const images = image === "none" ? [] : splitTopLevel(image, ",").map((value) => value.trim());
|
|
17
|
+
const listValue = (property, fallback, index) => {
|
|
18
|
+
const values = splitTopLevel(read(property, fallback), ",");
|
|
19
|
+
return values[index % values.length]?.trim() || fallback;
|
|
20
|
+
};
|
|
21
|
+
const layers = images.map((value, index) => {
|
|
22
|
+
const flat = /^linear-gradient\((.*)\)$/s.exec(value);
|
|
23
|
+
const colors = flat ? splitTopLevel(flat[1], ",").map((color) => color.trim()) : [];
|
|
24
|
+
const solid = colors.length === 2 && colors[0] === colors[1];
|
|
25
|
+
return {
|
|
26
|
+
mode: solid ? "solid" : backgroundMode(value),
|
|
27
|
+
baseColor: false,
|
|
28
|
+
value: solid ? colors[0] : value,
|
|
29
|
+
blend: listValue("background-blend-mode", "normal", index),
|
|
30
|
+
size: listValue("background-size", "auto", index),
|
|
31
|
+
repeat: listValue("background-repeat", "repeat", index),
|
|
32
|
+
position: listValue("background-position", "0% 0%", index),
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
const color = read("background-color", "transparent");
|
|
36
|
+
if (!isEmptyColorValue(color))
|
|
37
|
+
layers.push({ ...newBackgroundLayer(), value: color });
|
|
38
|
+
return layers;
|
|
39
|
+
}
|
|
40
|
+
export function backgroundDeclarations(layers) {
|
|
41
|
+
const base = layers.at(-1)?.mode === "solid" && layers.at(-1)?.baseColor ? layers.at(-1) : undefined;
|
|
42
|
+
const images = base ? layers.slice(0, -1) : layers;
|
|
43
|
+
return [
|
|
44
|
+
{ property: "background-color", value: base?.value ?? "transparent" },
|
|
45
|
+
{ property: "background-image", value: images.map((layer) => layer.mode === "solid" ? `linear-gradient(${layer.value}, ${layer.value})` : layer.value).join(", ") || "none" },
|
|
46
|
+
...["blend", "size", "repeat", "position"].map((field) => ({
|
|
47
|
+
property: field === "blend" ? "background-blend-mode" : `background-${field}`,
|
|
48
|
+
value: images.map((layer) => layer[field]).join(", ") || newBackgroundLayer()[field],
|
|
49
|
+
})),
|
|
50
|
+
];
|
|
51
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type GradientType = "linear" | "radial" | "conic";
|
|
2
|
+
export interface GradientStop {
|
|
3
|
+
color: string;
|
|
4
|
+
position: number;
|
|
5
|
+
}
|
|
6
|
+
export interface GradientValue {
|
|
7
|
+
type: GradientType;
|
|
8
|
+
repeating: boolean;
|
|
9
|
+
angle: number;
|
|
10
|
+
/** Preserve the computed shape, origin, and interpolation method. */
|
|
11
|
+
prelude: string;
|
|
12
|
+
stops: GradientStop[];
|
|
13
|
+
}
|
|
14
|
+
export declare function defaultGradient(): GradientValue;
|
|
15
|
+
/** Parse editable computed gradients. Leave unsupported syntax in the CSS field. */
|
|
16
|
+
export declare function parseGradient(value: string): GradientValue | null;
|
|
17
|
+
export declare function serializeGradient(gradient: GradientValue): string;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { splitTopLevel, splitTopLevelWhitespace } from "../../css/value-semantics/cssSyntax.js";
|
|
2
|
+
export function defaultGradient() {
|
|
3
|
+
return { type: "linear", repeating: false, angle: 90, prelude: "", stops: [
|
|
4
|
+
{ color: "#dddddd", position: 0 }, { color: "#a4a4a4", position: 100 },
|
|
5
|
+
] };
|
|
6
|
+
}
|
|
7
|
+
function angleDegrees(value) {
|
|
8
|
+
const match = /^([+-]?(?:\d*\.)?\d+)(deg|grad|rad|turn)$/.exec(value);
|
|
9
|
+
if (!match)
|
|
10
|
+
return null;
|
|
11
|
+
const number = Number(match[1]);
|
|
12
|
+
switch (match[2]) {
|
|
13
|
+
case "grad": return number * 0.9;
|
|
14
|
+
case "rad": return number * 180 / Math.PI;
|
|
15
|
+
case "turn": return number * 360;
|
|
16
|
+
default: return number;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Parse editable computed gradients. Leave unsupported syntax in the CSS field. */
|
|
20
|
+
export function parseGradient(value) {
|
|
21
|
+
if (splitTopLevel(value, ",").length !== 1)
|
|
22
|
+
return null;
|
|
23
|
+
const match = /^(repeating-)?(linear|radial|conic)-gradient\((.*)\)$/s.exec(value.trim());
|
|
24
|
+
if (!match)
|
|
25
|
+
return null;
|
|
26
|
+
const type = match[2];
|
|
27
|
+
const parts = splitTopLevel(match[3], ",").map((part) => part.trim());
|
|
28
|
+
let prelude = "";
|
|
29
|
+
let angle = type === "linear" ? 180 : 0;
|
|
30
|
+
const first = parts[0] ?? "";
|
|
31
|
+
if (/^(?:to\s|[+-]?(?:\d*\.)?\d+(?:deg|grad|rad|turn)\b|circle\b|ellipse\b|closest-|farthest-|at\s|from\s|in\s)/.test(first)
|
|
32
|
+
|| (type === "radial" && /\b(?:px|%)\b/.test(first))) {
|
|
33
|
+
prelude = parts.shift();
|
|
34
|
+
if (type === "linear") {
|
|
35
|
+
const direction = /^(to\s+(?:(?:left|right|top|bottom)(?:\s+|$)){1,2})/.exec(prelude)?.[0]?.trim();
|
|
36
|
+
if (direction) {
|
|
37
|
+
const directions = { top: 0, "top right": 45, "right top": 45, right: 90, "bottom right": 135, "right bottom": 135, bottom: 180, "bottom left": 225, "left bottom": 225, left: 270, "top left": 315, "left top": 315 };
|
|
38
|
+
// Corner directions depend on the element's aspect ratio. Keep them as CSS.
|
|
39
|
+
if (direction.slice(3).includes(" "))
|
|
40
|
+
return null;
|
|
41
|
+
angle = directions[direction.slice(3)] ?? 180;
|
|
42
|
+
prelude = prelude.slice(direction.length).trim();
|
|
43
|
+
}
|
|
44
|
+
else if (!prelude.startsWith("in ")) {
|
|
45
|
+
const rawAngle = splitTopLevelWhitespace(prelude)[0];
|
|
46
|
+
const parsed = angleDegrees(rawAngle);
|
|
47
|
+
if (parsed === null)
|
|
48
|
+
return null;
|
|
49
|
+
angle = parsed;
|
|
50
|
+
prelude = prelude.slice(rawAngle.length).trim();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else if (type === "conic" && prelude.startsWith("from ")) {
|
|
54
|
+
const rawAngle = splitTopLevelWhitespace(prelude)[1];
|
|
55
|
+
const parsed = angleDegrees(rawAngle);
|
|
56
|
+
if (parsed === null)
|
|
57
|
+
return null;
|
|
58
|
+
angle = parsed;
|
|
59
|
+
prelude = prelude.slice(5 + rawAngle.length).trim();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const stops = [];
|
|
63
|
+
for (const part of parts) {
|
|
64
|
+
const words = splitTopLevelWhitespace(part);
|
|
65
|
+
const positions = [];
|
|
66
|
+
while (words.length > 1) {
|
|
67
|
+
const last = words[words.length - 1];
|
|
68
|
+
const percent = /^([+-]?(?:\d*\.)?\d+)%$/.exec(last);
|
|
69
|
+
const degrees = type === "conic" ? angleDegrees(last) : null;
|
|
70
|
+
if (!percent && degrees === null && last !== "0")
|
|
71
|
+
break;
|
|
72
|
+
positions.unshift(percent ? Number(percent[1]) : degrees !== null ? degrees / 3.6 : 0);
|
|
73
|
+
words.pop();
|
|
74
|
+
}
|
|
75
|
+
const color = words.join(" ");
|
|
76
|
+
// Length stops and color hints cannot be represented by a percentage slider.
|
|
77
|
+
if (!color || words.length !== 1 || /^([+-]?\d|calc\(|var\()/i.test(color) || positions.length > 2)
|
|
78
|
+
return null;
|
|
79
|
+
if (positions.length)
|
|
80
|
+
positions.forEach((position) => stops.push({ color, position }));
|
|
81
|
+
else
|
|
82
|
+
stops.push({ color, position: null });
|
|
83
|
+
}
|
|
84
|
+
if (stops.length < 2)
|
|
85
|
+
return null;
|
|
86
|
+
stops[0].position ??= 0;
|
|
87
|
+
stops[stops.length - 1].position ??= 100;
|
|
88
|
+
let previous = stops[0].position;
|
|
89
|
+
for (const stop of stops) {
|
|
90
|
+
if (stop.position !== null) {
|
|
91
|
+
stop.position = Math.max(previous, stop.position);
|
|
92
|
+
previous = stop.position;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
let start = 0;
|
|
96
|
+
while (start < stops.length - 1) {
|
|
97
|
+
let end = start + 1;
|
|
98
|
+
while (stops[end].position === null)
|
|
99
|
+
end++;
|
|
100
|
+
for (let index = start + 1; index < end; index++) {
|
|
101
|
+
stops[index].position = stops[start].position + (stops[end].position - stops[start].position) * (index - start) / (end - start);
|
|
102
|
+
}
|
|
103
|
+
start = end;
|
|
104
|
+
}
|
|
105
|
+
return { type, repeating: Boolean(match[1]), angle, prelude, stops: stops };
|
|
106
|
+
}
|
|
107
|
+
export function serializeGradient(gradient) {
|
|
108
|
+
const prefix = gradient.type === "linear" ? `${gradient.angle}deg`
|
|
109
|
+
: gradient.type === "conic" ? `from ${gradient.angle}deg` : "";
|
|
110
|
+
const prelude = [prefix, gradient.prelude].filter(Boolean).join(" ");
|
|
111
|
+
const stops = [...gradient.stops].sort((a, b) => a.position - b.position)
|
|
112
|
+
.map((stop) => `${stop.color} ${Number(stop.position.toFixed(3))}%`).join(", ");
|
|
113
|
+
return `${gradient.repeating ? "repeating-" : ""}${gradient.type}-gradient(${prelude ? `${prelude}, ` : ""}${stops})`;
|
|
114
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the inline-authored source when `el` carries the property — or a
|
|
3
|
+
* shorthand that sets it — in its `style` attribute. Managed previews can
|
|
4
|
+
* never beat inline styles in the cascade, so a non-null result means the
|
|
5
|
+
* field must present as blocked instead of accepting edits that silently
|
|
6
|
+
* revert (see `verifyPreview`'s "inline-style" conflict).
|
|
7
|
+
*/
|
|
8
|
+
export declare function inlineAuthoredValue(el: HTMLElement, property: string): string | null;
|
|
9
|
+
/** First inline-authored source that blocks any of `properties` on any of `elements`. */
|
|
10
|
+
export declare function inlineBlockedBy(elements: readonly HTMLElement[], ...properties: readonly string[]): string | null;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { isLayoutPreview } from "./layoutPreviewState.js";
|
|
2
|
+
/**
|
|
3
|
+
* Shorthands that also author the keyed longhand when present in a `style`
|
|
4
|
+
* attribute. Managed-stylesheet previews lose the cascade to any of these,
|
|
5
|
+
* so fields must treat the longhand as inline-blocked either way.
|
|
6
|
+
*/
|
|
7
|
+
const SHORTHAND_SOURCES = {
|
|
8
|
+
"font-family": ["font"],
|
|
9
|
+
"font-style": ["font"],
|
|
10
|
+
"font-weight": ["font"],
|
|
11
|
+
"font-size": ["font"],
|
|
12
|
+
"line-height": ["font"],
|
|
13
|
+
"background-color": ["background"],
|
|
14
|
+
"background-image": ["background"],
|
|
15
|
+
"row-gap": ["gap"],
|
|
16
|
+
"column-gap": ["gap"],
|
|
17
|
+
"flex-grow": ["flex"],
|
|
18
|
+
"flex-shrink": ["flex"],
|
|
19
|
+
"flex-basis": ["flex"],
|
|
20
|
+
"flex-direction": ["flex-flow"],
|
|
21
|
+
"flex-wrap": ["flex-flow"],
|
|
22
|
+
"align-content": ["place-content"],
|
|
23
|
+
"justify-content": ["place-content"],
|
|
24
|
+
"align-items": ["place-items"],
|
|
25
|
+
"justify-items": ["place-items"],
|
|
26
|
+
"align-self": ["place-self"],
|
|
27
|
+
"justify-self": ["place-self"],
|
|
28
|
+
"border-width": ["border"],
|
|
29
|
+
"border-style": ["border"],
|
|
30
|
+
"border-color": ["border"],
|
|
31
|
+
"border-top-left-radius": ["border-radius"],
|
|
32
|
+
"border-top-right-radius": ["border-radius"],
|
|
33
|
+
"border-bottom-right-radius": ["border-radius"],
|
|
34
|
+
"border-bottom-left-radius": ["border-radius"],
|
|
35
|
+
"border-top-width": ["border-top", "border-width", "border"],
|
|
36
|
+
"border-right-width": ["border-right", "border-width", "border"],
|
|
37
|
+
"border-bottom-width": ["border-bottom", "border-width", "border"],
|
|
38
|
+
"border-left-width": ["border-left", "border-width", "border"],
|
|
39
|
+
"border-top-style": ["border-top", "border-style", "border"],
|
|
40
|
+
"border-right-style": ["border-right", "border-style", "border"],
|
|
41
|
+
"border-bottom-style": ["border-bottom", "border-style", "border"],
|
|
42
|
+
"border-left-style": ["border-left", "border-style", "border"],
|
|
43
|
+
"border-top-color": ["border-top", "border-color", "border"],
|
|
44
|
+
"border-right-color": ["border-right", "border-color", "border"],
|
|
45
|
+
"border-bottom-color": ["border-bottom", "border-color", "border"],
|
|
46
|
+
"border-left-color": ["border-left", "border-color", "border"],
|
|
47
|
+
"margin-top": ["margin", "margin-block", "margin-block-start", "margin-block-end"],
|
|
48
|
+
"margin-right": ["margin", "margin-inline", "margin-inline-start", "margin-inline-end"],
|
|
49
|
+
"margin-bottom": ["margin", "margin-block", "margin-block-start", "margin-block-end"],
|
|
50
|
+
"margin-left": ["margin", "margin-inline", "margin-inline-start", "margin-inline-end"],
|
|
51
|
+
"padding-top": ["padding", "padding-block", "padding-block-start", "padding-block-end"],
|
|
52
|
+
"padding-right": ["padding", "padding-inline", "padding-inline-start", "padding-inline-end"],
|
|
53
|
+
"padding-bottom": ["padding", "padding-block", "padding-block-start", "padding-block-end"],
|
|
54
|
+
"padding-left": ["padding", "padding-inline", "padding-inline-start", "padding-inline-end"],
|
|
55
|
+
top: ["inset", "inset-block", "inset-block-start", "inset-block-end"],
|
|
56
|
+
right: ["inset", "inset-inline", "inset-inline-start", "inset-inline-end"],
|
|
57
|
+
bottom: ["inset", "inset-block", "inset-block-start", "inset-block-end"],
|
|
58
|
+
left: ["inset", "inset-inline", "inset-inline-start", "inset-inline-end"],
|
|
59
|
+
"grid-template-columns": ["grid-template", "grid"],
|
|
60
|
+
"grid-template-rows": ["grid-template", "grid"],
|
|
61
|
+
"grid-template-areas": ["grid-template", "grid"],
|
|
62
|
+
"grid-auto-columns": ["grid-auto", "grid"],
|
|
63
|
+
"grid-auto-rows": ["grid-auto", "grid"],
|
|
64
|
+
"grid-auto-flow": ["grid-auto", "grid"],
|
|
65
|
+
"grid-row": ["grid-area", "grid"],
|
|
66
|
+
"grid-column": ["grid-area", "grid"],
|
|
67
|
+
"grid-row-start": ["grid-row", "grid-area", "grid"],
|
|
68
|
+
"grid-row-end": ["grid-row", "grid-area", "grid"],
|
|
69
|
+
"grid-column-start": ["grid-column", "grid-area", "grid"],
|
|
70
|
+
"grid-column-end": ["grid-column", "grid-area", "grid"],
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Longhands edited together by one linked field. The field is blocked when
|
|
74
|
+
* any of them is inline-authored, even if the shorthand itself is not.
|
|
75
|
+
*/
|
|
76
|
+
const LINKED_LONGHANDS = {
|
|
77
|
+
"border-width": ["border-top-width", "border-right-width", "border-bottom-width", "border-left-width"],
|
|
78
|
+
"border-style": ["border-top-style", "border-right-style", "border-bottom-style", "border-left-style"],
|
|
79
|
+
"border-color": ["border-top-color", "border-right-color", "border-bottom-color", "border-left-color"],
|
|
80
|
+
"border-radius": ["border-top-left-radius", "border-top-right-radius", "border-bottom-right-radius", "border-bottom-left-radius"],
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Returns the inline-authored source when `el` carries the property — or a
|
|
84
|
+
* shorthand that sets it — in its `style` attribute. Managed previews can
|
|
85
|
+
* never beat inline styles in the cascade, so a non-null result means the
|
|
86
|
+
* field must present as blocked instead of accepting edits that silently
|
|
87
|
+
* revert (see `verifyPreview`'s "inline-style" conflict).
|
|
88
|
+
*/
|
|
89
|
+
export function inlineAuthoredValue(el, property) {
|
|
90
|
+
if (isLayoutPreview(el, property))
|
|
91
|
+
return null;
|
|
92
|
+
for (const source of [property, ...(SHORTHAND_SOURCES[property] ?? [])]) {
|
|
93
|
+
const value = el.style.getPropertyValue(source).trim();
|
|
94
|
+
if (value)
|
|
95
|
+
return `${source}: ${value}`;
|
|
96
|
+
}
|
|
97
|
+
for (const longhand of LINKED_LONGHANDS[property] ?? []) {
|
|
98
|
+
const value = inlineAuthoredValue(el, longhand);
|
|
99
|
+
if (value)
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
/** First inline-authored source that blocks any of `properties` on any of `elements`. */
|
|
105
|
+
export function inlineBlockedBy(elements, ...properties) {
|
|
106
|
+
for (const property of properties) {
|
|
107
|
+
for (const el of elements) {
|
|
108
|
+
const value = inlineAuthoredValue(el, property);
|
|
109
|
+
if (value)
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
@@ -4,14 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export declare function meaningfulLayoutValue(el: HTMLElement, property: string): string;
|
|
6
6
|
export declare function readAuthoredStyleValue(el: HTMLElement, property: string): string | null;
|
|
7
|
-
/**
|
|
8
|
-
* Returns the inline-authored source when `el` carries the property — or a
|
|
9
|
-
* shorthand that sets it — in its `style` attribute. Managed previews can
|
|
10
|
-
* never beat inline styles in the cascade, so a non-null result means the
|
|
11
|
-
* field must present as blocked instead of accepting edits that silently
|
|
12
|
-
* revert (see `verifyPreview`'s "inline-style" conflict).
|
|
13
|
-
*/
|
|
14
|
-
export declare function inlineAuthoredValue(el: HTMLElement, property: string): string | null;
|
|
15
7
|
export interface LayoutValue {
|
|
16
8
|
property: string;
|
|
17
9
|
authored: string | null;
|
|
@@ -2,7 +2,6 @@ import { getActiveStyleState } from "../shell/styleState.js";
|
|
|
2
2
|
import { getStateStyleValue } from "../shell/stateValue.js";
|
|
3
3
|
import { getElementComputedStyle } from "../runtime/domRealm.js";
|
|
4
4
|
import { getBrowserCssInspection } from "../inspection/browserCssInspectionRegistry.js";
|
|
5
|
-
import { isLayoutPreview } from "./layoutPreviewState.js";
|
|
6
5
|
const DEFAULT_LAYOUT_VALUES = {
|
|
7
6
|
width: "auto",
|
|
8
7
|
height: "auto",
|
|
@@ -48,47 +47,6 @@ export function readAuthoredStyleValue(el, property) {
|
|
|
48
47
|
const inline = el.style.getPropertyValue(property).trim();
|
|
49
48
|
return inline || null;
|
|
50
49
|
}
|
|
51
|
-
/**
|
|
52
|
-
* Shorthands that also author the keyed longhand when present in a `style`
|
|
53
|
-
* attribute. Managed-stylesheet previews lose the cascade to any of these,
|
|
54
|
-
* so fields must treat the longhand as inline-blocked either way.
|
|
55
|
-
*/
|
|
56
|
-
const INLINE_SHORTHAND_SOURCES = {
|
|
57
|
-
"row-gap": ["gap"],
|
|
58
|
-
"column-gap": ["gap"],
|
|
59
|
-
"margin-top": ["margin"],
|
|
60
|
-
"margin-right": ["margin"],
|
|
61
|
-
"margin-bottom": ["margin"],
|
|
62
|
-
"margin-left": ["margin"],
|
|
63
|
-
"padding-top": ["padding"],
|
|
64
|
-
"padding-right": ["padding"],
|
|
65
|
-
"padding-bottom": ["padding"],
|
|
66
|
-
"padding-left": ["padding"],
|
|
67
|
-
top: ["inset"],
|
|
68
|
-
right: ["inset"],
|
|
69
|
-
bottom: ["inset"],
|
|
70
|
-
left: ["inset"],
|
|
71
|
-
};
|
|
72
|
-
/**
|
|
73
|
-
* Returns the inline-authored source when `el` carries the property — or a
|
|
74
|
-
* shorthand that sets it — in its `style` attribute. Managed previews can
|
|
75
|
-
* never beat inline styles in the cascade, so a non-null result means the
|
|
76
|
-
* field must present as blocked instead of accepting edits that silently
|
|
77
|
-
* revert (see `verifyPreview`'s "inline-style" conflict).
|
|
78
|
-
*/
|
|
79
|
-
export function inlineAuthoredValue(el, property) {
|
|
80
|
-
if (isLayoutPreview(el, property))
|
|
81
|
-
return null;
|
|
82
|
-
const direct = el.style.getPropertyValue(property).trim();
|
|
83
|
-
if (direct)
|
|
84
|
-
return `${property}: ${direct}`;
|
|
85
|
-
for (const shorthand of INLINE_SHORTHAND_SOURCES[property] ?? []) {
|
|
86
|
-
const value = el.style.getPropertyValue(shorthand).trim();
|
|
87
|
-
if (value)
|
|
88
|
-
return `${shorthand}: ${value}`;
|
|
89
|
-
}
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
50
|
/**
|
|
93
51
|
* Reads both representations needed by a layout editor. Authored CSS is the
|
|
94
52
|
* editable source of truth; computed CSS is only the browser preview/fallback.
|
|
@@ -7,8 +7,8 @@ export declare function nudgeCssValue(property: string, rawValue: string, direct
|
|
|
7
7
|
/** Returns whether a style field exposes the horizontal drag nudge affordance. */
|
|
8
8
|
export declare function supportsDragNudge(property: string): boolean;
|
|
9
9
|
/**
|
|
10
|
-
* Applies one regular nudge per
|
|
11
|
-
* nudge per
|
|
10
|
+
* Applies one regular nudge per sixteen horizontal pointer pixels and one large
|
|
11
|
+
* nudge per eight horizontal pointer pixels. When a large drag starts from an
|
|
12
12
|
* off-grid value, the first step snaps to the next large-step boundary in the
|
|
13
13
|
* drag direction.
|
|
14
14
|
*/
|
|
@@ -2,14 +2,16 @@ import { completeCssValue, CSS_NUMBER_SOURCE } from "./completeCssValue.js";
|
|
|
2
2
|
import { valuePolicyFor } from "./valuePolicy.js";
|
|
3
3
|
const CSS_NUMBER = new RegExp(`^(?:${CSS_NUMBER_SOURCE})$`, "i");
|
|
4
4
|
const CSS_NUMERIC_LITERAL = new RegExp(`^(${CSS_NUMBER_SOURCE})(px|rem|em|%)?$`, "i");
|
|
5
|
-
const DRAG_PIXELS_PER_STEP =
|
|
6
|
-
const LARGE_DRAG_PIXELS_PER_STEP =
|
|
7
|
-
const DRAG_NUDGE_PROPERTY = /^(?:(?:padding|margin)-(?:top|right|bottom|left|horizontal|vertical)|(?:top|right|bottom|left|inset|inset-(?:horizontal|vertical))|(?:row|column)-gap|border-radius|border-(?:top-left|top-right|bottom-right|bottom-left)-radius)$/;
|
|
5
|
+
const DRAG_PIXELS_PER_STEP = 16;
|
|
6
|
+
const LARGE_DRAG_PIXELS_PER_STEP = 8;
|
|
7
|
+
const DRAG_NUDGE_PROPERTY = /^(?:(?:padding|margin)-(?:top|right|bottom|left|horizontal|vertical)|(?:top|right|bottom|left|inset|inset-(?:horizontal|vertical))|(?:row|column)-gap|(?:font-size|letter-spacing|line-height|opacity)|border-radius|border-(?:top-left|top-right|bottom-right|bottom-left)-radius)$/;
|
|
8
8
|
/**
|
|
9
9
|
* Returns a nudged CSS numeric literal, or null when the value is not a
|
|
10
10
|
* standalone number that the inspector can safely edit.
|
|
11
11
|
*/
|
|
12
12
|
export function nudgeCssValue(property, rawValue, direction, large = false) {
|
|
13
|
+
if (property === "opacity")
|
|
14
|
+
return nudgeOpacityValue(rawValue, direction, large);
|
|
13
15
|
const normalised = normaliseForNudge(property, rawValue);
|
|
14
16
|
const parsed = parseNumericLiteral(normalised);
|
|
15
17
|
if (!parsed)
|
|
@@ -25,8 +27,8 @@ export function supportsDragNudge(property) {
|
|
|
25
27
|
return DRAG_NUDGE_PROPERTY.test(property);
|
|
26
28
|
}
|
|
27
29
|
/**
|
|
28
|
-
* Applies one regular nudge per
|
|
29
|
-
* nudge per
|
|
30
|
+
* Applies one regular nudge per sixteen horizontal pointer pixels and one large
|
|
31
|
+
* nudge per eight horizontal pointer pixels. When a large drag starts from an
|
|
30
32
|
* off-grid value, the first step snaps to the next large-step boundary in the
|
|
31
33
|
* drag direction.
|
|
32
34
|
*/
|
|
@@ -59,11 +61,11 @@ export function canNudgeCssValueByDrag(property, rawValue) {
|
|
|
59
61
|
}
|
|
60
62
|
/** Returns a clamped opacity percentage nudged by 1%, or 10% with Shift. */
|
|
61
63
|
export function nudgeOpacityValue(rawValue, direction, large = false) {
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
64
|
+
const percent = parseOpacityPercent(rawValue);
|
|
65
|
+
if (percent === null)
|
|
64
66
|
return null;
|
|
65
67
|
const step = large ? 10 : 1;
|
|
66
|
-
const next = Math.min(100, Math.max(0,
|
|
68
|
+
const next = Math.min(100, Math.max(0, percent + direction * step));
|
|
67
69
|
return `${formatNumber(next)}%`;
|
|
68
70
|
}
|
|
69
71
|
function normaliseForNudge(property, rawValue) {
|
|
@@ -118,6 +120,8 @@ function snapCssValueToLargeStep(property, rawValue, direction) {
|
|
|
118
120
|
return snappedParts.join(", ");
|
|
119
121
|
}
|
|
120
122
|
function snapNumericValueToLargeStep(property, rawValue, direction) {
|
|
123
|
+
if (property === "opacity")
|
|
124
|
+
return snapOpacityValueToLargeStep(rawValue, direction);
|
|
121
125
|
const normalised = normaliseForNudge(property, rawValue);
|
|
122
126
|
const parsed = parseNumericLiteral(normalised);
|
|
123
127
|
if (!parsed)
|
|
@@ -134,6 +138,30 @@ function snapNumericValueToLargeStep(property, rawValue, direction) {
|
|
|
134
138
|
next = clampNudgeNumber(property, next);
|
|
135
139
|
return `${formatNumber(next)}${parsed.unit}`;
|
|
136
140
|
}
|
|
141
|
+
function snapOpacityValueToLargeStep(rawValue, direction) {
|
|
142
|
+
const percent = parseOpacityPercent(rawValue);
|
|
143
|
+
if (percent === null)
|
|
144
|
+
return null;
|
|
145
|
+
const step = 10;
|
|
146
|
+
let next = direction > 0
|
|
147
|
+
? Math.ceil(percent / step) * step
|
|
148
|
+
: Math.floor(percent / step) * step;
|
|
149
|
+
if ((direction > 0 && next <= percent) || (direction < 0 && next >= percent)) {
|
|
150
|
+
next += direction * step;
|
|
151
|
+
}
|
|
152
|
+
next = Math.min(100, Math.max(0, next));
|
|
153
|
+
return `${formatNumber(next)}%`;
|
|
154
|
+
}
|
|
155
|
+
function parseOpacityPercent(rawValue) {
|
|
156
|
+
const parsed = parseNumericLiteral(rawValue);
|
|
157
|
+
if (!parsed)
|
|
158
|
+
return null;
|
|
159
|
+
if (parsed.unit === "%")
|
|
160
|
+
return parsed.number;
|
|
161
|
+
if (parsed.unit === "" && parsed.number >= 0 && parsed.number <= 1)
|
|
162
|
+
return parsed.number * 100;
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
137
165
|
function isLineHeight(property) {
|
|
138
166
|
return property === "line-height" || property.includes("line-height");
|
|
139
167
|
}
|