Merge branch 'master' into grid-column-formatting
This commit is contained in:
commit
d446fb358d
|
@ -80,7 +80,7 @@
|
||||||
"dayjs": "^1.10.8",
|
"dayjs": "^1.10.8",
|
||||||
"easymde": "^2.16.1",
|
"easymde": "^2.16.1",
|
||||||
"svelte-dnd-action": "^0.9.8",
|
"svelte-dnd-action": "^0.9.8",
|
||||||
"svelte-portal": "^1.0.0"
|
"svelte-portal": "^2.2.1"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"loader-utils": "1.4.1"
|
"loader-utils": "1.4.1"
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
type ClickOutsideCallback = (event: MouseEvent) => void | undefined
|
||||||
|
|
||||||
|
interface ClickOutsideOpts {
|
||||||
|
callback?: ClickOutsideCallback
|
||||||
|
anchor?: HTMLElement
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Handler {
|
||||||
|
id: number
|
||||||
|
element: HTMLElement
|
||||||
|
anchor: HTMLElement
|
||||||
|
callback?: ClickOutsideCallback
|
||||||
|
}
|
||||||
|
|
||||||
// These class names will never trigger a callback if clicked, no matter what
|
// These class names will never trigger a callback if clicked, no matter what
|
||||||
const ignoredClasses = [
|
const ignoredClasses = [
|
||||||
".download-js-link",
|
".download-js-link",
|
||||||
|
@ -14,18 +28,20 @@ const conditionallyIgnoredClasses = [
|
||||||
".drawer-wrapper",
|
".drawer-wrapper",
|
||||||
".spectrum-Popover",
|
".spectrum-Popover",
|
||||||
]
|
]
|
||||||
let clickHandlers = []
|
let clickHandlers: Handler[] = []
|
||||||
let candidateTarget
|
let candidateTarget: HTMLElement | undefined
|
||||||
|
|
||||||
// Processes a "click outside" event and invokes callbacks if our source element
|
// Processes a "click outside" event and invokes callbacks if our source element
|
||||||
// is valid
|
// is valid
|
||||||
const handleClick = event => {
|
const handleClick = (e: MouseEvent) => {
|
||||||
|
const target = e.target as HTMLElement
|
||||||
|
|
||||||
// Ignore click if this is an ignored class
|
// Ignore click if this is an ignored class
|
||||||
if (event.target.closest('[data-ignore-click-outside="true"]')) {
|
if (target.closest('[data-ignore-click-outside="true"]')) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for (let className of ignoredClasses) {
|
for (let className of ignoredClasses) {
|
||||||
if (event.target.closest(className)) {
|
if (target.closest(className)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,41 +49,41 @@ const handleClick = event => {
|
||||||
// Process handlers
|
// Process handlers
|
||||||
clickHandlers.forEach(handler => {
|
clickHandlers.forEach(handler => {
|
||||||
// Check that the click isn't inside the target
|
// Check that the click isn't inside the target
|
||||||
if (handler.element.contains(event.target)) {
|
if (handler.element.contains(target)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore clicks for certain classes unless we're nested inside them
|
// Ignore clicks for certain classes unless we're nested inside them
|
||||||
for (let className of conditionallyIgnoredClasses) {
|
for (let className of conditionallyIgnoredClasses) {
|
||||||
const sourceInside = handler.anchor.closest(className) != null
|
const sourceInside = handler.anchor.closest(className) != null
|
||||||
const clickInside = event.target.closest(className) != null
|
const clickInside = target.closest(className) != null
|
||||||
if (clickInside && !sourceInside) {
|
if (clickInside && !sourceInside) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.callback?.(event)
|
handler.callback?.(e)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// On mouse up we only trigger a "click outside" callback if we targetted the
|
// On mouse up we only trigger a "click outside" callback if we targetted the
|
||||||
// same element that we did on mouse down. This fixes all sorts of issues where
|
// same element that we did on mouse down. This fixes all sorts of issues where
|
||||||
// we get annoying callbacks firing when we drag to select text.
|
// we get annoying callbacks firing when we drag to select text.
|
||||||
const handleMouseUp = e => {
|
const handleMouseUp = (e: MouseEvent) => {
|
||||||
if (candidateTarget === e.target) {
|
if (candidateTarget === e.target) {
|
||||||
handleClick(e)
|
handleClick(e)
|
||||||
}
|
}
|
||||||
candidateTarget = null
|
candidateTarget = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
// On mouse down we store which element was targetted for comparison later
|
// On mouse down we store which element was targetted for comparison later
|
||||||
const handleMouseDown = e => {
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
// Only handle the primary mouse button here.
|
// Only handle the primary mouse button here.
|
||||||
// We handle context menu (right click) events in another handler.
|
// We handle context menu (right click) events in another handler.
|
||||||
if (e.button !== 0) {
|
if (e.button !== 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
candidateTarget = e.target
|
candidateTarget = e.target as HTMLElement
|
||||||
|
|
||||||
// Clear any previous listeners in case of multiple down events, and register
|
// Clear any previous listeners in case of multiple down events, and register
|
||||||
// a single mouse up listener
|
// a single mouse up listener
|
||||||
|
@ -82,7 +98,12 @@ document.addEventListener("contextmenu", handleClick)
|
||||||
/**
|
/**
|
||||||
* Adds or updates a click handler
|
* Adds or updates a click handler
|
||||||
*/
|
*/
|
||||||
const updateHandler = (id, element, anchor, callback) => {
|
const updateHandler = (
|
||||||
|
id: number,
|
||||||
|
element: HTMLElement,
|
||||||
|
anchor: HTMLElement,
|
||||||
|
callback: ClickOutsideCallback | undefined
|
||||||
|
) => {
|
||||||
let existingHandler = clickHandlers.find(x => x.id === id)
|
let existingHandler = clickHandlers.find(x => x.id === id)
|
||||||
if (!existingHandler) {
|
if (!existingHandler) {
|
||||||
clickHandlers.push({ id, element, anchor, callback })
|
clickHandlers.push({ id, element, anchor, callback })
|
||||||
|
@ -94,27 +115,52 @@ const updateHandler = (id, element, anchor, callback) => {
|
||||||
/**
|
/**
|
||||||
* Removes a click handler
|
* Removes a click handler
|
||||||
*/
|
*/
|
||||||
const removeHandler = id => {
|
const removeHandler = (id: number) => {
|
||||||
clickHandlers = clickHandlers.filter(x => x.id !== id)
|
clickHandlers = clickHandlers.filter(x => x.id !== id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Svelte action to apply a click outside handler for a certain element
|
* Svelte action to apply a click outside handler for a certain element.
|
||||||
* opts.anchor is an optional param specifying the real root source of the
|
* opts.anchor is an optional param specifying the real root source of the
|
||||||
* component being observed. This is required for things like popovers, where
|
* component being observed. This is required for things like popovers, where
|
||||||
* the element using the clickoutside action is the popover, but the popover is
|
* the element using the clickoutside action is the popover, but the popover is
|
||||||
* rendered at the root of the DOM somewhere, whereas the popover anchor is the
|
* rendered at the root of the DOM somewhere, whereas the popover anchor is the
|
||||||
* element we actually want to consider when determining the source component.
|
* element we actually want to consider when determining the source component.
|
||||||
*/
|
*/
|
||||||
export default (element, opts) => {
|
export default (
|
||||||
|
element: HTMLElement,
|
||||||
|
opts?: ClickOutsideOpts | ClickOutsideCallback
|
||||||
|
) => {
|
||||||
const id = Math.random()
|
const id = Math.random()
|
||||||
const update = newOpts => {
|
|
||||||
const callback =
|
const isCallback = (
|
||||||
newOpts?.callback || (typeof newOpts === "function" ? newOpts : null)
|
opts?: ClickOutsideOpts | ClickOutsideCallback
|
||||||
const anchor = newOpts?.anchor || element
|
): opts is ClickOutsideCallback => {
|
||||||
|
return typeof opts === "function"
|
||||||
|
}
|
||||||
|
|
||||||
|
const isOpts = (
|
||||||
|
opts?: ClickOutsideOpts | ClickOutsideCallback
|
||||||
|
): opts is ClickOutsideOpts => {
|
||||||
|
return opts != null && typeof opts === "object"
|
||||||
|
}
|
||||||
|
|
||||||
|
const update = (newOpts?: ClickOutsideOpts | ClickOutsideCallback) => {
|
||||||
|
let callback: ClickOutsideCallback | undefined
|
||||||
|
let anchor = element
|
||||||
|
if (isCallback(newOpts)) {
|
||||||
|
callback = newOpts
|
||||||
|
} else if (isOpts(newOpts)) {
|
||||||
|
callback = newOpts.callback
|
||||||
|
if (newOpts.anchor) {
|
||||||
|
anchor = newOpts.anchor
|
||||||
|
}
|
||||||
|
}
|
||||||
updateHandler(id, element, anchor, callback)
|
updateHandler(id, element, anchor, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
update(opts)
|
update(opts)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
update,
|
update,
|
||||||
destroy: () => removeHandler(id),
|
destroy: () => removeHandler(id),
|
|
@ -1,13 +1,7 @@
|
||||||
/**
|
|
||||||
* Valid alignment options are
|
|
||||||
* - left
|
|
||||||
* - right
|
|
||||||
* - left-outside
|
|
||||||
* - right-outside
|
|
||||||
**/
|
|
||||||
|
|
||||||
// Strategies are defined as [Popover]To[Anchor].
|
// Strategies are defined as [Popover]To[Anchor].
|
||||||
// They can apply for both horizontal and vertical alignment.
|
// They can apply for both horizontal and vertical alignment.
|
||||||
|
import { PopoverAlignment } from "../constants"
|
||||||
|
|
||||||
type Strategy =
|
type Strategy =
|
||||||
| "StartToStart"
|
| "StartToStart"
|
||||||
| "EndToEnd"
|
| "EndToEnd"
|
||||||
|
@ -33,7 +27,7 @@ export type UpdateHandler = (
|
||||||
|
|
||||||
interface Opts {
|
interface Opts {
|
||||||
anchor?: HTMLElement
|
anchor?: HTMLElement
|
||||||
align: string
|
align: PopoverAlignment
|
||||||
maxHeight?: number
|
maxHeight?: number
|
||||||
maxWidth?: number
|
maxWidth?: number
|
||||||
minWidth?: number
|
minWidth?: number
|
||||||
|
@ -174,24 +168,33 @@ export default function positionDropdown(element: HTMLElement, opts: Opts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine X strategy
|
// Determine X strategy
|
||||||
if (align === "right") {
|
if (align === PopoverAlignment.Right) {
|
||||||
applyXStrategy("EndToEnd")
|
applyXStrategy("EndToEnd")
|
||||||
} else if (align === "right-outside" || align === "right-context-menu") {
|
} else if (
|
||||||
|
align === PopoverAlignment.RightOutside ||
|
||||||
|
align === PopoverAlignment.RightContextMenu
|
||||||
|
) {
|
||||||
applyXStrategy("StartToEnd")
|
applyXStrategy("StartToEnd")
|
||||||
} else if (align === "left-outside" || align === "left-context-menu") {
|
} else if (
|
||||||
|
align === PopoverAlignment.LeftOutside ||
|
||||||
|
align === PopoverAlignment.LeftContextMenu
|
||||||
|
) {
|
||||||
applyXStrategy("EndToStart")
|
applyXStrategy("EndToStart")
|
||||||
} else if (align === "center") {
|
} else if (align === PopoverAlignment.Center) {
|
||||||
applyXStrategy("MidPoint")
|
applyXStrategy("MidPoint")
|
||||||
} else {
|
} else {
|
||||||
applyXStrategy("StartToStart")
|
applyXStrategy("StartToStart")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine Y strategy
|
// Determine Y strategy
|
||||||
if (align === "right-outside" || align === "left-outside") {
|
if (
|
||||||
|
align === PopoverAlignment.RightOutside ||
|
||||||
|
align === PopoverAlignment.LeftOutside
|
||||||
|
) {
|
||||||
applyYStrategy("MidPoint")
|
applyYStrategy("MidPoint")
|
||||||
} else if (
|
} else if (
|
||||||
align === "right-context-menu" ||
|
align === PopoverAlignment.RightContextMenu ||
|
||||||
align === "left-context-menu"
|
align === PopoverAlignment.LeftContextMenu
|
||||||
) {
|
) {
|
||||||
applyYStrategy("StartToStart")
|
applyYStrategy("StartToStart")
|
||||||
if (styles.top) {
|
if (styles.top) {
|
||||||
|
@ -204,11 +207,11 @@ export default function positionDropdown(element: HTMLElement, opts: Opts) {
|
||||||
// Handle screen overflow
|
// Handle screen overflow
|
||||||
if (doesXOverflow()) {
|
if (doesXOverflow()) {
|
||||||
// Swap left to right
|
// Swap left to right
|
||||||
if (align === "left") {
|
if (align === PopoverAlignment.Left) {
|
||||||
applyXStrategy("EndToEnd")
|
applyXStrategy("EndToEnd")
|
||||||
}
|
}
|
||||||
// Swap right-outside to left-outside
|
// Swap right-outside to left-outside
|
||||||
else if (align === "right-outside") {
|
else if (align === PopoverAlignment.RightOutside) {
|
||||||
applyXStrategy("EndToStart")
|
applyXStrategy("EndToStart")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,10 +228,13 @@ export default function positionDropdown(element: HTMLElement, opts: Opts) {
|
||||||
applyXStrategy("EndToStart")
|
applyXStrategy("EndToStart")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Othewise invert as normal
|
// Otherwise invert as normal
|
||||||
else {
|
else {
|
||||||
// If using an outside strategy then lock to the bottom of the screen
|
// If using an outside strategy then lock to the bottom of the screen
|
||||||
if (align === "left-outside" || align === "right-outside") {
|
if (
|
||||||
|
align === PopoverAlignment.LeftOutside ||
|
||||||
|
align === PopoverAlignment.RightOutside
|
||||||
|
) {
|
||||||
applyYStrategy("ScreenEdge")
|
applyYStrategy("ScreenEdge")
|
||||||
}
|
}
|
||||||
// Otherwise flip above
|
// Otherwise flip above
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import clickOutside from "../../Actions/click_outside"
|
import clickOutside from "../../Actions/click_outside"
|
||||||
import Popover from "../../Popover/Popover.svelte"
|
import Popover from "../../Popover/Popover.svelte"
|
||||||
|
import { PopoverAlignment } from "../../constants"
|
||||||
|
|
||||||
export let value: string | undefined = undefined
|
export let value: string | undefined = undefined
|
||||||
export let id: string | undefined = undefined
|
export let id: string | undefined = undefined
|
||||||
|
@ -97,11 +98,16 @@
|
||||||
<Popover
|
<Popover
|
||||||
{anchor}
|
{anchor}
|
||||||
{open}
|
{open}
|
||||||
align="left"
|
align={PopoverAlignment.Left}
|
||||||
on:close={() => (open = false)}
|
on:close={() => (open = false)}
|
||||||
useAnchorWidth
|
useAnchorWidth
|
||||||
>
|
>
|
||||||
<div class="popover-content" use:clickOutside={() => (open = false)}>
|
<div
|
||||||
|
class="popover-content"
|
||||||
|
use:clickOutside={() => {
|
||||||
|
open = false
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ul class="spectrum-Menu" role="listbox">
|
<ul class="spectrum-Menu" role="listbox">
|
||||||
{#if options && Array.isArray(options)}
|
{#if options && Array.isArray(options)}
|
||||||
{#each options as option}
|
{#each options as option}
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
<script>
|
<script lang="ts" context="module">
|
||||||
|
type O = any
|
||||||
|
type V = any
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
import "@spectrum-css/picker/dist/index-vars.css"
|
import "@spectrum-css/picker/dist/index-vars.css"
|
||||||
import "@spectrum-css/popover/dist/index-vars.css"
|
import "@spectrum-css/popover/dist/index-vars.css"
|
||||||
import "@spectrum-css/menu/dist/index-vars.css"
|
import "@spectrum-css/menu/dist/index-vars.css"
|
||||||
|
@ -11,43 +16,55 @@
|
||||||
import Tags from "../../Tags/Tags.svelte"
|
import Tags from "../../Tags/Tags.svelte"
|
||||||
import Tag from "../../Tags/Tag.svelte"
|
import Tag from "../../Tags/Tag.svelte"
|
||||||
import ProgressCircle from "../../ProgressCircle/ProgressCircle.svelte"
|
import ProgressCircle from "../../ProgressCircle/ProgressCircle.svelte"
|
||||||
|
import { PopoverAlignment } from "../../constants"
|
||||||
|
|
||||||
export let id = null
|
export let id: string | undefined = undefined
|
||||||
export let disabled = false
|
export let disabled: boolean = false
|
||||||
export let fieldText = ""
|
export let fieldText: string = ""
|
||||||
export let fieldIcon = ""
|
export let fieldIcon: string = ""
|
||||||
export let fieldColour = ""
|
export let fieldColour: string = ""
|
||||||
export let isPlaceholder = false
|
export let isPlaceholder: boolean = false
|
||||||
export let placeholderOption = null
|
export let placeholderOption: string | undefined | boolean = undefined
|
||||||
export let options = []
|
export let options: O[] = []
|
||||||
export let isOptionSelected = () => false
|
export let isOptionSelected = (option: O) => option as unknown as boolean
|
||||||
export let isOptionEnabled = () => true
|
export let isOptionEnabled = (option: O, _index?: number) =>
|
||||||
export let onSelectOption = () => {}
|
option as unknown as boolean
|
||||||
export let getOptionLabel = option => option
|
export let onSelectOption: (_value: V) => void = () => {}
|
||||||
export let getOptionValue = option => option
|
export let getOptionLabel = (option: O, _index?: number) => `${option}`
|
||||||
export let getOptionIcon = () => null
|
export let getOptionValue = (option: O, _index?: number) =>
|
||||||
|
option as unknown as V
|
||||||
|
export let getOptionIcon = (option: O, _index?: number) =>
|
||||||
|
option as unknown as O
|
||||||
export let useOptionIconImage = false
|
export let useOptionIconImage = false
|
||||||
export let getOptionColour = () => null
|
export let getOptionColour = (option: O, _index?: number) =>
|
||||||
export let getOptionSubtitle = () => null
|
option as unknown as O
|
||||||
export let open = false
|
export let getOptionSubtitle = (option: O, _index?: number) =>
|
||||||
export let readonly = false
|
option as unknown as O
|
||||||
export let quiet = false
|
export let open: boolean = false
|
||||||
export let autoWidth = false
|
export let readonly: boolean = false
|
||||||
export let autocomplete = false
|
export let quiet: boolean = false
|
||||||
export let sort = false
|
export let autoWidth: boolean | undefined = false
|
||||||
export let searchTerm = null
|
export let autocomplete: boolean = false
|
||||||
export let customPopoverHeight
|
export let sort: boolean = false
|
||||||
export let align = "left"
|
export let searchTerm: string | null = null
|
||||||
export let footer = null
|
export let customPopoverHeight: string | undefined = undefined
|
||||||
export let customAnchor = null
|
export let align: PopoverAlignment | undefined = PopoverAlignment.Left
|
||||||
export let loading
|
export let footer: string | undefined = undefined
|
||||||
export let onOptionMouseenter = () => {}
|
export let customAnchor: HTMLElement | undefined = undefined
|
||||||
export let onOptionMouseleave = () => {}
|
export let loading: boolean = false
|
||||||
|
export let onOptionMouseenter: (
|
||||||
|
_e: MouseEvent,
|
||||||
|
_option: any
|
||||||
|
) => void = () => {}
|
||||||
|
export let onOptionMouseleave: (
|
||||||
|
_e: MouseEvent,
|
||||||
|
_option: any
|
||||||
|
) => void = () => {}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let button
|
let button: any
|
||||||
let component
|
let component: any
|
||||||
|
|
||||||
$: sortedOptions = getSortedOptions(options, getOptionLabel, sort)
|
$: sortedOptions = getSortedOptions(options, getOptionLabel, sort)
|
||||||
$: filteredOptions = getFilteredOptions(
|
$: filteredOptions = getFilteredOptions(
|
||||||
|
@ -56,7 +73,7 @@
|
||||||
getOptionLabel
|
getOptionLabel
|
||||||
)
|
)
|
||||||
|
|
||||||
const onClick = e => {
|
const onClick = (e: MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
dispatch("click")
|
dispatch("click")
|
||||||
|
@ -67,7 +84,11 @@
|
||||||
open = !open
|
open = !open
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSortedOptions = (options, getLabel, sort) => {
|
const getSortedOptions = (
|
||||||
|
options: any[],
|
||||||
|
getLabel: (_option: any) => string,
|
||||||
|
sort: boolean
|
||||||
|
) => {
|
||||||
if (!options?.length || !Array.isArray(options)) {
|
if (!options?.length || !Array.isArray(options)) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
@ -81,17 +102,21 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFilteredOptions = (options, term, getLabel) => {
|
const getFilteredOptions = (
|
||||||
|
options: any[],
|
||||||
|
term: string | null,
|
||||||
|
getLabel: (_option: any) => string
|
||||||
|
) => {
|
||||||
if (autocomplete && term) {
|
if (autocomplete && term) {
|
||||||
const lowerCaseTerm = term.toLowerCase()
|
const lowerCaseTerm = term.toLowerCase()
|
||||||
return options.filter(option => {
|
return options.filter((option: any) => {
|
||||||
return `${getLabel(option)}`.toLowerCase().includes(lowerCaseTerm)
|
return `${getLabel(option)}`.toLowerCase().includes(lowerCaseTerm)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
const onScroll = e => {
|
const onScroll = (e: any) => {
|
||||||
const scrollPxThreshold = 100
|
const scrollPxThreshold = 100
|
||||||
const scrollPositionFromBottom =
|
const scrollPositionFromBottom =
|
||||||
e.target.scrollHeight - e.target.clientHeight - e.target.scrollTop
|
e.target.scrollHeight - e.target.clientHeight - e.target.scrollTop
|
||||||
|
@ -151,18 +176,20 @@
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<Popover
|
<Popover
|
||||||
anchor={customAnchor ? customAnchor : button}
|
anchor={customAnchor ? customAnchor : button}
|
||||||
align={align || "left"}
|
align={align || PopoverAlignment.Left}
|
||||||
{open}
|
{open}
|
||||||
on:close={() => (open = false)}
|
on:close={() => (open = false)}
|
||||||
useAnchorWidth={!autoWidth}
|
useAnchorWidth={!autoWidth}
|
||||||
maxWidth={autoWidth ? 400 : null}
|
maxWidth={autoWidth ? 400 : undefined}
|
||||||
customHeight={customPopoverHeight}
|
customHeight={customPopoverHeight}
|
||||||
maxHeight={360}
|
maxHeight={360}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="popover-content"
|
class="popover-content"
|
||||||
class:auto-width={autoWidth}
|
class:auto-width={autoWidth}
|
||||||
use:clickOutside={() => (open = false)}
|
use:clickOutside={() => {
|
||||||
|
open = false
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{#if autocomplete}
|
{#if autocomplete}
|
||||||
<Search
|
<Search
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import "@spectrum-css/search/dist/index-vars.css"
|
import "@spectrum-css/search/dist/index-vars.css"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
export let value = null
|
export let value: any = null
|
||||||
export let placeholder = null
|
export let placeholder: string | undefined = undefined
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let id = null
|
export let id = null
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let inputRef
|
export let inputRef: HTMLElement | undefined = undefined
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let focus = false
|
let focus = false
|
||||||
|
|
||||||
const updateValue = value => {
|
const updateValue = (value: any) => {
|
||||||
dispatch("change", value)
|
dispatch("change", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,19 +21,19 @@
|
||||||
focus = true
|
focus = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const onBlur = event => {
|
const onBlur = (event: any) => {
|
||||||
focus = false
|
focus = false
|
||||||
updateValue(event.target.value)
|
updateValue(event.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onInput = event => {
|
const onInput = (event: any) => {
|
||||||
if (!updateOnChange) {
|
if (!updateOnChange) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
updateValue(event.target.value)
|
updateValue(event.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateValueOnEnter = event => {
|
const updateValueOnEnter = (event: any) => {
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
updateValue(event.target.value)
|
updateValue(event.target.value)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,44 @@
|
||||||
<script>
|
<script lang="ts" context="module">
|
||||||
|
type O = any
|
||||||
|
type V = any
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import Picker from "./Picker.svelte"
|
import Picker from "./Picker.svelte"
|
||||||
|
import { PopoverAlignment } from "../../constants"
|
||||||
|
|
||||||
export let value = null
|
export let value: V | null = null
|
||||||
export let id = null
|
export let id: string | undefined = undefined
|
||||||
export let placeholder = "Choose an option"
|
export let placeholder: string | boolean = "Choose an option"
|
||||||
export let disabled = false
|
export let disabled: boolean = false
|
||||||
export let options = []
|
export let options: O[] = []
|
||||||
export let getOptionLabel = option => option
|
export let getOptionLabel = (option: O, _index?: number) => `${option}`
|
||||||
export let getOptionValue = option => option
|
export let getOptionValue = (option: O, _index?: number) =>
|
||||||
export let getOptionIcon = () => null
|
option as unknown as V
|
||||||
export let getOptionColour = () => null
|
export let getOptionIcon = (option: O, _index?: number) =>
|
||||||
export let getOptionSubtitle = () => null
|
option as unknown as string
|
||||||
export let compare = null
|
export let getOptionColour = (option: O, _index?: number) =>
|
||||||
|
option as unknown as string
|
||||||
|
export let getOptionSubtitle = (option: O, _index?: number) =>
|
||||||
|
option as unknown as string
|
||||||
|
export let compare = (option: O, value: V) => option === value
|
||||||
export let useOptionIconImage = false
|
export let useOptionIconImage = false
|
||||||
export let isOptionEnabled
|
export let isOptionEnabled = (option: O, _index?: number) =>
|
||||||
export let readonly = false
|
option as unknown as boolean
|
||||||
export let quiet = false
|
export let readonly: boolean = false
|
||||||
export let autoWidth = false
|
export let quiet: boolean = false
|
||||||
export let autocomplete = false
|
export let autoWidth: boolean = false
|
||||||
export let sort = false
|
export let autocomplete: boolean = false
|
||||||
export let align
|
export let sort: boolean = false
|
||||||
export let footer = null
|
export let align: PopoverAlignment | undefined = PopoverAlignment.Left
|
||||||
export let open = false
|
export let footer: string | undefined = undefined
|
||||||
export let tag = null
|
export let open: boolean = false
|
||||||
export let searchTerm = null
|
export let searchTerm: string | undefined = undefined
|
||||||
export let loading
|
export let loading: boolean | undefined = undefined
|
||||||
export let onOptionMouseenter = () => {}
|
export let onOptionMouseenter = () => {}
|
||||||
export let onOptionMouseleave = () => {}
|
export let onOptionMouseleave = () => {}
|
||||||
|
export let customPopoverHeight: string | undefined = undefined
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -35,24 +46,28 @@
|
||||||
$: fieldIcon = getFieldAttribute(getOptionIcon, value, options)
|
$: fieldIcon = getFieldAttribute(getOptionIcon, value, options)
|
||||||
$: fieldColour = getFieldAttribute(getOptionColour, value, options)
|
$: fieldColour = getFieldAttribute(getOptionColour, value, options)
|
||||||
|
|
||||||
function compareOptionAndValue(option, value) {
|
function compareOptionAndValue(option: O, value: V) {
|
||||||
return typeof compare === "function"
|
return typeof compare === "function"
|
||||||
? compare(option, value)
|
? compare(option, value)
|
||||||
: option === value
|
: option === value
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFieldAttribute = (getAttribute, value, options) => {
|
const getFieldAttribute = (getAttribute: any, value: V[], options: O[]) => {
|
||||||
// Wait for options to load if there is a value but no options
|
// Wait for options to load if there is a value but no options
|
||||||
if (!options?.length) {
|
if (!options?.length) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
const index = options.findIndex((option, idx) =>
|
const index = options.findIndex((option: any, idx: number) =>
|
||||||
compareOptionAndValue(getOptionValue(option, idx), value)
|
compareOptionAndValue(getOptionValue(option, idx), value)
|
||||||
)
|
)
|
||||||
return index !== -1 ? getAttribute(options[index], index) : null
|
return index !== -1 ? getAttribute(options[index], index) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFieldText = (value, options, placeholder) => {
|
const getFieldText = (
|
||||||
|
value: any,
|
||||||
|
options: any,
|
||||||
|
placeholder: boolean | string
|
||||||
|
) => {
|
||||||
if (value == null || value === "") {
|
if (value == null || value === "") {
|
||||||
// Explicit false means use no placeholder and allow an empty fields
|
// Explicit false means use no placeholder and allow an empty fields
|
||||||
if (placeholder === false) {
|
if (placeholder === false) {
|
||||||
|
@ -67,7 +82,7 @@
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectOption = value => {
|
const selectOption = (value: V) => {
|
||||||
dispatch("change", value)
|
dispatch("change", value)
|
||||||
open = false
|
open = false
|
||||||
}
|
}
|
||||||
|
@ -98,14 +113,14 @@
|
||||||
{isOptionEnabled}
|
{isOptionEnabled}
|
||||||
{autocomplete}
|
{autocomplete}
|
||||||
{sort}
|
{sort}
|
||||||
{tag}
|
|
||||||
{onOptionMouseenter}
|
{onOptionMouseenter}
|
||||||
{onOptionMouseleave}
|
{onOptionMouseleave}
|
||||||
isPlaceholder={value == null || value === ""}
|
isPlaceholder={value == null || value === ""}
|
||||||
placeholderOption={placeholder === false
|
placeholderOption={placeholder === false
|
||||||
? null
|
? undefined
|
||||||
: placeholder || "Choose an option"}
|
: placeholder || "Choose an option"}
|
||||||
isOptionSelected={option => compareOptionAndValue(option, value)}
|
isOptionSelected={option => compareOptionAndValue(option, value)}
|
||||||
onSelectOption={selectOption}
|
onSelectOption={selectOption}
|
||||||
{loading}
|
{loading}
|
||||||
|
{customPopoverHeight}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import "@spectrum-css/textfield/dist/index-vars.css"
|
import "@spectrum-css/textfield/dist/index-vars.css"
|
||||||
import { createEventDispatcher, onMount, tick } from "svelte"
|
import { createEventDispatcher, onMount, tick } from "svelte"
|
||||||
|
|
||||||
export let value = null
|
export let value = null
|
||||||
export let placeholder = null
|
export let placeholder: string | undefined = undefined
|
||||||
export let type = "text"
|
export let type = "text"
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let id = null
|
export let id = null
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let align
|
export let align: "left" | "right" | "center" | undefined = undefined
|
||||||
export let autofocus = false
|
export let autofocus = false
|
||||||
export let autocomplete = null
|
export let autocomplete: boolean | undefined
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let field
|
let field: any
|
||||||
let focus = false
|
let focus = false
|
||||||
|
|
||||||
const updateValue = newValue => {
|
const updateValue = (newValue: any) => {
|
||||||
if (readonly || disabled) {
|
if (readonly || disabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
focus = true
|
focus = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const onBlur = event => {
|
const onBlur = (event: any) => {
|
||||||
if (readonly || disabled) {
|
if (readonly || disabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -45,14 +45,14 @@
|
||||||
updateValue(event.target.value)
|
updateValue(event.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onInput = event => {
|
const onInput = (event: any) => {
|
||||||
if (readonly || !updateOnChange || disabled) {
|
if (readonly || !updateOnChange || disabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
updateValue(event.target.value)
|
updateValue(event.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateValueOnEnter = event => {
|
const updateValueOnEnter = (event: any) => {
|
||||||
if (readonly || disabled) {
|
if (readonly || disabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -61,13 +61,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getInputMode = type => {
|
const getInputMode = (type: any) => {
|
||||||
if (type === "bigint") {
|
if (type === "bigint") {
|
||||||
return "numeric"
|
return "numeric"
|
||||||
}
|
}
|
||||||
return type === "number" ? "decimal" : "text"
|
return type === "number" ? "decimal" : "text"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: autocompleteValue =
|
||||||
|
typeof autocomplete === "boolean"
|
||||||
|
? autocomplete
|
||||||
|
? "on"
|
||||||
|
: "off"
|
||||||
|
: undefined
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (disabled) return
|
if (disabled) return
|
||||||
focus = autofocus
|
focus = autofocus
|
||||||
|
@ -104,7 +111,7 @@
|
||||||
class="spectrum-Textfield-input"
|
class="spectrum-Textfield-input"
|
||||||
style={align ? `text-align: ${align};` : ""}
|
style={align ? `text-align: ${align};` : ""}
|
||||||
inputmode={getInputMode(type)}
|
inputmode={getInputMode(type)}
|
||||||
{autocomplete}
|
autocomplete={autocompleteValue}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
||||||
import FieldLabel from "./FieldLabel.svelte"
|
import FieldLabel from "./FieldLabel.svelte"
|
||||||
import Icon from "../Icon/Icon.svelte"
|
import Icon from "../Icon/Icon.svelte"
|
||||||
|
|
||||||
export let id = null
|
export let id: string | undefined = undefined
|
||||||
export let label = null
|
export let label: string | undefined = undefined
|
||||||
export let labelPosition = "above"
|
export let labelPosition: string = "above"
|
||||||
export let error = null
|
export let error: string | undefined = undefined
|
||||||
export let helpText = null
|
export let helpText: string | undefined = undefined
|
||||||
export let tooltip = ""
|
export let tooltip: string | undefined = undefined
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="spectrum-Form-item" class:above={labelPosition === "above"}>
|
<div class="spectrum-Form-item" class:above={labelPosition === "above"}>
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import Field from "./Field.svelte"
|
import Field from "./Field.svelte"
|
||||||
import TextField from "./Core/TextField.svelte"
|
import TextField from "./Core/TextField.svelte"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
export let value = null
|
export let value: any = undefined
|
||||||
export let label = null
|
export let label: string | undefined = undefined
|
||||||
export let labelPosition = "above"
|
export let labelPosition = "above"
|
||||||
export let placeholder = null
|
export let placeholder: string | undefined = undefined
|
||||||
export let type = "text"
|
export let type = "text"
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let error = null
|
export let error: string | undefined = undefined
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let autofocus
|
export let autofocus: boolean | undefined = undefined
|
||||||
export let autocomplete
|
export let autocomplete: boolean | undefined = undefined
|
||||||
export let helpText = null
|
export let helpText: string | undefined = undefined
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = (e: any) => {
|
||||||
value = e.detail
|
value = e.detail
|
||||||
dispatch("change", e.detail)
|
dispatch("change", e.detail)
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,6 @@
|
||||||
<Field {helpText} {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<TextField
|
<TextField
|
||||||
{updateOnChange}
|
{updateOnChange}
|
||||||
{error}
|
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
{value}
|
{value}
|
||||||
|
|
|
@ -1,44 +1,54 @@
|
||||||
<script>
|
<script lang="ts" context="module">
|
||||||
|
type O = any
|
||||||
|
type V = any
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
import Field from "./Field.svelte"
|
import Field from "./Field.svelte"
|
||||||
import Select from "./Core/Select.svelte"
|
import Select from "./Core/Select.svelte"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import { PopoverAlignment } from "../constants"
|
||||||
|
|
||||||
export let value = null
|
export let value: V | undefined = undefined
|
||||||
export let label = undefined
|
export let label: string | undefined = undefined
|
||||||
export let disabled = false
|
export let disabled: boolean = false
|
||||||
export let readonly = false
|
export let readonly: boolean = false
|
||||||
export let labelPosition = "above"
|
export let labelPosition: string = "above"
|
||||||
export let error = null
|
export let error: string | undefined = undefined
|
||||||
export let placeholder = "Choose an option"
|
export let placeholder: string | boolean = "Choose an option"
|
||||||
export let options = []
|
export let options: O[] = []
|
||||||
export let getOptionLabel = option => extractProperty(option, "label")
|
export let getOptionLabel = (option: O, _index?: number) =>
|
||||||
export let getOptionValue = option => extractProperty(option, "value")
|
extractProperty(option, "label")
|
||||||
export let getOptionSubtitle = option => option?.subtitle
|
export let getOptionValue = (option: O, _index?: number) =>
|
||||||
export let getOptionIcon = option => option?.icon
|
extractProperty(option, "value")
|
||||||
export let getOptionColour = option => option?.colour
|
export let getOptionSubtitle = (option: O, _index?: number) =>
|
||||||
|
option?.subtitle
|
||||||
|
export let getOptionIcon = (option: O, _index?: number) => option?.icon
|
||||||
|
export let getOptionColour = (option: O, _index?: number) => option?.colour
|
||||||
export let useOptionIconImage = false
|
export let useOptionIconImage = false
|
||||||
export let isOptionEnabled = undefined
|
export let isOptionEnabled:
|
||||||
export let quiet = false
|
| ((_option: O, _index?: number) => boolean)
|
||||||
export let autoWidth = false
|
| undefined = undefined
|
||||||
export let sort = false
|
export let quiet: boolean = false
|
||||||
export let tooltip = ""
|
export let autoWidth: boolean = false
|
||||||
export let autocomplete = false
|
export let sort: boolean = false
|
||||||
export let customPopoverHeight = undefined
|
export let tooltip: string | undefined = undefined
|
||||||
export let align = undefined
|
export let autocomplete: boolean = false
|
||||||
export let footer = null
|
export let customPopoverHeight: string | undefined = undefined
|
||||||
export let tag = null
|
export let align: PopoverAlignment | undefined = PopoverAlignment.Left
|
||||||
export let helpText = null
|
export let footer: string | undefined = undefined
|
||||||
export let compare = undefined
|
export let helpText: string | undefined = undefined
|
||||||
|
export let compare: any = undefined
|
||||||
export let onOptionMouseenter = () => {}
|
export let onOptionMouseenter = () => {}
|
||||||
export let onOptionMouseleave = () => {}
|
export let onOptionMouseleave = () => {}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = (e: CustomEvent<any>) => {
|
||||||
value = e.detail
|
value = e.detail
|
||||||
dispatch("change", e.detail)
|
dispatch("change", e.detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
const extractProperty = (value, property) => {
|
const extractProperty = (value: any, property: any) => {
|
||||||
if (value && typeof value === "object") {
|
if (value && typeof value === "object") {
|
||||||
return value[property]
|
return value[property]
|
||||||
}
|
}
|
||||||
|
@ -49,7 +59,6 @@
|
||||||
<Field {helpText} {label} {labelPosition} {error} {tooltip}>
|
<Field {helpText} {label} {labelPosition} {error} {tooltip}>
|
||||||
<Select
|
<Select
|
||||||
{quiet}
|
{quiet}
|
||||||
{error}
|
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
{value}
|
{value}
|
||||||
|
@ -68,7 +77,6 @@
|
||||||
{isOptionEnabled}
|
{isOptionEnabled}
|
||||||
{autocomplete}
|
{autocomplete}
|
||||||
{customPopoverHeight}
|
{customPopoverHeight}
|
||||||
{tag}
|
|
||||||
{compare}
|
{compare}
|
||||||
{onOptionMouseenter}
|
{onOptionMouseenter}
|
||||||
{onOptionMouseleave}
|
{onOptionMouseleave}
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import AbsTooltip from "../Tooltip/AbsTooltip.svelte"
|
||||||
default as AbsTooltip,
|
import { TooltipPosition, TooltipType } from "../constants"
|
||||||
TooltipPosition,
|
|
||||||
TooltipType,
|
|
||||||
} from "../Tooltip/AbsTooltip.svelte"
|
|
||||||
|
|
||||||
export let name: string = "Add"
|
export let name: string = "Add"
|
||||||
|
export let size: "XS" | "S" | "M" | "L" | "XL" = "M"
|
||||||
export let hidden: boolean = false
|
export let hidden: boolean = false
|
||||||
export let size = "M"
|
|
||||||
export let hoverable: boolean = false
|
export let hoverable: boolean = false
|
||||||
export let disabled: boolean = false
|
export let disabled: boolean = false
|
||||||
export let color: string | undefined = undefined
|
export let color: string | undefined = undefined
|
||||||
|
@ -81,17 +78,6 @@
|
||||||
color: var(--spectrum-global-color-gray-500) !important;
|
color: var(--spectrum-global-color-gray-500) !important;
|
||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip {
|
|
||||||
position: absolute;
|
|
||||||
pointer-events: none;
|
|
||||||
left: 50%;
|
|
||||||
bottom: calc(100% + 4px);
|
|
||||||
transform: translateX(-50%);
|
|
||||||
text-align: center;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spectrum-Icon--sizeXS {
|
.spectrum-Icon--sizeXS {
|
||||||
width: var(--spectrum-global-dimension-size-150);
|
width: var(--spectrum-global-dimension-size-150);
|
||||||
height: var(--spectrum-global-dimension-size-150);
|
height: var(--spectrum-global-dimension-size-150);
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
export let primary = false
|
export let primary = false
|
||||||
export let secondary = false
|
export let secondary = false
|
||||||
export let overBackground = false
|
export let overBackground = false
|
||||||
export let target
|
export let target = undefined
|
||||||
export let download
|
export let download = undefined
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let tooltip = null
|
export let tooltip = null
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
|
<script context="module" lang="ts">
|
||||||
|
export interface PopoverAPI {
|
||||||
|
show: () => void
|
||||||
|
hide: () => void
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "@spectrum-css/popover/dist/index-vars.css"
|
import "@spectrum-css/popover/dist/index-vars.css"
|
||||||
// @ts-expect-error no types for the version of svelte-portal we're on.
|
|
||||||
import Portal from "svelte-portal"
|
import Portal from "svelte-portal"
|
||||||
import { createEventDispatcher, getContext, onDestroy } from "svelte"
|
import { createEventDispatcher, getContext, onDestroy } from "svelte"
|
||||||
import positionDropdown, {
|
import positionDropdown, {
|
||||||
|
@ -10,12 +16,10 @@
|
||||||
import { fly } from "svelte/transition"
|
import { fly } from "svelte/transition"
|
||||||
import Context from "../context"
|
import Context from "../context"
|
||||||
import type { KeyboardEventHandler } from "svelte/elements"
|
import type { KeyboardEventHandler } from "svelte/elements"
|
||||||
|
import { PopoverAlignment } from "../constants"
|
||||||
const dispatch = createEventDispatcher<{ open: void; close: void }>()
|
|
||||||
|
|
||||||
export let anchor: HTMLElement
|
export let anchor: HTMLElement
|
||||||
export let align: "left" | "right" | "left-outside" | "right-outside" =
|
export let align: PopoverAlignment = PopoverAlignment.Right
|
||||||
"right"
|
|
||||||
export let portalTarget: string | undefined = undefined
|
export let portalTarget: string | undefined = undefined
|
||||||
export let minWidth: number | undefined = undefined
|
export let minWidth: number | undefined = undefined
|
||||||
export let maxWidth: number | undefined = undefined
|
export let maxWidth: number | undefined = undefined
|
||||||
|
@ -26,19 +30,24 @@
|
||||||
export let offset = 4
|
export let offset = 4
|
||||||
export let customHeight: string | undefined = undefined
|
export let customHeight: string | undefined = undefined
|
||||||
export let animate = true
|
export let animate = true
|
||||||
export let customZindex: string | undefined = undefined
|
export let customZIndex: number | undefined = undefined
|
||||||
export let handlePostionUpdate: UpdateHandler | undefined = undefined
|
export let handlePositionUpdate: UpdateHandler | undefined = undefined
|
||||||
export let showPopover = true
|
export let showPopover = true
|
||||||
export let clickOutsideOverride = false
|
export let clickOutsideOverride = false
|
||||||
export let resizable = true
|
export let resizable = true
|
||||||
export let wrap = false
|
export let wrap = false
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher<{ open: void; close: void }>()
|
||||||
const animationDuration = 260
|
const animationDuration = 260
|
||||||
|
|
||||||
let timeout: ReturnType<typeof setTimeout>
|
let timeout: ReturnType<typeof setTimeout>
|
||||||
let blockPointerEvents = false
|
let blockPointerEvents = false
|
||||||
|
|
||||||
$: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum"
|
// Portal library lacks types, so we have to type this as any even though it's
|
||||||
|
// actually a string
|
||||||
|
$: target = (portalTarget ||
|
||||||
|
getContext(Context.PopoverRoot) ||
|
||||||
|
".spectrum") as any
|
||||||
$: {
|
$: {
|
||||||
// Disable pointer events for the initial part of the animation, because we
|
// Disable pointer events for the initial part of the animation, because we
|
||||||
// fly from top to bottom and initially can be positioned under the cursor,
|
// fly from top to bottom and initially can be positioned under the cursor,
|
||||||
|
@ -118,7 +127,7 @@
|
||||||
minWidth,
|
minWidth,
|
||||||
useAnchorWidth,
|
useAnchorWidth,
|
||||||
offset,
|
offset,
|
||||||
customUpdate: handlePostionUpdate,
|
customUpdate: handlePositionUpdate,
|
||||||
resizable,
|
resizable,
|
||||||
wrap,
|
wrap,
|
||||||
}}
|
}}
|
||||||
|
@ -128,11 +137,11 @@
|
||||||
}}
|
}}
|
||||||
on:keydown={handleEscape}
|
on:keydown={handleEscape}
|
||||||
class="spectrum-Popover is-open"
|
class="spectrum-Popover is-open"
|
||||||
class:customZindex
|
class:customZIndex
|
||||||
class:hidden={!showPopover}
|
class:hidden={!showPopover}
|
||||||
class:blockPointerEvents
|
class:blockPointerEvents
|
||||||
role="presentation"
|
role="presentation"
|
||||||
style="height: {customHeight}; --customZindex: {customZindex};"
|
style="height: {customHeight}; --customZIndex: {customZIndex};"
|
||||||
transition:fly|local={{
|
transition:fly|local={{
|
||||||
y: -20,
|
y: -20,
|
||||||
duration: animate ? animationDuration : 0,
|
duration: animate ? animationDuration : 0,
|
||||||
|
@ -162,7 +171,7 @@
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.customZindex {
|
.customZIndex {
|
||||||
z-index: var(--customZindex) !important;
|
z-index: var(--customZIndex) !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,120 +0,0 @@
|
||||||
<script>
|
|
||||||
import { View } from "svench";
|
|
||||||
import Popover from "./Popover.svelte";
|
|
||||||
import Button from "../Button/Button.svelte";
|
|
||||||
import TextButton from "../Button/TextButton.svelte";
|
|
||||||
import Icon from "../Icons/Icon.svelte";
|
|
||||||
import Input from "../Form/Input.svelte";
|
|
||||||
import Select from "../Form/Select.svelte";
|
|
||||||
|
|
||||||
let anchorRight;
|
|
||||||
let anchorLeft;
|
|
||||||
let dropdownRight;
|
|
||||||
let dropdownLeft;
|
|
||||||
|
|
||||||
const options = ["Column 1", "Column 2", "Super cool column"];
|
|
||||||
const option1s = ["Is", "Is not", "Contains" , "Does not contain"];
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.button-group {
|
|
||||||
margin-top: var(--spacing-l);
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: var(--spacing-s);
|
|
||||||
}
|
|
||||||
|
|
||||||
h6 {
|
|
||||||
font-size: var(--font-size-m);
|
|
||||||
margin: 0 0 var(--spacing-l) 0;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group-column {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--spacing-s);
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: [boolean-start] 60px [boolean-end property-start] 120px [property-end opererator-start] 110px [operator-end value-start] auto [value-end menu-start] 32px [menu-end];
|
|
||||||
gap: var(--spacing-s);
|
|
||||||
margin-bottom: var(--spacing-l);
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin:0;
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<View name="Simple popover">
|
|
||||||
<div bind:this={anchorLeft}>
|
|
||||||
<Button text on:click={dropdownLeft.show}>
|
|
||||||
<Icon name="view" />
|
|
||||||
Add View
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<Popover bind:this={dropdownLeft} anchor={anchorLeft} align="left">
|
|
||||||
<h6>Add New View</h6>
|
|
||||||
<Input thin placeholder="Enter your name" />
|
|
||||||
<div class="button-group">
|
|
||||||
<Button secondary on:click={() => alert('Clicked!')}>Cancel</Button>
|
|
||||||
<Button primary on:click={() => alert('Clicked!')}>Add New View</Button>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View name="Stacked columns">
|
|
||||||
<div bind:this={anchorRight}>
|
|
||||||
<Button text on:click={dropdownRight.show}>
|
|
||||||
<Icon name="addrow" />
|
|
||||||
Add Row
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<Popover bind:this={dropdownRight} anchor={anchorRight}>
|
|
||||||
<h6>Add New Row</h6>
|
|
||||||
<div class="input-group-column">
|
|
||||||
<Input thin placeholder="Enter your string" />
|
|
||||||
<Input thin placeholder="Enter your string" />
|
|
||||||
<Input thin placeholder="Enter your string" />
|
|
||||||
</div>
|
|
||||||
<div class="button-group">
|
|
||||||
<Button secondary on:click={() => alert('Clicked!')}>Cancel</Button>
|
|
||||||
<Button primary on:click={() => alert('Clicked!')}>Add New Row</Button>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View name="Multiple inputs in a row">
|
|
||||||
<div bind:this={anchorLeft}>
|
|
||||||
<Button text on:click={dropdownLeft.show}>
|
|
||||||
<Icon name="filter" />
|
|
||||||
Add Filter
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<Popover bind:this={dropdownLeft} anchor={anchorLeft} align="left">
|
|
||||||
<h6>Add New Filter</h6>
|
|
||||||
<div class="input-group-row">
|
|
||||||
<p>Where</p>
|
|
||||||
<Select secondary thin name="Test">
|
|
||||||
{#each options as option}
|
|
||||||
<option value={option}>{option}</option>
|
|
||||||
{/each}
|
|
||||||
</Select>
|
|
||||||
<Select secondary thin name="Test">
|
|
||||||
{#each option1s as option1}
|
|
||||||
<option value={option1}>{option1}</option>
|
|
||||||
{/each}
|
|
||||||
</Select>
|
|
||||||
<Input thin placeholder="Enter your name" />
|
|
||||||
<Button text on:click={() => alert('Clicked!')}>
|
|
||||||
<Icon name="close" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<Button text on:click={() => alert('Clicked!')}>Add Filter</Button>
|
|
||||||
</Popover>
|
|
||||||
</View>
|
|
|
@ -1,25 +1,25 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import "@spectrum-css/statuslight"
|
import "@spectrum-css/statuslight"
|
||||||
|
|
||||||
export let size = "M"
|
export let size: string = "M"
|
||||||
export let celery = false
|
export let celery: boolean = false
|
||||||
export let yellow = false
|
export let yellow: boolean = false
|
||||||
export let fuchsia = false
|
export let fuchsia: boolean = false
|
||||||
export let indigo = false
|
export let indigo: boolean = false
|
||||||
export let seafoam = false
|
export let seafoam: boolean = false
|
||||||
export let chartreuse = false
|
export let chartreuse: boolean = false
|
||||||
export let magenta = false
|
export let magenta: boolean = false
|
||||||
export let purple = false
|
export let purple: boolean = false
|
||||||
export let neutral = false
|
export let neutral: boolean = false
|
||||||
export let info = false
|
export let info: boolean = false
|
||||||
export let positive = false
|
export let positive: boolean = false
|
||||||
export let notice = false
|
export let notice: boolean = false
|
||||||
export let negative = false
|
export let negative: boolean = false
|
||||||
export let disabled = false
|
export let disabled: boolean = false
|
||||||
export let active = false
|
export let active: boolean = false
|
||||||
export let color = null
|
export let color: string | undefined = undefined
|
||||||
export let square = false
|
export let square: boolean = false
|
||||||
export let hoverable = false
|
export let hoverable: boolean = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
|
|
@ -1,38 +1,24 @@
|
||||||
<script context="module">
|
<script lang="ts">
|
||||||
export const TooltipPosition = {
|
|
||||||
Top: "top",
|
|
||||||
Right: "right",
|
|
||||||
Bottom: "bottom",
|
|
||||||
Left: "left",
|
|
||||||
}
|
|
||||||
export const TooltipType = {
|
|
||||||
Default: "default",
|
|
||||||
Info: "info",
|
|
||||||
Positive: "positive",
|
|
||||||
Negative: "negative",
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Portal from "svelte-portal"
|
import Portal from "svelte-portal"
|
||||||
import { fade } from "svelte/transition"
|
import { fade } from "svelte/transition"
|
||||||
import "@spectrum-css/tooltip/dist/index-vars.css"
|
import "@spectrum-css/tooltip/dist/index-vars.css"
|
||||||
import { onDestroy } from "svelte"
|
import { onDestroy } from "svelte"
|
||||||
|
import { TooltipPosition, TooltipType } from "../constants"
|
||||||
|
|
||||||
export let position = TooltipPosition.Top
|
export let position: TooltipPosition = TooltipPosition.Top
|
||||||
export let type = TooltipType.Default
|
export let type: TooltipType = TooltipType.Default
|
||||||
export let text = ""
|
export let text: string = ""
|
||||||
export let fixed = false
|
export let fixed: boolean = false
|
||||||
export let color = ""
|
export let color: string | undefined = undefined
|
||||||
export let noWrap = false
|
export let noWrap: boolean = false
|
||||||
|
|
||||||
let wrapper
|
let wrapper: HTMLElement | undefined
|
||||||
let hovered = false
|
let hovered = false
|
||||||
let left
|
let left: number | undefined
|
||||||
let top
|
let top: number | undefined
|
||||||
let visible = false
|
let visible = false
|
||||||
let timeout
|
let timeout: ReturnType<typeof setTimeout> | undefined
|
||||||
let interval
|
let interval: ReturnType<typeof setInterval> | undefined
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (hovered || fixed) {
|
if (hovered || fixed) {
|
||||||
|
@ -49,8 +35,8 @@
|
||||||
const updateTooltipPosition = () => {
|
const updateTooltipPosition = () => {
|
||||||
const node = wrapper?.children?.[0]
|
const node = wrapper?.children?.[0]
|
||||||
if (!node) {
|
if (!node) {
|
||||||
left = null
|
left = undefined
|
||||||
top = null
|
top = undefined
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const bounds = node.getBoundingClientRect()
|
const bounds = node.getBoundingClientRect()
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
export enum PopoverAlignment {
|
||||||
|
Left = "left",
|
||||||
|
Right = "right",
|
||||||
|
LeftOutside = "left-outside",
|
||||||
|
RightOutside = "right-outside",
|
||||||
|
Center = "center",
|
||||||
|
RightContextMenu = "right-context-menu",
|
||||||
|
LeftContextMenu = "left-context-menu",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TooltipPosition {
|
||||||
|
Top = "top",
|
||||||
|
Right = "right",
|
||||||
|
Bottom = "bottom",
|
||||||
|
Left = "left",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TooltipType {
|
||||||
|
Default = "default",
|
||||||
|
Info = "info",
|
||||||
|
Positive = "positive",
|
||||||
|
Negative = "negative",
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
import "./bbui.css"
|
import "./bbui.css"
|
||||||
|
|
||||||
// Spectrum icons
|
|
||||||
import "@spectrum-css/icon/dist/index-vars.css"
|
import "@spectrum-css/icon/dist/index-vars.css"
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
export * from "./constants"
|
||||||
|
|
||||||
// Form components
|
// Form components
|
||||||
export { default as Input } from "./Form/Input.svelte"
|
export { default as Input } from "./Form/Input.svelte"
|
||||||
export { default as Stepper } from "./Form/Stepper.svelte"
|
export { default as Stepper } from "./Form/Stepper.svelte"
|
||||||
|
@ -45,7 +46,7 @@ export { default as ClearButton } from "./ClearButton/ClearButton.svelte"
|
||||||
export { default as Icon } from "./Icon/Icon.svelte"
|
export { default as Icon } from "./Icon/Icon.svelte"
|
||||||
export { default as IconAvatar } from "./Icon/IconAvatar.svelte"
|
export { default as IconAvatar } from "./Icon/IconAvatar.svelte"
|
||||||
export { default as DetailSummary } from "./DetailSummary/DetailSummary.svelte"
|
export { default as DetailSummary } from "./DetailSummary/DetailSummary.svelte"
|
||||||
export { default as Popover } from "./Popover/Popover.svelte"
|
export { default as Popover, type PopoverAPI } from "./Popover/Popover.svelte"
|
||||||
export { default as ProgressBar } from "./ProgressBar/ProgressBar.svelte"
|
export { default as ProgressBar } from "./ProgressBar/ProgressBar.svelte"
|
||||||
export { default as ProgressCircle } from "./ProgressCircle/ProgressCircle.svelte"
|
export { default as ProgressCircle } from "./ProgressCircle/ProgressCircle.svelte"
|
||||||
export { default as Label } from "./Label/Label.svelte"
|
export { default as Label } from "./Label/Label.svelte"
|
||||||
|
@ -92,7 +93,6 @@ export { default as IconSideNav } from "./IconSideNav/IconSideNav.svelte"
|
||||||
export { default as IconSideNavItem } from "./IconSideNav/IconSideNavItem.svelte"
|
export { default as IconSideNavItem } from "./IconSideNav/IconSideNavItem.svelte"
|
||||||
export { default as Accordion } from "./Accordion/Accordion.svelte"
|
export { default as Accordion } from "./Accordion/Accordion.svelte"
|
||||||
export { default as AbsTooltip } from "./Tooltip/AbsTooltip.svelte"
|
export { default as AbsTooltip } from "./Tooltip/AbsTooltip.svelte"
|
||||||
export { TooltipPosition, TooltipType } from "./Tooltip/AbsTooltip.svelte"
|
|
||||||
|
|
||||||
// Renderers
|
// Renderers
|
||||||
export { default as BoldRenderer } from "./Table/BoldRenderer.svelte"
|
export { default as BoldRenderer } from "./Table/BoldRenderer.svelte"
|
||||||
|
|
|
@ -81,7 +81,7 @@
|
||||||
"shortid": "2.2.15",
|
"shortid": "2.2.15",
|
||||||
"svelte-dnd-action": "^0.9.8",
|
"svelte-dnd-action": "^0.9.8",
|
||||||
"svelte-loading-spinners": "^0.1.1",
|
"svelte-loading-spinners": "^0.1.1",
|
||||||
"svelte-portal": "1.0.0",
|
"svelte-portal": "^2.2.1",
|
||||||
"yup": "^0.32.11"
|
"yup": "^0.32.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -1,14 +1,20 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { Popover, Icon } from "@budibase/bbui"
|
import {
|
||||||
|
Popover,
|
||||||
|
Icon,
|
||||||
|
PopoverAlignment,
|
||||||
|
type PopoverAPI,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
|
||||||
export let title
|
export let title: string = ""
|
||||||
export let align = "left"
|
export let subtitle: string | undefined = undefined
|
||||||
export let showPopover
|
export let align: PopoverAlignment = PopoverAlignment.Left
|
||||||
export let width
|
export let showPopover: boolean = true
|
||||||
|
export let width: number | undefined = undefined
|
||||||
|
|
||||||
let popover
|
let popover: PopoverAPI | undefined
|
||||||
let anchor
|
let anchor: HTMLElement | undefined
|
||||||
let open
|
let open: boolean = false
|
||||||
|
|
||||||
export const show = () => popover?.show()
|
export const show = () => popover?.show()
|
||||||
export const hide = () => popover?.hide()
|
export const hide = () => popover?.hide()
|
||||||
|
@ -30,21 +36,25 @@
|
||||||
{showPopover}
|
{showPopover}
|
||||||
on:open
|
on:open
|
||||||
on:close
|
on:close
|
||||||
customZindex={100}
|
customZIndex={100}
|
||||||
>
|
>
|
||||||
<div class="detail-popover">
|
<div class="detail-popover">
|
||||||
<div class="detail-popover__header">
|
<div class="detail-popover__header">
|
||||||
<div class="detail-popover__title">
|
<div class="detail-popover__title">
|
||||||
{title}
|
{title}
|
||||||
</div>
|
|
||||||
<Icon
|
<Icon
|
||||||
name="Close"
|
name="Close"
|
||||||
hoverable
|
hoverable
|
||||||
color="var(--spectrum-global-color-gray-600)"
|
color="var(--spectrum-global-color-gray-600)"
|
||||||
hoverColor="var(--spectum-global-color-gray-900)"
|
hoverColor="var(--spectrum-global-color-gray-900)"
|
||||||
on:click={hide}
|
on:click={hide}
|
||||||
|
size="S"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{#if subtitle}
|
||||||
|
<div class="detail-popover__subtitle">{subtitle}</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
<div class="detail-popover__body">
|
<div class="detail-popover__body">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
@ -56,14 +66,18 @@
|
||||||
background-color: var(--spectrum-alias-background-color-primary);
|
background-color: var(--spectrum-alias-background-color-primary);
|
||||||
}
|
}
|
||||||
.detail-popover__header {
|
.detail-popover__header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
border-bottom: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
padding: var(--spacing-l) var(--spacing-xl);
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
|
.detail-popover__title {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-bottom: 1px solid var(--spectrum-global-color-gray-300);
|
|
||||||
padding: var(--spacing-l) var(--spacing-xl);
|
|
||||||
}
|
|
||||||
.detail-popover__title {
|
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,281 @@
|
||||||
|
<script context="module" lang="ts">
|
||||||
|
interface JSONViewerClickContext {
|
||||||
|
label: string | undefined
|
||||||
|
value: any
|
||||||
|
path: (string | number)[]
|
||||||
|
}
|
||||||
|
export interface JSONViewerClickEvent {
|
||||||
|
detail: JSONViewerClickContext
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Icon } from "@budibase/bbui"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
|
export let label: string | undefined = undefined
|
||||||
|
export let value: any = undefined
|
||||||
|
export let root: boolean = true
|
||||||
|
export let path: (string | number)[] = []
|
||||||
|
export let showCopyIcon: boolean = false
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const Colors = {
|
||||||
|
Array: "var(--spectrum-global-color-gray-600)",
|
||||||
|
Object: "var(--spectrum-global-color-gray-600)",
|
||||||
|
Other: "var(--spectrum-global-color-blue-700)",
|
||||||
|
Undefined: "var(--spectrum-global-color-gray-600)",
|
||||||
|
Null: "var(--spectrum-global-color-yellow-700)",
|
||||||
|
String: "var(--spectrum-global-color-orange-700)",
|
||||||
|
Number: "var(--spectrum-global-color-purple-700)",
|
||||||
|
True: "var(--spectrum-global-color-celery-700)",
|
||||||
|
False: "var(--spectrum-global-color-red-700)",
|
||||||
|
Date: "var(--spectrum-global-color-green-700)",
|
||||||
|
}
|
||||||
|
|
||||||
|
let expanded = false
|
||||||
|
let valueExpanded = false
|
||||||
|
let clickContext: JSONViewerClickContext
|
||||||
|
|
||||||
|
$: isArray = Array.isArray(value)
|
||||||
|
$: isObject = value?.toString?.() === "[object Object]"
|
||||||
|
$: primitive = !(isArray || isObject)
|
||||||
|
$: keys = getKeys(isArray, isObject, value)
|
||||||
|
$: expandable = keys.length > 0
|
||||||
|
$: displayValue = getDisplayValue(isArray, isObject, keys, value)
|
||||||
|
$: style = getStyle(isArray, isObject, value)
|
||||||
|
$: clickContext = { value, label, path }
|
||||||
|
|
||||||
|
const getKeys = (isArray: boolean, isObject: boolean, value: any) => {
|
||||||
|
if (isArray) {
|
||||||
|
return [...value.keys()]
|
||||||
|
}
|
||||||
|
if (isObject) {
|
||||||
|
return Object.keys(value).sort()
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluralise = (text: string, number: number) => {
|
||||||
|
return number === 1 ? text : text + "s"
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDisplayValue = (
|
||||||
|
isArray: boolean,
|
||||||
|
isObject: boolean,
|
||||||
|
keys: any[],
|
||||||
|
value: any
|
||||||
|
) => {
|
||||||
|
if (isArray) {
|
||||||
|
return `[] ${keys.length} ${pluralise("item", keys.length)}`
|
||||||
|
}
|
||||||
|
if (isObject) {
|
||||||
|
return `{} ${keys.length} ${pluralise("key", keys.length)}`
|
||||||
|
}
|
||||||
|
if (typeof value === "object" && typeof value?.toString === "function") {
|
||||||
|
return value.toString()
|
||||||
|
} else {
|
||||||
|
return JSON.stringify(value, null, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyle = (isArray: boolean, isObject: boolean, value: any) => {
|
||||||
|
return `color:${getColor(isArray, isObject, value)};`
|
||||||
|
}
|
||||||
|
|
||||||
|
const getColor = (isArray: boolean, isObject: boolean, value: any) => {
|
||||||
|
if (isArray) {
|
||||||
|
return Colors.Array
|
||||||
|
}
|
||||||
|
if (isObject) {
|
||||||
|
return Colors.Object
|
||||||
|
}
|
||||||
|
if (value instanceof Date) {
|
||||||
|
return Colors.Date
|
||||||
|
}
|
||||||
|
switch (value) {
|
||||||
|
case undefined:
|
||||||
|
return Colors.Undefined
|
||||||
|
case null:
|
||||||
|
return Colors.Null
|
||||||
|
case true:
|
||||||
|
return Colors.True
|
||||||
|
case false:
|
||||||
|
return Colors.False
|
||||||
|
}
|
||||||
|
switch (typeof value) {
|
||||||
|
case "string":
|
||||||
|
return Colors.String
|
||||||
|
case "number":
|
||||||
|
return Colors.Number
|
||||||
|
}
|
||||||
|
return Colors.Other
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<div class="binding-node">
|
||||||
|
{#if label != null}
|
||||||
|
<div class="binding-text">
|
||||||
|
<div class="binding-arrow" class:expanded>
|
||||||
|
{#if expandable}
|
||||||
|
<Icon
|
||||||
|
name="Play"
|
||||||
|
hoverable
|
||||||
|
color="var(--spectrum-global-color-gray-600)"
|
||||||
|
hoverColor="var(--spectrum-global-color-gray-900)"
|
||||||
|
on:click={() => (expanded = !expanded)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="binding-label"
|
||||||
|
class:primitive
|
||||||
|
class:expandable
|
||||||
|
on:click={() => (expanded = !expanded)}
|
||||||
|
on:click={() => dispatch("click-label", clickContext)}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="binding-value"
|
||||||
|
class:primitive
|
||||||
|
class:expanded={valueExpanded}
|
||||||
|
{style}
|
||||||
|
on:click={() => (valueExpanded = !valueExpanded)}
|
||||||
|
on:click={() => dispatch("click-value", clickContext)}
|
||||||
|
>
|
||||||
|
{displayValue}
|
||||||
|
</div>
|
||||||
|
{#if showCopyIcon}
|
||||||
|
<div class="copy-value-icon">
|
||||||
|
<Icon
|
||||||
|
name="Copy"
|
||||||
|
size="XS"
|
||||||
|
hoverable
|
||||||
|
color="var(--spectrum-global-color-gray-600)"
|
||||||
|
hoverColor="var(--spectrum-global-color-gray-900)"
|
||||||
|
on:click={() => dispatch("click-copy", clickContext)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if expandable && (expanded || label == null)}
|
||||||
|
<div class="binding-children" class:root>
|
||||||
|
{#each keys as key}
|
||||||
|
<svelte:self
|
||||||
|
label={key}
|
||||||
|
value={value[key]}
|
||||||
|
root={false}
|
||||||
|
path={[...path, key]}
|
||||||
|
{showCopyIcon}
|
||||||
|
on:click-label
|
||||||
|
on:click-value
|
||||||
|
on:click-copy
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.binding-node {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Expand arrow */
|
||||||
|
.binding-arrow {
|
||||||
|
margin: -3px 6px -2px 4px;
|
||||||
|
flex: 0 0 9px;
|
||||||
|
transition: transform 130ms ease-out;
|
||||||
|
}
|
||||||
|
.binding-arrow :global(svg) {
|
||||||
|
width: 9px;
|
||||||
|
}
|
||||||
|
.binding-arrow.expanded {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main text wrapper */
|
||||||
|
.binding-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
align-items: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Size label and value according to type */
|
||||||
|
.binding-label {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
margin-right: 8px;
|
||||||
|
transition: color 130ms ease-out;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.binding-label.expandable:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--spectrum-global-color-gray-900);
|
||||||
|
}
|
||||||
|
.binding-value {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
transition: filter 130ms ease-out;
|
||||||
|
}
|
||||||
|
.binding-value.primitive:hover {
|
||||||
|
filter: brightness(1.25);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.binding-value.expanded {
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: wrap;
|
||||||
|
}
|
||||||
|
.binding-label.primitive {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
max-width: 75%;
|
||||||
|
}
|
||||||
|
.binding-value.primitive {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Trim spans in the highlighted HTML */
|
||||||
|
.binding-value :global(span) {
|
||||||
|
overflow: hidden !important;
|
||||||
|
text-overflow: ellipsis !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy icon for value */
|
||||||
|
.copy-value-icon {
|
||||||
|
display: none;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
.binding-text:hover .copy-value-icon {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Children wrapper */
|
||||||
|
.binding-children {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
border-left: 1px solid var(--spectrum-global-color-gray-400);
|
||||||
|
margin-left: 20px;
|
||||||
|
padding-left: 3px;
|
||||||
|
}
|
||||||
|
.binding-children.root {
|
||||||
|
border-left: none;
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -25,7 +25,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Popover
|
<Popover
|
||||||
customZindex={998}
|
customZIndex={998}
|
||||||
bind:this={formPopover}
|
bind:this={formPopover}
|
||||||
align="center"
|
align="center"
|
||||||
anchor={formPopoverAnchor}
|
anchor={formPopoverAnchor}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { Icon, Input, Drawer, Button } from "@budibase/bbui"
|
import { Icon, Input, Drawer, Button } from "@budibase/bbui"
|
||||||
import {
|
import {
|
||||||
readableToRuntimeBinding,
|
readableToRuntimeBinding,
|
||||||
|
@ -10,25 +10,25 @@
|
||||||
import { builderStore } from "@/stores/builder"
|
import { builderStore } from "@/stores/builder"
|
||||||
|
|
||||||
export let panel = ClientBindingPanel
|
export let panel = ClientBindingPanel
|
||||||
export let value = ""
|
export let value: any = ""
|
||||||
export let bindings = []
|
export let bindings: any[] = []
|
||||||
export let title
|
export let title: string | undefined = undefined
|
||||||
export let placeholder
|
export let placeholder: string | undefined = undefined
|
||||||
export let label
|
export let label: string | undefined = undefined
|
||||||
export let disabled = false
|
export let disabled: boolean = false
|
||||||
export let allowHBS = true
|
export let allowHBS: boolean = true
|
||||||
export let allowJS = true
|
export let allowJS: boolean = true
|
||||||
export let allowHelpers = true
|
export let allowHelpers: boolean = true
|
||||||
export let updateOnChange = true
|
export let updateOnChange: boolean = true
|
||||||
export let key
|
export let key: string | null = null
|
||||||
export let disableBindings = false
|
export let disableBindings: boolean = false
|
||||||
export let forceModal = false
|
export let forceModal: boolean = false
|
||||||
export let context = null
|
export let context = null
|
||||||
export let autocomplete
|
export let autocomplete: boolean | undefined = undefined
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let bindingDrawer
|
let bindingDrawer: any
|
||||||
let currentVal = value
|
let currentVal = value
|
||||||
|
|
||||||
$: readableValue = runtimeToReadableBinding(bindings, value)
|
$: readableValue = runtimeToReadableBinding(bindings, value)
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
const saveBinding = () => {
|
const saveBinding = () => {
|
||||||
onChange(tempValue)
|
onChange(tempValue)
|
||||||
onBlur()
|
onBlur()
|
||||||
builderStore.propertyFocus()
|
builderStore.propertyFocus(null)
|
||||||
bindingDrawer.hide()
|
bindingDrawer.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
save: saveBinding,
|
save: saveBinding,
|
||||||
})
|
})
|
||||||
|
|
||||||
const onChange = value => {
|
const onChange = (value: any) => {
|
||||||
currentVal = readableToRuntimeBinding(bindings, value)
|
currentVal = readableToRuntimeBinding(bindings, value)
|
||||||
dispatch("change", currentVal)
|
dispatch("change", currentVal)
|
||||||
}
|
}
|
||||||
|
@ -55,8 +55,8 @@
|
||||||
dispatch("blur", currentVal)
|
dispatch("blur", currentVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onDrawerHide = e => {
|
const onDrawerHide = (e: any) => {
|
||||||
builderStore.propertyFocus()
|
builderStore.propertyFocus(null)
|
||||||
dispatch("drawerHide", e.detail)
|
dispatch("drawerHide", e.detail)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
runtimeToReadableBinding,
|
runtimeToReadableBinding,
|
||||||
} from "@/dataBinding"
|
} from "@/dataBinding"
|
||||||
import { builderStore } from "@/stores/builder"
|
import { builderStore } from "@/stores/builder"
|
||||||
import { onDestroy } from "svelte"
|
|
||||||
|
|
||||||
export let label = ""
|
export let label = ""
|
||||||
export let labelHidden = false
|
export let labelHidden = false
|
||||||
|
@ -32,7 +31,7 @@
|
||||||
$: safeValue = getSafeValue(value, defaultValue, allBindings)
|
$: safeValue = getSafeValue(value, defaultValue, allBindings)
|
||||||
$: replaceBindings = val => readableToRuntimeBinding(allBindings, val)
|
$: replaceBindings = val => readableToRuntimeBinding(allBindings, val)
|
||||||
|
|
||||||
$: if (!Array.isArray(value)) {
|
$: if (value) {
|
||||||
highlightType =
|
highlightType =
|
||||||
highlightedProp?.key === key ? `highlighted-${highlightedProp?.type}` : ""
|
highlightedProp?.key === key ? `highlighted-${highlightedProp?.type}` : ""
|
||||||
}
|
}
|
||||||
|
@ -75,12 +74,6 @@
|
||||||
? defaultValue
|
? defaultValue
|
||||||
: enriched
|
: enriched
|
||||||
}
|
}
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
if (highlightedProp) {
|
|
||||||
builderStore.highlightSetting(null)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -150,10 +143,10 @@
|
||||||
.property-control.highlighted {
|
.property-control.highlighted {
|
||||||
background: var(--spectrum-global-color-gray-300);
|
background: var(--spectrum-global-color-gray-300);
|
||||||
border-color: var(--spectrum-global-color-static-red-600);
|
border-color: var(--spectrum-global-color-static-red-600);
|
||||||
margin-top: -3.5px;
|
margin-top: -4px;
|
||||||
margin-bottom: -3.5px;
|
margin-bottom: -4px;
|
||||||
padding-bottom: 3.5px;
|
padding-bottom: 4px;
|
||||||
padding-top: 3.5px;
|
padding-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.property-control.property-focus :global(input) {
|
.property-control.property-focus :global(input) {
|
||||||
|
|
|
@ -96,8 +96,8 @@
|
||||||
maxWidth={300}
|
maxWidth={300}
|
||||||
dismissible={false}
|
dismissible={false}
|
||||||
offset={12}
|
offset={12}
|
||||||
handlePostionUpdate={tourStep?.positionHandler}
|
handlePositionUpdate={tourStep?.positionHandler}
|
||||||
customZindex={3}
|
customZIndex={3}
|
||||||
>
|
>
|
||||||
<div class="tour-content">
|
<div class="tour-content">
|
||||||
<Layout noPadding gap="M">
|
<Layout noPadding gap="M">
|
||||||
|
|
|
@ -1159,10 +1159,17 @@ export const buildFormSchema = (component, asset) => {
|
||||||
* Returns an array of the keys of any state variables which are set anywhere
|
* Returns an array of the keys of any state variables which are set anywhere
|
||||||
* in the app.
|
* in the app.
|
||||||
*/
|
*/
|
||||||
export const getAllStateVariables = () => {
|
export const getAllStateVariables = screen => {
|
||||||
// Find all button action settings in all components
|
let assets = []
|
||||||
|
if (screen) {
|
||||||
|
// only include state variables from a specific screen
|
||||||
|
assets.push(screen)
|
||||||
|
} else {
|
||||||
|
// otherwise include state variables from all screens
|
||||||
|
assets = getAllAssets()
|
||||||
|
}
|
||||||
let eventSettings = []
|
let eventSettings = []
|
||||||
getAllAssets().forEach(asset => {
|
assets.forEach(asset => {
|
||||||
findAllMatchingComponents(asset.props, component => {
|
findAllMatchingComponents(asset.props, component => {
|
||||||
const settings = componentStore.getComponentSettings(component._component)
|
const settings = componentStore.getComponentSettings(component._component)
|
||||||
const nestedTypes = [
|
const nestedTypes = [
|
||||||
|
@ -1214,11 +1221,17 @@ export const getAllStateVariables = () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add on load settings from screens
|
// Add on load settings from screens
|
||||||
|
if (screen) {
|
||||||
|
if (screen.onLoad) {
|
||||||
|
eventSettings.push(screen.onLoad)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
get(screenStore).screens.forEach(screen => {
|
get(screenStore).screens.forEach(screen => {
|
||||||
if (screen.onLoad) {
|
if (screen.onLoad) {
|
||||||
eventSettings.push(screen.onLoad)
|
eventSettings.push(screen.onLoad)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Extract all state keys from any "update state" actions in each setting
|
// Extract all state keys from any "update state" actions in each setting
|
||||||
let bindingSet = new Set()
|
let bindingSet = new Set()
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
} from "@/dataBinding"
|
} from "@/dataBinding"
|
||||||
import { ActionButton, notifications } from "@budibase/bbui"
|
import { ActionButton, notifications } from "@budibase/bbui"
|
||||||
import { capitalise } from "@/helpers"
|
import { capitalise } from "@/helpers"
|
||||||
|
import { builderStore } from "@/stores/builder"
|
||||||
import TourWrap from "@/components/portal/onboarding/TourWrap.svelte"
|
import TourWrap from "@/components/portal/onboarding/TourWrap.svelte"
|
||||||
import { TOUR_STEP_KEYS } from "@/components/portal/onboarding/tours.js"
|
import { TOUR_STEP_KEYS } from "@/components/portal/onboarding/tours.js"
|
||||||
|
|
||||||
|
@ -55,6 +56,17 @@
|
||||||
$: id = $selectedComponent?._id
|
$: id = $selectedComponent?._id
|
||||||
$: id, (section = tabs[0])
|
$: id, (section = tabs[0])
|
||||||
$: componentName = getComponentName(componentInstance)
|
$: componentName = getComponentName(componentInstance)
|
||||||
|
|
||||||
|
$: highlightedSetting = $builderStore.highlightedSetting
|
||||||
|
$: if (highlightedSetting) {
|
||||||
|
if (highlightedSetting.key === "_conditions") {
|
||||||
|
section = "conditions"
|
||||||
|
} else if (highlightedSetting.key === "_styles") {
|
||||||
|
section = "styles"
|
||||||
|
} else {
|
||||||
|
section = "settings"
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $selectedComponent}
|
{#if $selectedComponent}
|
||||||
|
@ -98,7 +110,7 @@
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
{#if section == "settings"}
|
{#if section === "settings"}
|
||||||
<TourWrap
|
<TourWrap
|
||||||
stepKeys={[
|
stepKeys={[
|
||||||
BUILDER_FORM_CREATE_STEPS,
|
BUILDER_FORM_CREATE_STEPS,
|
||||||
|
@ -115,7 +127,7 @@
|
||||||
/>
|
/>
|
||||||
</TourWrap>
|
</TourWrap>
|
||||||
{/if}
|
{/if}
|
||||||
{#if section == "styles"}
|
{#if section === "styles"}
|
||||||
<DesignSection
|
<DesignSection
|
||||||
{componentInstance}
|
{componentInstance}
|
||||||
{componentBindings}
|
{componentBindings}
|
||||||
|
@ -130,7 +142,7 @@
|
||||||
componentTitle={title}
|
componentTitle={title}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if section == "conditions"}
|
{#if section === "conditions"}
|
||||||
<ConditionalUISection
|
<ConditionalUISection
|
||||||
{componentInstance}
|
{componentInstance}
|
||||||
{componentDefinition}
|
{componentDefinition}
|
||||||
|
|
|
@ -190,7 +190,7 @@
|
||||||
<Icon name="DragHandle" size="XL" />
|
<Icon name="DragHandle" size="XL" />
|
||||||
</div>
|
</div>
|
||||||
<Select
|
<Select
|
||||||
placeholder={null}
|
placeholder={false}
|
||||||
options={actionOptions}
|
options={actionOptions}
|
||||||
bind:value={condition.action}
|
bind:value={condition.action}
|
||||||
/>
|
/>
|
||||||
|
@ -227,7 +227,7 @@
|
||||||
on:change={e => (condition.newValue = e.detail)}
|
on:change={e => (condition.newValue = e.detail)}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
placeholder={null}
|
placeholder={false}
|
||||||
options={getOperatorOptions(condition)}
|
options={getOperatorOptions(condition)}
|
||||||
bind:value={condition.operator}
|
bind:value={condition.operator}
|
||||||
on:change={e => onOperatorChange(condition, e.detail)}
|
on:change={e => onOperatorChange(condition, e.detail)}
|
||||||
|
@ -236,7 +236,7 @@
|
||||||
disabled={condition.noValue || condition.operator === "oneOf"}
|
disabled={condition.noValue || condition.operator === "oneOf"}
|
||||||
options={valueTypeOptions}
|
options={valueTypeOptions}
|
||||||
bind:value={condition.valueType}
|
bind:value={condition.valueType}
|
||||||
placeholder={null}
|
placeholder={false}
|
||||||
on:change={e => onValueTypeChange(condition, e.detail)}
|
on:change={e => onValueTypeChange(condition, e.detail)}
|
||||||
/>
|
/>
|
||||||
{#if ["string", "number"].includes(condition.valueType)}
|
{#if ["string", "number"].includes(condition.valueType)}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import { componentStore } from "@/stores/builder"
|
import { componentStore } from "@/stores/builder"
|
||||||
import ConditionalUIDrawer from "./ConditionalUIDrawer.svelte"
|
import ConditionalUIDrawer from "./ConditionalUIDrawer.svelte"
|
||||||
import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
|
import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
|
||||||
|
import { builderStore } from "@/stores/builder"
|
||||||
|
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
export let componentDefinition
|
export let componentDefinition
|
||||||
|
@ -18,6 +19,8 @@
|
||||||
let tempValue
|
let tempValue
|
||||||
let drawer
|
let drawer
|
||||||
|
|
||||||
|
$: highlighted = $builderStore.highlightedSetting?.key === "_conditions"
|
||||||
|
|
||||||
const openDrawer = () => {
|
const openDrawer = () => {
|
||||||
tempValue = JSON.parse(JSON.stringify(componentInstance?._conditions ?? []))
|
tempValue = JSON.parse(JSON.stringify(componentInstance?._conditions ?? []))
|
||||||
drawer.show()
|
drawer.show()
|
||||||
|
@ -52,7 +55,9 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DetailSummary name={"Conditions"} collapsible={false}>
|
<DetailSummary name={"Conditions"} collapsible={false}>
|
||||||
<ActionButton on:click={openDrawer}>{conditionText}</ActionButton>
|
<div class:highlighted>
|
||||||
|
<ActionButton fullWidth on:click={openDrawer}>{conditionText}</ActionButton>
|
||||||
|
</div>
|
||||||
</DetailSummary>
|
</DetailSummary>
|
||||||
<Drawer bind:this={drawer} title="Conditions">
|
<Drawer bind:this={drawer} title="Conditions">
|
||||||
<svelte:fragment slot="description">
|
<svelte:fragment slot="description">
|
||||||
|
@ -61,3 +66,13 @@
|
||||||
<Button cta slot="buttons" on:click={() => save()}>Save</Button>
|
<Button cta slot="buttons" on:click={() => save()}>Save</Button>
|
||||||
<ConditionalUIDrawer slot="body" bind:conditions={tempValue} {bindings} />
|
<ConditionalUIDrawer slot="body" bind:conditions={tempValue} {bindings} />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.highlighted {
|
||||||
|
background: var(--spectrum-global-color-gray-300);
|
||||||
|
border-left: 4px solid var(--spectrum-semantic-informative-color-background);
|
||||||
|
transition: background 130ms ease-out, border-color 130ms ease-out;
|
||||||
|
margin: -4px calc(-1 * var(--spacing-xl));
|
||||||
|
padding: 4px var(--spacing-xl) 4px calc(var(--spacing-xl) - 4px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
readableToRuntimeBinding,
|
readableToRuntimeBinding,
|
||||||
runtimeToReadableBinding,
|
runtimeToReadableBinding,
|
||||||
} from "@/dataBinding"
|
} from "@/dataBinding"
|
||||||
|
import { builderStore } from "@/stores/builder"
|
||||||
|
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
export let componentDefinition
|
export let componentDefinition
|
||||||
|
@ -32,6 +33,8 @@
|
||||||
|
|
||||||
$: icon = componentDefinition?.icon
|
$: icon = componentDefinition?.icon
|
||||||
|
|
||||||
|
$: highlighted = $builderStore.highlightedSetting?.key === "_styles"
|
||||||
|
|
||||||
const openDrawer = () => {
|
const openDrawer = () => {
|
||||||
tempValue = runtimeToReadableBinding(
|
tempValue = runtimeToReadableBinding(
|
||||||
bindings,
|
bindings,
|
||||||
|
@ -55,7 +58,7 @@
|
||||||
name={`Custom CSS${componentInstance?._styles?.custom ? " *" : ""}`}
|
name={`Custom CSS${componentInstance?._styles?.custom ? " *" : ""}`}
|
||||||
collapsible={false}
|
collapsible={false}
|
||||||
>
|
>
|
||||||
<div>
|
<div class:highlighted>
|
||||||
<ActionButton on:click={openDrawer}>Edit custom CSS</ActionButton>
|
<ActionButton on:click={openDrawer}>Edit custom CSS</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
</DetailSummary>
|
</DetailSummary>
|
||||||
|
@ -97,4 +100,12 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-m);
|
gap: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.highlighted {
|
||||||
|
background: var(--spectrum-global-color-gray-300);
|
||||||
|
border-left: 4px solid var(--spectrum-semantic-informative-color-background);
|
||||||
|
transition: background 130ms ease-out, border-color 130ms ease-out;
|
||||||
|
margin: -4px calc(-1 * var(--spacing-xl));
|
||||||
|
padding: 4px var(--spacing-xl) 4px calc(var(--spacing-xl) - 4px);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Layout gap="XS" paddingX="L" paddingY="XL">
|
<Layout gap="XS" paddingX="XL" paddingY="XL">
|
||||||
{#if activeTab === "theme"}
|
{#if activeTab === "theme"}
|
||||||
<ThemePanel />
|
<ThemePanel />
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
if (id === `${$screenStore.selectedScreenId}-screen`) return true
|
if (id === `${$screenStore.selectedScreenId}-screen`) return true
|
||||||
if (id === `${$screenStore.selectedScreenId}-navigation`) return true
|
if (id === `${$screenStore.selectedScreenId}-navigation`) return true
|
||||||
|
|
||||||
return !!findComponent($selectedScreen.props, id)
|
return !!findComponent($selectedScreen?.props, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep URL and state in sync for selected component ID
|
// Keep URL and state in sync for selected component ID
|
||||||
|
|
|
@ -50,6 +50,9 @@
|
||||||
margin-bottom: 9px;
|
margin-bottom: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
.header-left :global(div) {
|
.header-left :global(div) {
|
||||||
border-right: none;
|
border-right: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import { Helpers, notifications } from "@budibase/bbui"
|
||||||
|
import { processObjectSync } from "@budibase/string-templates"
|
||||||
|
import {
|
||||||
|
previewStore,
|
||||||
|
selectedScreen,
|
||||||
|
componentStore,
|
||||||
|
snippets,
|
||||||
|
} from "@/stores/builder"
|
||||||
|
import { getBindableProperties } from "@/dataBinding"
|
||||||
|
import JSONViewer, {
|
||||||
|
type JSONViewerClickEvent,
|
||||||
|
} from "@/components/common/JSONViewer.svelte"
|
||||||
|
|
||||||
|
// Minimal typing for the real data binding structure, as none exists
|
||||||
|
type DataBinding = {
|
||||||
|
category: string
|
||||||
|
runtimeBinding: string
|
||||||
|
readableBinding: string
|
||||||
|
}
|
||||||
|
|
||||||
|
$: previewContext = $previewStore.selectedComponentContext || {}
|
||||||
|
$: selectedComponentId = $componentStore.selectedComponentId
|
||||||
|
$: context = makeContext(previewContext, bindings)
|
||||||
|
$: bindings = getBindableProperties($selectedScreen, selectedComponentId)
|
||||||
|
|
||||||
|
const makeContext = (
|
||||||
|
previewContext: Record<string, any>,
|
||||||
|
bindings: DataBinding[]
|
||||||
|
) => {
|
||||||
|
// Create a single big array to enrich in one go
|
||||||
|
const bindingStrings = bindings.map(binding => {
|
||||||
|
if (binding.runtimeBinding.startsWith('trim "')) {
|
||||||
|
// Account for nasty hardcoded HBS bindings for roles, for legacy
|
||||||
|
// compatibility
|
||||||
|
return `{{ ${binding.runtimeBinding} }}`
|
||||||
|
} else {
|
||||||
|
return `{{ literal ${binding.runtimeBinding} }}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const bindingEvaluations = processObjectSync(bindingStrings, {
|
||||||
|
...previewContext,
|
||||||
|
snippets: $snippets,
|
||||||
|
}) as any[]
|
||||||
|
|
||||||
|
// Deeply set values for all readable bindings
|
||||||
|
const enrichedBindings: any[] = bindings.map((binding, idx) => {
|
||||||
|
return {
|
||||||
|
...binding,
|
||||||
|
value: bindingEvaluations[idx],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let context = {}
|
||||||
|
for (let binding of enrichedBindings) {
|
||||||
|
Helpers.deepSet(context, binding.readableBinding, binding.value)
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyBinding = (e: JSONViewerClickEvent) => {
|
||||||
|
const readableBinding = `{{ ${e.detail.path.join(".")} }}`
|
||||||
|
Helpers.copyToClipboard(readableBinding)
|
||||||
|
notifications.success("Binding copied to clipboard")
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(previewStore.requestComponentContext)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="bindings-panel">
|
||||||
|
<JSONViewer value={context} showCopyIcon on:click-copy={copyBinding} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.bindings-panel {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: var(--spacing-xl) var(--spacing-l) var(--spacing-l)
|
||||||
|
var(--spacing-l);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,6 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { notifications, Icon, Body } from "@budibase/bbui"
|
import { notifications, Icon } from "@budibase/bbui"
|
||||||
import { isActive, goto } from "@roxi/routify"
|
|
||||||
import {
|
import {
|
||||||
selectedScreen,
|
selectedScreen,
|
||||||
screenStore,
|
screenStore,
|
||||||
|
@ -13,23 +12,12 @@
|
||||||
import ComponentTree from "./ComponentTree.svelte"
|
import ComponentTree from "./ComponentTree.svelte"
|
||||||
import { dndStore, DropPosition } from "./dndStore.js"
|
import { dndStore, DropPosition } from "./dndStore.js"
|
||||||
import DNDPositionIndicator from "./DNDPositionIndicator.svelte"
|
import DNDPositionIndicator from "./DNDPositionIndicator.svelte"
|
||||||
import ComponentKeyHandler from "./ComponentKeyHandler.svelte"
|
|
||||||
import ComponentScrollWrapper from "./ComponentScrollWrapper.svelte"
|
import ComponentScrollWrapper from "./ComponentScrollWrapper.svelte"
|
||||||
import getScreenContextMenuItems from "./getScreenContextMenuItems"
|
import getScreenContextMenuItems from "./getScreenContextMenuItems"
|
||||||
|
|
||||||
let scrolling = false
|
|
||||||
|
|
||||||
$: screenComponentId = `${$screenStore.selectedScreenId}-screen`
|
$: screenComponentId = `${$screenStore.selectedScreenId}-screen`
|
||||||
$: navComponentId = `${$screenStore.selectedScreenId}-navigation`
|
$: navComponentId = `${$screenStore.selectedScreenId}-navigation`
|
||||||
|
|
||||||
const toNewComponentRoute = () => {
|
|
||||||
if ($isActive(`./:componentId/new`)) {
|
|
||||||
$goto(`./:componentId`)
|
|
||||||
} else {
|
|
||||||
$goto(`./:componentId/new`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onDrop = async () => {
|
const onDrop = async () => {
|
||||||
try {
|
try {
|
||||||
await dndStore.actions.drop()
|
await dndStore.actions.drop()
|
||||||
|
@ -39,10 +27,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleScroll = e => {
|
|
||||||
scrolling = e.target.scrollTop !== 0
|
|
||||||
}
|
|
||||||
|
|
||||||
const hover = hoverStore.hover
|
const hover = hoverStore.hover
|
||||||
|
|
||||||
// showCopy is used to hide the copy button when the user right-clicks the empty
|
// showCopy is used to hide the copy button when the user right-clicks the empty
|
||||||
|
@ -72,17 +56,9 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
||||||
<div class="components">
|
<div class="components">
|
||||||
<div class="header" class:scrolling>
|
|
||||||
<Body size="S">Components</Body>
|
|
||||||
<div on:click={toNewComponentRoute} class="addButton">
|
|
||||||
<Icon name="Add" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="list-panel">
|
<div class="list-panel">
|
||||||
<ComponentScrollWrapper on:scroll={handleScroll}>
|
<ComponentScrollWrapper>
|
||||||
<ul
|
<ul
|
||||||
class="componentTree"
|
class="componentTree"
|
||||||
on:contextmenu={e => openScreenContextMenu(e, false)}
|
on:contextmenu={e => openScreenContextMenu(e, false)}
|
||||||
|
@ -159,7 +135,6 @@
|
||||||
</ul>
|
</ul>
|
||||||
</ComponentScrollWrapper>
|
</ComponentScrollWrapper>
|
||||||
</div>
|
</div>
|
||||||
<ComponentKeyHandler />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -168,35 +143,13 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
padding-top: var(--spacing-l);
|
||||||
|
|
||||||
.header {
|
|
||||||
height: 50px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: var(--spacing-l);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
border-bottom: 2px solid transparent;
|
|
||||||
transition: border-bottom 130ms ease-out;
|
|
||||||
}
|
|
||||||
.header.scrolling {
|
|
||||||
border-bottom: var(--border-light);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.components :global(.nav-item) {
|
.components :global(.nav-item) {
|
||||||
padding-right: 8px !important;
|
padding-right: 8px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.addButton {
|
|
||||||
margin-left: auto;
|
|
||||||
color: var(--grey-7);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.addButton:hover {
|
|
||||||
color: var(--ink);
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-panel {
|
.list-panel {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -1,25 +1,60 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import ScreenList from "./ScreenList/index.svelte"
|
import ScreenList from "./ScreenList/index.svelte"
|
||||||
import ComponentList from "./ComponentList/index.svelte"
|
import ComponentList from "./ComponentList/index.svelte"
|
||||||
import { getHorizontalResizeActions } from "@/components/common/resizable"
|
import { getHorizontalResizeActions } from "@/components/common/resizable"
|
||||||
|
import { ActionButton } from "@budibase/bbui"
|
||||||
|
import StatePanel from "./StatePanel.svelte"
|
||||||
|
import BindingsPanel from "./BindingsPanel.svelte"
|
||||||
|
import ComponentKeyHandler from "./ComponentKeyHandler.svelte"
|
||||||
|
|
||||||
const [resizable, resizableHandle] = getHorizontalResizeActions()
|
const [resizable, resizableHandle] = getHorizontalResizeActions()
|
||||||
|
|
||||||
|
const Tabs = {
|
||||||
|
Components: "Components",
|
||||||
|
Bindings: "Bindings",
|
||||||
|
State: "State",
|
||||||
|
}
|
||||||
|
|
||||||
|
let activeTab = Tabs.Components
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="panel" use:resizable>
|
<div class="panel" use:resizable>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<ScreenList />
|
<ScreenList />
|
||||||
|
<div class="tabs">
|
||||||
|
{#each Object.values(Tabs) as tab}
|
||||||
|
<ActionButton
|
||||||
|
quiet
|
||||||
|
selected={activeTab === tab}
|
||||||
|
on:click={() => (activeTab = tab)}
|
||||||
|
>
|
||||||
|
<div class="tab-label">
|
||||||
|
{tab}
|
||||||
|
{#if tab !== Tabs.Components}
|
||||||
|
<div class="new">NEW</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</ActionButton>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{#if activeTab === Tabs.Components}
|
||||||
<ComponentList />
|
<ComponentList />
|
||||||
|
{:else if activeTab === Tabs.Bindings}
|
||||||
|
<BindingsPanel />
|
||||||
|
{:else if activeTab === Tabs.State}
|
||||||
|
<div class="tab-content"><StatePanel /></div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="divider">
|
<div class="divider">
|
||||||
<div class="dividerClickExtender" role="separator" use:resizableHandle />
|
<div class="dividerClickExtender" role="separator" use:resizableHandle />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ComponentKeyHandler />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.panel {
|
.panel {
|
||||||
display: flex;
|
display: flex;
|
||||||
min-width: 270px;
|
min-width: 310px;
|
||||||
width: 310px;
|
width: 310px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
@ -34,6 +69,34 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 8px;
|
||||||
|
padding: var(--spacing-m) var(--spacing-l);
|
||||||
|
border-bottom: var(--border-light);
|
||||||
|
}
|
||||||
|
.tab-content {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: var(--spacing-l);
|
||||||
|
}
|
||||||
|
.tab-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.new {
|
||||||
|
font-size: 8px;
|
||||||
|
background: var(--bb-indigo);
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 1px 3px;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.divider {
|
.divider {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -45,7 +108,6 @@
|
||||||
background: var(--spectrum-global-color-gray-300);
|
background: var(--spectrum-global-color-gray-300);
|
||||||
cursor: row-resize;
|
cursor: row-resize;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dividerClickExtender {
|
.dividerClickExtender {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: col-resize;
|
cursor: col-resize;
|
||||||
|
|
|
@ -0,0 +1,336 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import { Select } from "@budibase/bbui"
|
||||||
|
import type {
|
||||||
|
Component,
|
||||||
|
ComponentCondition,
|
||||||
|
EventHandler,
|
||||||
|
Screen,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import { getAllStateVariables, getBindableProperties } from "@/dataBinding"
|
||||||
|
import {
|
||||||
|
componentStore,
|
||||||
|
selectedScreen,
|
||||||
|
builderStore,
|
||||||
|
previewStore,
|
||||||
|
} from "@/stores/builder"
|
||||||
|
import {
|
||||||
|
decodeJSBinding,
|
||||||
|
findHBSBlocks,
|
||||||
|
isJSBinding,
|
||||||
|
processStringSync,
|
||||||
|
} from "@budibase/string-templates"
|
||||||
|
import DrawerBindableInput from "@/components/common/bindings/DrawerBindableInput.svelte"
|
||||||
|
import { type ComponentSetting } from "@/stores/builder/components"
|
||||||
|
|
||||||
|
interface ComponentUsingState {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
setting: string
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedKey: string | undefined = undefined
|
||||||
|
let componentsUsingState: ComponentUsingState[] = []
|
||||||
|
let componentsUpdatingState: ComponentUsingState[] = []
|
||||||
|
let editorValue: string = ""
|
||||||
|
|
||||||
|
$: selectStateKey($selectedScreen, selectedKey)
|
||||||
|
$: keyOptions = getAllStateVariables($selectedScreen)
|
||||||
|
$: bindings = getBindableProperties(
|
||||||
|
$selectedScreen,
|
||||||
|
$componentStore.selectedComponentId
|
||||||
|
)
|
||||||
|
|
||||||
|
// Auto-select first valid state key
|
||||||
|
$: {
|
||||||
|
if (keyOptions.length && !keyOptions.includes(selectedKey)) {
|
||||||
|
selectedKey = keyOptions[0]
|
||||||
|
} else if (!keyOptions.length) {
|
||||||
|
selectedKey = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectStateKey = (
|
||||||
|
screen: Screen | undefined,
|
||||||
|
key: string | undefined
|
||||||
|
) => {
|
||||||
|
if (screen && key) {
|
||||||
|
searchComponents(screen, key)
|
||||||
|
editorValue = $previewStore.selectedComponentContext?.state?.[key] ?? ""
|
||||||
|
} else {
|
||||||
|
editorValue = ""
|
||||||
|
componentsUsingState = []
|
||||||
|
componentsUpdatingState = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchComponents = (screen: Screen, stateKey: string) => {
|
||||||
|
const { props, onLoad, _id } = screen
|
||||||
|
componentsUsingState = findComponentsUsingState(props, stateKey)
|
||||||
|
componentsUpdatingState = findComponentsUpdatingState(props, stateKey)
|
||||||
|
|
||||||
|
// Check screen load actions which are outside the component hierarchy
|
||||||
|
if (eventUpdatesState(onLoad, stateKey)) {
|
||||||
|
componentsUpdatingState.push({
|
||||||
|
id: _id!,
|
||||||
|
name: "Screen - On load",
|
||||||
|
setting: "onLoad",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if an event setting updates a certain state key
|
||||||
|
const eventUpdatesState = (
|
||||||
|
handlers: EventHandler[] | undefined,
|
||||||
|
stateKey: string
|
||||||
|
) => {
|
||||||
|
return handlers?.some(handler => {
|
||||||
|
return (
|
||||||
|
handler["##eventHandlerType"] === "Update State" &&
|
||||||
|
handler.parameters?.key === stateKey
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if a setting for the given component updates a certain state key
|
||||||
|
const settingUpdatesState = (
|
||||||
|
component: Record<string, any>,
|
||||||
|
setting: ComponentSetting,
|
||||||
|
stateKey: string
|
||||||
|
) => {
|
||||||
|
if (setting.type === "event") {
|
||||||
|
return eventUpdatesState(component[setting.key], stateKey)
|
||||||
|
} else if (setting.type === "buttonConfiguration") {
|
||||||
|
const buttons = component[setting.key]
|
||||||
|
if (Array.isArray(buttons)) {
|
||||||
|
for (let button of buttons) {
|
||||||
|
if (eventUpdatesState(button.onClick, stateKey)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if a condition updates a certain state key
|
||||||
|
const conditionUpdatesState = (
|
||||||
|
condition: ComponentCondition,
|
||||||
|
settings: ComponentSetting[],
|
||||||
|
stateKey: string
|
||||||
|
) => {
|
||||||
|
const setting = settings.find(s => s.key === condition.setting)
|
||||||
|
if (!setting) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const component = { [setting.key]: condition.settingValue }
|
||||||
|
return settingUpdatesState(component, setting, stateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
const findComponentsUpdatingState = (
|
||||||
|
component: Component,
|
||||||
|
stateKey: string,
|
||||||
|
foundComponents: ComponentUsingState[] = []
|
||||||
|
): ComponentUsingState[] => {
|
||||||
|
const { _children, _conditions, _component, _instanceName, _id } = component
|
||||||
|
const settings = componentStore
|
||||||
|
.getComponentSettings(_component)
|
||||||
|
.filter(s => s.type === "event" || s.type === "buttonConfiguration")
|
||||||
|
|
||||||
|
// Check all settings of this component
|
||||||
|
settings.forEach(setting => {
|
||||||
|
if (settingUpdatesState(component, setting, stateKey)) {
|
||||||
|
const label = setting.label || setting.key
|
||||||
|
foundComponents.push({
|
||||||
|
id: _id!,
|
||||||
|
name: `${_instanceName} - ${label}`,
|
||||||
|
setting: setting.key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check if conditions update these settings to update this state key
|
||||||
|
if (_conditions?.some(c => conditionUpdatesState(c, settings, stateKey))) {
|
||||||
|
foundComponents.push({
|
||||||
|
id: _id!,
|
||||||
|
name: `${_instanceName} - Conditions`,
|
||||||
|
setting: "_conditions",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check children
|
||||||
|
_children?.forEach(child => {
|
||||||
|
findComponentsUpdatingState(child, stateKey, foundComponents)
|
||||||
|
})
|
||||||
|
return foundComponents
|
||||||
|
}
|
||||||
|
|
||||||
|
const findComponentsUsingState = (
|
||||||
|
component: Component,
|
||||||
|
stateKey: string,
|
||||||
|
componentsUsingState: ComponentUsingState[] = []
|
||||||
|
): ComponentUsingState[] => {
|
||||||
|
const settings = componentStore.getComponentSettings(component._component)
|
||||||
|
|
||||||
|
// Check all settings of this component
|
||||||
|
const settingsWithState = getSettingsUsingState(component, stateKey)
|
||||||
|
settingsWithState.forEach(setting => {
|
||||||
|
// Get readable label for this setting
|
||||||
|
let label = settings.find(s => s.key === setting)?.label || setting
|
||||||
|
if (setting === "_conditions") {
|
||||||
|
label = "Conditions"
|
||||||
|
} else if (setting === "_styles") {
|
||||||
|
label = "Styles"
|
||||||
|
}
|
||||||
|
componentsUsingState.push({
|
||||||
|
id: component._id!,
|
||||||
|
name: `${component._instanceName} - ${label}`,
|
||||||
|
setting,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check children
|
||||||
|
component._children?.forEach(child => {
|
||||||
|
findComponentsUsingState(child, stateKey, componentsUsingState)
|
||||||
|
})
|
||||||
|
return componentsUsingState
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSettingsUsingState = (
|
||||||
|
component: Component,
|
||||||
|
stateKey: string
|
||||||
|
): string[] => {
|
||||||
|
return Object.entries(component)
|
||||||
|
.filter(([key]) => key !== "_children")
|
||||||
|
.filter(([_, value]) => hasStateBinding(JSON.stringify(value), stateKey))
|
||||||
|
.map(([key]) => key)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasStateBinding = (value: string, stateKey: string): boolean => {
|
||||||
|
const bindings = findHBSBlocks(value).map(binding => {
|
||||||
|
const sanitizedBinding = binding.replace(/\\"/g, '"')
|
||||||
|
return isJSBinding(sanitizedBinding)
|
||||||
|
? decodeJSBinding(sanitizedBinding)
|
||||||
|
: sanitizedBinding
|
||||||
|
})
|
||||||
|
return bindings.join(" ").includes(stateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClickComponentLink = (component: ComponentUsingState) => {
|
||||||
|
componentStore.select(component.id)
|
||||||
|
builderStore.highlightSetting(component.setting)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleStateInspectorChange = (e: CustomEvent) => {
|
||||||
|
if (!selectedKey || !$previewStore.selectedComponentContext) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const stateUpdate = {
|
||||||
|
[selectedKey]: processStringSync(
|
||||||
|
e.detail,
|
||||||
|
$previewStore.selectedComponentContext
|
||||||
|
),
|
||||||
|
}
|
||||||
|
previewStore.updateState(stateUpdate)
|
||||||
|
editorValue = e.detail
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
previewStore.requestComponentContext()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="state-panel">
|
||||||
|
<Select
|
||||||
|
label="State variable"
|
||||||
|
bind:value={selectedKey}
|
||||||
|
placeholder={keyOptions.length > 0 ? false : "No state variables found"}
|
||||||
|
options={keyOptions}
|
||||||
|
/>
|
||||||
|
{#if selectedKey && keyOptions.length > 0}
|
||||||
|
<DrawerBindableInput
|
||||||
|
value={editorValue}
|
||||||
|
title={`Set value for "${selectedKey}"`}
|
||||||
|
placeholder="Enter a value"
|
||||||
|
label="Set temporary value for design preview"
|
||||||
|
on:change={e => handleStateInspectorChange(e)}
|
||||||
|
{bindings}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{#if componentsUsingState.length > 0}
|
||||||
|
<div class="section">
|
||||||
|
<span class="text">Updates</span>
|
||||||
|
<div class="updates-section">
|
||||||
|
{#each componentsUsingState as component}
|
||||||
|
<button
|
||||||
|
class="component-link updates-colour"
|
||||||
|
on:click={() => onClickComponentLink(component)}
|
||||||
|
>
|
||||||
|
{component.name}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if componentsUpdatingState.length > 0}
|
||||||
|
<div class="section">
|
||||||
|
<span class="text">Controlled by</span>
|
||||||
|
<div class="updates-section">
|
||||||
|
{#each componentsUpdatingState as component}
|
||||||
|
<button
|
||||||
|
class="component-link controlled-by-colour"
|
||||||
|
on:click={() => onClickComponentLink(component)}
|
||||||
|
>
|
||||||
|
{component.name}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.state-panel {
|
||||||
|
background-color: var(--spectrum-alias-background-color-primary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
margin-top: var(--spacing-s);
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
color: var(--spectrum-global-color-gray-700);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.updates-colour {
|
||||||
|
color: var(--bb-indigo-light);
|
||||||
|
}
|
||||||
|
.controlled-by-colour {
|
||||||
|
color: var(--spectrum-global-color-orange-700);
|
||||||
|
}
|
||||||
|
.component-link {
|
||||||
|
display: inline-block;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 12px;
|
||||||
|
transition: filter 130ms ease-out;
|
||||||
|
}
|
||||||
|
.component-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
filter: brightness(1.2);
|
||||||
|
}
|
||||||
|
.updates-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -40,26 +40,33 @@ function setupEnv(hosting, features = {}, flags = {}) {
|
||||||
describe("AISettings", () => {
|
describe("AISettings", () => {
|
||||||
let instance = null
|
let instance = null
|
||||||
|
|
||||||
|
const setupDOM = () => {
|
||||||
|
instance = render(AISettings, {})
|
||||||
|
const modalContainer = document.createElement("div")
|
||||||
|
modalContainer.classList.add("modal-container")
|
||||||
|
instance.baseElement.appendChild(modalContainer)
|
||||||
|
}
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.restoreAllMocks()
|
vi.restoreAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("that the AISettings is rendered", () => {
|
it("that the AISettings is rendered", () => {
|
||||||
instance = render(AISettings, {})
|
setupDOM()
|
||||||
expect(instance).toBeDefined()
|
expect(instance).toBeDefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Licensing", () => {
|
describe("Licensing", () => {
|
||||||
it("should show the premium label on self host for custom configs", async () => {
|
it("should show the premium label on self host for custom configs", async () => {
|
||||||
setupEnv(Hosting.Self)
|
setupEnv(Hosting.Self)
|
||||||
instance = render(AISettings, {})
|
setupDOM()
|
||||||
const premiumTag = instance.queryByText("Premium")
|
const premiumTag = instance.queryByText("Premium")
|
||||||
expect(premiumTag).toBeInTheDocument()
|
expect(premiumTag).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should show the enterprise label on cloud for custom configs", async () => {
|
it("should show the enterprise label on cloud for custom configs", async () => {
|
||||||
setupEnv(Hosting.Cloud)
|
setupEnv(Hosting.Cloud)
|
||||||
instance = render(AISettings, {})
|
setupDOM()
|
||||||
const enterpriseTag = instance.queryByText("Enterprise")
|
const enterpriseTag = instance.queryByText("Enterprise")
|
||||||
expect(enterpriseTag).toBeInTheDocument()
|
expect(enterpriseTag).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
@ -69,7 +76,7 @@ describe("AISettings", () => {
|
||||||
let configModal
|
let configModal
|
||||||
|
|
||||||
setupEnv(Hosting.Cloud)
|
setupEnv(Hosting.Cloud)
|
||||||
instance = render(AISettings)
|
setupDOM()
|
||||||
addConfigurationButton = instance.queryByText("Add configuration")
|
addConfigurationButton = instance.queryByText("Add configuration")
|
||||||
expect(addConfigurationButton).toBeInTheDocument()
|
expect(addConfigurationButton).toBeInTheDocument()
|
||||||
await fireEvent.click(addConfigurationButton)
|
await fireEvent.click(addConfigurationButton)
|
||||||
|
@ -86,7 +93,7 @@ describe("AISettings", () => {
|
||||||
{ customAIConfigsEnabled: true },
|
{ customAIConfigsEnabled: true },
|
||||||
{ AI_CUSTOM_CONFIGS: true }
|
{ AI_CUSTOM_CONFIGS: true }
|
||||||
)
|
)
|
||||||
instance = render(AISettings)
|
setupDOM()
|
||||||
addConfigurationButton = instance.queryByText("Add configuration")
|
addConfigurationButton = instance.queryByText("Add configuration")
|
||||||
expect(addConfigurationButton).toBeInTheDocument()
|
expect(addConfigurationButton).toBeInTheDocument()
|
||||||
await fireEvent.click(addConfigurationButton)
|
await fireEvent.click(addConfigurationButton)
|
||||||
|
@ -103,7 +110,7 @@ describe("AISettings", () => {
|
||||||
{ customAIConfigsEnabled: true },
|
{ customAIConfigsEnabled: true },
|
||||||
{ AI_CUSTOM_CONFIGS: true }
|
{ AI_CUSTOM_CONFIGS: true }
|
||||||
)
|
)
|
||||||
instance = render(AISettings)
|
setupDOM()
|
||||||
addConfigurationButton = instance.queryByText("Add configuration")
|
addConfigurationButton = instance.queryByText("Add configuration")
|
||||||
expect(addConfigurationButton).toBeInTheDocument()
|
expect(addConfigurationButton).toBeInTheDocument()
|
||||||
await fireEvent.click(addConfigurationButton)
|
await fireEvent.click(addConfigurationButton)
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
previewStore,
|
previewStore,
|
||||||
tables,
|
tables,
|
||||||
componentTreeNodesStore,
|
componentTreeNodesStore,
|
||||||
|
builderStore,
|
||||||
screenComponents,
|
screenComponents,
|
||||||
} from "@/stores/builder"
|
} from "@/stores/builder"
|
||||||
import { buildFormSchema, getSchemaForDatasource } from "@/dataBinding"
|
import { buildFormSchema, getSchemaForDatasource } from "@/dataBinding"
|
||||||
|
@ -33,6 +34,7 @@ import { BudiStore } from "../BudiStore"
|
||||||
import { Utils } from "@budibase/frontend-core"
|
import { Utils } from "@budibase/frontend-core"
|
||||||
import {
|
import {
|
||||||
Component as ComponentType,
|
Component as ComponentType,
|
||||||
|
ComponentCondition,
|
||||||
FieldType,
|
FieldType,
|
||||||
Screen,
|
Screen,
|
||||||
Table,
|
Table,
|
||||||
|
@ -68,6 +70,7 @@ export interface ComponentDefinition {
|
||||||
export interface ComponentSetting {
|
export interface ComponentSetting {
|
||||||
key: string
|
key: string
|
||||||
type: string
|
type: string
|
||||||
|
label?: string
|
||||||
section?: string
|
section?: string
|
||||||
name?: string
|
name?: string
|
||||||
defaultValue?: any
|
defaultValue?: any
|
||||||
|
@ -743,14 +746,16 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
select(id: string) {
|
||||||
*
|
|
||||||
* @param {string} componentId
|
|
||||||
*/
|
|
||||||
select(componentId: string) {
|
|
||||||
this.update(state => {
|
this.update(state => {
|
||||||
state.selectedComponentId = componentId
|
// Only clear highlights if selecting a different component
|
||||||
return state
|
if (!id.includes(state.selectedComponentId!)) {
|
||||||
|
builderStore.highlightSetting()
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
selectedComponentId: id,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1132,7 +1137,7 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateConditions(conditions: Record<string, any>) {
|
async updateConditions(conditions: ComponentCondition[]) {
|
||||||
await this.patch((component: Component) => {
|
await this.patch((component: Component) => {
|
||||||
component._conditions = conditions
|
component._conditions = conditions
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,7 +16,11 @@ import { userStore, userSelectedResourceMap, isOnlyUser } from "./users.js"
|
||||||
import { deploymentStore } from "./deployments.js"
|
import { deploymentStore } from "./deployments.js"
|
||||||
import { contextMenuStore } from "./contextMenu.js"
|
import { contextMenuStore } from "./contextMenu.js"
|
||||||
import { snippets } from "./snippets"
|
import { snippets } from "./snippets"
|
||||||
import { screenComponents, screenComponentErrors } from "./screenComponent"
|
import {
|
||||||
|
screenComponents,
|
||||||
|
screenComponentErrors,
|
||||||
|
findComponentsBySettingsType,
|
||||||
|
} from "./screenComponent"
|
||||||
|
|
||||||
// Backend
|
// Backend
|
||||||
import { tables } from "./tables"
|
import { tables } from "./tables"
|
||||||
|
@ -70,6 +74,7 @@ export {
|
||||||
appPublished,
|
appPublished,
|
||||||
screenComponents,
|
screenComponents,
|
||||||
screenComponentErrors,
|
screenComponentErrors,
|
||||||
|
findComponentsBySettingsType,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const reset = () => {
|
export const reset = () => {
|
||||||
|
|
|
@ -82,6 +82,10 @@ export class PreviewStore extends BudiStore<PreviewState> {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateState(data: Record<string, any>) {
|
||||||
|
this.sendEvent("builder-state", data)
|
||||||
|
}
|
||||||
|
|
||||||
requestComponentContext() {
|
requestComponentContext() {
|
||||||
this.sendEvent("request-context")
|
this.sendEvent("request-context")
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,7 +124,7 @@ export const screenComponentErrors = derived(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
function findComponentsBySettingsType(
|
export function findComponentsBySettingsType(
|
||||||
screen: Screen,
|
screen: Screen,
|
||||||
type: string | string[],
|
type: string | string[],
|
||||||
definitions: Record<string, ComponentDefinition>
|
definitions: Record<string, ComponentDefinition>
|
||||||
|
|
|
@ -15,6 +15,7 @@ export const ActionTypes = {
|
||||||
|
|
||||||
export const DNDPlaceholderID = "dnd-placeholder"
|
export const DNDPlaceholderID = "dnd-placeholder"
|
||||||
export const ScreenslotType = "screenslot"
|
export const ScreenslotType = "screenslot"
|
||||||
|
export const ScreenslotID = "screenslot"
|
||||||
export const GridRowHeight = 24
|
export const GridRowHeight = 24
|
||||||
export const GridColumns = 12
|
export const GridColumns = 12
|
||||||
export const GridSpacing = 4
|
export const GridSpacing = 4
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
dndStore,
|
dndStore,
|
||||||
eventStore,
|
eventStore,
|
||||||
hoverStore,
|
hoverStore,
|
||||||
|
stateStore,
|
||||||
} from "./stores"
|
} from "./stores"
|
||||||
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-vite.js"
|
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-vite.js"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
|
@ -87,8 +88,10 @@ const loadBudibase = async () => {
|
||||||
dndStore.actions.reset()
|
dndStore.actions.reset()
|
||||||
}
|
}
|
||||||
} else if (type === "request-context") {
|
} else if (type === "request-context") {
|
||||||
const { selectedComponentInstance } = get(componentStore)
|
const { selectedComponentInstance, screenslotInstance } =
|
||||||
const context = selectedComponentInstance?.getDataContext()
|
get(componentStore)
|
||||||
|
const instance = selectedComponentInstance || screenslotInstance
|
||||||
|
const context = instance?.getDataContext()
|
||||||
let stringifiedContext = null
|
let stringifiedContext = null
|
||||||
try {
|
try {
|
||||||
stringifiedContext = JSON.stringify(context)
|
stringifiedContext = JSON.stringify(context)
|
||||||
|
@ -102,6 +105,9 @@ const loadBudibase = async () => {
|
||||||
hoverStore.actions.hoverComponent(data, false)
|
hoverStore.actions.hoverComponent(data, false)
|
||||||
} else if (type === "builder-meta") {
|
} else if (type === "builder-meta") {
|
||||||
builderStore.actions.setMetadata(data)
|
builderStore.actions.setMetadata(data)
|
||||||
|
} else if (type === "builder-state") {
|
||||||
|
const [[key, value]] = Object.entries(data)
|
||||||
|
stateStore.actions.setValue(key, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { screenStore } from "./screens"
|
||||||
import { builderStore } from "./builder"
|
import { builderStore } from "./builder"
|
||||||
import Router from "../components/Router.svelte"
|
import Router from "../components/Router.svelte"
|
||||||
import * as AppComponents from "../components/app/index.js"
|
import * as AppComponents from "../components/app/index.js"
|
||||||
import { ScreenslotType } from "../constants"
|
import { ScreenslotID, ScreenslotType } from "../constants"
|
||||||
|
|
||||||
export const BudibasePrefix = "@budibase/standard-components/"
|
export const BudibasePrefix = "@budibase/standard-components/"
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ const createComponentStore = () => {
|
||||||
selectedComponentDefinition: definition,
|
selectedComponentDefinition: definition,
|
||||||
selectedComponentPath: selectedPath?.map(component => component._id),
|
selectedComponentPath: selectedPath?.map(component => component._id),
|
||||||
mountedComponentCount: Object.keys($store.mountedComponents).length,
|
mountedComponentCount: Object.keys($store.mountedComponents).length,
|
||||||
|
screenslotInstance: $store.mountedComponents[ScreenslotID],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { dndIndex, dndParent, dndIsNewComponent, dndBounds } from "./dnd.js"
|
||||||
import { RoleUtils } from "@budibase/frontend-core"
|
import { RoleUtils } from "@budibase/frontend-core"
|
||||||
import { findComponentById, findComponentParent } from "../utils/components.js"
|
import { findComponentById, findComponentParent } from "../utils/components.js"
|
||||||
import { Helpers } from "@budibase/bbui"
|
import { Helpers } from "@budibase/bbui"
|
||||||
import { DNDPlaceholderID } from "constants"
|
import { DNDPlaceholderID, ScreenslotID, ScreenslotType } from "constants"
|
||||||
|
|
||||||
const createScreenStore = () => {
|
const createScreenStore = () => {
|
||||||
const store = derived(
|
const store = derived(
|
||||||
|
@ -171,8 +171,8 @@ const createScreenStore = () => {
|
||||||
_component: "@budibase/standard-components/layout",
|
_component: "@budibase/standard-components/layout",
|
||||||
_children: [
|
_children: [
|
||||||
{
|
{
|
||||||
_component: "screenslot",
|
_component: ScreenslotType,
|
||||||
_id: "screenslot",
|
_id: ScreenslotID,
|
||||||
_styles: {
|
_styles: {
|
||||||
normal: {
|
normal: {
|
||||||
flex: "1 1 auto",
|
flex: "1 1 auto",
|
||||||
|
|
|
@ -1,9 +1,22 @@
|
||||||
import { Document } from "../document"
|
import { Document } from "../document"
|
||||||
|
import { BasicOperator } from "../../sdk"
|
||||||
|
|
||||||
export interface Component extends Document {
|
export interface Component extends Document {
|
||||||
_instanceName: string
|
_instanceName: string
|
||||||
_styles: { [key: string]: any }
|
_styles: { [key: string]: any }
|
||||||
_component: string
|
_component: string
|
||||||
_children?: Component[]
|
_children?: Component[]
|
||||||
|
_conditions?: ComponentCondition[]
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ComponentCondition {
|
||||||
|
id: string
|
||||||
|
operator: BasicOperator
|
||||||
|
action: "update" | "show" | "hide"
|
||||||
|
valueType: "string" | "number" | "datetime" | "boolean"
|
||||||
|
newValue?: any
|
||||||
|
referenceValue?: any
|
||||||
|
setting?: string
|
||||||
|
settingValue?: any
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ export interface Screen extends Document {
|
||||||
props: ScreenProps
|
props: ScreenProps
|
||||||
name?: string
|
name?: string
|
||||||
pluginAdded?: boolean
|
pluginAdded?: boolean
|
||||||
|
onLoad?: EventHandler[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScreenRoutesViewOutput extends Document {
|
export interface ScreenRoutesViewOutput extends Document {
|
||||||
|
@ -36,3 +37,14 @@ export type ScreenRoutingJson = Record<
|
||||||
subpaths: Record<string, any>
|
subpaths: Record<string, any>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
export interface EventHandler {
|
||||||
|
parameters: {
|
||||||
|
key: string
|
||||||
|
type: string
|
||||||
|
value: string
|
||||||
|
persist: any | null
|
||||||
|
}
|
||||||
|
"##eventHandlerType": string
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
|
@ -18993,10 +18993,10 @@ svelte-loading-spinners@^0.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/svelte-loading-spinners/-/svelte-loading-spinners-0.1.7.tgz#3fa6fa0ef67ab635773bf20b07d0b071debbadaa"
|
resolved "https://registry.yarnpkg.com/svelte-loading-spinners/-/svelte-loading-spinners-0.1.7.tgz#3fa6fa0ef67ab635773bf20b07d0b071debbadaa"
|
||||||
integrity sha512-EKCId1DjVL2RSUVJJsvtNcqQHox03XIgh4xh/4p7r6ST7d8mut6INY9/LqK4A17PFU64+3quZmqiSfOlf480CA==
|
integrity sha512-EKCId1DjVL2RSUVJJsvtNcqQHox03XIgh4xh/4p7r6ST7d8mut6INY9/LqK4A17PFU64+3quZmqiSfOlf480CA==
|
||||||
|
|
||||||
svelte-portal@1.0.0, svelte-portal@^1.0.0:
|
svelte-portal@^2.2.1:
|
||||||
version "1.0.0"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-1.0.0.tgz#36a47c5578b1a4d9b4dc60fa32a904640ec4cdd3"
|
resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-2.2.1.tgz#b1d7bed78e56318db245996beb5483d8de6b9740"
|
||||||
integrity sha512-nHf+DS/jZ6jjnZSleBMSaZua9JlG5rZv9lOGKgJuaZStfevtjIlUJrkLc3vbV8QdBvPPVmvcjTlazAzfKu0v3Q==
|
integrity sha512-uF7is5sM4aq5iN7QF/67XLnTUvQCf2iiG/B1BHTqLwYVY1dsVmTeXZ/LeEyU6dLjApOQdbEG9lkqHzxiQtOLEQ==
|
||||||
|
|
||||||
svelte-spa-router@^4.0.1:
|
svelte-spa-router@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
|
|
Loading…
Reference in New Issue