Convert multiple files to TS and update BBUI to support TS
This commit is contained in:
parent
5acd6ab3d6
commit
050f1e7a5f
|
@ -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",
|
||||||
|
|
|
@ -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,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" ||
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
declare module "./helpers" {
|
|
||||||
export const cloneDeep: <T>(obj: T) => T
|
|
||||||
}
|
|
|
@ -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"
|
|
@ -0,0 +1,7 @@
|
||||||
|
const { vitePreprocess } = require("@sveltejs/vite-plugin-svelte")
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
preprocess: vitePreprocess(),
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = config
|
|
@ -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"]
|
||||||
|
}
|
|
@ -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"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -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,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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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}
|
|
||||||
cancelText="Close"
|
|
||||||
size="M"
|
|
||||||
>
|
>
|
||||||
|
<svelte:fragment slot="anchor" let:open>
|
||||||
|
<ActionButton
|
||||||
|
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>
|
||||||
|
|
Loading…
Reference in New Issue