Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/visualBuilder/generators/generateOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import { FieldDataType } from "../utils/types/index.types";
import { getFieldType } from "../utils/getFieldType";
import { getMultilinePlaintext } from "../utils/getMultilinePlaintext";
import { showAllHiddenHighlightedCommentIcons } from "./generateHighlightedComment";
import {
restoreTruncateStyles,
hasStoredLineClampStyles,
} from "../utils/truncateHandler";

/**
* Adds a focus overlay to the target element.
Expand Down Expand Up @@ -224,6 +228,18 @@ export function hideOverlay(params: HideOverlayParams): void {
VisualBuilder.VisualBuilderGlobalState.value.focusElementObserver =
null;
}

// Restore truncate styles for the previously selected element before hiding overlay
const previousSelectedElement =
VisualBuilder.VisualBuilderGlobalState.value
.previousSelectedEditableDOM;
if (
previousSelectedElement &&
hasStoredLineClampStyles(previousSelectedElement)
) {
restoreTruncateStyles(previousSelectedElement);
}

hideFocusOverlay({
visualBuilderContainer: params.visualBuilderContainer,
visualBuilderOverlayWrapper: params.visualBuilderOverlayWrapper,
Expand Down
17 changes: 17 additions & 0 deletions src/visualBuilder/listeners/mouseClick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ import { fixSvgXPath } from "../utils/collabUtils";
import { v4 as uuidV4 } from "uuid";
import { CslpData } from "../../cslp/types/cslp.types";
import { fetchEntryPermissionsAndStageDetails } from "../utils/fetchEntryPermissionsAndStageDetails";
import {
removeTruncateStyles,
restoreTruncateStyles,
hasStoredLineClampStyles,
} from "../utils/truncateHandler";

export type HandleBuilderInteractionParams = Omit<
EventListenerHandlerParams,
Expand Down Expand Up @@ -243,6 +248,14 @@ function cleanResidualsIfNeeded(
previousSelectedElement !== editableElement) ||
params.reEvaluate
) {
// Restore truncate styles for the previously selected element
if (
previousSelectedElement &&
hasStoredLineClampStyles(previousSelectedElement)
) {
restoreTruncateStyles(previousSelectedElement);
}

cleanIndividualFieldResidual({
overlayWrapper: params.overlayWrapper!,
visualBuilderContainer: params.visualBuilderContainer,
Expand Down Expand Up @@ -277,6 +290,10 @@ function addOverlayAndToolbar(
isVariant: boolean
) {
VisualBuilder.VisualBuilderGlobalState.value.isFocussed = true;

// Remove truncate styles from the focused element
removeTruncateStyles(editableElement);

addOverlay({
overlayWrapper: params.overlayWrapper,
resizeObserver: params.resizeObserver,
Expand Down
6 changes: 5 additions & 1 deletion src/visualBuilder/utils/enableInlineEditing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ export function enableInlineEditing({
{ textContent: expectedFieldData }
);

(editableElement as HTMLElement).style.visibility = "hidden";
// Hide original element immediately, disabling any transitions/animations
const originalElement = editableElement as HTMLElement;
originalElement.style.visibility = "hidden";
originalElement.style.transition = "none";
originalElement.style.animation = "none";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make a utility for hiding and showing the original element.


// set field type attribute to the pseudo editable field
// ensures proper keydown handling similar to the actual editable field
Expand Down
144 changes: 144 additions & 0 deletions src/visualBuilder/utils/truncateHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/**
* Utility module for handling -webkit-line-clamp, overflow, and white-space properties
* when elements are focused/unfocused in the visual builder
*/

interface LineClampStyles {
webkitLineClamp?: string;
overflow?: string;
display?: string;
webkitBoxOrient?: string;
whiteSpace?: string;
}

// WeakMap to store original styles for each element
const originalStyles = new WeakMap<Element, LineClampStyles>();

