Add proper hot reloading of app preview when styles change
This commit is contained in:
parent
99c167c54f
commit
18a0f3888b
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { getContext, setContext } from "svelte"
|
||||
import { writable } from "svelte/store"
|
||||
import * as ComponentLibrary from "@budibase/standard-components"
|
||||
import Router from "./Router.svelte"
|
||||
|
||||
|
@ -28,21 +29,23 @@
|
|||
}
|
||||
|
||||
// Extract component definition info
|
||||
const componentName = extractComponentName(definition._component)
|
||||
const constructor = getComponentConstructor(componentName)
|
||||
const componentProps = extractValidProps(definition)
|
||||
const dataContext = getContext("data")
|
||||
const enrichedProps = dataContext.actions.enrichDataBindings(componentProps)
|
||||
const children = definition._children
|
||||
$: componentName = extractComponentName(definition._component)
|
||||
$: constructor = getComponentConstructor(componentName)
|
||||
$: componentProps = extractValidProps(definition)
|
||||
$: dataContext = getContext("data")
|
||||
$: enrichedProps = dataContext.actions.enrichDataBindings(componentProps)
|
||||
$: children = definition._children
|
||||
|
||||
// Set contexts to be consumed by component
|
||||
setContext("style", { ...definition._styles, id: definition._id })
|
||||
// Set observable style context
|
||||
const styleStore = writable({})
|
||||
setContext("style", styleStore)
|
||||
$: styleStore.set({ ...definition._styles, id: definition._id })
|
||||
</script>
|
||||
|
||||
{#if constructor}
|
||||
<svelte:component this={constructor} {...enrichedProps}>
|
||||
{#if children && children.length}
|
||||
{#each children as child}
|
||||
{#each children as child (child._id)}
|
||||
<svelte:self definition={child} />
|
||||
{/each}
|
||||
{/if}
|
||||
|
|
|
@ -1,25 +1,19 @@
|
|||
<script>
|
||||
import { onMount, getContext, setContext } from "svelte"
|
||||
import { getContext, setContext } from "svelte"
|
||||
import { createDataContextStore } from "../store"
|
||||
|
||||
export let row
|
||||
|
||||
// Get current contexts
|
||||
const dataContext = getContext("data")
|
||||
const { id } = getContext("style")
|
||||
const styles = getContext("style")
|
||||
|
||||
// Clone current context to this context
|
||||
const newDataContext = createDataContextStore($dataContext)
|
||||
setContext("data", newDataContext)
|
||||
|
||||
// Add additional layer to context
|
||||
let loaded = false
|
||||
onMount(() => {
|
||||
newDataContext.actions.addContext(row, id)
|
||||
loaded = true
|
||||
})
|
||||
$: newDataContext.actions.addContext(row, $styles.id)
|
||||
</script>
|
||||
|
||||
{#if loaded}
|
||||
<slot />
|
||||
{/if}
|
||||
<slot />
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
import Router from "svelte-spa-router"
|
||||
import { routeStore, screenStore } from "../store"
|
||||
import { routeStore } from "../store"
|
||||
import Screen from "./Screen.svelte"
|
||||
|
||||
const { styleable } = getContext("sdk")
|
||||
|
@ -26,7 +26,7 @@
|
|||
</script>
|
||||
|
||||
{#if routerConfig}
|
||||
<div use:styleable={styles}>
|
||||
<div use:styleable={$styles}>
|
||||
<Router on:routeLoading={onRouteLoading} routes={routerConfig} />
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -11,8 +11,11 @@
|
|||
|
||||
// Redirect to home page if no matching route
|
||||
$: screenDefinition == null && routeStore.actions.navigate("/")
|
||||
|
||||
// Make a screen array so we can use keying to properly re-render each screen
|
||||
$: screens = screenDefinition ? [screenDefinition] : []
|
||||
</script>
|
||||
|
||||
{#if screenDefinition}
|
||||
<Component definition={screenDefinition} />
|
||||
{/if}
|
||||
{#each screens as screen (screen._id)}
|
||||
<Component definition={screen} />
|
||||
{/each}
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
import ClientApp from "./components/ClientApp.svelte"
|
||||
import { builderStore } from "./store"
|
||||
|
||||
let app
|
||||
|
||||
const loadBudibase = () => {
|
||||
// Destroy old app if one exists
|
||||
if (app) {
|
||||
app.$destroy()
|
||||
}
|
||||
// Create new app
|
||||
app = new ClientApp({
|
||||
target: window.document.body,
|
||||
// Update builder store with any builder flags
|
||||
builderStore.set({
|
||||
inBuilder: !!window["##BUDIBASE_IN_BUILDER##"],
|
||||
page: window["##BUDIBASE_PREVIEW_PAGE##"],
|
||||
screen: window["##BUDIBASE_PREVIEW_SCREEN##"],
|
||||
})
|
||||
|
||||
// Create app if one hasn't been created yet
|
||||
if (!app) {
|
||||
app = new ClientApp({
|
||||
target: window.document.body,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Attach to window so the HTML template can call this when it loads
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { writable } from "svelte/store"
|
||||
|
||||
const createBuilderStore = () => {
|
||||
const initialState = {
|
||||
inBuilder: false,
|
||||
page: null,
|
||||
screen: null,
|
||||
}
|
||||
return writable(initialState)
|
||||
}
|
||||
|
||||
export const builderStore = createBuilderStore()
|
|
@ -2,3 +2,4 @@ export { authStore } from "./auth"
|
|||
export { routeStore } from "./routes"
|
||||
export { screenStore } from "./screens"
|
||||
export { createDataContextStore } from "./dataContext"
|
||||
export { builderStore } from "./builder"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { writable, derived } from "svelte/store"
|
||||
import { routeStore } from "./routes"
|
||||
import { builderStore } from "./builder"
|
||||
import * as API from "../api"
|
||||
import { getAppId } from "../utils"
|
||||
|
||||
|
@ -8,36 +9,37 @@ const createScreenStore = () => {
|
|||
screens: [],
|
||||
page: {},
|
||||
})
|
||||
const store = derived([config, routeStore], ([$config, $routeStore]) => {
|
||||
const { screens, page } = $config
|
||||
const activeScreen =
|
||||
screens.length === 1
|
||||
? screens[0]
|
||||
: screens.find(
|
||||
const store = derived(
|
||||
[config, routeStore, builderStore],
|
||||
([$config, $routeStore, $builderStore]) => {
|
||||
let page
|
||||
let activeScreen
|
||||
if ($builderStore.inBuilder) {
|
||||
// Use builder defined definitions if inside the builder preview
|
||||
page = $builderStore.page
|
||||
activeScreen = $builderStore.screen
|
||||
} else {
|
||||
// Otherwise find the correct screen by matching the current route
|
||||
page = $config.page
|
||||
const { screens } = $config
|
||||
if (screens.length === 1) {
|
||||
activeScreen = screens[0]
|
||||
} else {
|
||||
activeScreen = screens.find(
|
||||
screen => screen.routing.route === $routeStore.activeRoute
|
||||
)
|
||||
return {
|
||||
screens,
|
||||
page,
|
||||
activeScreen,
|
||||
}
|
||||
}
|
||||
return { page, activeScreen }
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const fetchScreens = async () => {
|
||||
let screens
|
||||
let page
|
||||
const inBuilder = !!window["##BUDIBASE_IN_BUILDER##"]
|
||||
if (inBuilder) {
|
||||
// Load screen and page from the window object if in the builder
|
||||
screens = [window["##BUDIBASE_PREVIEW_SCREEN##"]]
|
||||
page = window["##BUDIBASE_PREVIEW_PAGE##"]
|
||||
} else {
|
||||
// Otherwise load from API
|
||||
const appDefinition = await API.fetchAppDefinition(getAppId())
|
||||
screens = appDefinition.screens
|
||||
page = appDefinition.page
|
||||
}
|
||||
config.set({ screens, page })
|
||||
const appDefinition = await API.fetchAppDefinition(getAppId())
|
||||
config.set({
|
||||
screens: appDefinition.screens,
|
||||
page: appDefinition.page,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import { getContext } from "svelte"
|
||||
import { get } from "svelte/store"
|
||||
|
||||
/**
|
||||
* Helper to build a CSS string from a style object
|
||||
*/
|
||||
const buildStyleString = styles => {
|
||||
let str = ""
|
||||
Object.entries(styles).forEach(([style, value]) => {
|
||||
if (style && value) {
|
||||
if (style && value != null) {
|
||||
str += `${style}: ${value}; `
|
||||
}
|
||||
})
|
||||
|
@ -12,35 +18,52 @@ const buildStyleString = styles => {
|
|||
* Svelte action to apply correct component styles.
|
||||
*/
|
||||
export const styleable = (node, styles = {}) => {
|
||||
const normalStyles = styles.normal || {}
|
||||
const hoverStyles = {
|
||||
...normalStyles,
|
||||
...styles.hover,
|
||||
let applyNormalStyles
|
||||
let applyHoverStyles
|
||||
|
||||
// Creates event listeners and applies initial styles
|
||||
const setupStyles = newStyles => {
|
||||
const normalStyles = newStyles.normal || {}
|
||||
const hoverStyles = {
|
||||
...normalStyles,
|
||||
...newStyles.hover,
|
||||
}
|
||||
|
||||
applyNormalStyles = () => {
|
||||
node.style = buildStyleString(normalStyles)
|
||||
}
|
||||
|
||||
applyHoverStyles = () => {
|
||||
node.style = buildStyleString(hoverStyles)
|
||||
}
|
||||
|
||||
// Add listeners to toggle hover styles
|
||||
node.addEventListener("mouseover", applyHoverStyles)
|
||||
node.addEventListener("mouseout", applyNormalStyles)
|
||||
node.setAttribute("data-bb-id", newStyles.id)
|
||||
|
||||
// Apply initial normal styles
|
||||
applyNormalStyles()
|
||||
}
|
||||
|
||||
function applyNormalStyles() {
|
||||
node.style = buildStyleString(normalStyles)
|
||||
// Removes the current event listeners
|
||||
const removeListeners = () => {
|
||||
node.removeEventListener("mouseover", applyHoverStyles)
|
||||
node.removeEventListener("mouseout", applyNormalStyles)
|
||||
}
|
||||
|
||||
function applyHoverStyles() {
|
||||
node.style = buildStyleString(hoverStyles)
|
||||
}
|
||||
|
||||
// Add listeners to toggle hover styles
|
||||
node.addEventListener("mouseover", applyHoverStyles)
|
||||
node.addEventListener("mouseout", applyNormalStyles)
|
||||
|
||||
// Apply normal styles initially
|
||||
applyNormalStyles()
|
||||
|
||||
// Also apply data tags so we know how to reference each component
|
||||
node.setAttribute("data-bb-id", styles.id)
|
||||
// Apply initial styles
|
||||
setupStyles(styles)
|
||||
|
||||
return {
|
||||
// Clean up event listeners when component is destroyed
|
||||
// Clean up old listeners and apply new ones on update
|
||||
update: newStyles => {
|
||||
removeListeners()
|
||||
setupStyles(newStyles)
|
||||
},
|
||||
// Clean up listeners when component is destroyed
|
||||
destroy: () => {
|
||||
node.removeEventListener("mouseover", applyHoverStyles)
|
||||
node.removeEventListener("mouseout", applyNormalStyles)
|
||||
removeListeners()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
export let text
|
||||
</script>
|
||||
|
||||
<button class="default" disabled={disabled || false} use:styleable={styles}>
|
||||
<button class="default" disabled={disabled || false} use:styleable={$styles}>
|
||||
{text}
|
||||
</button>
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
$: showImage = !!imageUrl
|
||||
</script>
|
||||
|
||||
<div use:cssVars={cssVariables} class="container" use:styleable={styles}>
|
||||
<div use:cssVars={cssVariables} class="container" use:styleable={$styles}>
|
||||
{#if showImage}<img class="image" src={imageUrl} alt="" />{/if}
|
||||
<div class="content">
|
||||
<h2 class="heading">{heading}</h2>
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
$: showImage = !!imageUrl
|
||||
</script>
|
||||
|
||||
<div use:cssVars={cssVariables} class="container" use:styleable={styles}>
|
||||
<div use:cssVars={cssVariables} class="container" use:styleable={$styles}>
|
||||
{#if showImage}<img class="image" src={imageUrl} alt="" />{/if}
|
||||
<div class="content">
|
||||
<main>
|
||||
|
|
|
@ -9,55 +9,55 @@
|
|||
</script>
|
||||
|
||||
{#if type === 'div'}
|
||||
<div use:styleable={styles}>
|
||||
<div use:styleable={$styles}>
|
||||
<slot />
|
||||
</div>
|
||||
{:else if type === 'header'}
|
||||
<header use:styleable={styles}>
|
||||
<header use:styleable={$styles}>
|
||||
<slot />
|
||||
</header>
|
||||
{:else if type === 'main'}
|
||||
<main use:styleable={styles}>
|
||||
<main use:styleable={$styles}>
|
||||
<slot />
|
||||
</main>
|
||||
{:else if type === 'footer'}
|
||||
<footer use:styleable={styles}>
|
||||
<footer use:styleable={$styles}>
|
||||
<slot />
|
||||
</footer>
|
||||
{:else if type === 'aside'}
|
||||
<aside use:styleable={styles}>
|
||||
<aside use:styleable={$styles}>
|
||||
<slot />
|
||||
</aside>
|
||||
{:else if type === 'summary'}
|
||||
<summary use:styleable={styles}>
|
||||
<summary use:styleable={$styles}>
|
||||
<slot />
|
||||
</summary>
|
||||
{:else if type === 'details'}
|
||||
<details use:styleable={styles}>
|
||||
<details use:styleable={$styles}>
|
||||
<slot />
|
||||
</details>
|
||||
{:else if type === 'article'}
|
||||
<article use:styleable={styles}>
|
||||
<article use:styleable={$styles}>
|
||||
<slot />
|
||||
</article>
|
||||
{:else if type === 'nav'}
|
||||
<nav use:styleable={styles}>
|
||||
<nav use:styleable={$styles}>
|
||||
<slot />
|
||||
</nav>
|
||||
{:else if type === 'mark'}
|
||||
<mark use:styleable={styles}>
|
||||
<mark use:styleable={$styles}>
|
||||
<slot />
|
||||
</mark>
|
||||
{:else if type === 'figure'}
|
||||
<figure use:styleable={styles}>
|
||||
<figure use:styleable={$styles}>
|
||||
<slot />
|
||||
</figure>
|
||||
{:else if type === 'figcaption'}
|
||||
<figcaption use:styleable={styles}>
|
||||
<figcaption use:styleable={$styles}>
|
||||
<slot />
|
||||
</figcaption>
|
||||
{:else if type === 'paragraph'}
|
||||
<p use:styleable={styles}>
|
||||
<p use:styleable={$styles}>
|
||||
<slot />
|
||||
</p>
|
||||
{/if}
|
||||
|
|
|
@ -14,6 +14,6 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div use:styleable={styles}>
|
||||
<div use:styleable={$styles}>
|
||||
<DatePicker {placeholder} on:change={handleChange} {value} />
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
export let embed
|
||||
</script>
|
||||
|
||||
<div use:styleable={styles}>
|
||||
<div use:styleable={$styles}>
|
||||
{@html embed}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="form-content" use:styleable={styles}>
|
||||
<div class="form-content" use:styleable={$styles}>
|
||||
<!-- <ErrorsBox errors={$store.saveRowErrors || {}} />-->
|
||||
{#each fields as field}
|
||||
<div class="form-field" class:wide>
|
||||
|
|
|
@ -10,15 +10,15 @@
|
|||
</script>
|
||||
|
||||
{#if type === 'h1'}
|
||||
<h1 class={className} use:styleable={styles}>{text}</h1>
|
||||
<h1 class={className} use:styleable={$styles}>{text}</h1>
|
||||
{:else if type === 'h2'}
|
||||
<h2 class={className} use:styleable={styles}>{text}</h2>
|
||||
<h2 class={className} use:styleable={$styles}>{text}</h2>
|
||||
{:else if type === 'h3'}
|
||||
<h3 class={className} use:styleable={styles}>{text}</h3>
|
||||
<h3 class={className} use:styleable={$styles}>{text}</h3>
|
||||
{:else if type === 'h4'}
|
||||
<h4 class={className} use:styleable={styles}>{text}</h4>
|
||||
<h4 class={className} use:styleable={$styles}>{text}</h4>
|
||||
{:else if type === 'h5'}
|
||||
<h5 class={className} use:styleable={styles}>{text}</h5>
|
||||
<h5 class={className} use:styleable={$styles}>{text}</h5>
|
||||
{:else if type === 'h6'}
|
||||
<h6 class={className} use:styleable={styles}>{text}</h6>
|
||||
<h6 class={className} use:styleable={$styles}>{text}</h6>
|
||||
{/if}
|
||||
|
|
|
@ -13,4 +13,4 @@
|
|||
<i
|
||||
style={`color: ${color};`}
|
||||
class={`${icon} ${size}`}
|
||||
use:styleable={styles} />
|
||||
use:styleable={$styles} />
|
||||
|
|
|
@ -17,4 +17,4 @@
|
|||
class={className}
|
||||
src={url}
|
||||
alt={description}
|
||||
use:styleable={styles} />
|
||||
use:styleable={$styles} />
|
||||
|
|
|
@ -18,4 +18,4 @@
|
|||
{type}
|
||||
{value}
|
||||
on:change={onchange}
|
||||
use:styleable={styles} />
|
||||
use:styleable={$styles} />
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
$: target = openInNewTab ? "_blank" : "_self"
|
||||
</script>
|
||||
|
||||
<a href={url} use:linkable {target} use:styleable={styles}>
|
||||
<a href={url} use:linkable {target} use:styleable={$styles}>
|
||||
{text}
|
||||
<slot />
|
||||
</a>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
})
|
||||
</script>
|
||||
|
||||
<div use:styleable={styles}>
|
||||
<div use:styleable={$styles}>
|
||||
{#each rows as row}
|
||||
<DataProvider {row}>
|
||||
<slot />
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="root" use:styleable={styles}>
|
||||
<div class="root" use:styleable={$styles}>
|
||||
<div class="content">
|
||||
{#if logo}
|
||||
<div class="logo-container"><img src={logo} alt="logo" /></div>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="nav" use:styleable={styles}>
|
||||
<div class="nav" use:styleable={$styles}>
|
||||
<div class="nav__top">
|
||||
<a href="/" use:linkable>
|
||||
{#if logoUrl}
|
||||
|
|
|
@ -23,6 +23,6 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div use:styleable={styles}>
|
||||
<div use:styleable={$styles}>
|
||||
<RichText bind:content={value} {options} />
|
||||
</div>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
const { styleable } = getContext("sdk")
|
||||
</script>
|
||||
|
||||
<div use:styleable={styles}>
|
||||
<div use:styleable={$styles}>
|
||||
<h1>Screen Slot</h1>
|
||||
<span>
|
||||
The screens that you create will be displayed inside this box.
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
$: showImage = !!imageUrl
|
||||
</script>
|
||||
|
||||
<div class="container" use:styleable={styles}>
|
||||
<div class="container" use:styleable={$styles}>
|
||||
<a href={destinationUrl}>
|
||||
<div class="content">
|
||||
{#if showImage}
|
||||
|
|
|
@ -12,28 +12,28 @@
|
|||
</script>
|
||||
|
||||
{#if isTag('none')}
|
||||
<span use:styleable={styles}>{text}</span>
|
||||
<span use:styleable={$styles}>{text}</span>
|
||||
{:else if isTag('bold')}
|
||||
<b class={className} use:styleable={styles}>{text}</b>
|
||||
<b class={className} use:styleable={$styles}>{text}</b>
|
||||
{:else if isTag('strong')}
|
||||
<strong class={className} use:styleable={styles}>{text}</strong>
|
||||
<strong class={className} use:styleable={$styles}>{text}</strong>
|
||||
{:else if isTag('italic')}
|
||||
<i class={className} use:styleable={styles}>{text}</i>
|
||||
<i class={className} use:styleable={$styles}>{text}</i>
|
||||
{:else if isTag('emphasis')}
|
||||
<em class={className} use:styleable={styles}>{text}</em>
|
||||
<em class={className} use:styleable={$styles}>{text}</em>
|
||||
{:else if isTag('mark')}
|
||||
<mark class={className} use:styleable={styles}>{text}</mark>
|
||||
<mark class={className} use:styleable={$styles}>{text}</mark>
|
||||
{:else if isTag('small')}
|
||||
<small class={className} use:styleable={styles}>{text}</small>
|
||||
<small class={className} use:styleable={$styles}>{text}</small>
|
||||
{:else if isTag('del')}
|
||||
<del class={className} use:styleable={styles}>{text}</del>
|
||||
<del class={className} use:styleable={$styles}>{text}</del>
|
||||
{:else if isTag('ins')}
|
||||
<ins class={className} use:styleable={styles}>{text}</ins>
|
||||
<ins class={className} use:styleable={$styles}>{text}</ins>
|
||||
{:else if isTag('sub')}
|
||||
<sub class={className} use:styleable={styles}>{text}</sub>
|
||||
<sub class={className} use:styleable={$styles}>{text}</sub>
|
||||
{:else if isTag('sup')}
|
||||
<sup class={className} use:styleable={styles}>{text}</sup>
|
||||
{:else}<span use:styleable={styles}>{text}</span>{/if}
|
||||
<sup class={className} use:styleable={$styles}>{text}</sup>
|
||||
{:else}<span use:styleable={$styles}>{text}</span>{/if}
|
||||
|
||||
<style>
|
||||
span {
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
</script>
|
||||
|
||||
{#if options}
|
||||
<div use:chart={options} use:styleable={styles} />
|
||||
<div use:chart={options} use:styleable={$styles} />
|
||||
{:else if options === false}
|
||||
<div use:styleable={styles}>Invalid chart options</div>
|
||||
<div use:styleable={$styles}>Invalid chart options</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
|
|
|
@ -27,7 +27,13 @@
|
|||
export let detailUrl
|
||||
|
||||
// Add setting height as css var to allow grid to use correct height
|
||||
styles.normal["--grid-height"] = `${height}px`
|
||||
$: gridStyles = {
|
||||
...$styles,
|
||||
normal: {
|
||||
...$styles.normal,
|
||||
["--grid-height"]: `${height}px`,
|
||||
},
|
||||
}
|
||||
|
||||
// These can never change at runtime so don't need to be reactive
|
||||
let canEdit = editable && datasource && datasource.type !== "view"
|
||||
|
@ -143,7 +149,7 @@
|
|||
href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css" />
|
||||
</svelte:head>
|
||||
|
||||
<div class="container" use:styleable={styles}>
|
||||
<div class="container" use:styleable={gridStyles}>
|
||||
{#if dataLoaded}
|
||||
{#if canAddDelete}
|
||||
<div class="controls">
|
||||
|
|
Loading…
Reference in New Issue