Update grid layout action to provide explicit values for all variables and simplify inheritance logic

This commit is contained in:
Andrew Kingston 2024-08-10 14:16:41 +01:00
parent a6fd2ceb47
commit 781a749a07
No known key found for this signature in database
2 changed files with 82 additions and 124 deletions

View File

@ -53,42 +53,40 @@
onMount(() => { onMount(() => {
let observer let observer
if ($builderStore.inBuilder) { // Set up an observer to watch for changes in metadata attributes of child
// Set up an observer to watch for changes in metadata attributes of child // components, as well as child addition and deletion
// components, as well as child addition and deletion observer = new MutationObserver(mutations => {
observer = new MutationObserver(mutations => { for (let mutation of mutations) {
for (let mutation of mutations) { const { target, type, addedNodes, removedNodes } = mutation
const { target, type, addedNodes, removedNodes } = mutation if (target === ref) {
if (target === ref) { if (addedNodes[0]?.classList?.contains("component")) {
if (addedNodes[0]?.classList?.contains("component")) { // We've added a new child component inside the grid, so we need
// We've added a new child component inside the grid, so we need // to consider it when determining required rows
// to consider it when determining required rows storeChild(addedNodes[0])
storeChild(addedNodes[0]) } else if (removedNodes[0]?.classList?.contains("component")) {
} else if (removedNodes[0]?.classList?.contains("component")) { // We've removed a child component inside the grid, so we need
// We've removed a child component inside the grid, so we need // to stop considering it when determining required rows
// to stop considering it when determining required rows removeChild(removedNodes[0])
removeChild(removedNodes[0])
}
} else if (
type === "attributes" &&
target.parentNode === ref &&
target.classList.contains("component")
) {
// We've updated the size or position of a child
storeChild(target)
} }
} else if (
type === "attributes" &&
target.parentNode === ref &&
target.classList.contains("component")
) {
// We've updated the size or position of a child
storeChild(target)
} }
}) }
observer.observe(ref, { })
childList: true, observer.observe(ref, {
attributes: true, childList: true,
subtree: true, attributes: true,
attributeFilter: [ subtree: true,
"data-grid-desktop-row-end", attributeFilter: [
"data-grid-mobile-row-end", "data-grid-desktop-row-end",
], "data-grid-mobile-row-end",
}) ],
} })
// Now that the observer is set up, we mark the grid as mounted to mount // Now that the observer is set up, we mark the grid as mounted to mount
// our child components // our child components
@ -138,26 +136,11 @@
<style> <style>
.grid { .grid {
position: relative; position: relative;
/*
Prevent cross-grid variable inheritance. The other variables for alignment
are always set on each component, so we don't need to worry about
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, .grid,
.underlay { .underlay {
height: var(--height) !important; height: var(--height) !important;
min-height: none !important; min-height: 0 !important;
max-height: none !important; max-height: none !important;
display: grid; display: grid;
grid-template-rows: repeat(var(--rows), calc(var(--row-size) * 1px)); grid-template-rows: repeat(var(--rows), calc(var(--row-size) * 1px));
@ -206,27 +189,10 @@
margin: calc(var(--grid-spacing) * 1px); margin: calc(var(--grid-spacing) * 1px);
/* On desktop, use desktop metadata and fall back to mobile */ /* On desktop, use desktop metadata and fall back to mobile */
/* Position vars */ --col-start: var(--grid-desktop-col-start, var(--grid-mobile-col-start));
--col-start: var(--grid-desktop-col-start, var(--grid-mobile-col-start, 1)); --col-end: var(--grid-desktop-col-end, var(--grid-mobile-col-end));
--col-end: var( --row-start: var(--grid-desktop-row-start, var(--grid-mobile-row-start));
--grid-desktop-col-end,
var(
--grid-mobile-col-end,
round(
up,
calc(
(var(--grid-spacing) * 2 + var(--default-width)) / var(--col-size) +
1
)
)
)
);
/* Row end is always provided by the gridLayout action */
--row-start: var(--grid-desktop-row-start, var(--grid-mobile-row-start, 1));
--row-end: var(--grid-desktop-row-end, var(--grid-mobile-row-end)); --row-end: var(--grid-desktop-row-end, var(--grid-mobile-row-end));
/* Flex vars */
--h-align: var(--grid-desktop-h-align, var(--grid-mobile-h-align)); --h-align: var(--grid-desktop-h-align, var(--grid-mobile-h-align));
--v-align: var(--grid-desktop-v-align, var(--grid-mobile-v-align)); --v-align: var(--grid-desktop-v-align, var(--grid-mobile-v-align));
@ -247,24 +213,10 @@
/* On mobile, use mobile metadata and fall back to desktop */ /* On mobile, use mobile metadata and fall back to desktop */
.grid.mobile :global(> .component) { .grid.mobile :global(> .component) {
/* Position vars */ --col-start: var(--grid-mobile-col-start, var(--grid-desktop-col-start));
--col-start: var(--grid-mobile-col-start, var(--grid-desktop-col-start, 1)); --col-end: var(--grid-mobile-col-end, var(--grid-desktop-col-end));
--col-end: var( --row-start: var(--grid-mobile-row-start, var(--grid-desktop-row-start));
--grid-mobile-col-end,
var(
--grid-desktop-col-end,
round(
up,
calc(
(var(--spacing) * 2 + var(--default-width)) / var(--col-size) + 1
)
)
)
);
--row-start: var(--grid-mobile-row-start, var(--grid-desktop-row-start, 1));
--row-end: var(--grid-mobile-row-end, var(--grid-desktop-row-end)); --row-end: var(--grid-mobile-row-end, var(--grid-desktop-row-end));
/* Flex vars */
--h-align: var(--grid-mobile-h-align, var(--grid-desktop-h-align)); --h-align: var(--grid-mobile-h-align, var(--grid-desktop-h-align));
--v-align: var(--grid-mobile-v-align, var(--grid-desktop-v-align)); --v-align: var(--grid-mobile-v-align, var(--grid-desktop-v-align));
} }
@ -273,11 +225,12 @@
.grid :global(> .component > *) { .grid :global(> .component > *) {
flex: 0 0 auto !important; flex: 0 0 auto !important;
} }
.grid:not(.mobile) :global(> .component.grid-desktop-grow > *) { .grid:not(.mobile)
:global(> .component[data-grid-desktop-v-align="stretch"] > *) {
flex: 1 1 0 !important; flex: 1 1 0 !important;
height: 0 !important; height: 0 !important;
} }
.grid.mobile :global(> .component.grid-mobile-grow > *) { .grid.mobile :global(> .component[data-grid-mobile-v-align="stretch"] > *) {
flex: 1 1 0 !important; flex: 1 1 0 !important;
height: 0 !important; height: 0 !important;
} }

View File

@ -82,57 +82,62 @@ export const gridLayout = (node, metadata) => {
builderStore.actions.selectComponent(id) builderStore.actions.selectComponent(id)
} }
// Generate base set of grid CSS vars based for this component // Determine default width and height of component
let width = errored ? 500 : definition.size?.width || 200 let width = errored ? 500 : definition.size?.width || 200
let height = errored ? 60 : definition.size?.height || 200 let height = errored ? 60 : definition.size?.height || 200
width += 2 * GridSpacing width += 2 * GridSpacing
height += 2 * GridSpacing height += 2 * GridSpacing
const hAlign = errored ? "stretch" : definition?.grid?.hAlign || "stretch" let vars = {
const vAlign = errored ? "stretch" : definition?.grid?.vAlign || "center"
const vars = {
"--default-width": width, "--default-width": width,
"--default-height": height, "--default-height": height,
"--grid-desktop-h-align": hAlign,
"--grid-mobile-h-align": hAlign,
"--grid-desktop-v-align": vAlign,
"--grid-mobile-v-align": vAlign,
// Variables for automatically determining grid height
"--grid-desktop-row-end": Math.ceil(height / GridRowHeight) + 1,
"--grid-mobile-row-end": Math.ceil(height / GridRowHeight) + 1,
} }
// Extract any other CSS variables from the saved component styles // Generate defaults for all grid params
for (let style of Object.keys(styles)) { const defaults = {
if (style.startsWith("--")) { [GridParams.HAlign]: definition?.grid?.hAlign || "stretch",
vars[style] = styles[style] [GridParams.VAlign]: definition?.grid?.vAlign || "center",
delete styles[style] [GridParams.ColStart]: 1,
} [GridParams.ColEnd]:
"round(up, calc((var(--grid-spacing) * 2 + var(--default-width)) / var(--col-size) + 1))",
[GridParams.RowStart]: 1,
[GridParams.RowEnd]: Math.ceil(height / GridRowHeight) + 1,
}
// Specify values for all grid params for all devices, and strip these CSS
// variables from the styles being applied to the inner component, as we
// want to apply these to the wrapper instead
for (let param of Object.values(GridParams)) {
let dVar = getGridVar(Devices.Desktop, param)
let mVar = getGridVar(Devices.Mobile, param)
vars[dVar] = styles[dVar] || styles[mVar] || defaults[param]
vars[mVar] = styles[mVar] || styles[dVar] || defaults[param]
delete styles[dVar]
delete styles[mVar]
}
// Apply some overrides depending on component state
if (errored) {
vars[getGridVar(Devices.Desktop, GridParams.HAlign)] = "stretch"
vars[getGridVar(Devices.Mobile, GridParams.HAlign)] = "stretch"
vars[getGridVar(Devices.Desktop, GridParams.VAlign)] = "stretch"
vars[getGridVar(Devices.Mobile, GridParams.VAlign)] = "stretch"
} }
// Apply some metadata to data attributes to speed up lookups // Apply some metadata to data attributes to speed up lookups
const desktopRowEnd = `${vars["--grid-desktop-row-end"]}` const addDataTag = (tagName, device, param) => {
const mobileRowEnd = `${vars["--grid-mobile-row-end"]}` const val = `${vars[getGridVar(device, param)]}`
if (node.dataset.gridDesktopRowEnd !== desktopRowEnd) { if (node.dataset[tagName] !== val) {
node.dataset.gridDesktopRowEnd = desktopRowEnd node.dataset[tagName] = val
} }
if (node.dataset.gridMobileRowEnd !== mobileRowEnd) {
node.dataset.gridMobileRowEnd = mobileRowEnd
} }
addDataTag("gridDesktopRowEnd", Devices.Desktop, GridParams.RowEnd)
addDataTag("gridMobileRowEnd", Devices.Mobile, GridParams.RowEnd)
addDataTag("gridDesktopVAlign", Devices.Desktop, GridParams.VAlign)
addDataTag("gridMobileVAlign", Devices.Mobile, GridParams.VAlign)
// Apply all CSS variables to the wrapper // Apply all CSS variables to the wrapper
node.style = buildStyleString(vars) node.style = buildStyleString(vars)
// Toggle classes to specify whether our children should fill
node.classList.toggle(
GridClasses.DesktopFill,
vars[getGridVar(Devices.Desktop, GridParams.VAlign)] === "stretch"
)
node.classList.toggle(
GridClasses.MobileFill,
vars[getGridVar(Devices.Mobile, GridParams.VAlign)] === "stretch"
)
// Add a listener to select this node on click // Add a listener to select this node on click
if (interactive) { if (interactive) {
node.addEventListener("click", selectComponent, false) node.addEventListener("click", selectComponent, false)