nudge-ui 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/nudge-ui.mjs +3 -1
- package/dist/cli/agentCommand.d.ts +8 -0
- package/dist/cli/agentCommand.js +59 -0
- package/dist/client.mjs +878 -11
- package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
- package/dist/hosts/astro/clientTransport.js +63 -11
- package/dist/hosts/astro/integration.d.ts +2 -2
- package/dist/hosts/astro/integration.js +4 -3
- package/dist/hosts/environment.d.ts +15 -0
- package/dist/hosts/environment.js +16 -0
- package/dist/hosts/next/sidecar.d.ts +4 -0
- package/dist/hosts/next/sidecar.js +56 -7
- package/dist/hosts/next/wrapper.d.ts +2 -0
- package/dist/hosts/next/wrapper.js +32 -3
- package/dist/hosts/projectBridge.d.ts +21 -0
- package/dist/hosts/projectBridge.js +98 -0
- package/dist/hosts/static/cli.d.ts +1 -1
- package/dist/hosts/static/cli.js +6 -1
- package/dist/hosts/static/manifest.d.ts +1 -1
- package/dist/hosts/static/manifest.js +3 -1
- package/dist/hosts/static/server.js +45 -3
- package/dist/hosts/vite/vite.d.ts +6 -1
- package/dist/hosts/vite/vite.js +82 -15
- package/dist/inspector/agent/McpConnectionDialog.d.ts +5 -4
- package/dist/inspector/agent/McpConnectionDialog.js +20 -37
- package/dist/inspector/agent/client.d.ts +16 -3
- package/dist/inspector/agent/client.js +114 -11
- package/dist/inspector/agent/connectionStatus.d.ts +3 -3
- package/dist/inspector/agent/connectionStatus.js +11 -4
- package/dist/inspector/agent/httpTransport.d.ts +8 -0
- package/dist/inspector/agent/httpTransport.js +78 -18
- package/dist/inspector/agent/protocol.d.ts +11 -3
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +1 -1
- package/dist/inspector/agent/verification.js +69 -12
- package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
- package/dist/inspector/canvas/CanvasCard.js +181 -91
- package/dist/inspector/canvas/CanvasElementOverlay.js +288 -52
- package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
- package/dist/inspector/canvas/CanvasToolbar.js +24 -0
- package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
- package/dist/inspector/canvas/CanvasWorkspace.js +410 -72
- package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
- package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
- package/dist/inspector/canvas/canvasResize.d.ts +30 -0
- package/dist/inspector/canvas/canvasResize.js +52 -0
- package/dist/inspector/canvas/canvasStore.d.ts +30 -6
- package/dist/inspector/canvas/canvasStore.js +128 -61
- package/dist/inspector/canvas/frameProtocol.d.ts +82 -5
- package/dist/inspector/canvas/frameProtocol.js +63 -2
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -0
- package/dist/inspector/canvas/keyboardShortcuts.js +16 -0
- package/dist/inspector/canvas/linkEligibility.d.ts +0 -5
- package/dist/inspector/canvas/linkEligibility.js +0 -29
- package/dist/inspector/canvas/projection.d.ts +7 -0
- package/dist/inspector/canvas/projection.js +118 -25
- package/dist/inspector/canvas/rendererBootstrap.js +42 -29
- package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
- package/dist/inspector/canvas/rendererElementSelector.js +385 -58
- package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
- package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
- package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
- package/dist/inspector/canvas/sessionStore.d.ts +1 -0
- package/dist/inspector/canvas/sessionStore.js +12 -4
- package/dist/inspector/canvas/staleChangeDetector.js +16 -14
- package/dist/inspector/changes/changesLog.js +31 -1
- package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
- package/dist/inspector/changes/managedStyleProjection.js +3 -3
- package/dist/inspector/changes/structuralTypes.d.ts +4 -0
- package/dist/inspector/changes/workspaceChanges.d.ts +4 -0
- package/dist/inspector/changes/workspaceChanges.js +29 -3
- package/dist/inspector/clientManifest.d.ts +7 -0
- package/dist/inspector/clientManifest.js +12 -0
- package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
- package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
- package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
- package/dist/inspector/index.d.ts +2 -0
- package/dist/inspector/index.js +80 -32
- package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
- package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
- package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
- package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
- package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
- package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
- package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
- package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
- package/dist/inspector/inline-text/textEditChange.js +165 -0
- package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
- package/dist/inspector/inspection/selectionProperty.js +1 -0
- package/dist/inspector/overlay/DomNavigator.d.ts +10 -0
- package/dist/inspector/overlay/DomNavigator.js +40 -0
- package/dist/inspector/overlay/InspectorOverlay.js +6 -2
- package/dist/inspector/overlay/clickPolicy.d.ts +18 -2
- package/dist/inspector/overlay/clickPolicy.js +34 -3
- package/dist/inspector/overlay/elementSelector.d.ts +1 -1
- package/dist/inspector/overlay/elementSelector.js +18 -7
- package/dist/inspector/overlay/selectedGeometry.js +21 -2
- package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
- package/dist/inspector/overlay/spacingGestures.js +351 -0
- package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
- package/dist/inspector/overlay/structuralGestures.js +12 -10
- package/dist/inspector/projection/managedStylesheet.js +4 -1
- package/dist/inspector/projection/renderedInstance.d.ts +2 -0
- package/dist/inspector/projection/renderedInstance.js +5 -1
- package/dist/inspector/projection/structuralProjection.d.ts +2 -0
- package/dist/inspector/projection/structuralProjection.js +85 -1
- package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
- package/dist/inspector/projection/textProjection.d.ts +2 -0
- package/dist/inspector/projection/textProjection.js +10 -4
- package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
- package/dist/inspector/prompt/clipboardHandoff.js +3 -3
- package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
- package/dist/inspector/prompt/generatePrompt.js +19 -2
- package/dist/inspector/runtime/editorShell.d.ts +2 -0
- package/dist/inspector/runtime/editorShell.js +4 -0
- package/dist/inspector/runtime/runtimeConfig.d.ts +5 -1
- package/dist/inspector/runtime/runtimeConfig.js +4 -0
- package/dist/inspector/selection/hierarchy.d.ts +6 -0
- package/dist/inspector/selection/hierarchy.js +8 -0
- package/dist/inspector/selection/selectionStore.js +2 -0
- package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
- package/dist/inspector/settings/SettingsDialog.js +12 -2
- package/dist/inspector/shell/AppShell.js +5 -1
- package/dist/inspector/shell/ChangesLog.js +26 -22
- package/dist/inspector/shell/CopyPromptButton.js +102 -19
- package/dist/inspector/shell/EmptyState.js +1 -1
- package/dist/inspector/shell/InspectorShell.d.ts +0 -1
- package/dist/inspector/shell/InspectorShell.js +38 -42
- package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
- package/dist/inspector/shell/PeekOriginalButton.js +57 -0
- package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
- package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
- package/dist/inspector/shell/originalPreview.d.ts +9 -0
- package/dist/inspector/shell/originalPreview.js +61 -0
- package/dist/inspector/shell/shortcuts.d.ts +3 -0
- package/dist/inspector/shell/shortcuts.js +15 -0
- package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
- package/dist/inspector/sketch/SketchChanges.js +72 -0
- package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
- package/dist/inspector/sketch/SketchEditor.js +402 -0
- package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
- package/dist/inspector/sketch/SketchEntryButton.js +11 -0
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
- package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
- package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
- package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
- package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
- package/dist/inspector/sketch/SketchOverlay.js +420 -0
- package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
- package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
- package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
- package/dist/inspector/sketch/SketchWorkspace.js +152 -0
- package/dist/inspector/sketch/capture.d.ts +55 -0
- package/dist/inspector/sketch/capture.js +350 -0
- package/dist/inspector/sketch/freehand.d.ts +13 -0
- package/dist/inspector/sketch/freehand.js +64 -0
- package/dist/inspector/sketch/geometry.d.ts +17 -0
- package/dist/inspector/sketch/geometry.js +98 -0
- package/dist/inspector/sketch/handoff.d.ts +21 -0
- package/dist/inspector/sketch/handoff.js +72 -0
- package/dist/inspector/sketch/interaction.d.ts +21 -0
- package/dist/inspector/sketch/interaction.js +98 -0
- package/dist/inspector/sketch/model.d.ts +104 -0
- package/dist/inspector/sketch/model.js +282 -0
- package/dist/inspector/sketch/persistence.d.ts +21 -0
- package/dist/inspector/sketch/persistence.js +205 -0
- package/dist/inspector/sketch/raster.d.ts +22 -0
- package/dist/inspector/sketch/raster.js +203 -0
- package/dist/inspector/sketch/sketchNote.d.ts +10 -0
- package/dist/inspector/sketch/sketchNote.js +30 -0
- package/dist/inspector/sketch/store.d.ts +47 -0
- package/dist/inspector/sketch/store.js +434 -0
- package/dist/inspector/styleEditors/BorderEditor.js +15 -19
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
- package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
- package/dist/inspector/styleEditors/ColorPicker.js +11 -36
- package/dist/inspector/styleEditors/GapField.d.ts +3 -2
- package/dist/inspector/styleEditors/GapField.js +2 -2
- package/dist/inspector/styleEditors/GridSection.js +1 -1
- package/dist/inspector/styleEditors/LayoutSection.js +11 -8
- package/dist/inspector/styleEditors/SpacingBox.js +31 -33
- package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
- package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
- package/dist/inspector/styleEditors/layoutValue.js +3 -0
- package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
- package/dist/inspector/styleEditors/nudgeValue.js +93 -5
- package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
- package/dist/inspector/styleEditors/stylePresence.js +22 -0
- package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
- package/dist/inspector/styleEditors/textRelevance.js +35 -0
- package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
- package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
- package/dist/inspector/tokens/TokenField.js +130 -7
- package/dist/inspector/tokens/TokensPanel.js +2 -1
- package/dist/inspector/tokens/dragPointerLock.d.ts +8 -0
- package/dist/inspector/tokens/dragPointerLock.js +70 -0
- package/dist/inspector/tokens/editActions.d.ts +2 -0
- package/dist/inspector/tokens/editActions.js +2 -2
- package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
- package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
- package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
- package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
- package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
- package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
- package/dist/inspector/tokens/resolution.d.ts +3 -26
- package/dist/inspector/tokens/resolution.js +44 -735
- package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
- package/dist/inspector/tokens/tokenAvailability.js +204 -0
- package/dist/inspector/ui/Button.d.ts +1 -1
- package/dist/inspector/ui/Disclosure.d.ts +22 -0
- package/dist/inspector/ui/Disclosure.js +13 -0
- package/dist/inspector/ui/SideValuesField.js +2 -2
- package/dist/inspector/ui/StatusCallout.d.ts +4 -1
- package/dist/inspector/ui/StatusCallout.js +2 -2
- package/dist/inspector/ui/dragCursor.d.ts +8 -0
- package/dist/inspector/ui/dragCursor.js +21 -0
- package/dist/inspector/ui/styles.js +21 -17
- package/dist/internal/agent-protocol.d.ts +65 -1
- package/dist/internal/agent-protocol.js +183 -3
- package/dist/nudge-ui.mjs +276 -58
- package/dist/transport/editor.d.ts +47 -0
- package/dist/transport/editor.js +163 -0
- package/dist/transport/index.d.ts +1 -0
- package/dist/transport/index.js +1 -0
- package/dist/transport/routes.d.ts +10 -0
- package/dist/transport/routes.js +10 -0
- package/package.json +3 -1
- package/dist/inspector/shell/DomNavigation.d.ts +0 -8
- package/dist/inspector/shell/DomNavigation.js +0 -30
- package/dist/inspector/shell/panelLayout.d.ts +0 -2
- package/dist/inspector/shell/panelLayout.js +0 -29
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ReactElement } from "react";
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
2
|
import type { TokenEntry } from "../../css/model/index.js";
|
|
3
3
|
import type { ResolvedProperty } from "../../css/model/index.js";
|
|
4
4
|
import type { EditTarget } from "../selection/editTarget.js";
|
|
@@ -11,7 +11,8 @@ export interface GapFieldProps {
|
|
|
11
11
|
selection?: StyleSelection | null;
|
|
12
12
|
entries: TokenEntry[];
|
|
13
13
|
tokenRows: ResolvedProperty[];
|
|
14
|
+
leading?: ReactNode;
|
|
14
15
|
onAfterEdit?: () => void;
|
|
15
16
|
}
|
|
16
17
|
/** Renders a gap value with a token chip or the legacy raw-value fallback. */
|
|
17
|
-
export declare function GapField({ property, domElement: el, editTarget, selection, entries, tokenRows, onAfterEdit, }: GapFieldProps): ReactElement;
|
|
18
|
+
export declare function GapField({ property, domElement: el, editTarget, selection, entries, tokenRows, leading, onAfterEdit, }: GapFieldProps): ReactElement;
|
|
@@ -5,8 +5,8 @@ import { inlineAuthoredValue, meaningfulLayoutValue } from "./layoutValue.js";
|
|
|
5
5
|
import { LayoutBlockedIndicator } from "./LayoutBlockedIndicator.js";
|
|
6
6
|
export const GAP_PRESETS = ["0", "0.25rem", "0.5rem", "0.75rem", "1rem", "1.5rem", "2rem", "3rem"];
|
|
7
7
|
/** Renders a gap value with a token chip or the legacy raw-value fallback. */
|
|
8
|
-
export function GapField({ property, domElement: el, editTarget, selection, entries, tokenRows, onAfterEdit, }) {
|
|
8
|
+
export function GapField({ property, domElement: el, editTarget, selection, entries, tokenRows, leading, onAfterEdit, }) {
|
|
9
9
|
const tokenRow = tokenRows.find((row) => row.property === property) ?? null;
|
|
10
10
|
const blockedBy = inlineAuthoredValue(el, property);
|
|
11
|
-
return (_jsxs("span", { className: `layout-combo${blockedBy ? " layout-combo--blocked" : ""}`, "data-test": "layout-combo", "data-property": property, children: [_jsx(TokenField, { property: property, selection: selection, tokenRow: tokenRow, initialValue: meaningfulLayoutValue(el, property), domElement: el, editTarget: editTarget, entries: entries, suggestions: GAP_PRESETS, inputDataTest: `layout-combo-input-${property}`, label: formatInspectorLabel(property), chipVariant: "small", disabled: blockedBy !== null, onAfterEdit: onAfterEdit }), blockedBy ? _jsx(LayoutBlockedIndicator, { blockedBy: blockedBy }) : null] }));
|
|
11
|
+
return (_jsxs("span", { className: `layout-combo${blockedBy ? " layout-combo--blocked" : ""}`, "data-test": "layout-combo", "data-property": property, children: [_jsx(TokenField, { property: property, selection: selection, tokenRow: tokenRow, initialValue: meaningfulLayoutValue(el, property), domElement: el, editTarget: editTarget, entries: entries, suggestions: GAP_PRESETS, leading: leading, inputDataTest: `layout-combo-input-${property}`, label: formatInspectorLabel(property), chipVariant: "small", disabled: blockedBy !== null, onAfterEdit: onAfterEdit }), blockedBy ? _jsx(LayoutBlockedIndicator, { blockedBy: blockedBy }) : null] }));
|
|
12
12
|
}
|
|
@@ -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: [
|
|
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] }));
|
|
27
27
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
3
3
|
import { IconAdjustmentsHorizontal, IconArrowsMaximize, IconArrowsMinimize, IconCheck, IconLetterH, IconLetterW, IconSettings, IconSpacingHorizontal, IconSpacingVertical, IconTextWrap } from "@tabler/icons-react";
|
|
4
4
|
import { TokenField } from "../tokens/TokenField.js";
|
|
5
5
|
import { LayoutDropdown } from "./LayoutDropdown.js";
|
|
@@ -20,6 +20,7 @@ import { getElementComputedStyle } from "../runtime/domRealm.js";
|
|
|
20
20
|
import { FieldRow } from "../ui/FieldRow.js";
|
|
21
21
|
import { ControlSurface } from "../ui/ControlSurface.js";
|
|
22
22
|
import { getNudgeUiTokenEntries } from "../runtime/runtimeConfig.js";
|
|
23
|
+
import { getCanvasProjectionAcknowledgementVersion, subscribeCanvasProjectionAcknowledgements, } from "../canvas/projection.js";
|
|
23
24
|
const DISPLAY_OPTIONS = ["block", "inline", "inline-block", "flex", "inline-flex", "grid", "inline-grid", "none", "contents"];
|
|
24
25
|
const POSITION_OPTIONS = ["static", "relative", "absolute", "fixed", "sticky"];
|
|
25
26
|
const FLEX_DIRECTION_OPTIONS = ["row", "row-reverse", "column", "column-reverse"];
|
|
@@ -46,8 +47,10 @@ export function LayoutSection(props) {
|
|
|
46
47
|
const [primaryIsGridChild, setPrimaryIsGridChild] = useState(false);
|
|
47
48
|
const [primaryPosition, setPrimaryPosition] = useState(() => getStateStyleValue(el, "position", "static"));
|
|
48
49
|
const [layoutRevision, setLayoutRevision] = useState(0);
|
|
49
|
-
const
|
|
50
|
-
const
|
|
50
|
+
const projectionAcknowledgementVersion = useSyncExternalStore(subscribeCanvasProjectionAcknowledgements, getCanvasProjectionAcknowledgementVersion, getCanvasProjectionAcknowledgementVersion);
|
|
51
|
+
const computedRevision = layoutRevision + projectionAcknowledgementVersion;
|
|
52
|
+
const [flexDirection] = useComputedLayoutValue(el, "flex-direction", "row", computedRevision);
|
|
53
|
+
const [flexWrap] = useComputedLayoutValue(el, "flex-wrap", "nowrap", computedRevision);
|
|
51
54
|
const selectedDirection = selection?.getProperty("flex-direction")?.value;
|
|
52
55
|
const selectedWrap = selection?.getProperty("flex-wrap")?.value;
|
|
53
56
|
const mixedFlexDirection = selectedDirection?.kind === "mixed";
|
|
@@ -76,7 +79,7 @@ export function LayoutSection(props) {
|
|
|
76
79
|
catch {
|
|
77
80
|
// noop
|
|
78
81
|
}
|
|
79
|
-
}, [el,
|
|
82
|
+
}, [el, computedRevision]);
|
|
80
83
|
useEffect(() => {
|
|
81
84
|
try {
|
|
82
85
|
const parentEl = el.parentElement;
|
|
@@ -93,7 +96,7 @@ export function LayoutSection(props) {
|
|
|
93
96
|
setPrimaryIsFlexChild(false);
|
|
94
97
|
setPrimaryIsGridChild(false);
|
|
95
98
|
}
|
|
96
|
-
}, [el,
|
|
99
|
+
}, [el, computedRevision]);
|
|
97
100
|
const isFlexContainer = selection ? selection.supportsRole("flex-container") : primaryIsFlexContainer;
|
|
98
101
|
const isFlexChild = selection ? selection.supportsRole("flex-child") : primaryIsFlexChild;
|
|
99
102
|
const isGridContainer = selection ? selection.supportsRole("grid-container") : primaryIsGridContainer;
|
|
@@ -103,7 +106,7 @@ export function LayoutSection(props) {
|
|
|
103
106
|
const showPositionInsets = selection
|
|
104
107
|
? selection.supportsRole("inset-position")
|
|
105
108
|
: position === "absolute" || position === "fixed";
|
|
106
|
-
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:
|
|
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] })] }));
|
|
107
110
|
}
|
|
108
111
|
function SizeSection({ domElement: el, editTarget, selection, entries, tokenRows, revision, onAfterEdit }) {
|
|
109
112
|
const [expanded, setExpanded] = useState(false);
|
|
@@ -132,8 +135,8 @@ function FlexChildSettingsMenu({ domElement, editTarget, selection, revision = 0
|
|
|
132
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 })] }) }));
|
|
133
136
|
}
|
|
134
137
|
function FlexGapField({ property, domElement, editTarget, selection, entries, tokenRows, onAfterEdit }) {
|
|
135
|
-
const
|
|
136
|
-
return (
|
|
138
|
+
const GapIcon = property === "column-gap" ? IconSpacingHorizontal : IconSpacingVertical;
|
|
139
|
+
return (_jsx(ControlSurface, { className: "layout__spacing-field", children: _jsx(GapField, { property: property, domElement: domElement, editTarget: editTarget, selection: selection, entries: entries, tokenRows: tokenRows, leading: (_jsx(GapIcon, { className: "layout__spacing-icon", size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true", "data-test": `layout-spacing-icon-${property}` })), onAfterEdit: onAfterEdit }) }));
|
|
137
140
|
}
|
|
138
141
|
function FlexDirectionControl({ domElement, editTarget, selection, revision = 0, onAfterEdit }) {
|
|
139
142
|
const [direction, setDirection] = useComputedLayoutValue(domElement, "flex-direction", "row", revision);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
3
2
|
import { IconPlus } from "@tabler/icons-react";
|
|
4
3
|
import { findTokenRow, metadataFor } from "./rowLookup.js";
|
|
5
4
|
import { TokenField, TokenValueField } from "../tokens/TokenField.js";
|
|
@@ -10,6 +9,8 @@ import { completeCssValue } from "./completeCssValue.js";
|
|
|
10
9
|
import { valuePolicyFor } from "./valuePolicy.js";
|
|
11
10
|
import { projectInspectorValues, projectInspectorValuesForSelection, } from "../spacing/projection.js";
|
|
12
11
|
import { getStateStyleValue } from "../shell/stateValue.js";
|
|
12
|
+
import { isZeroCssValue } from "./stylePresence.js";
|
|
13
|
+
import { useFieldVisibility } from "./useFieldVisibility.js";
|
|
13
14
|
export function SpacingBox(props) {
|
|
14
15
|
const { element, selection, entries, tokenRows = [], onAfterEdit } = props;
|
|
15
16
|
const el = element.domElement;
|
|
@@ -27,44 +28,37 @@ export function SpacingBox(props) {
|
|
|
27
28
|
return (_jsxs("div", { className: "editor", "data-test": "spacing-box", children: [_jsx("div", { className: "editor__title", children: "Spacing" }), _jsxs("div", { className: "spacing", children: [_jsx(SpacingField, { property: "padding", projection: projection.spacing.padding, domElement: el, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, onAfterEdit: onAfterEdit }), _jsx(SpacingField, { property: "margin", projection: projection.spacing.margin, domElement: el, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, onAfterEdit: onAfterEdit }), showRelativeInset ? _jsx(SpacingField, { property: "inset", projection: projection.spacing.inset, domElement: el, editTarget: editTarget, selection: selection, entries: allEntries, tokenRows: tokenRows, onAfterEdit: onAfterEdit }) : null] })] }));
|
|
28
29
|
}
|
|
29
30
|
export function SpacingField({ property, projection: spacingProjection, domElement: el, editTarget, selection, entries, tokenRows, onAfterEdit, showLabel = true, showEmptyState = true, suggestions, }) {
|
|
30
|
-
const [fieldsAdded, setFieldsAdded] = useState(false);
|
|
31
31
|
const pairDefinitions = [
|
|
32
32
|
{ axis: "horizontal", sideProperties: [sideProperty(property, "right"), sideProperty(property, "left")] },
|
|
33
33
|
{ axis: "vertical", sideProperties: [sideProperty(property, "top"), sideProperty(property, "bottom")] },
|
|
34
34
|
];
|
|
35
|
-
const sideSlots = SIDE_NAMES.map((side) =>
|
|
36
|
-
side,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
35
|
+
const sideSlots = SIDE_NAMES.map((side) => {
|
|
36
|
+
const icon = property === "padding" ? _jsx(PaddingSideIndicator, { side: side }) : _jsx(MarginSideIndicator, { side: side });
|
|
37
|
+
const sideName = sideProperty(property, side);
|
|
38
|
+
const field = spacingProjection.fields[side];
|
|
39
|
+
return {
|
|
40
|
+
side,
|
|
41
|
+
icon: null,
|
|
42
|
+
control: (_jsx(TokenField, { property: sideName, selection: selection, tokenRow: findTokenRow(tokenRows, sideName), domElement: el, editTarget: editTarget, entries: entries, suggestions: suggestions, displayValue: isCalcValue(field.authoredValue) && field.value ? field.value : undefined, editMetadata: metadataFor(findTokenRow(tokenRows, sideName)), onAfterEdit: onAfterEdit, chipVariant: "small", leading: icon })),
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
const hasSpacing = SIDE_NAMES.some((side) => {
|
|
46
|
+
// Zero spacing is visually empty regardless of whether it came from a
|
|
47
|
+
// reset, a component rule, or a token. Open editors stay open below.
|
|
48
|
+
const value = spacingProjection.fields[side].value.trim();
|
|
49
|
+
return value !== "" && !isZeroCssValue(value) && (property !== "inset" || value !== "auto");
|
|
50
|
+
});
|
|
51
|
+
const visibility = useFieldVisibility(selection?.domElements ?? [el], property, hasSpacing);
|
|
50
52
|
const pairSlots = pairDefinitions.map(({ axis, sideProperties }) => ({
|
|
51
53
|
axis,
|
|
52
|
-
icon:
|
|
53
|
-
control: (_jsx(PairedTokenField, { displayProperty: `${property}-${axis}`, sideProperties: sideProperties, domElement: el, editTarget: editTarget, axisProjection: spacingProjection.axes[axis], entries: entries, suggestions: suggestions, onAfterEdit: onAfterEdit, chipVariant: "small" })),
|
|
54
|
+
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 }) })),
|
|
54
56
|
}));
|
|
55
|
-
return (_jsx(SideValuesField, { label: property, "data-test": property === "inset" ? "layout-inset" : `spacing-${property}`, "data-property": property, resetKey: el, pairedControls: pairSlots, showLabel: showLabel, empty: showEmptyState &&
|
|
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", title: "Add Inset Values", onClick: visibility.show, children: _jsx(IconPlus, { size: 16, "aria-hidden": "true" }) })) : undefined, sides: sideSlots }));
|
|
56
58
|
}
|
|
57
59
|
function sideProperty(property, side) {
|
|
58
60
|
return property === "inset" ? side : `${property}-${side}`;
|
|
59
61
|
}
|
|
60
|
-
function isZeroSpacingValue(value) {
|
|
61
|
-
const normalized = value.trim().toLowerCase();
|
|
62
|
-
return !normalized || /^-?0(?:\.0+)?(?:[a-z%]+)?$/.test(normalized);
|
|
63
|
-
}
|
|
64
|
-
function isEmptyInsetValue(value) {
|
|
65
|
-
const normalized = value.trim().toLowerCase();
|
|
66
|
-
return !normalized || normalized === "auto" || /^-?0(?:\.0+)?(?:[a-z%]+)?$/.test(normalized);
|
|
67
|
-
}
|
|
68
62
|
function PaddingSideIndicator({ side }) {
|
|
69
63
|
if (side === "left") {
|
|
70
64
|
return (_jsxs("svg", { className: "side-values__icon side-values__side-icon", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [_jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", stroke: "currentColor", strokeWidth: "var(--icon-stroke-width)" }), _jsx("line", { x1: "6.75", y1: "7", x2: "6.75", y2: "17", stroke: "currentColor", strokeWidth: "var(--icon-stroke-width)", strokeLinecap: "round" })] }));
|
|
@@ -89,14 +83,15 @@ function SpacingAxisIndicator({ property, axis, }) {
|
|
|
89
83
|
}
|
|
90
84
|
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" })] }));
|
|
91
85
|
}
|
|
92
|
-
function PairedTokenField({ displayProperty, sideProperties, domElement: el, editTarget, axisProjection, entries, suggestions, onAfterEdit, chipVariant, }) {
|
|
86
|
+
function PairedTokenField({ displayProperty, sideProperties, domElement: el, editTarget, axisProjection, entries, suggestions, onAfterEdit, chipVariant, leading, }) {
|
|
93
87
|
const row = pairTokenRow(displayProperty, axisProjection);
|
|
94
88
|
const groupMixed = axisProjection.fields.some((field) => field.value === "Mixed");
|
|
95
89
|
const valuesMatch = axisProjection.state === "shared" && !groupMixed;
|
|
96
90
|
const expression = Boolean(row && (row.capability === "raw" || row.capability === "composite"
|
|
97
91
|
|| row.modifiers?.some((modifier) => modifier.kind === "alpha")));
|
|
98
92
|
const calcAuthored = row?.authored ?? row?.declaredValue ?? "";
|
|
99
|
-
const
|
|
93
|
+
const isCalcAuthored = isCalcValue(calcAuthored);
|
|
94
|
+
const activeTokenName = !valuesMatch || expression || isCalcAuthored ? null : row.tokenName;
|
|
100
95
|
// Some browsers expose the used pixel size for horizontal auto margins.
|
|
101
96
|
// Keep the authored keyword visible because replacing it with that transient
|
|
102
97
|
// size would misrepresent the declaration and change the edit semantics.
|
|
@@ -105,7 +100,7 @@ function PairedTokenField({ displayProperty, sideProperties, domElement: el, edi
|
|
|
105
100
|
: valuesMatch
|
|
106
101
|
? calcAuthored.trim().toLowerCase() === "auto"
|
|
107
102
|
? calcAuthored.trim()
|
|
108
|
-
: expression ? row.authored || row.declaredValue || row.resolvedValue : row.resolvedValue
|
|
103
|
+
: isCalcAuthored ? row.resolvedValue : expression ? row.authored || row.declaredValue || row.resolvedValue : row.resolvedValue
|
|
109
104
|
: axisProjection.fields.map(fieldDisplayValue).join(", ");
|
|
110
105
|
const resolvedValue = valuesMatch
|
|
111
106
|
? row.resolvedValue
|
|
@@ -130,10 +125,13 @@ function PairedTokenField({ displayProperty, sideProperties, domElement: el, edi
|
|
|
130
125
|
})));
|
|
131
126
|
if (records.some(Boolean))
|
|
132
127
|
onAfterEdit?.();
|
|
133
|
-
}, onUnlink: commitAxisValue }));
|
|
128
|
+
}, onUnlink: commitAxisValue, leading: leading }));
|
|
134
129
|
}
|
|
135
130
|
function fieldDisplayValue(field) {
|
|
136
|
-
return field.authoredValue.trim() || field.value;
|
|
131
|
+
return isCalcValue(field.authoredValue) ? field.value : field.authoredValue.trim() || field.value;
|
|
132
|
+
}
|
|
133
|
+
function isCalcValue(value) {
|
|
134
|
+
return /\bcalc\s*\(/i.test(value);
|
|
137
135
|
}
|
|
138
136
|
function splitAxisValue(value) {
|
|
139
137
|
let depth = 0;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Marks a temporary inline preview so layout fields do not treat it as authored CSS. */
|
|
2
|
+
export declare function beginLayoutPreview(el: HTMLElement, property: string): void;
|
|
3
|
+
/** Clears a temporary inline preview after the canvas drag is restored or committed. */
|
|
4
|
+
export declare function endLayoutPreview(el: HTMLElement, property: string): void;
|
|
5
|
+
export declare function isLayoutPreview(el: HTMLElement, property: string): boolean;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const activePreviews = new WeakMap();
|
|
2
|
+
/** Marks a temporary inline preview so layout fields do not treat it as authored CSS. */
|
|
3
|
+
export function beginLayoutPreview(el, property) {
|
|
4
|
+
const properties = activePreviews.get(el) ?? new Set();
|
|
5
|
+
properties.add(property);
|
|
6
|
+
activePreviews.set(el, properties);
|
|
7
|
+
}
|
|
8
|
+
/** Clears a temporary inline preview after the canvas drag is restored or committed. */
|
|
9
|
+
export function endLayoutPreview(el, property) {
|
|
10
|
+
const properties = activePreviews.get(el);
|
|
11
|
+
if (!properties)
|
|
12
|
+
return;
|
|
13
|
+
properties.delete(property);
|
|
14
|
+
if (properties.size === 0)
|
|
15
|
+
activePreviews.delete(el);
|
|
16
|
+
}
|
|
17
|
+
export function isLayoutPreview(el, property) {
|
|
18
|
+
return activePreviews.get(el)?.has(property) ?? false;
|
|
19
|
+
}
|
|
@@ -2,6 +2,7 @@ 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";
|
|
5
6
|
const DEFAULT_LAYOUT_VALUES = {
|
|
6
7
|
width: "auto",
|
|
7
8
|
height: "auto",
|
|
@@ -76,6 +77,8 @@ const INLINE_SHORTHAND_SOURCES = {
|
|
|
76
77
|
* revert (see `verifyPreview`'s "inline-style" conflict).
|
|
77
78
|
*/
|
|
78
79
|
export function inlineAuthoredValue(el, property) {
|
|
80
|
+
if (isLayoutPreview(el, property))
|
|
81
|
+
return null;
|
|
79
82
|
const direct = el.style.getPropertyValue(property).trim();
|
|
80
83
|
if (direct)
|
|
81
84
|
return `${property}: ${direct}`;
|
|
@@ -4,5 +4,16 @@ export type NudgeDirection = -1 | 1;
|
|
|
4
4
|
* standalone number that the inspector can safely edit.
|
|
5
5
|
*/
|
|
6
6
|
export declare function nudgeCssValue(property: string, rawValue: string, direction: NudgeDirection, large?: boolean): string | null;
|
|
7
|
+
/** Returns whether a style field exposes the horizontal drag nudge affordance. */
|
|
8
|
+
export declare function supportsDragNudge(property: string): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Applies one regular nudge per four horizontal pointer pixels and one large
|
|
11
|
+
* nudge per two horizontal pointer pixels. When a large drag starts from an
|
|
12
|
+
* off-grid value, the first step snaps to the next large-step boundary in the
|
|
13
|
+
* drag direction.
|
|
14
|
+
*/
|
|
15
|
+
export declare function nudgeCssValueByDrag(property: string, rawValue: string, deltaX: number, large?: boolean, snapToLargeStep?: boolean): string | null;
|
|
16
|
+
/** Returns whether a field has a numeric value that can respond to dragging. */
|
|
17
|
+
export declare function canNudgeCssValueByDrag(property: string, rawValue: string): boolean;
|
|
7
18
|
/** Returns a clamped opacity percentage nudged by 1%, or 10% with Shift. */
|
|
8
19
|
export declare function nudgeOpacityValue(rawValue: string, direction: NudgeDirection, large?: boolean): string | null;
|
|
@@ -2,6 +2,9 @@ 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 = 4;
|
|
6
|
+
const LARGE_DRAG_PIXELS_PER_STEP = 2;
|
|
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
8
|
/**
|
|
6
9
|
* Returns a nudged CSS numeric literal, or null when the value is not a
|
|
7
10
|
* standalone number that the inspector can safely edit.
|
|
@@ -14,13 +17,46 @@ export function nudgeCssValue(property, rawValue, direction, large = false) {
|
|
|
14
17
|
const step = nudgeStep(property, parsed.unit, large);
|
|
15
18
|
if (step === null)
|
|
16
19
|
return null;
|
|
17
|
-
|
|
18
|
-
if (isFontWeight(property))
|
|
19
|
-
next = Math.min(1000, Math.max(1, next));
|
|
20
|
-
if (isLineHeight(property))
|
|
21
|
-
next = Math.max(0, next);
|
|
20
|
+
const next = clampNudgeNumber(property, parsed.number + direction * step);
|
|
22
21
|
return `${formatNumber(next)}${parsed.unit}`;
|
|
23
22
|
}
|
|
23
|
+
/** Returns whether a style field exposes the horizontal drag nudge affordance. */
|
|
24
|
+
export function supportsDragNudge(property) {
|
|
25
|
+
return DRAG_NUDGE_PROPERTY.test(property);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Applies one regular nudge per four horizontal pointer pixels and one large
|
|
29
|
+
* nudge per two horizontal pointer pixels. When a large drag starts from an
|
|
30
|
+
* off-grid value, the first step snaps to the next large-step boundary in the
|
|
31
|
+
* drag direction.
|
|
32
|
+
*/
|
|
33
|
+
export function nudgeCssValueByDrag(property, rawValue, deltaX, large = false, snapToLargeStep = false) {
|
|
34
|
+
const steps = Math.trunc(Math.abs(deltaX) / (large ? LARGE_DRAG_PIXELS_PER_STEP : DRAG_PIXELS_PER_STEP));
|
|
35
|
+
if (steps === 0)
|
|
36
|
+
return null;
|
|
37
|
+
const direction = deltaX < 0 ? -1 : 1;
|
|
38
|
+
let next = rawValue;
|
|
39
|
+
let firstRegularStep = 0;
|
|
40
|
+
if (large && snapToLargeStep) {
|
|
41
|
+
const snapped = snapCssValueToLargeStep(property, next, direction);
|
|
42
|
+
if (snapped === null)
|
|
43
|
+
return null;
|
|
44
|
+
next = snapped;
|
|
45
|
+
firstRegularStep = 1;
|
|
46
|
+
}
|
|
47
|
+
for (let index = firstRegularStep; index < steps; index += 1) {
|
|
48
|
+
const nudged = nudgeDragStep(property, next, direction, large);
|
|
49
|
+
if (nudged === null)
|
|
50
|
+
return null;
|
|
51
|
+
next = nudged;
|
|
52
|
+
}
|
|
53
|
+
return next;
|
|
54
|
+
}
|
|
55
|
+
/** Returns whether a field has a numeric value that can respond to dragging. */
|
|
56
|
+
export function canNudgeCssValueByDrag(property, rawValue) {
|
|
57
|
+
return nudgeCssValueByDrag(property, rawValue, DRAG_PIXELS_PER_STEP) !== null
|
|
58
|
+
|| nudgeCssValueByDrag(property, rawValue, -DRAG_PIXELS_PER_STEP) !== null;
|
|
59
|
+
}
|
|
24
60
|
/** Returns a clamped opacity percentage nudged by 1%, or 10% with Shift. */
|
|
25
61
|
export function nudgeOpacityValue(rawValue, direction, large = false) {
|
|
26
62
|
const parsed = parseNumericLiteral(rawValue);
|
|
@@ -59,12 +95,64 @@ function nudgeStep(property, unit, large) {
|
|
|
59
95
|
return large ? 1 : 0.125;
|
|
60
96
|
return null;
|
|
61
97
|
}
|
|
98
|
+
function nudgeDragStep(property, rawValue, direction, large) {
|
|
99
|
+
if (!isAxisPairProperty(property))
|
|
100
|
+
return nudgeCssValue(property, rawValue, direction, large);
|
|
101
|
+
const parts = rawValue.split(",").map((part) => part.trim());
|
|
102
|
+
if (parts.length !== 2)
|
|
103
|
+
return nudgeCssValue(property, rawValue, direction, large);
|
|
104
|
+
const nudgedParts = parts.map((part) => nudgeCssValue(property, part, direction, large));
|
|
105
|
+
if (nudgedParts.some((part) => part === null))
|
|
106
|
+
return null;
|
|
107
|
+
return nudgedParts.join(", ");
|
|
108
|
+
}
|
|
109
|
+
function snapCssValueToLargeStep(property, rawValue, direction) {
|
|
110
|
+
if (!isAxisPairProperty(property))
|
|
111
|
+
return snapNumericValueToLargeStep(property, rawValue, direction);
|
|
112
|
+
const parts = rawValue.split(",").map((part) => part.trim());
|
|
113
|
+
if (parts.length !== 2)
|
|
114
|
+
return snapNumericValueToLargeStep(property, rawValue, direction);
|
|
115
|
+
const snappedParts = parts.map((part) => snapNumericValueToLargeStep(property, part, direction));
|
|
116
|
+
if (snappedParts.some((part) => part === null))
|
|
117
|
+
return null;
|
|
118
|
+
return snappedParts.join(", ");
|
|
119
|
+
}
|
|
120
|
+
function snapNumericValueToLargeStep(property, rawValue, direction) {
|
|
121
|
+
const normalised = normaliseForNudge(property, rawValue);
|
|
122
|
+
const parsed = parseNumericLiteral(normalised);
|
|
123
|
+
if (!parsed)
|
|
124
|
+
return null;
|
|
125
|
+
const step = nudgeStep(property, parsed.unit, true);
|
|
126
|
+
if (step === null)
|
|
127
|
+
return null;
|
|
128
|
+
let next = direction > 0
|
|
129
|
+
? Math.ceil(parsed.number / step) * step
|
|
130
|
+
: Math.floor(parsed.number / step) * step;
|
|
131
|
+
if ((direction > 0 && next <= parsed.number) || (direction < 0 && next >= parsed.number)) {
|
|
132
|
+
next += direction * step;
|
|
133
|
+
}
|
|
134
|
+
next = clampNudgeNumber(property, next);
|
|
135
|
+
return `${formatNumber(next)}${parsed.unit}`;
|
|
136
|
+
}
|
|
62
137
|
function isLineHeight(property) {
|
|
63
138
|
return property === "line-height" || property.includes("line-height");
|
|
64
139
|
}
|
|
65
140
|
function isFontWeight(property) {
|
|
66
141
|
return property === "font-weight" || property.includes("font-weight");
|
|
67
142
|
}
|
|
143
|
+
function isNonNegativeDimension(property) {
|
|
144
|
+
return /^(?:padding|(?:row|column)-gap|border-(?:top-left|top-right|bottom-right|bottom-left)-radius|border-radius)/.test(property);
|
|
145
|
+
}
|
|
146
|
+
function isAxisPairProperty(property) {
|
|
147
|
+
return /^(?:padding|margin|inset)-(?:horizontal|vertical)$/.test(property);
|
|
148
|
+
}
|
|
149
|
+
function clampNudgeNumber(property, value) {
|
|
150
|
+
if (isFontWeight(property))
|
|
151
|
+
return Math.min(1000, Math.max(1, value));
|
|
152
|
+
if (isLineHeight(property) || isNonNegativeDimension(property))
|
|
153
|
+
return Math.max(0, value);
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
68
156
|
function formatNumber(value) {
|
|
69
157
|
const rounded = Number(value.toFixed(12));
|
|
70
158
|
return Object.is(rounded, -0) ? "0" : String(rounded);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ResolvedProperty } from "../../css/model/index.js";
|
|
2
|
+
import type { StyleSelection } from "../selection/styleSelection.js";
|
|
3
|
+
/** Includes every selected element, even when their resolved zero values agree. */
|
|
4
|
+
export declare function hasAuthoredProperty(selection: StyleSelection | null | undefined, property: string, row?: ResolvedProperty | null): boolean;
|
|
5
|
+
export declare function isZeroCssValue(value: string): boolean;
|
|
6
|
+
/** A direct declaration is meaningful even when its resolved value is empty. */
|
|
7
|
+
export declare function hasAuthoredStyle(row: ResolvedProperty | null | undefined): boolean;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Includes every selected element, even when their resolved zero values agree. */
|
|
2
|
+
export function hasAuthoredProperty(selection, property, row) {
|
|
3
|
+
return selection
|
|
4
|
+
? selection.getProperty(property)?.rows.some(hasAuthoredStyle) ?? false
|
|
5
|
+
: hasAuthoredStyle(row);
|
|
6
|
+
}
|
|
7
|
+
export function isZeroCssValue(value) {
|
|
8
|
+
return /^[+-]?(?:0+(?:\.0*)?|\.0+)(?:[a-z%]+)?$/i.test(value.trim());
|
|
9
|
+
}
|
|
10
|
+
/** A direct declaration is meaningful even when its resolved value is empty. */
|
|
11
|
+
export function hasAuthoredStyle(row) {
|
|
12
|
+
if (!row || row.evidence.inheritedFrom)
|
|
13
|
+
return false;
|
|
14
|
+
const value = (row.authored ?? row.declaredValue).trim();
|
|
15
|
+
if (!value)
|
|
16
|
+
return false;
|
|
17
|
+
// Keep tokens and expressions. Only neutral literals from blanket rules
|
|
18
|
+
// (such as Preflight's `* { padding: 0; border: 0 solid; }`) are baseline.
|
|
19
|
+
const blanketRule = row.evidence.selector?.split(",").every((selector) => /^(?:\*|\*?::?(?:before|after|backdrop))$/.test(selector.trim()));
|
|
20
|
+
const neutral = value.split(/\s+/).every((part) => isZeroCssValue(part) || ["none", "solid", "transparent", "currentcolor"].includes(part.toLowerCase()));
|
|
21
|
+
return !blanketRule || !neutral;
|
|
22
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getElementComputedStyle } from "../runtime/domRealm.js";
|
|
2
|
+
import { hasAuthoredStyle } from "./stylePresence.js";
|
|
3
|
+
const NON_TEXT_ELEMENTS = new Set(["VIDEO", "AUDIO", "IMG", "CANVAS", "IFRAME", "OBJECT", "EMBED", "SCRIPT", "STYLE"]);
|
|
4
|
+
const TEXT_INPUT_TYPES = new Set(["text", "search", "email", "url", "tel", "password", "number", "submit", "reset", "button"]);
|
|
5
|
+
const INLINE_TEXT_ELEMENTS = new Set(["SPAN", "A", "STRONG", "EM", "B", "I", "U", "S", "SMALL", "LABEL", "CODE", "SUP", "SUB"]);
|
|
6
|
+
const TYPOGRAPHY_PROPERTIES = new Set([
|
|
7
|
+
"font", "font-family", "font-size", "font-style", "font-weight", "line-height",
|
|
8
|
+
"letter-spacing", "word-spacing", "text-align", "text-transform", "white-space",
|
|
9
|
+
]);
|
|
10
|
+
function hasTextContent(element, includeBlocks) {
|
|
11
|
+
if (NON_TEXT_ELEMENTS.has(element.tagName))
|
|
12
|
+
return false;
|
|
13
|
+
const style = getElementComputedStyle(element);
|
|
14
|
+
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse")
|
|
15
|
+
return false;
|
|
16
|
+
if (element.tagName === "TEXTAREA" || element.tagName === "SELECT")
|
|
17
|
+
return true;
|
|
18
|
+
if (element.tagName === "INPUT")
|
|
19
|
+
return TEXT_INPUT_TYPES.has(element.getAttribute("type")?.toLowerCase() ?? "text");
|
|
20
|
+
return Array.from(element.childNodes).some((node) => {
|
|
21
|
+
if (node.nodeType === 3)
|
|
22
|
+
return Boolean(node.textContent?.trim());
|
|
23
|
+
if (node.nodeType !== 1)
|
|
24
|
+
return false;
|
|
25
|
+
// SAFETY: ELEMENT_NODE above establishes the child is an Element.
|
|
26
|
+
const child = node;
|
|
27
|
+
const inline = INLINE_TEXT_ELEMENTS.has(child.tagName) || getElementComputedStyle(child).display.startsWith("inline");
|
|
28
|
+
return (includeBlocks || inline) && hasTextContent(child, includeBlocks);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/** Text styling is useful for text runs and containers that author descendant typography. */
|
|
32
|
+
export function isTextRelevant(element, rows) {
|
|
33
|
+
const authorsTypography = rows.some((row) => TYPOGRAPHY_PROPERTIES.has(row.property) && hasAuthoredStyle(row));
|
|
34
|
+
return hasTextContent(element, authorsTypography);
|
|
35
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { getActiveStyleState } from "../shell/styleState.js";
|
|
3
|
+
/** Keeps an opened field mounted when an edit reaches zero, none, or transparent. */
|
|
4
|
+
export function useFieldVisibility(elements, property, present) {
|
|
5
|
+
const styleState = getActiveStyleState();
|
|
6
|
+
const [previous, setPrevious] = useState({ elements, property, styleState, present, visible: present });
|
|
7
|
+
const selectionChanged = property !== previous.property
|
|
8
|
+
|| styleState !== previous.styleState
|
|
9
|
+
|| elements.length !== previous.elements.length
|
|
10
|
+
|| elements.some((element, index) => element !== previous.elements[index]);
|
|
11
|
+
let current = previous;
|
|
12
|
+
if (selectionChanged || present !== previous.present) {
|
|
13
|
+
current = {
|
|
14
|
+
elements, property, styleState, present,
|
|
15
|
+
visible: selectionChanged ? present : previous.visible || present,
|
|
16
|
+
};
|
|
17
|
+
// Adjust before rendering children so a selection change cannot briefly
|
|
18
|
+
// mount the previous selection's controls or interrupt an active drag.
|
|
19
|
+
setPrevious(current);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
visible: current.visible,
|
|
23
|
+
show: () => setPrevious({ ...current, visible: true }),
|
|
24
|
+
hide: () => setPrevious({ ...current, visible: false }),
|
|
25
|
+
};
|
|
26
|
+
}
|