Update grid layout to include nested flex wrappers for more layout control
This commit is contained in:
parent
0d6e7bd5d3
commit
29ddeab0d4
|
@ -132,8 +132,8 @@
|
||||||
hoverStore.hover(data.id, false)
|
hoverStore.hover(data.id, false)
|
||||||
} else if (type === "update-prop") {
|
} else if (type === "update-prop") {
|
||||||
await componentStore.updateSetting(data.prop, data.value)
|
await componentStore.updateSetting(data.prop, data.value)
|
||||||
} else if (type === "update-styles") {
|
} else if (type === "update-meta-styles") {
|
||||||
await componentStore.updateStyles(data.styles, data.id)
|
await componentStore.updateMetaStyles(data.styles, data.id)
|
||||||
} else if (type === "delete-component" && data.id) {
|
} else if (type === "delete-component" && data.id) {
|
||||||
// Legacy type, can be deleted in future
|
// Legacy type, can be deleted in future
|
||||||
confirmDeleteComponent(data.id)
|
confirmDeleteComponent(data.id)
|
||||||
|
|
|
@ -64,6 +64,7 @@ export class ComponentStore extends BudiStore {
|
||||||
this.moveDown = this.moveDown.bind(this)
|
this.moveDown = this.moveDown.bind(this)
|
||||||
this.updateStyle = this.updateStyle.bind(this)
|
this.updateStyle = this.updateStyle.bind(this)
|
||||||
this.updateStyles = this.updateStyles.bind(this)
|
this.updateStyles = this.updateStyles.bind(this)
|
||||||
|
this.updateMetaStyles = this.updateMetaStyles.bind(this)
|
||||||
this.updateCustomStyle = this.updateCustomStyle.bind(this)
|
this.updateCustomStyle = this.updateCustomStyle.bind(this)
|
||||||
this.updateConditions = this.updateConditions.bind(this)
|
this.updateConditions = this.updateConditions.bind(this)
|
||||||
this.requestEjectBlock = this.requestEjectBlock.bind(this)
|
this.requestEjectBlock = this.requestEjectBlock.bind(this)
|
||||||
|
@ -978,6 +979,16 @@ export class ComponentStore extends BudiStore {
|
||||||
await this.patch(patchFn, id)
|
await this.patch(patchFn, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateMetaStyles(styles, id) {
|
||||||
|
const patchFn = component => {
|
||||||
|
component._styles.meta = {
|
||||||
|
...component._styles.meta,
|
||||||
|
...styles,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await this.patch(patchFn, id)
|
||||||
|
}
|
||||||
|
|
||||||
async updateCustomStyle(style) {
|
async updateCustomStyle(style) {
|
||||||
await this.patch(component => {
|
await this.patch(component => {
|
||||||
component._styles.custom = style
|
component._styles.custom = style
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
getActionContextKey,
|
getActionContextKey,
|
||||||
getActionDependentContextKeys,
|
getActionDependentContextKeys,
|
||||||
} from "../utils/buttonActions.js"
|
} from "../utils/buttonActions.js"
|
||||||
|
import { buildStyleString } from "utils/styleable.js"
|
||||||
|
|
||||||
export let instance = {}
|
export let instance = {}
|
||||||
export let isLayout = false
|
export let isLayout = false
|
||||||
|
@ -102,8 +103,8 @@
|
||||||
let settingsDefinitionMap
|
let settingsDefinitionMap
|
||||||
let missingRequiredSettings = false
|
let missingRequiredSettings = false
|
||||||
|
|
||||||
// Temporary styles which can be added in the app preview for things like DND.
|
// Temporary meta styles which can be added in the app preview for things like
|
||||||
// We clear these whenever a new instance is received.
|
// DND. We clear these whenever a new instance is received.
|
||||||
let ephemeralStyles
|
let ephemeralStyles
|
||||||
|
|
||||||
// Single string of all HBS blocks, used to check if we use a certain binding
|
// Single string of all HBS blocks, used to check if we use a certain binding
|
||||||
|
@ -196,16 +197,20 @@
|
||||||
$: currentTheme = $context?.device?.theme
|
$: currentTheme = $context?.device?.theme
|
||||||
$: darkMode = !currentTheme?.includes("light")
|
$: darkMode = !currentTheme?.includes("light")
|
||||||
|
|
||||||
|
// Build meta styles and stringify to apply to the wrapper node
|
||||||
|
$: metaStyles = {
|
||||||
|
...instance._styles?.meta,
|
||||||
|
...ephemeralStyles,
|
||||||
|
}
|
||||||
|
$: metaCSS = buildStyleString(metaStyles)
|
||||||
|
|
||||||
// Update component context
|
// Update component context
|
||||||
$: store.set({
|
$: store.set({
|
||||||
id,
|
id,
|
||||||
children: children.length,
|
children: children.length,
|
||||||
styles: {
|
styles: {
|
||||||
...instance._styles,
|
...instance._styles,
|
||||||
normal: {
|
meta: metaStyles,
|
||||||
...instance._styles?.normal,
|
|
||||||
...ephemeralStyles,
|
|
||||||
},
|
|
||||||
custom: customCSS,
|
custom: customCSS,
|
||||||
id,
|
id,
|
||||||
empty: emptyState,
|
empty: emptyState,
|
||||||
|
@ -605,6 +610,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const select = e => {
|
||||||
|
if (isBlock) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.stopPropagation()
|
||||||
|
builderStore.actions.selectComponent(id)
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Register this component instance for external access
|
// Register this component instance for external access
|
||||||
if ($appStore.isDevApp) {
|
if ($appStore.isDevApp) {
|
||||||
|
@ -635,6 +648,8 @@
|
||||||
{#if constructor && initialSettings && (visible || inSelectedPath) && !builderHidden}
|
{#if constructor && initialSettings && (visible || inSelectedPath) && !builderHidden}
|
||||||
<!-- The ID is used as a class because getElementsByClassName is O(1) -->
|
<!-- The ID is used as a class because getElementsByClassName is O(1) -->
|
||||||
<!-- and the performance matters for the selection indicators -->
|
<!-- and the performance matters for the selection indicators -->
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div
|
<div
|
||||||
class={`component ${id}`}
|
class={`component ${id}`}
|
||||||
class:draggable
|
class:draggable
|
||||||
|
@ -650,6 +665,9 @@
|
||||||
data-name={name}
|
data-name={name}
|
||||||
data-icon={icon}
|
data-icon={icon}
|
||||||
data-parent={$component.id}
|
data-parent={$component.id}
|
||||||
|
style={metaCSS}
|
||||||
|
{draggable}
|
||||||
|
on:click={select}
|
||||||
>
|
>
|
||||||
{#if errorState}
|
{#if errorState}
|
||||||
<ComponentErrorState
|
<ComponentErrorState
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
export let wrap
|
export let wrap
|
||||||
export let onClick
|
export let onClick
|
||||||
|
|
||||||
$: directionClass = direction ? `valid-container direction-${direction}` : ""
|
$: directionClass = direction ? `flex-container direction-${direction}` : ""
|
||||||
$: hAlignClass = hAlign ? `hAlign-${hAlign}` : ""
|
$: hAlignClass = hAlign ? `hAlign-${hAlign}` : ""
|
||||||
$: vAlignClass = vAlign ? `vAlign-${vAlign}` : ""
|
$: vAlignClass = vAlign ? `vAlign-${vAlign}` : ""
|
||||||
$: sizeClass = size ? `size-${size}` : ""
|
$: sizeClass = size ? `size-${size}` : ""
|
||||||
|
@ -39,11 +39,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.valid-container {
|
.flex-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
.valid-container :global(.component > *) {
|
.flex-container :global(.component > *) {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
.direction-row {
|
.direction-row {
|
||||||
|
|
|
@ -8,10 +8,16 @@
|
||||||
export let cols = 12
|
export let cols = 12
|
||||||
export let rows = 12
|
export let rows = 12
|
||||||
|
|
||||||
|
let width
|
||||||
|
let height
|
||||||
|
|
||||||
$: cols = cols || 12
|
$: cols = cols || 12
|
||||||
$: rows = rows || 12
|
$: rows = rows || 12
|
||||||
$: coords = generateCoords(rows, cols)
|
$: coords = generateCoords(rows, cols)
|
||||||
$: mobile = $context.device.mobile
|
$: mobile = $context.device.mobile
|
||||||
|
$: empty = $component.empty
|
||||||
|
$: colSize = width / cols
|
||||||
|
$: rowSize = height / rows
|
||||||
|
|
||||||
const generateCoords = (rows, cols) => {
|
const generateCoords = (rows, cols) => {
|
||||||
let grid = []
|
let grid = []
|
||||||
|
@ -27,14 +33,18 @@
|
||||||
<div
|
<div
|
||||||
class="grid"
|
class="grid"
|
||||||
class:mobile
|
class:mobile
|
||||||
|
bind:clientWidth={width}
|
||||||
|
bind:clientHeight={height}
|
||||||
use:styleable={{
|
use:styleable={{
|
||||||
...$component.styles,
|
...$component.styles,
|
||||||
normal: {
|
normal: {
|
||||||
...$component.styles?.normal,
|
...$component.styles?.normal,
|
||||||
"--cols": cols,
|
"--cols": cols,
|
||||||
"--rows": rows,
|
"--rows": rows,
|
||||||
gap: "0 !important",
|
"--col-size": `${colSize}px`,
|
||||||
|
"--row-size": `${rowSize}px`,
|
||||||
},
|
},
|
||||||
|
empty: false,
|
||||||
}}
|
}}
|
||||||
data-rows={rows}
|
data-rows={rows}
|
||||||
data-cols={cols}
|
data-cols={cols}
|
||||||
|
@ -46,55 +56,18 @@
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<slot />
|
|
||||||
|
<!-- Only render the slot if not empty, as we don't want the placeholder -->
|
||||||
|
{#if !empty}
|
||||||
|
<slot />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/*
|
|
||||||
Ensure all children of containers which are top level children of
|
|
||||||
grids do not overflow
|
|
||||||
*/
|
|
||||||
:global(.grid > .component > .valid-container > .component > *) {
|
|
||||||
max-height: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure all top level children have some grid styles set */
|
|
||||||
:global(.grid > .component > *) {
|
|
||||||
overflow: hidden;
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
max-height: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
|
|
||||||
/* On desktop, use desktop metadata and fall back to mobile */
|
|
||||||
--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, 2));
|
|
||||||
--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, 2));
|
|
||||||
|
|
||||||
/* Ensure grid metadata falls within limits */
|
|
||||||
grid-column-start: min(max(1, var(--col-start)), var(--cols)) !important;
|
|
||||||
grid-column-end: min(
|
|
||||||
max(2, var(--col-end)),
|
|
||||||
calc(var(--cols) + 1)
|
|
||||||
) !important;
|
|
||||||
grid-row-start: min(max(1, var(--row-start)), var(--rows)) !important;
|
|
||||||
grid-row-end: min(max(2, var(--row-end)), calc(var(--rows) + 1)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* On mobile, use mobile metadata and fall back to desktop */
|
|
||||||
:global(.grid.mobile > .component > *) {
|
|
||||||
--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, 2));
|
|
||||||
--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, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 400px;
|
height: 400px;
|
||||||
|
gap: 0;
|
||||||
}
|
}
|
||||||
.grid,
|
.grid,
|
||||||
.underlay {
|
.underlay {
|
||||||
|
@ -118,4 +91,36 @@
|
||||||
.placeholder {
|
.placeholder {
|
||||||
background-color: var(--spectrum-global-color-gray-100);
|
background-color: var(--spectrum-global-color-gray-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Ensure all top level children have grid styles applied */
|
||||||
|
.grid :global(> .component) {
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
|
/* On desktop, use desktop metadata and fall back to mobile */
|
||||||
|
--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, 2));
|
||||||
|
--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, 2));
|
||||||
|
|
||||||
|
/* Ensure grid metadata falls within limits */
|
||||||
|
grid-column-start: min(max(1, var(--col-start)), var(--cols)) !important;
|
||||||
|
grid-column-end: min(
|
||||||
|
max(2, var(--col-end)),
|
||||||
|
calc(var(--cols) + 1)
|
||||||
|
) !important;
|
||||||
|
grid-row-start: min(max(1, var(--row-start)), var(--rows)) !important;
|
||||||
|
grid-row-end: min(max(2, var(--row-end)), calc(var(--rows) + 1)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* On mobile, use mobile metadata and fall back to desktop */
|
||||||
|
.grid.mobile :global(> .component) {
|
||||||
|
--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, 2));
|
||||||
|
--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, 2));
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -200,7 +200,7 @@
|
||||||
const domGrid = getDOMNode(dragInfo.gridId)
|
const domGrid = getDOMNode(dragInfo.gridId)
|
||||||
const gridCols = parseInt(domGrid.dataset.cols)
|
const gridCols = parseInt(domGrid.dataset.cols)
|
||||||
const gridRows = parseInt(domGrid.dataset.rows)
|
const gridRows = parseInt(domGrid.dataset.rows)
|
||||||
const domNode = getDOMNode(dragInfo.id)
|
const domNode = getDOMNode(dragInfo.id)?.parentNode
|
||||||
const styles = window.getComputedStyle(domNode)
|
const styles = window.getComputedStyle(domNode)
|
||||||
if (domGrid) {
|
if (domGrid) {
|
||||||
// Util to get the current grid CSS variable for this device. If unset,
|
// Util to get the current grid CSS variable for this device. If unset,
|
||||||
|
@ -237,7 +237,7 @@
|
||||||
const stopDragging = async () => {
|
const stopDragging = async () => {
|
||||||
// Save changes
|
// Save changes
|
||||||
if ($gridStyles) {
|
if ($gridStyles) {
|
||||||
await builderStore.actions.updateStyles($gridStyles, dragInfo.id)
|
await builderStore.actions.updateMetaStyles($gridStyles, dragInfo.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset listener
|
// Reset listener
|
||||||
|
|
|
@ -124,9 +124,8 @@
|
||||||
|
|
||||||
// Extract valid children
|
// Extract valid children
|
||||||
// Sanity limit of 100 active indicators
|
// Sanity limit of 100 active indicators
|
||||||
const children = Array.from(
|
const className = nextState.insideGrid ? componentId : `${componentId}-dom`
|
||||||
document.getElementsByClassName(`${componentId}-dom`)
|
const children = Array.from(document.getElementsByClassName(className))
|
||||||
)
|
|
||||||
.filter(x => x != null)
|
.filter(x => x != null)
|
||||||
.slice(0, 100)
|
.slice(0, 100)
|
||||||
|
|
||||||
|
|
|
@ -40,8 +40,11 @@ const createBuilderStore = () => {
|
||||||
updateProp: (prop, value) => {
|
updateProp: (prop, value) => {
|
||||||
eventStore.actions.dispatchEvent("update-prop", { prop, value })
|
eventStore.actions.dispatchEvent("update-prop", { prop, value })
|
||||||
},
|
},
|
||||||
updateStyles: async (styles, id) => {
|
updateMetaStyles: async (styles, id) => {
|
||||||
await eventStore.actions.dispatchEvent("update-styles", { styles, id })
|
await eventStore.actions.dispatchEvent("update-meta-styles", {
|
||||||
|
styles,
|
||||||
|
id,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
keyDown: (key, ctrlKey) => {
|
keyDown: (key, ctrlKey) => {
|
||||||
eventStore.actions.dispatchEvent("key-down", { key, ctrlKey })
|
eventStore.actions.dispatchEvent("key-down", { key, ctrlKey })
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { builderStore } from "stores"
|
||||||
/**
|
/**
|
||||||
* Helper to build a CSS string from a style object.
|
* Helper to build a CSS string from a style object.
|
||||||
*/
|
*/
|
||||||
const buildStyleString = (styleObject, customStyles) => {
|
export const buildStyleString = (styleObject, customStyles) => {
|
||||||
let str = ""
|
let str = ""
|
||||||
Object.entries(styleObject || {}).forEach(([style, value]) => {
|
Object.entries(styleObject || {}).forEach(([style, value]) => {
|
||||||
if (style && value != null) {
|
if (style && value != null) {
|
||||||
|
|
Loading…
Reference in New Issue