"use client"; import * as React from "react"; import { ControlInlineGroup } from "@repo/ui"; import type { ToolcraftControlLayoutGroupSchema, ToolcraftControlSchema, } from "../../../schema/types"; import { getControlName, shouldRenderCompoundControlSectionDivider, type RenderedControlRenderGroup, } from "./controls-panel-layout"; const maxInlineSwitchLabelLength = 16; const maxInlineSwitchLabelWordCount = 2; function hasColorOpacityControl( controlsById: Record, layoutGroup: ToolcraftControlLayoutGroupSchema, ): boolean { return layoutGroup.controls.some( (controlId) => controlsById[controlId]?.type === "colorOpacity", ); } function hasSliderControl( controlsById: Record, layoutGroup: ToolcraftControlLayoutGroupSchema, ): boolean { return layoutGroup.controls.some( (controlId) => controlsById[controlId]?.type === "slider" || controlsById[controlId]?.type === "rangeSlider", ); } function hasFullWidthChoiceControl( controlsById: Record, layoutGroup: ToolcraftControlLayoutGroupSchema, ): boolean { return layoutGroup.controls.some( (controlId) => controlsById[controlId]?.type === "segmented" || controlsById[controlId]?.type === "tabs", ); } function hasSectionedCompoundControl( controlsById: Record, layoutGroup: ToolcraftControlLayoutGroupSchema, ): boolean { return layoutGroup.controls.some((controlId) => { const control = controlsById[controlId]; return control ? shouldRenderCompoundControlSectionDivider(control) : false; }); } function isInlineSwitchLabelSafe( controlId: string, control: ToolcraftControlSchema, ): boolean { const label = getControlName(controlId, control.label).trim(); const wordCount = label.split(/\s+/u).filter(Boolean).length; return label.length <= maxInlineSwitchLabelLength && wordCount <= maxInlineSwitchLabelWordCount; } function hasUnsafeInlineSwitchLabels( controlsById: Record, layoutGroup: ToolcraftControlLayoutGroupSchema, ): boolean { const switchEntries = layoutGroup.controls .map((controlId) => [controlId, controlsById[controlId]] as const) .filter( (entry): entry is readonly [string, ToolcraftControlSchema] => Boolean(entry[1]) && entry[1].type === "switch", ); return ( switchEntries.length > 1 && switchEntries.some(([controlId, control]) => !isInlineSwitchLabelSafe(controlId, control)) ); } function isBooleanControl(control: ToolcraftControlSchema | undefined): boolean { return control?.type === "checkbox" || control?.type === "switch"; } function isToggleParameterLayoutGroup( controlsById: Record, layoutGroup: ToolcraftControlLayoutGroupSchema, ): boolean { if (layoutGroup.controls.length !== 2) { return false; } const controls = layoutGroup.controls.map((controlId) => controlsById[controlId]); const booleanControlCount = controls.filter((control) => isBooleanControl(control)).length; const parameterControlCount = controls.filter((control) => !isBooleanControl(control)).length; return booleanControlCount === 1 && parameterControlCount === 1; } export function shouldHideToggleParameterControlLabel({ control, controlsById, layoutGroup, }: { control: ToolcraftControlSchema; controlsById: Record; layoutGroup: ToolcraftControlLayoutGroupSchema | undefined; }): boolean { return Boolean( layoutGroup && isToggleParameterLayoutGroup(controlsById, layoutGroup) && !isBooleanControl(control), ); } function getControlTargetEntityKey(control: ToolcraftControlSchema): string | null { const parts = control.target.split("."); if (parts.length < 2) { return null; } return parts.slice(0, -1).join("."); } function shouldAutoInlineBooleanPair( controlsById: Record, firstId: string, secondId: string, ): boolean { const firstControl = controlsById[firstId]; const secondControl = controlsById[secondId]; if (!isBooleanControl(firstControl) || !isBooleanControl(secondControl)) { return false; } if ( !isInlineSwitchLabelSafe(firstId, firstControl) || !isInlineSwitchLabelSafe(secondId, secondControl) ) { return false; } const firstEntityKey = getControlTargetEntityKey(firstControl); const secondEntityKey = getControlTargetEntityKey(secondControl); return firstEntityKey !== null && firstEntityKey === secondEntityKey; } function getAutoInlineBooleanLayoutGroups({ controlsById, renderedGroups, reservedControlIds, }: { controlsById: Record; renderedGroups: readonly RenderedControlRenderGroup[]; reservedControlIds: ReadonlySet; }): ToolcraftControlLayoutGroupSchema[] { const layoutGroups: ToolcraftControlLayoutGroupSchema[] = []; let index = 0; while (index < renderedGroups.length) { const firstIds = renderedGroups[index]?.ids; const secondIds = renderedGroups[index + 1]?.ids; const firstId = firstIds?.length === 1 ? firstIds[0] : undefined; const secondId = secondIds?.length === 1 ? secondIds[0] : undefined; if ( firstId && secondId && !reservedControlIds.has(firstId) && !reservedControlIds.has(secondId) && shouldAutoInlineBooleanPair(controlsById, firstId, secondId) ) { layoutGroups.push({ columns: 2, controls: [firstId, secondId], layout: "inline", }); index += 2; continue; } index += 1; } return layoutGroups; } export function renderControlLayoutGroups({ controlsById, layoutGroups, renderedGroups, }: { controlsById: Record; layoutGroups?: readonly ToolcraftControlLayoutGroupSchema[]; renderedGroups: readonly RenderedControlRenderGroup[]; }): React.ReactNode[] { if (!layoutGroups?.length) { const autoLayoutGroups = getAutoInlineBooleanLayoutGroups({ controlsById, renderedGroups, reservedControlIds: new Set(), }); if (autoLayoutGroups.length === 0) { return renderedGroups.map((group) => group.node); } return renderControlLayoutGroups({ controlsById, layoutGroups: autoLayoutGroups, renderedGroups, }); } const reservedControlIds = new Set(layoutGroups.flatMap((group) => group.controls)); const resolvedLayoutGroups = [ ...layoutGroups, ...getAutoInlineBooleanLayoutGroups({ controlsById, renderedGroups, reservedControlIds, }), ]; const layoutGroupByControlId = getInlineLayoutGroupByControlId({ controlsById, layoutGroups: resolvedLayoutGroups, }); const nodes: React.ReactNode[] = []; for (const renderedGroup of renderedGroups) { const layoutGroup = renderedGroup.ids .map((id) => layoutGroupByControlId.get(id)) .find((group): group is ToolcraftControlLayoutGroupSchema => Boolean(group)); if (!layoutGroup) { nodes.push(renderedGroup.node); continue; } const groupedRenderedControls = renderedGroups.filter((candidate) => candidate.ids.some((id) => layoutGroup.controls.includes(id)), ); const firstGroupedControl = groupedRenderedControls[0]; if (firstGroupedControl !== renderedGroup) { continue; } if (groupedRenderedControls.length < 2) { nodes.push(renderedGroup.node); continue; } nodes.push( {groupedRenderedControls.map((group) => group.node)} , ); } return nodes; } export function getInlineLayoutGroupByControlId({ controlsById, layoutGroups, }: { controlsById: Record; layoutGroups?: readonly ToolcraftControlLayoutGroupSchema[]; }): Map { const layoutGroupByControlId = new Map(); for (const layoutGroup of layoutGroups ?? []) { if (layoutGroup.layout !== "inline") { continue; } if ( hasColorOpacityControl(controlsById, layoutGroup) || hasSliderControl(controlsById, layoutGroup) || hasFullWidthChoiceControl(controlsById, layoutGroup) || hasSectionedCompoundControl(controlsById, layoutGroup) || hasUnsafeInlineSwitchLabels(controlsById, layoutGroup) ) { continue; } for (const controlId of layoutGroup.controls) { layoutGroupByControlId.set(controlId, layoutGroup); } } return layoutGroupByControlId; }