Update builder preview to be interactive and improve builder preview experience

This commit is contained in:
Andrew Kingston 2021-01-27 15:52:12 +00:00
parent f059739d3d
commit 6631fe2af8
13 changed files with 81 additions and 166 deletions

View File

@ -11,9 +11,6 @@
*, *:before, *:after { *, *:before, *:after {
box-sizing: border-box; box-sizing: border-box;
} }
* {
pointer-events: none;
}
</style> </style>
<script src='/assets/budibase-client.js'></script> <script src='/assets/budibase-client.js'></script>
<script> <script>

View File

@ -120,7 +120,7 @@
value={componentInstance[setting.key] ?? componentInstance[setting.key]?.defaultValue} value={componentInstance[setting.key] ?? componentInstance[setting.key]?.defaultValue}
{componentInstance} {componentInstance}
onChange={val => onChange(setting.key, val)} onChange={val => onChange(setting.key, val)}
props={{ options: setting.options }} /> props={{ options: setting.options, placeholder: setting.placeholder }} />
{/if} {/if}
{/each} {/each}
{:else} {:else}

View File

@ -9,7 +9,6 @@ export const layout = [
key: "display", key: "display",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "Block", value: "block" }, { label: "Block", value: "block" },
{ label: "Inline Block", value: "inline-block" }, { label: "Inline Block", value: "inline-block" },
{ label: "Flex", value: "flex" }, { label: "Flex", value: "flex" },
@ -37,7 +36,6 @@ export const layout = [
key: "justify-content", key: "justify-content",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "Flex Start", value: "flex-start" }, { label: "Flex Start", value: "flex-start" },
{ label: "Flex End", value: "flex-end" }, { label: "Flex End", value: "flex-end" },
{ label: "Center", value: "center" }, { label: "Center", value: "center" },
@ -51,7 +49,6 @@ export const layout = [
key: "align-items", key: "align-items",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "Flex Start", value: "flex-start" }, { label: "Flex Start", value: "flex-start" },
{ label: "Flex End", value: "flex-end" }, { label: "Flex End", value: "flex-end" },
{ label: "Center", value: "center" }, { label: "Center", value: "center" },
@ -64,7 +61,6 @@ export const layout = [
key: "flex-wrap", key: "flex-wrap",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "Wrap", value: "wrap" }, { label: "Wrap", value: "wrap" },
{ label: "No wrap", value: "nowrap" }, { label: "No wrap", value: "nowrap" },
], ],
@ -74,7 +70,6 @@ export const layout = [
key: "gap", key: "gap",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" }, { label: "None", value: "0px" },
{ label: "4px", value: "4px" }, { label: "4px", value: "4px" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
@ -93,7 +88,6 @@ export const margin = [
key: "margin", key: "margin",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" }, { label: "None", value: "0px" },
{ label: "4px", value: "4px" }, { label: "4px", value: "4px" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
@ -113,7 +107,6 @@ export const margin = [
key: "margin-top", key: "margin-top",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" }, { label: "None", value: "0px" },
{ label: "4px", value: "4px" }, { label: "4px", value: "4px" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
@ -133,7 +126,6 @@ export const margin = [
key: "margin-right", key: "margin-right",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" }, { label: "None", value: "0px" },
{ label: "4px", value: "4px" }, { label: "4px", value: "4px" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
@ -153,7 +145,6 @@ export const margin = [
key: "margin-bottom", key: "margin-bottom",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" }, { label: "None", value: "0px" },
{ label: "4px", value: "4px" }, { label: "4px", value: "4px" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
@ -173,7 +164,6 @@ export const margin = [
key: "margin-left", key: "margin-left",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" }, { label: "None", value: "0px" },
{ label: "4px", value: "4px" }, { label: "4px", value: "4px" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
@ -196,7 +186,6 @@ export const padding = [
key: "padding", key: "padding",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" }, { label: "None", value: "0px" },
{ label: "4px", value: "4px" }, { label: "4px", value: "4px" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
@ -214,7 +203,6 @@ export const padding = [
key: "padding-top", key: "padding-top",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" }, { label: "None", value: "0px" },
{ label: "4px", value: "4px" }, { label: "4px", value: "4px" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
@ -232,7 +220,6 @@ export const padding = [
key: "padding-right", key: "padding-right",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" }, { label: "None", value: "0px" },
{ label: "4px", value: "4px" }, { label: "4px", value: "4px" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
@ -250,7 +237,6 @@ export const padding = [
key: "padding-bottom", key: "padding-bottom",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" }, { label: "None", value: "0px" },
{ label: "4px", value: "4px" }, { label: "4px", value: "4px" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
@ -268,7 +254,6 @@ export const padding = [
key: "padding-left", key: "padding-left",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" }, { label: "None", value: "0px" },
{ label: "4px", value: "4px" }, { label: "4px", value: "4px" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
@ -289,7 +274,6 @@ export const size = [
key: "flex", key: "flex",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "Shrink", value: "0 1 auto" }, { label: "Shrink", value: "0 1 auto" },
{ label: "Grow", value: "1 1 auto" }, { label: "Grow", value: "1 1 auto" },
], ],
@ -338,7 +322,6 @@ export const position = [
key: "position", key: "position",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "Static", value: "static" }, { label: "Static", value: "static" },
{ label: "Relative", value: "relative" }, { label: "Relative", value: "relative" },
{ label: "Fixed", value: "fixed" }, { label: "Fixed", value: "fixed" },
@ -375,7 +358,6 @@ export const position = [
key: "z-index", key: "z-index",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "-9999", value: "-9999" }, { label: "-9999", value: "-9999" },
{ label: "-3", value: "-3" }, { label: "-3", value: "-3" },
{ label: "-2", value: "-2" }, { label: "-2", value: "-2" },
@ -395,7 +377,6 @@ export const typography = [
key: "font-family", key: "font-family",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "Arial", value: "Arial" }, { label: "Arial", value: "Arial" },
{ label: "Arial Black", value: "Arial Black" }, { label: "Arial Black", value: "Arial Black" },
{ label: "Cursive", value: "Cursive" }, { label: "Cursive", value: "Cursive" },
@ -418,7 +399,6 @@ export const typography = [
key: "font-weight", key: "font-weight",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "200", value: "200" }, { label: "200", value: "200" },
{ label: "300", value: "300" }, { label: "300", value: "300" },
{ label: "400", value: "400" }, { label: "400", value: "400" },
@ -434,7 +414,6 @@ export const typography = [
key: "font-size", key: "font-size",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
{ label: "10px", value: "10px" }, { label: "10px", value: "10px" },
{ label: "12px", value: "12px" }, { label: "12px", value: "12px" },
@ -454,7 +433,6 @@ export const typography = [
key: "line-height", key: "line-height",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "1", value: "1" }, { label: "1", value: "1" },
{ label: "1.25", value: "1.25" }, { label: "1.25", value: "1.25" },
{ label: "1.5", value: "1.5" }, { label: "1.5", value: "1.5" },
@ -496,7 +474,6 @@ export const typography = [
key: "text-decoration-line", key: "text-decoration-line",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "Underline", value: "underline" }, { label: "Underline", value: "underline" },
{ label: "Overline", value: "overline" }, { label: "Overline", value: "overline" },
{ label: "Line-through", value: "line-through" }, { label: "Line-through", value: "line-through" },
@ -516,7 +493,6 @@ export const background = [
key: "background-image", key: "background-image",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "none" }, { label: "None", value: "none" },
{ {
label: "Warm Flame", label: "Warm Flame",
@ -603,7 +579,6 @@ export const border = [
key: "border-radius", key: "border-radius",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0" }, { label: "None", value: "0" },
{ label: "X Small", value: "0.125rem" }, { label: "X Small", value: "0.125rem" },
{ label: "Small", value: "0.25rem" }, { label: "Small", value: "0.25rem" },
@ -619,7 +594,6 @@ export const border = [
key: "border-width", key: "border-width",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0" }, { label: "None", value: "0" },
{ label: "X Small", value: "0.5px" }, { label: "X Small", value: "0.5px" },
{ label: "Small", value: "1px" }, { label: "Small", value: "1px" },
@ -638,7 +612,6 @@ export const border = [
key: "border-style", key: "border-style",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "none" }, { label: "None", value: "none" },
{ label: "Hidden", value: "hidden" }, { label: "Hidden", value: "hidden" },
{ label: "Dotted", value: "dotted" }, { label: "Dotted", value: "dotted" },
@ -659,7 +632,6 @@ export const effects = [
key: "opacity", key: "opacity",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "0", value: "0" }, { label: "0", value: "0" },
{ label: "0.2", value: "0.2" }, { label: "0.2", value: "0.2" },
{ label: "0.4", value: "0.4" }, { label: "0.4", value: "0.4" },
@ -673,7 +645,6 @@ export const effects = [
key: "transform", key: "transform",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0" }, { label: "None", value: "0" },
{ label: "45 deg", value: "rotate(45deg)" }, { label: "45 deg", value: "rotate(45deg)" },
{ label: "90 deg", value: "rotate(90deg)" }, { label: "90 deg", value: "rotate(90deg)" },
@ -690,7 +661,6 @@ export const effects = [
key: "box-shadow", key: "box-shadow",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "none" }, { label: "None", value: "none" },
{ label: "X Small", value: "0 1px 2px 0 rgba(0, 0, 0, 0.05)" }, { label: "X Small", value: "0 1px 2px 0 rgba(0, 0, 0, 0.05)" },
{ {
@ -723,7 +693,6 @@ export const transitions = [
key: "transition-property", key: "transition-property",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "none" }, { label: "None", value: "none" },
{ label: "All", value: "all" }, { label: "All", value: "all" },
{ label: "Background Color", value: "background color" }, { label: "Background Color", value: "background color" },
@ -745,7 +714,6 @@ export const transitions = [
control: OptionSelect, control: OptionSelect,
placeholder: "sec", placeholder: "sec",
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "0.4s", value: "0.4s" }, { label: "0.4s", value: "0.4s" },
{ label: "0.6s", value: "0.6s" }, { label: "0.6s", value: "0.6s" },
{ label: "0.8s", value: "0.8s" }, { label: "0.8s", value: "0.8s" },
@ -759,7 +727,6 @@ export const transitions = [
key: "transition-timing-function", key: "transition-timing-function",
control: OptionSelect, control: OptionSelect,
options: [ options: [
{ label: "Choose option", value: "" },
{ label: "Linear", value: "linear" }, { label: "Linear", value: "linear" },
{ label: "Ease", value: "ease" }, { label: "Ease", value: "ease" },
{ label: "Ease in", value: "ease-in" }, { label: "Ease in", value: "ease-in" },

View File

@ -2,9 +2,14 @@
import { writable } from "svelte/store" import { writable } from "svelte/store"
import { setContext, onMount } from "svelte" import { setContext, onMount } from "svelte"
import Component from "./Component.svelte" import Component from "./Component.svelte"
import NotificationDisplay from './NotificationDisplay.svelte' import NotificationDisplay from "./NotificationDisplay.svelte"
import SDK from "../sdk" import SDK from "../sdk"
import { createDataStore, initialise, screenStore, notificationStore } from "../store" import {
createDataStore,
initialise,
screenStore,
builderStore,
} from "../store"
// Provide contexts // Provide contexts
setContext("sdk", SDK) setContext("sdk", SDK)
@ -23,5 +28,5 @@
{#if loaded && $screenStore.activeLayout} {#if loaded && $screenStore.activeLayout}
<Component definition={$screenStore.activeLayout.props} /> <Component definition={$screenStore.activeLayout.props} />
<NotificationDisplay />
{/if} {/if}
<NotificationDisplay />

View File

@ -1,19 +1,23 @@
<script> <script>
import { getContext, setContext } from "svelte" import { getContext, setContext } from "svelte"
import { writable } from "svelte/store" import { writable, get } from "svelte/store"
import * as ComponentLibrary from "@budibase/standard-components" import * as ComponentLibrary from "@budibase/standard-components"
import Router from "./Router.svelte" import Router from "./Router.svelte"
import { enrichProps, propsAreSame } from "../utils/componentProps" import { enrichProps, propsAreSame } from "../utils/componentProps"
import { bindingStore, builderStore } from "../store" import { bindingStore, builderStore } from "../store"
import { hashString } from "../utils/hash"
export let definition = {} export let definition = {}
let enrichedProps let enrichedProps
let componentProps let componentProps
// Props are hashed when inside the builder preview and used as a key, so that
// components fully remount whenever any props change
let propsHash = 0
// Get contexts // Get contexts
const dataContext = getContext("data") const dataContext = getContext("data")
const screenslotContext = getContext("screenslot")
// Create component context // Create component context
const componentStore = writable({}) const componentStore = writable({})
@ -27,16 +31,11 @@
$: updateProps(enrichedProps) $: updateProps(enrichedProps)
$: styles = definition._styles $: styles = definition._styles
// Allow component selection in the builder preview if we're previewing a
// layout, or we're preview a screen and we're inside the screenslot
$: allowSelection =
$builderStore.previewType === "layout" || screenslotContext
// Update component context // Update component context
$: componentStore.set({ $: componentStore.set({
id, id,
children: children.length, children: children.length,
styles: { ...styles, id, allowSelection }, styles: { ...styles, id },
}) })
// Updates the component props. // Updates the component props.
@ -46,14 +45,20 @@
if (!props) { if (!props) {
return return
} }
let propsChanged = false
if (!componentProps) { if (!componentProps) {
componentProps = {} componentProps = {}
propsChanged = true
} }
Object.keys(props).forEach(key => { Object.keys(props).forEach(key => {
if (!propsAreSame(props[key], componentProps[key])) { if (!propsAreSame(props[key], componentProps[key])) {
propsChanged = true
componentProps[key] = props[key] componentProps[key] = props[key]
} }
}) })
if (get(builderStore).inBuilder && propsChanged) {
propsHash = hashString(JSON.stringify(componentProps))
}
} }
// Gets the component constructor for the specified component // Gets the component constructor for the specified component
@ -70,22 +75,16 @@
const enrichComponentProps = async (definition, context, bindingStore) => { const enrichComponentProps = async (definition, context, bindingStore) => {
enrichedProps = await enrichProps(definition, context, bindingStore) enrichedProps = await enrichProps(definition, context, bindingStore)
} }
// Returns a unique key to let svelte know when to remount components.
// If a component is selected we want to remount it every time any props
// change.
const getChildKey = childId => {
const selected = childId === $builderStore.selectedComponentId
return selected ? `${childId}-${$builderStore.previewId}` : childId
}
</script> </script>
{#if constructor && componentProps} {#if constructor && componentProps}
{#key propsHash}
<svelte:component this={constructor} {...componentProps}> <svelte:component this={constructor} {...componentProps}>
{#if children.length} {#if children.length}
{#each children as child (getChildKey(child._id))} {#each children as child (child._id)}
<svelte:self definition={child} /> <svelte:self definition={child} />
{/each} {/each}
{/if} {/if}
</svelte:component> </svelte:component>
{/key}
{/if} {/if}

View File

@ -7,9 +7,6 @@
const { styleable } = getContext("sdk") const { styleable } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
// Set context flag so components know that we're now inside the screenslot
setContext("screenslot", true)
// Only wrap this as an array to take advantage of svelte keying, // Only wrap this as an array to take advantage of svelte keying,
// to ensure the svelte-spa-router is fully remounted when route config // to ensure the svelte-spa-router is fully remounted when route config
// changes // changes

View File

@ -0,0 +1,12 @@
export const hashString = str => {
if (!str) {
return 0
}
let hash = 0
for (let i = 0; i < str.length; i++) {
let char = str.charCodeAt(i)
hash = (hash << 5) - hash + char
hash = hash & hash // Convert to 32bit integer
}
return hash
}

View File

@ -1,9 +1,6 @@
import { get } from "svelte/store" import { get } from "svelte/store"
import { builderStore } from "../store" import { builderStore } from "../store"
const selectedComponentWidth = 2
const selectedComponentColor = "#4285f4"
/** /**
* Helper to build a CSS string from a style object. * Helper to build a CSS string from a style object.
*/ */
@ -23,24 +20,14 @@ const buildStyleString = (styleObject, customStyles) => {
* events for any selectable components (overriding the blanket ban on pointer * events for any selectable components (overriding the blanket ban on pointer
* events in the iframe HTML). * events in the iframe HTML).
*/ */
const addBuilderPreviewStyles = (styleString, componentId, selectable) => { const addBuilderPreviewStyles = (node, styleString, componentId) => {
let str = styleString if (componentId === get(builderStore).selectedComponentId) {
const style = window.getComputedStyle(node)
// Apply extra styles if we're in the builder preview const property = style?.display === "table-row" ? "outline" : "border"
const state = get(builderStore) return styleString + `;${property}: 2px solid #4285f4 !important;`
if (state.inBuilder) { } else {
// Allow pointer events and always enable cursor return styleString
if (selectable) {
str += ";pointer-events: all !important; cursor: pointer !important;"
} }
// Highlighted selected element
if (componentId === state.selectedComponentId) {
str += `;border: ${selectedComponentWidth}px solid ${selectedComponentColor} !important;`
}
}
return str
} }
/** /**
@ -52,17 +39,9 @@ export const styleable = (node, styles = {}) => {
let applyHoverStyles let applyHoverStyles
let selectComponent let selectComponent
// Kill JS even bubbling
const blockEvent = event => {
event.preventDefault()
event.stopPropagation()
return false
}
// Creates event listeners and applies initial styles // Creates event listeners and applies initial styles
const setupStyles = (newStyles = {}) => { const setupStyles = (newStyles = {}) => {
const componentId = newStyles.id const componentId = newStyles.id
const selectable = !!newStyles.allowSelection
const customStyles = newStyles.custom || "" const customStyles = newStyles.custom || ""
const normalStyles = newStyles.normal || {} const normalStyles = newStyles.normal || {}
const hoverStyles = { const hoverStyles = {
@ -70,10 +49,9 @@ export const styleable = (node, styles = {}) => {
...(newStyles.hover || {}), ...(newStyles.hover || {}),
} }
// Applies a style string to a DOM node, enriching it for the builder // Applies a style string to a DOM node
// preview
const applyStyles = styleString => { const applyStyles = styleString => {
node.style = addBuilderPreviewStyles(styleString, componentId, selectable) node.style = addBuilderPreviewStyles(node, styleString, componentId)
node.dataset.componentId = componentId node.dataset.componentId = componentId
} }
@ -91,7 +69,9 @@ export const styleable = (node, styles = {}) => {
// builder preview // builder preview
selectComponent = event => { selectComponent = event => {
builderStore.actions.selectComponent(componentId) builderStore.actions.selectComponent(componentId)
return blockEvent(event) event.preventDefault()
event.stopPropagation()
return false
} }
// Add listeners to toggle hover styles // Add listeners to toggle hover styles
@ -101,10 +81,6 @@ export const styleable = (node, styles = {}) => {
// Add builder preview click listener // Add builder preview click listener
if (get(builderStore).inBuilder) { if (get(builderStore).inBuilder) {
node.addEventListener("click", selectComponent, false) node.addEventListener("click", selectComponent, false)
// Kill other interaction events
node.addEventListener("mousedown", blockEvent)
node.addEventListener("mouseup", blockEvent)
} }
// Apply initial normal styles // Apply initial normal styles
@ -119,8 +95,6 @@ export const styleable = (node, styles = {}) => {
// Remove builder preview click listener // Remove builder preview click listener
if (get(builderStore).inBuilder) { if (get(builderStore).inBuilder) {
node.removeEventListener("click", selectComponent) node.removeEventListener("click", selectComponent)
node.removeEventListener("mousedown", blockEvent)
node.removeEventListener("mouseup", blockEvent)
} }
} }

View File

@ -1,7 +1,7 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
const { authStore, styleable } = getContext("sdk") const { authStore, styleable, builderStore } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
export let buttonText = "Log In" export let buttonText = "Log In"
@ -23,6 +23,9 @@
} }
const login = async () => { const login = async () => {
if ($builderStore.inBuilder) {
return
}
loading = true loading = true
await authStore.actions.logIn({ email, password }) await authStore.actions.logIn({ email, password })
loading = false loading = false

View File

@ -1,12 +1,15 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
const { authStore, linkable, styleable } = getContext("sdk") const { authStore, linkable, styleable, builderStore } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
export let logoUrl export let logoUrl
const logOut = async () => { const logOut = async () => {
if ($builderStore.inBuilder) {
return
}
await authStore.actions.logOut() await authStore.actions.logOut()
} }
</script> </script>

View File

@ -92,16 +92,4 @@
{/each} {/each}
</ul> </ul>
</div> </div>
{#if $fieldState.error}
<div class="error">{$fieldState.error}</div>
{/if}
</SpectrumField> </SpectrumField>
<style>
.error {
color: var(
--spectrum-semantic-negative-color-default,
var(--spectrum-global-color-red-500)
) !important;
}
</style>

View File

@ -10,7 +10,7 @@
const component = getContext("component") const component = getContext("component")
const { labelPosition, formApi } = formContext || {} const { labelPosition, formApi } = formContext || {}
const formField = formApi?.registerField(field) ?? {} const formField = formApi?.registerField(field) ?? {}
const { fieldId } = formField const { fieldId, fieldState } = formField
$: labelPositionClass = $: labelPositionClass =
labelPosition === "top" ? "" : `spectrum-FieldLabel--${labelPosition}` labelPosition === "top" ? "" : `spectrum-FieldLabel--${labelPosition}`
@ -31,6 +31,20 @@
{/if} {/if}
<div class="spectrum-Form-itemField"> <div class="spectrum-Form-itemField">
<slot /> <slot />
{#if $fieldState.error}
<div class="error">{$fieldState.error}</div>
{/if}
</div> </div>
</div> </div>
{/if} {/if}
<style>
.error {
color: var(
--spectrum-semantic-negative-color-default,
var(--spectrum-global-color-red-500)
);
font-size: var(--spectrum-global-dimension-font-size-75);
margin-top: var(--spectrum-global-dimension-size-75);
}
</style>

View File

@ -1,7 +1,5 @@
<script> <script>
import "@spectrum-css/textfield/dist/index-vars.css" import "@spectrum-css/textfield/dist/index-vars.css"
import "@spectrum-css/actionbutton/dist/index-vars.css"
import "@spectrum-css/stepper/dist/index-vars.css"
import { getContext } from "svelte" import { getContext } from "svelte"
import SpectrumField from "./SpectrumField.svelte" import SpectrumField from "./SpectrumField.svelte"
@ -15,8 +13,6 @@
const formField = formApi?.registerField(field) ?? {} const formField = formApi?.registerField(field) ?? {}
const { fieldApi, fieldState } = formField const { fieldApi, fieldState } = formField
$: numeric = type === "number"
// Update value on blur only // Update value on blur only
const onBlur = event => { const onBlur = event => {
fieldApi.setValue(event.target.value) fieldApi.setValue(event.target.value)
@ -24,11 +20,8 @@
</script> </script>
<SpectrumField {label} {field}> <SpectrumField {label} {field}>
<div class:spectrum-Stepper={type === 'number'}> <div>
<div <div class="spectrum-Textfield" class:is-invalid={!$fieldState.valid}>
class="spectrum-Textfield"
class:spectrum-Stepper-textfield={numeric}
class:is-invalid={!$fieldState.valid}>
{#if !$fieldState.valid} {#if !$fieldState.valid}
<svg <svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon" class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
@ -43,44 +36,7 @@
placeholder={placeholder || ''} placeholder={placeholder || ''}
on:blur={onBlur} on:blur={onBlur}
{type} {type}
class="spectrum-Textfield-input" class="spectrum-Textfield-input" />
class:spectrum-Stepper-input={numeric} />
</div> </div>
{#if numeric}
<span class="spectrum-Stepper-buttons">
<button
class="spectrum-ActionButton spectrum-ActionButton--sizeM spectrum-Stepper-stepUp"
tabindex="-1">
<svg
class="spectrum-Icon spectrum-UIIcon-ChevronUp75 spectrum-Stepper-stepUpIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Chevron75" />
</svg>
</button>
<button
class="spectrum-ActionButton spectrum-ActionButton--sizeM spectrum-Stepper-stepDown"
tabindex="-1">
<svg
class="spectrum-Icon spectrum-UIIcon-ChevronDown75 spectrum-Stepper-stepDownIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Chevron75" />
</svg>
</button>
</span>
{/if}
{#if $fieldState.error}
<div class="error">{$fieldState.error}</div>
{/if}
</div> </div>
</SpectrumField> </SpectrumField>
<style>
.error {
color: var(
--spectrum-semantic-negative-color-default,
var(--spectrum-global-color-red-500)
) !important;
}
</style>