Convert multiple files to TS and update BBUI to support TS

This commit is contained in:
Andrew Kingston 2025-01-17 09:39:33 +00:00
parent 5acd6ab3d6
commit 050f1e7a5f
No known key found for this signature in database
15 changed files with 297 additions and 158 deletions

View File

@ -3,7 +3,7 @@
"description": "A UI solution used in the different Budibase projects.", "description": "A UI solution used in the different Budibase projects.",
"version": "0.0.0", "version": "0.0.0",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.ts",
"module": "dist/bbui.mjs", "module": "dist/bbui.mjs",
"exports": { "exports": {
".": { ".": {
@ -14,7 +14,8 @@
"./spectrum-icons-vite.js": "./src/spectrum-icons-vite.js" "./spectrum-icons-vite.js": "./src/spectrum-icons-vite.js"
}, },
"scripts": { "scripts": {
"build": "vite build" "build": "vite build",
"dev": "vite build --watch --mode=dev"
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/vite-plugin-svelte": "1.4.0", "@sveltejs/vite-plugin-svelte": "1.4.0",

View File

@ -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),

View File

@ -1,10 +1,4 @@
/** import { PopoverAlignment } from "../Popover/Popover.svelte"
* 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.
@ -149,20 +143,29 @@ export default function positionDropdown(element, opts) {
} }
// Determine X strategy // Determine X strategy
if (align === "right") { if (align === PopoverAlignment.Right) {
applyXStrategy(Strategies.EndToEnd) applyXStrategy(Strategies.EndToEnd)
} else if (align === "right-outside" || align === "right-context-menu") { } else if (
align === PopoverAlignment.RightOutside ||
align === PopoverAlignment.RightContextMenu
) {
applyXStrategy(Strategies.StartToEnd) applyXStrategy(Strategies.StartToEnd)
} else if (align === "left-outside" || align === "left-context-menu") { } else if (
align === PopoverAlignment.LeftOutside ||
align === PopoverAlignment.LeftContextMenu
) {
applyXStrategy(Strategies.EndToStart) applyXStrategy(Strategies.EndToStart)
} else if (align === "center") { } else if (align === PopoverAlignment.Center) {
applyXStrategy(Strategies.MidPoint) applyXStrategy(Strategies.MidPoint)
} else { } else {
applyXStrategy(Strategies.StartToStart) applyXStrategy(Strategies.StartToStart)
} }
// Determine Y strategy // Determine Y strategy
if (align === "right-outside" || align === "left-outside") { if (
align === PopoverAlignment.RightOutside ||
align === PopoverAlignment.LeftOutside
) {
applyYStrategy(Strategies.MidPoint) applyYStrategy(Strategies.MidPoint)
} else if ( } else if (
align === "right-context-menu" || align === "right-context-menu" ||

View File

@ -1,23 +1,23 @@
<script> <script lang="ts">
import { import {
default as AbsTooltip, default as AbsTooltip,
TooltipPosition, TooltipPosition,
TooltipType, TooltipType,
} from "../Tooltip/AbsTooltip.svelte" } from "../Tooltip/AbsTooltip.svelte"
export let name = "Add" export let name: string = "Add"
export let hidden = false export let hidden: boolean = false
export let size = "M" export let size: "XS" | "S" | "M" | "L" | "XL" = "M"
export let hoverable = false export let hoverable: boolean = false
export let disabled = false export let disabled: boolean = false
export let color export let color: string | undefined = undefined
export let hoverColor export let hoverColor: string | undefined = undefined
export let tooltip export let tooltip: string | undefined = undefined
export let tooltipPosition = TooltipPosition.Bottom export let tooltipPosition = TooltipPosition.Bottom
export let tooltipType = TooltipType.Default export let tooltipType = TooltipType.Default
export let tooltipColor export let tooltipColor: string | undefined = undefined
export let tooltipWrap = true export let tooltipWrap: boolean = true
export let newStyles = false export let newStyles: boolean = false
</script> </script>
<AbsTooltip <AbsTooltip

View File

@ -1,4 +1,21 @@
<script> <script context="module" lang="ts">
export enum PopoverAlignment {
Left = "left",
Right = "right",
LeftOutside = "left-outside",
RightOutside = "right-outside",
Center = "center",
RightContextMenu = "right-context-menu",
LeftContextMenu = "left-context-menu",
}
export interface PopoverAPI {
show: () => void
hide: () => void
}
</script>
<script lang="ts">
import "@spectrum-css/popover/dist/index-vars.css" import "@spectrum-css/popover/dist/index-vars.css"
import Portal from "svelte-portal" import Portal from "svelte-portal"
import { createEventDispatcher, getContext, onDestroy } from "svelte" import { createEventDispatcher, getContext, onDestroy } from "svelte"
@ -9,31 +26,35 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
export let anchor export let anchor: HTMLElement | undefined = undefined
export let align = "right" export let align: PopoverAlignment = PopoverAlignment.Right
export let portalTarget export let portalTarget: string | undefined = undefined
export let minWidth export let minWidth: number | undefined = undefined
export let maxWidth export let maxWidth: number | undefined = undefined
export let maxHeight export let maxHeight: number | undefined = undefined
export let open = false export let open: boolean = false
export let useAnchorWidth = false export let useAnchorWidth: boolean = false
export let dismissible = true export let dismissible: boolean = true
export let offset = 4 export let offset: number = 4
export let customHeight export let customHeight: string | undefined = undefined
export let animate = true export let animate: boolean = true
export let customZindex export let customZIndex: number | undefined = undefined
export let handlePostionUpdate export let handlePositionUpdate: Function | undefined = undefined
export let showPopover = true export let showPopover: boolean = true
export let clickOutsideOverride = false export let clickOutsideOverride: boolean = false
export let resizable = true export let resizable: boolean = true
export let wrap = false export let wrap: boolean = false
const animationDuration = 260 const animationDuration = 260
let timeout let timeout: ReturnType<typeof setTimeout> | undefined
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,
@ -113,7 +134,7 @@
minWidth, minWidth,
useAnchorWidth, useAnchorWidth,
offset, offset,
customUpdate: handlePostionUpdate, customUpdate: handlePositionUpdate,
resizable, resizable,
wrap, wrap,
}} }}
@ -123,11 +144,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,
@ -157,7 +178,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>

View File

@ -1,3 +0,0 @@
declare module "./helpers" {
export const cloneDeep: <T>(obj: T) => T
}

View File

@ -45,7 +45,11 @@ 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,
PopoverAlignment,
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"

View File

@ -0,0 +1,7 @@
const { vitePreprocess } = require("@sveltejs/vite-plugin-svelte")
const config = {
preprocess: vitePreprocess(),
}
module.exports = config

View File

@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.build.json",
"compilerOptions": {
"allowJs": true,
"outDir": "./dist",
"lib": ["ESNext"],
"baseUrl": ".",
},
"include": ["./src/**/*"],
"exclude": ["node_modules", "**/*.json", "**/*.spec.ts", "**/*.spec.js"]
}

View File

@ -9,7 +9,7 @@ export default defineConfig(({ mode }) => {
build: { build: {
sourcemap: !isProduction, sourcemap: !isProduction,
lib: { lib: {
entry: "src/index.js", entry: "src/index.ts",
formats: ["es"], formats: ["es"],
}, },
}, },

View File

@ -1,14 +1,20 @@
<script> <script lang="ts">
import { Popover, Icon } from "@budibase/bbui" import {
Popover,
PopoverAlignment,
type PopoverAPI,
Icon,
} 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,20 +36,24 @@
{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}
<Icon
name="Close"
hoverable
color="var(--spectrum-global-color-gray-600)"
hoverColor="var(--spectrum-global-color-gray-900)"
on:click={hide}
size="S"
/>
</div> </div>
<Icon {#if subtitle}
name="Close" <div class="detail-popover__subtitle">{subtitle}</div>
hoverable {/if}
color="var(--spectrum-global-color-gray-600)"
hoverColor="var(--spectum-global-color-gray-900)"
on:click={hide}
/>
</div> </div>
<div class="detail-popover__body"> <div class="detail-popover__body">
<slot /> <slot />
@ -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;
} }

View File

@ -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}

View File

@ -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">

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { AbsTooltip, Icon, TooltipPosition } from "@budibase/bbui" import { Icon } from "@budibase/bbui"
export let label: string | undefined = undefined export let label: string | undefined = undefined
export let value: any = undefined export let value: any = undefined
@ -9,20 +9,22 @@
const Colors = { const Colors = {
Array: "var(--spectrum-global-color-gray-600)", Array: "var(--spectrum-global-color-gray-600)",
Object: "var(--spectrum-global-color-gray-600)", Object: "var(--spectrum-global-color-gray-600)",
Other: "var(--spectrum-global-color-indigo-600)", Other: "var(--spectrum-global-color-blue-700)",
Undefined: "var(--spectrum-global-color-gray-500)", Undefined: "var(--spectrum-global-color-gray-600)",
Null: "var(--spectrum-global-color-magenta-600)", Null: "var(--spectrum-global-color-yellow-700)",
String: "var(--spectrum-global-color-orange-600)", String: "var(--spectrum-global-color-orange-700)",
Number: "var(--spectrum-global-color-blue-600)", Number: "var(--spectrum-global-color-purple-700)",
True: "var(--spectrum-global-color-green-600)", True: "var(--spectrum-global-color-celery-700)",
False: "var(--spectrum-global-color-red-600)", False: "var(--spectrum-global-color-red-700)",
Date: "var(--spectrum-global-color-green-600)", Date: "var(--spectrum-global-color-green-700)",
} }
let expanded = false let expanded = false
let valueExpanded = false
$: isArray = Array.isArray(value) $: isArray = Array.isArray(value)
$: isObject = value?.toString?.() === "[object Object]" $: isObject = value?.toString?.() === "[object Object]"
$: primitive = !(isArray || isObject)
$: keys = getKeys(isArray, isObject, value) $: keys = getKeys(isArray, isObject, value)
$: expandable = keys.length > 0 $: expandable = keys.length > 0
$: displayValue = getDisplayValue(isArray, isObject, keys, value) $: displayValue = getDisplayValue(isArray, isObject, keys, value)
@ -96,13 +98,15 @@
} }
</script> </script>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="binding-node"> <div class="binding-node">
{#if label != null} {#if label != null}
<div class="binding-text"> <div class="binding-text">
<div class="binding-arrow"> <div class="binding-arrow" class:expanded>
{#if expandable} {#if expandable}
<Icon <Icon
name={expanded ? "ChevronDown" : "ChevronRight"} name="Play"
hoverable hoverable
color="var(--spectrum-global-color-gray-600)" color="var(--spectrum-global-color-gray-600)"
hoverColor="var(--spectrum-global-color-gray-900)" hoverColor="var(--spectrum-global-color-gray-900)"
@ -112,19 +116,21 @@
</div> </div>
<div <div
class="binding-label" class="binding-label"
class:primitive
class:expandable class:expandable
on:click={() => (expanded = !expanded)} on:click={() => (expanded = !expanded)}
> >
{label} {label}
</div> </div>
<AbsTooltip <div
text={isArray || isObject ? null : displayValue} class="binding-value"
position={TooltipPosition.Right} class:primitive
class:expanded={valueExpanded}
{style}
on:click={() => (valueExpanded = !valueExpanded)}
> >
<div class="binding-value" class:expandable {style}> {displayValue}
{displayValue} </div>
</div>
</AbsTooltip>
</div> </div>
{/if} {/if}
{#if expandable && (expanded || label == null)} {#if expandable && (expanded || label == null)}
@ -150,24 +156,33 @@
overflow: hidden; overflow: hidden;
} }
.binding-arrow { .binding-arrow {
margin-right: 2px; margin: -3px 2px -2px 0;
flex: 0 0 18px; flex: 0 0 18px;
transition: transform 130ms ease-out;
}
.binding-arrow :global(svg) {
width: 9px;
}
.binding-arrow.expanded {
transform: rotate(90deg);
} }
.binding-text { .binding-text {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center;
font-family: monospace; font-family: monospace;
font-size: 12px; font-size: 12px;
align-items: flex-start;
width: 100%; width: 100%;
} }
.binding-children { .binding-children {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
/* border-left: 1px solid var(--spectrum-global-color-gray-400); */ /*padding-left: 18px;*/
/* margin-left: 20px; */
padding-left: 18px; border-left: 1px solid var(--spectrum-global-color-gray-400);
margin-left: 20px;
padding-left: 3px;
} }
.binding-children.root { .binding-children.root {
border-left: none; border-left: none;
@ -176,31 +191,40 @@
} }
/* Size label and value according to type */ /* Size label and value according to type */
.binding-label, .binding-label {
.binding-value { flex: 0 1 auto;
margin-right: 8px;
transition: color 130ms ease-out;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.binding-label {
flex: 0 0 auto;
max-width: 50%;
margin-right: 8px;
transition: color 130ms ease-out;
}
.binding-label.expandable:hover { .binding-label.expandable:hover {
cursor: pointer; cursor: pointer;
color: var(--spectrum-global-color-gray-900); color: var(--spectrum-global-color-gray-900);
} }
.binding-value { .binding-value {
flex: 0 1 auto;
}
.binding-label.expandable {
flex: 0 1 auto;
max-width: none;
}
.binding-value.expandable {
flex: 0 0 auto; 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: 50%;
}
.binding-value.primitive {
flex: 0 1 auto;
} }
/* Trim spans in the highlighted HTML */ /* Trim spans in the highlighted HTML */

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { ActionButton, Modal, ModalContent, Helpers } from "@budibase/bbui" import { ActionButton, Helpers, PopoverAlignment } from "@budibase/bbui"
import { import {
previewStore, previewStore,
selectedScreen, selectedScreen,
@ -9,6 +9,7 @@
import { getBindableProperties } from "@/dataBinding" import { getBindableProperties } from "@/dataBinding"
import BindingNode from "./BindingExplorer/BindingNode.svelte" import BindingNode from "./BindingExplorer/BindingNode.svelte"
import { processObjectSync } from "@budibase/string-templates" import { processObjectSync } from "@budibase/string-templates"
import DetailPopover from "@/components/common/DetailPopover.svelte"
// Minimal typing for the real data binding structure, as none exists // Minimal typing for the real data binding structure, as none exists
type DataBinding = { type DataBinding = {
@ -17,18 +18,11 @@
readableBinding: string readableBinding: string
} }
let modal: any
$: previewContext = $previewStore.selectedComponentContext || {} $: previewContext = $previewStore.selectedComponentContext || {}
$: selectedComponentId = $componentStore.selectedComponentId $: selectedComponentId = $componentStore.selectedComponentId
$: context = makeContext(previewContext, bindings) $: context = makeContext(previewContext, bindings)
$: bindings = getBindableProperties($selectedScreen, selectedComponentId) $: bindings = getBindableProperties($selectedScreen, selectedComponentId)
const show = () => {
previewStore.requestComponentContext()
modal.show()
}
const makeContext = ( const makeContext = (
previewContext: Record<string, any>, previewContext: Record<string, any>,
bindings: DataBinding[] bindings: DataBinding[]
@ -77,15 +71,32 @@
} }
</script> </script>
<ActionButton on:click={show}>Bindings</ActionButton> <DetailPopover
title="Data context"
<Modal bind:this={modal}> subtitle="Showing all bindable data context available on the /employees screen."
<ModalContent align={PopoverAlignment.Right}
title="Bindings" >
showConfirmButton={false} <svelte:fragment slot="anchor" let:open>
cancelText="Close" <ActionButton
size="M" icon="Code"
> quiet
selected={open}
on:click={previewStore.requestComponentContext}
>
Data context
</ActionButton>
</svelte:fragment>
<div class="bindings">
<BindingNode value={context} /> <BindingNode value={context} />
</ModalContent> </div>
</Modal> </DetailPopover>
<style>
.bindings {
margin: calc(-1 * var(--spacing-xl));
padding: 20px 12px;
background: var(--spectrum-global-color-gray-50);
overflow-y: auto;
max-height: 600px;
}
</style>