/**
* Removes truncate related styles from an element and stores the original values
* @param element - The element to remove line-clamp and white-space styles from
*/
export function removeTruncateStyles(element: Element): void {
if (!element || !(element instanceof HTMLElement)) return;

const computedStyles = window.getComputedStyle(element);
const styles: LineClampStyles = {};

// Store original styles if they exist
if (
computedStyles.webkitLineClamp &&
computedStyles.webkitLineClamp !== "none"
) {
styles.webkitLineClamp = computedStyles.webkitLineClamp;
}

if (computedStyles.overflow && computedStyles.overflow !== "visible") {
styles.overflow = computedStyles.overflow;
}

if (computedStyles.display && computedStyles.display.includes("box")) {
styles.display = computedStyles.display;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should avoid this. This will turn inline span into block elements.


if (
computedStyles.webkitBoxOrient &&
computedStyles.webkitBoxOrient !== "horizontal"
) {
styles.webkitBoxOrient = computedStyles.webkitBoxOrient;
}

if (computedStyles.whiteSpace && computedStyles.whiteSpace === "nowrap") {
styles.whiteSpace = computedStyles.whiteSpace;
}

// Only store if we found line-clamp related styles
if (Object.keys(styles).length > 0) {
originalStyles.set(element, styles);

// Remove the styles
element.style.webkitLineClamp = "none";
element.style.overflow = "visible";
// Reset display if it was -webkit-box
if (styles.display?.includes("box")) {
element.style.display = "block";
}
if (styles.webkitBoxOrient) {
element.style.webkitBoxOrient = "horizontal";
}
if (styles.whiteSpace) {
element.style.whiteSpace = "normal";
}
}
}

/**
* Restores the original truncate related styles to an element
* @param element - The element to restore line-clamp and white-space styles to
*/
export function restoreTruncateStyles(element: Element): void {
if (!element || !(element instanceof HTMLElement)) return;

const storedStyles = originalStyles.get(element);
if (!storedStyles) return;

// Restore original styles
if (storedStyles.webkitLineClamp) {
element.style.webkitLineClamp = storedStyles.webkitLineClamp;
} else {
element.style.removeProperty("-webkit-line-clamp");
}

if (storedStyles.overflow) {
element.style.overflow = storedStyles.overflow;
} else {
element.style.removeProperty("overflow");
}

if (storedStyles.display) {
element.style.display = storedStyles.display;
} else {
element.style.removeProperty("display");
}

if (storedStyles.webkitBoxOrient) {
element.style.webkitBoxOrient = storedStyles.webkitBoxOrient;
} else {
element.style.removeProperty("-webkit-box-orient");
}

if (storedStyles.whiteSpace) {
element.style.whiteSpace = storedStyles.whiteSpace;
} else {
element.style.removeProperty("white-space");
}

// Clean up stored styles
originalStyles.delete(element);
}

/**
* Checks if an element has truncate or white-space styles applied
* @param element - The element to check
* @returns boolean indicating if the element has line-clamp or white-space styles
*/
export function hasTruncateStyles(element: Element): boolean {
if (!element || !(element instanceof HTMLElement)) return false;

const computedStyles = window.getComputedStyle(element);
return (
computedStyles.webkitLineClamp !== "none" ||
(computedStyles.overflow === "hidden" &&
computedStyles.display?.includes("box") &&
computedStyles.webkitBoxOrient === "vertical") ||
computedStyles.whiteSpace === "nowrap"
);
}

/**
* Checks if an element currently has stored truncate styles
* @param element - The element to check
* @returns boolean indicating if the element has stored styles
*/
export function hasStoredLineClampStyles(element: Element): boolean {
return originalStyles.has(element);
}
14 changes: 14 additions & 0 deletions src/visualBuilder/utils/updateFocussedState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ import getChildrenDirection from "./getChildrenDirection";
import { getPsuedoEditableElementStyles } from "./getPsuedoEditableStylesElement";
import { isFieldDisabled } from "./isFieldDisabled";
import { fetchEntryPermissionsAndStageDetails } from "./fetchEntryPermissionsAndStageDetails";
import {
removeTruncateStyles,
restoreTruncateStyles,
hasStoredLineClampStyles,
} from "./truncateHandler";

interface ToolbarPositionParams {
focusedToolbar: HTMLElement | null;
Expand Down Expand Up @@ -140,6 +145,11 @@ export async function updateFocussedState({

hideHoverOutline(visualBuilderContainer);

// Remove line-clamp styles from the currently focused element
if (editableElement) {
removeTruncateStyles(editableElement);
}

// in every case, this function will bring cached values
// and this should be quick
const fieldSchema = await FieldSchemaMap.getFieldSchema(
Expand Down Expand Up @@ -265,6 +275,10 @@ export function updateFocussedStateOnMutation(
`[data-cslp-unique-id="${selectedElementCslpUniqueId}"]`
) || document.querySelector(`[data-cslp="${selectedElementCslp}"]`);
if (!newSelectedElement && resizeObserver) {
// Restore truncate styles for the selected element before hiding focus
if (selectedElement && hasStoredLineClampStyles(selectedElement)) {
restoreTruncateStyles(selectedElement);
}
hideFocusOverlay({
visualBuilderOverlayWrapper: focusOverlayWrapper,
focusedToolbar,
Expand Down
Loading