diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte index 00032cb23a..2a5211a253 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte @@ -103,7 +103,7 @@ key: "props.cols", label: "Columns", control: Stepper, - defaultValue: 12, + defaultValue: 24, props: { min: 2, max: 50, @@ -113,7 +113,7 @@ key: "props.rows", label: "Rows", control: Stepper, - defaultValue: 12, + defaultValue: 24, props: { min: 2, max: 50, diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 533da51f22..10503f30ac 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -1391,6 +1391,10 @@ "width": 25, "height": 25 }, + "grid": { + "hAlign": "center", + "vAlign": "center" + }, "settings": [ { "type": "icon", @@ -1704,6 +1708,10 @@ "width": 260, "height": 143 }, + "grid": { + "hAlign": "center", + "vAlign": "center" + }, "settings": [ { "type": "text", @@ -1737,6 +1745,10 @@ "width": 400, "height": 100 }, + "grid": { + "hAlign": "stretch", + "vAlign": "stretch" + }, "settings": [ { "type": "text", @@ -5240,6 +5252,10 @@ "width": 300, "height": 120 }, + "grid": { + "hAlign": "center", + "vAlign": "center" + }, "settings": [ { "type": "text", diff --git a/packages/client/src/components/ClientApp.svelte b/packages/client/src/components/ClientApp.svelte index d0369192a0..118aa483ab 100644 --- a/packages/client/src/components/ClientApp.svelte +++ b/packages/client/src/components/ClientApp.svelte @@ -284,7 +284,7 @@ visibility: hidden; padding: 0; margin: 0; - overflow: hidden; + overflow: clip; width: 100%; display: flex; flex-direction: row; @@ -301,7 +301,7 @@ width: 100%; height: 100%; position: relative; - overflow: hidden; + overflow: clip; background-color: transparent; } @@ -311,7 +311,7 @@ } #app-root { - overflow: hidden; + overflow: clip; height: 100%; width: 100%; display: flex; diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 7aea6ce6b9..6e184bde7e 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -39,8 +39,7 @@ getActionContextKey, getActionDependentContextKeys, } from "../utils/buttonActions.js" - import { buildStyleString } from "utils/styleable.js" - import { getBaseGridVars } from "utils/grid.js" + import { gridLayout } from "utils/grid.js" export let instance = {} export let isLayout = false @@ -198,15 +197,18 @@ $: currentTheme = $context?.device?.theme $: darkMode = !currentTheme?.includes("light") - // Build up full styles and split them into variables and non-variables - $: baseStyles = getBaseStyles(definition, errorState) - $: styles = { - ...baseStyles, + $: normalStyles = { ...instance._styles?.normal, ...ephemeralStyles, } - $: parsedStyles = parseStyles(styles) - $: wrapperCSS = buildStyleString(parsedStyles.variables) + $: gridMetadata = { + id, + interactive, + styles: normalStyles, + draggable, + definition, + errored: errorState, + } // Update component context $: store.set({ @@ -214,8 +216,7 @@ children: children.length, styles: { ...instance._styles, - normal: parsedStyles.nonVariables, - variables: parsedStyles.variables, + normal: normalStyles, custom: customCSS, id, empty: emptyState, @@ -615,31 +616,6 @@ } } - const handleWrapperClick = e => { - e.stopPropagation() - builderStore.actions.selectComponent(id) - } - - // Splits component styles into variables and non-variables - const parseStyles = styles => { - let variables = {} - let nonVariables = {} - for (let style of Object.keys(styles || {})) { - const group = style.startsWith("--") ? variables : nonVariables - group[style] = styles[style] - } - return { variables, nonVariables } - } - - // Generates any required base styles based on the component definition - const getBaseStyles = (definition, errored = false) => { - return { - "--default-width": errored ? 500 : definition.size?.width || 100, - "--default-height": errored ? 60 : definition.size?.height || 100, - ...getBaseGridVars(definition, errored), - } - } - onMount(() => { // Register this component instance for external access if ($appStore.isDevApp) { @@ -683,14 +659,11 @@ class:parent={hasChildren} class:block={isBlock} class:error={errorState} - class:fill={definition.grid?.fill} data-id={id} data-name={name} data-icon={icon} data-parent={$component.id} - style={wrapperCSS} - {draggable} - on:click|self={interactive ? handleWrapperClick : null} + use:gridLayout={gridMetadata} > {#if errorState} .grid) { + .main:has(.screenslot-dom > .component > .grid) { padding-top: 0; padding-bottom: 0; } diff --git a/packages/client/src/components/app/container/GridContainer.svelte b/packages/client/src/components/app/container/GridContainer.svelte index 2152bdf42f..b16fc88dbf 100644 --- a/packages/client/src/components/app/container/GridContainer.svelte +++ b/packages/client/src/components/app/container/GridContainer.svelte @@ -68,16 +68,6 @@ position: relative; height: 400px; gap: 0; - - /* Prevent cross-grid variable inheritance */ - /* --grid-desktop-col-start: initial; - --grid-desktop-col-end: initial; - --grid-desktop-row-start: initial; - --grid-desktop-row-end: initial; - --grid-mobile-col-start: initial; - --grid-mobile-col-end: initial; - --grid-mobile-row-start: initial; - --grid-mobile-row-end: initial;*/ } .grid, .underlay { @@ -128,12 +118,8 @@ ); /* Flex vars */ - --h-align: var(--grid-desktop-h-align, var(--grid-mobile-h-align, stretch)); - --v-align: var(--grid-desktop-v-align, var(--grid-mobile-v-align, center)); - --child-flex: var( - --grid-desktop-child-flex, - var(--grid-mobile-child-flex, 0 0 auto) - ); + --h-align: var(--grid-desktop-h-align, var(--grid-mobile-h-align)); + --v-align: var(--grid-desktop-v-align, var(--grid-mobile-v-align)); /* Ensure grid metadata falls within limits */ grid-column-start: min(max(1, var(--col-start)), var(--cols)) !important; @@ -171,16 +157,20 @@ ); /* Flex vars */ - --h-align: var(--grid-mobile-h-align, var(--grid-desktop-h-align, stretch)); - --v-align: var(--grid-mobile-v-align, var(--grid-desktop-v-align, center)); - --child-flex: var( - --grid-mobile-child-flex, - var(--grid-desktop-child-flex, 0 0 auto) - ); + --h-align: var(--grid-mobile-h-align, var(--grid-desktop-h-align)); + --v-align: var(--grid-mobile-v-align, var(--grid-desktop-v-align)); } /* Handle grid children which need to fill the outer component wrapper */ .grid :global(> .component > *) { - flex: var(--child-flex) !important; + flex: 0 0 auto !important; + } + .grid:not(.mobile) :global(> .component.grid-desktop-grow > *) { + flex: 1 1 0 !important; + height: 0 !important; + } + .grid.mobile :global(> .component.grid-mobile-grow > *) { + flex: 1 1 0 !important; + height: 0 !important; } diff --git a/packages/client/src/components/preview/GridDNDHandler.svelte b/packages/client/src/components/preview/GridDNDHandler.svelte index 7e2cbd50c8..12631b980a 100644 --- a/packages/client/src/components/preview/GridDNDHandler.svelte +++ b/packages/client/src/components/preview/GridDNDHandler.svelte @@ -2,7 +2,12 @@ import { onMount, onDestroy } from "svelte" import { builderStore, componentStore } from "stores" import { Utils, memo } from "@budibase/frontend-core" - import { isGridEvent, getGridParentID, getGridVar } from "utils/grid" + import { + isGridEvent, + getGridParentID, + gridCSSVars, + GridVars, + } from "utils/grid" // Smallest possible 1x1 transparent GIF const ghost = new Image(1, 1) @@ -15,10 +20,10 @@ // Grid CSS variables $: vars = { - colStart: $getGridVar("col-start"), - colEnd: $getGridVar("col-end"), - rowStart: $getGridVar("row-start"), - rowEnd: $getGridVar("row-end"), + colStart: $gridCSSVars[GridVars.ColStart], + colEnd: $gridCSSVars[GridVars.ColEnd], + rowStart: $gridCSSVars[GridVars.RowStart], + rowEnd: $gridCSSVars[GridVars.RowEnd], } // Some memoisation of primitive types for performance diff --git a/packages/client/src/components/preview/SettingsBar.svelte b/packages/client/src/components/preview/SettingsBar.svelte index 6b00221f9e..cd11529cfa 100644 --- a/packages/client/src/components/preview/SettingsBar.svelte +++ b/packages/client/src/components/preview/SettingsBar.svelte @@ -7,7 +7,7 @@ import { builderStore, componentStore, dndIsDragging } from "stores" import { Utils } from "@budibase/frontend-core" import { findComponentParent } from "utils/components" - import { getGridVar } from "utils/grid" + import { gridCSSVars, GridVars } from "utils/grid" const verticalOffset = 36 const horizontalOffset = 2 @@ -44,8 +44,8 @@ insideGrid && (definition?.grid?.hAlign !== "stretch" || definition?.grid?.vAlign !== "stretch") - $: gridHAlignVar = $getGridVar("h-align") - $: gridVAlignVar = $getGridVar("v-align") + $: gridHAlignVar = $gridCSSVars[GridVars.HAlign] + $: gridVAlignVar = $gridCSSVars[GridVars.VAlign] $: gridStyles = $state?.styles const getBarSettings = definition => { diff --git a/packages/client/src/utils/grid.js b/packages/client/src/utils/grid.js index 3052199656..d3b16aeafd 100644 --- a/packages/client/src/utils/grid.js +++ b/packages/client/src/utils/grid.js @@ -1,5 +1,6 @@ -import { builderStore, componentStore } from "stores" -import { derived, get, readable } from "svelte/store" +import { builderStore } from "stores" +import { derived } from "svelte/store" +import { buildStyleString } from "utils/styleable.js" /** * We use CSS variables on components to control positioning and layout of @@ -12,20 +13,42 @@ import { derived, get, readable } from "svelte/store" * `grid.hAlign` and `grid.vAlign` keys in the manifest. */ +// Enum representing the different CSS variables we use for grid metadata +export const GridVars = { + HAlign: "h-align", + VAlign: "v-align", + ColStart: "col-start", + ColEnd: "col-end", + RowStart: "row-start", + RowEnd: "row-end", +} + +// Classes used in selectors inside grid containers to control child styles +export const GridClasses = { + DesktopFill: "grid-desktop-grow", + MobileFill: "grid-mobile-grow", +} + // Enum for device preview type, included in grid CSS variables const Devices = { Desktop: "desktop", Mobile: "mobile", } -// Generates the CSS variable for a certain grid param suffix, for the current -// device +// A derived map of all CSS variables for the current device const previewDevice = derived(builderStore, $store => $store.previewDevice) -export const getGridVar = derived(previewDevice, device => suffix => { - const prefix = device === Devices.Mobile ? Devices.Mobile : Devices.Desktop - return `--grid-${prefix}-${suffix}` +export const gridCSSVars = derived(previewDevice, $device => { + const device = $device === Devices.Mobile ? Devices.Mobile : Devices.Desktop + let vars = {} + for (let type of Object.values(GridVars)) { + vars[type] = `--grid-${device}-${type}` + } + return vars }) +// Builds a CSS variable name for a certain piece of grid metadata +export const getGridCSSVar = (device, type) => `--grid-${device}-${type}` + // Generates the CSS variable for a certain grid param suffix, for the other // device variant than the one included in this variable export const getOtherDeviceGridVar = cssVar => { @@ -69,21 +92,6 @@ export const getGridParentID = node => { return node?.parentNode?.closest(".grid")?.parentNode.dataset.id } -// Generates the base set of grid CSS vars from a component definition -export const getBaseGridVars = (definition, errored = false) => { - const hAlign = errored ? "stretch" : definition?.grid?.hAlign || "stretch" - const vAlign = errored ? "stretch" : definition?.grid?.vAlign || "center" - const flexStyles = vAlign === "stretch" ? "1 1 0" : "0 0 auto" - return { - "--grid-desktop-h-align": hAlign, - "--grid-mobile-h-align": hAlign, - "--grid-desktop-v-align": vAlign, - "--grid-mobile-v-align": vAlign, - "--grid-desktop-child-flex": flexStyles, - "--grid-mobile-child-flex": flexStyles, - } -} - // Gets the current value of a certain grid CSS variable for a component export const getGridVarValue = (styles, variable) => { // Try the desired variable @@ -97,3 +105,73 @@ export const getGridVarValue = (styles, variable) => { // Otherwise use the default return val ? val : getDefaultGridVarValue(variable) } + +// Svelte action to apply required class names and styles to our component +// wrappers +export const gridLayout = (node, metadata) => { + let selectComponent + + const applyMetadata = metadata => { + const { id, styles, interactive, errored, definition } = metadata + consol.log(styles) + + // Callback to select the component when clicking on the wrapper + selectComponent = e => { + e.preventDefault() + builderStore.actions.selectComponent(id) + } + + // Generate base set of grid CSS vars based for this component + const hAlign = errored ? "stretch" : definition?.grid?.hAlign || "stretch" + const vAlign = errored ? "stretch" : definition?.grid?.vAlign || "center" + const vars = { + "--default-width": errored ? 500 : definition.size?.width || 100, + "--default-height": errored ? 60 : definition.size?.height || 100, + "--grid-desktop-h-align": hAlign, + "--grid-mobile-h-align": hAlign, + "--grid-desktop-v-align": vAlign, + "--grid-mobile-v-align": vAlign, + } + + // Extract any other CSS variables from the saved component styles + for (let style of Object.keys(styles)) { + if (style.startsWith("--")) { + vars[style] = styles[style] + delete styles[style] + } + } + + // Apply all CSS variables to the wrapper + node.style = buildStyleString(vars) + + // Toggle classes to specify whether our children should fill + const desktopVar = getGridCSSVar(Devices.Desktop, GridVars.VAlign) + const mobileVar = getGridCSSVar(Devices.Mobile, GridVars.VAlign) + node.classList.toggle( + GridClasses.DesktopFill, + vars[desktopVar] === "stretch" + ) + node.classList.toggle(GridClasses.MobileFill, vars[mobileVar] === "stretch") + + // Add a listener to select this node on click + if (interactive) { + node.addEventListener("click", selectComponent, false) + } + } + + const removeListeners = () => { + node.removeEventListener("click", selectComponent) + } + + applyMetadata(metadata) + + return { + update(newMetadata) { + removeListeners() + applyMetadata(newMetadata) + }, + destroy() { + removeListeners() + }, + } +}