diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 457d2c1451..6ace2303d9 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -6,6 +6,8 @@ labels: bug
assignees: ''
---
+**Checklist**
+- [ ] I have searched budibase discussions and github issues to check if my issue already exists
**Hosting**
diff --git a/lerna.json b/lerna.json
index aaa41afdd1..0606169dd3 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
- "version": "2.2.12-alpha.6",
+ "version": "2.2.12-alpha.32",
"npmClient": "yarn",
"packages": [
"packages/*"
diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json
index 86d4869c2d..e83a7084b1 100644
--- a/packages/backend-core/package.json
+++ b/packages/backend-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@budibase/backend-core",
- "version": "2.2.12-alpha.6",
+ "version": "2.2.12-alpha.32",
"description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
@@ -23,7 +23,7 @@
},
"dependencies": {
"@budibase/nano": "10.1.1",
- "@budibase/types": "2.2.12-alpha.6",
+ "@budibase/types": "2.2.12-alpha.32",
"@shopify/jest-koa-mocks": "5.0.1",
"@techpass/passport-openidconnect": "0.3.2",
"aws-cloudfront-sign": "2.2.0",
@@ -31,6 +31,7 @@
"bcrypt": "5.0.1",
"bcryptjs": "2.4.3",
"bull": "4.10.1",
+ "correlation-id": "4.0.0",
"dotenv": "16.0.1",
"emitter-listener": "1.1.2",
"ioredis": "4.28.0",
@@ -63,15 +64,17 @@
"@types/ioredis": "4.28.0",
"@types/jest": "27.5.1",
"@types/koa": "2.13.4",
+ "@types/koa-pino-logger": "3.0.0",
"@types/lodash": "4.14.180",
"@types/node": "14.18.20",
"@types/node-fetch": "2.6.1",
+ "@types/pino-http": "5.8.1",
"@types/pouchdb": "6.4.0",
"@types/redlock": "4.0.3",
"@types/semver": "7.3.7",
"@types/tar-fs": "2.0.1",
"@types/uuid": "8.3.4",
- "chance": "1.1.3",
+ "chance": "1.1.8",
"ioredis-mock": "5.8.0",
"jest": "28.1.1",
"koa": "2.13.4",
diff --git a/packages/backend-core/src/cloud/api.ts b/packages/backend-core/src/cloud/api.ts
index b601b2e64b..ef9b9d88e6 100644
--- a/packages/backend-core/src/cloud/api.ts
+++ b/packages/backend-core/src/cloud/api.ts
@@ -1,4 +1,5 @@
import fetch from "node-fetch"
+import * as logging from "../logging"
export default class API {
host: string
@@ -22,6 +23,9 @@ export default class API {
let json = options.headers["Content-Type"] === "application/json"
+ // add x-budibase-correlation-id header
+ logging.correlation.setHeader(options.headers)
+
const requestOptions = {
method: method,
body: json ? JSON.stringify(options.body) : options.body,
diff --git a/packages/backend-core/src/constants/misc.ts b/packages/backend-core/src/constants/misc.ts
index 61b3cea1f6..0bf3df4094 100644
--- a/packages/backend-core/src/constants/misc.ts
+++ b/packages/backend-core/src/constants/misc.ts
@@ -22,6 +22,7 @@ export enum Header {
TENANT_ID = "x-budibase-tenant-id",
TOKEN = "x-budibase-token",
CSRF_TOKEN = "x-csrf-token",
+ CORRELATION_ID = "x-budibase-correlation-id",
}
export enum GlobalRole {
diff --git a/packages/backend-core/src/events/processors/LoggingProcessor.ts b/packages/backend-core/src/events/processors/LoggingProcessor.ts
index d41a82fbb4..6bb691a83a 100644
--- a/packages/backend-core/src/events/processors/LoggingProcessor.ts
+++ b/packages/backend-core/src/events/processors/LoggingProcessor.ts
@@ -23,7 +23,7 @@ export default class LoggingProcessor implements EventProcessor {
return
}
let timestampString = getTimestampString(timestamp)
- let message = `[audit] [tenant=${identity.tenantId}] [identityType=${identity.type}] [identity=${identity.id}] ${timestampString} ${event} `
+ let message = `[audit] [identityType=${identity.type}] ${timestampString} ${event} `
if (env.isDev()) {
message = message + `[debug: [properties=${JSON.stringify(properties)}] ]`
}
diff --git a/packages/backend-core/src/events/publishers/rows.ts b/packages/backend-core/src/events/publishers/rows.ts
index b1180fd936..9608613e89 100644
--- a/packages/backend-core/src/events/publishers/rows.ts
+++ b/packages/backend-core/src/events/publishers/rows.ts
@@ -3,7 +3,6 @@ import {
Event,
RowsImportedEvent,
RowsCreatedEvent,
- RowImportFormat,
Table,
} from "@budibase/types"
@@ -16,14 +15,9 @@ const created = async (count: number, timestamp?: string | number) => {
await publishEvent(Event.ROWS_CREATED, properties, timestamp)
}
-const imported = async (
- table: Table,
- format: RowImportFormat,
- count: number
-) => {
+const imported = async (table: Table, count: number) => {
const properties: RowsImportedEvent = {
tableId: table._id as string,
- format,
count,
}
await publishEvent(Event.ROWS_IMPORTED, properties)
diff --git a/packages/backend-core/src/events/publishers/table.ts b/packages/backend-core/src/events/publishers/table.ts
index 878885c33b..d50f4df0e1 100644
--- a/packages/backend-core/src/events/publishers/table.ts
+++ b/packages/backend-core/src/events/publishers/table.ts
@@ -2,7 +2,6 @@ import { publishEvent } from "../events"
import {
Event,
TableExportFormat,
- TableImportFormat,
Table,
TableCreatedEvent,
TableUpdatedEvent,
@@ -40,10 +39,9 @@ async function exported(table: Table, format: TableExportFormat) {
await publishEvent(Event.TABLE_EXPORTED, properties)
}
-async function imported(table: Table, format: TableImportFormat) {
+async function imported(table: Table) {
const properties: TableImportedEvent = {
tableId: table._id as string,
- format,
}
await publishEvent(Event.TABLE_IMPORTED, properties)
}
diff --git a/packages/backend-core/src/logging.ts b/packages/backend-core/src/logging.ts
index beffc98e8d..8f2ae6e619 100644
--- a/packages/backend-core/src/logging.ts
+++ b/packages/backend-core/src/logging.ts
@@ -1,4 +1,8 @@
+import { Header } from "./constants"
import env from "./environment"
+const correlator = require("correlation-id")
+import { Options } from "pino-http"
+import { IncomingMessage } from "http"
const NonErrors = ["AccountError"]
@@ -31,14 +35,26 @@ export function logWarn(message: string) {
console.warn(`bb-warn: ${message}`)
}
-export function pinoSettings() {
+export function pinoSettings(): Options {
return {
prettyPrint: {
levelFirst: true,
},
+ genReqId: correlator.getId,
level: env.LOG_LEVEL || "error",
autoLogging: {
- ignore: (req: { url: string }) => req.url.includes("/health"),
+ ignore: (req: IncomingMessage) => !!req.url?.includes("/health"),
},
}
}
+
+const setCorrelationHeader = (headers: any) => {
+ const correlationId = correlator.getId()
+ if (correlationId) {
+ headers[Header.CORRELATION_ID] = correlationId
+ }
+}
+
+export const correlation = {
+ setHeader: setCorrelationHeader,
+}
diff --git a/packages/backend-core/src/middleware/index.ts b/packages/backend-core/src/middleware/index.ts
index 91de59d0f8..4986cde64b 100644
--- a/packages/backend-core/src/middleware/index.ts
+++ b/packages/backend-core/src/middleware/index.ts
@@ -15,4 +15,5 @@ export { default as csrf } from "./csrf"
export { default as adminOnly } from "./adminOnly"
export { default as builderOrAdmin } from "./builderOrAdmin"
export { default as builderOnly } from "./builderOnly"
+export { default as logging } from "./logging"
export * as joiValidator from "./joi-validator"
diff --git a/packages/backend-core/src/middleware/logging.ts b/packages/backend-core/src/middleware/logging.ts
new file mode 100644
index 0000000000..d1f2d6566b
--- /dev/null
+++ b/packages/backend-core/src/middleware/logging.ts
@@ -0,0 +1,88 @@
+const correlator = require("correlation-id")
+import { Header } from "../constants"
+import { v4 as uuid } from "uuid"
+import * as context from "../context"
+
+const debug = console.warn
+const trace = console.trace
+const log = console.log
+const info = console.info
+const warn = console.warn
+const error = console.error
+
+const getTenantId = () => {
+ let tenantId
+ try {
+ tenantId = context.getTenantId()
+ } catch (e: any) {
+ // do nothing
+ }
+ return tenantId
+}
+
+const getAppId = () => {
+ let appId
+ try {
+ appId = context.getAppId()
+ } catch (e) {
+ // do nothing
+ }
+ return appId
+}
+
+const getIdentityId = () => {
+ let identityId
+ try {
+ const identity = context.getIdentity()
+ identityId = identity?._id
+ } catch (e) {
+ // do nothing
+ }
+ return identityId
+}
+
+const print = (fn: any, data: any[]) => {
+ let message = ""
+
+ const correlationId = correlator.getId()
+ if (correlationId) {
+ message = message + `[correlationId=${correlator.getId()}]`
+ }
+
+ const tenantId = getTenantId()
+ if (tenantId) {
+ message = message + ` [tenantId=${tenantId}]`
+ }
+
+ const appId = getAppId()
+ if (appId) {
+ message = message + ` [appId=${appId}]`
+ }
+
+ const identityId = getIdentityId()
+ if (identityId) {
+ message = message + ` [identityId=${identityId}]`
+ }
+
+ fn(message, data)
+}
+
+const logging = (ctx: any, next: any) => {
+ // use the provided correlation id header if present
+ let correlationId = ctx.headers[Header.CORRELATION_ID]
+ if (!correlationId) {
+ correlationId = uuid()
+ }
+
+ return correlator.withId(correlationId, () => {
+ console.debug = data => print(debug, data)
+ console.trace = data => print(trace, data)
+ console.log = data => print(log, data)
+ console.info = data => print(info, data)
+ console.warn = data => print(warn, data)
+ console.error = data => print(error, data)
+ return next()
+ })
+}
+
+export default logging
diff --git a/packages/backend-core/src/utils/tests/utils.spec.ts b/packages/backend-core/src/utils/tests/utils.spec.ts
index bb76a93653..498aff1555 100644
--- a/packages/backend-core/src/utils/tests/utils.spec.ts
+++ b/packages/backend-core/src/utils/tests/utils.spec.ts
@@ -1,7 +1,8 @@
import { structures } from "../../../tests"
import * as utils from "../../utils"
import * as events from "../../events"
-import { DEFAULT_TENANT_ID } from "../../constants"
+import * as db from "../../db"
+import { DEFAULT_TENANT_ID, Header } from "../../constants"
import { doInTenant } from "../../context"
describe("utils", () => {
@@ -14,4 +15,95 @@ describe("utils", () => {
})
})
})
+
+ describe("getAppIdFromCtx", () => {
+ it("gets appId from header", async () => {
+ const ctx = structures.koa.newContext()
+ const expected = db.generateAppID()
+ ctx.request.headers = {
+ [Header.APP_ID]: expected,
+ }
+
+ const actual = await utils.getAppIdFromCtx(ctx)
+ expect(actual).toBe(expected)
+ })
+
+ it("gets appId from body", async () => {
+ const ctx = structures.koa.newContext()
+ const expected = db.generateAppID()
+ ctx.request.body = {
+ appId: expected,
+ }
+
+ const actual = await utils.getAppIdFromCtx(ctx)
+ expect(actual).toBe(expected)
+ })
+
+ it("gets appId from path", async () => {
+ const ctx = structures.koa.newContext()
+ const expected = db.generateAppID()
+ ctx.path = `/apps/${expected}`
+
+ const actual = await utils.getAppIdFromCtx(ctx)
+ expect(actual).toBe(expected)
+ })
+
+ it("gets appId from url", async () => {
+ const ctx = structures.koa.newContext()
+ const expected = db.generateAppID()
+ const app = structures.apps.app(expected)
+
+ // set custom url
+ const appUrl = "custom-url"
+ app.url = `/${appUrl}`
+ ctx.path = `/app/${appUrl}`
+
+ // save the app
+ const database = db.getDB(expected)
+ await database.put(app)
+
+ const actual = await utils.getAppIdFromCtx(ctx)
+ expect(actual).toBe(expected)
+ })
+
+ it("doesn't get appId from url when previewing", async () => {
+ const ctx = structures.koa.newContext()
+ const appId = db.generateAppID()
+ const app = structures.apps.app(appId)
+
+ // set custom url
+ const appUrl = "preview"
+ app.url = `/${appUrl}`
+ ctx.path = `/app/${appUrl}`
+
+ // save the app
+ const database = db.getDB(appId)
+ await database.put(app)
+
+ const actual = await utils.getAppIdFromCtx(ctx)
+ expect(actual).toBe(undefined)
+ })
+
+ it("gets appId from referer", async () => {
+ const ctx = structures.koa.newContext()
+ const expected = db.generateAppID()
+ ctx.request.headers = {
+ referer: `http://test.com/builder/app/${expected}/design/screen_123/screens`,
+ }
+
+ const actual = await utils.getAppIdFromCtx(ctx)
+ expect(actual).toBe(expected)
+ })
+
+ it("doesn't get appId from referer when not builder", async () => {
+ const ctx = structures.koa.newContext()
+ const appId = db.generateAppID()
+ ctx.request.headers = {
+ referer: `http://test.com/foo/app/${appId}/bar`,
+ }
+
+ const actual = await utils.getAppIdFromCtx(ctx)
+ expect(actual).toBe(undefined)
+ })
+ })
})
diff --git a/packages/backend-core/src/utils/utils.ts b/packages/backend-core/src/utils/utils.ts
index fd8d31b13f..c608686431 100644
--- a/packages/backend-core/src/utils/utils.ts
+++ b/packages/backend-core/src/utils/utils.ts
@@ -25,13 +25,16 @@ const jwt = require("jsonwebtoken")
const APP_PREFIX = DocumentType.APP + SEPARATOR
const PROD_APP_PREFIX = "/app/"
+const BUILDER_PREVIEW_PATH = "/app/preview"
+const BUILDER_REFERER_PREFIX = "/builder/app/"
+
function confirmAppId(possibleAppId: string | undefined) {
return possibleAppId && possibleAppId.startsWith(APP_PREFIX)
? possibleAppId
: undefined
}
-async function resolveAppUrl(ctx: Ctx) {
+export async function resolveAppUrl(ctx: Ctx) {
const appUrl = ctx.path.split("/")[2]
let possibleAppUrl = `/${appUrl.toLowerCase()}`
@@ -75,7 +78,7 @@ export function isServingApp(ctx: Ctx) {
*/
export async function getAppIdFromCtx(ctx: Ctx) {
// look in headers
- const options = [ctx.headers[Header.APP_ID]]
+ const options = [ctx.request.headers[Header.APP_ID]]
let appId
for (let option of options) {
appId = confirmAppId(option as string)
@@ -95,15 +98,23 @@ export async function getAppIdFromCtx(ctx: Ctx) {
appId = confirmAppId(pathId)
}
- // look in the referer
- const refererId = parseAppIdFromUrl(ctx.request.headers.referer)
- if (!appId && refererId) {
- appId = confirmAppId(refererId)
+ // lookup using custom url - prod apps only
+ // filter out the builder preview path which collides with the prod app path
+ // to ensure we don't load all apps excessively
+ const isBuilderPreview = ctx.path.startsWith(BUILDER_PREVIEW_PATH)
+ const isViewingProdApp =
+ ctx.path.startsWith(PROD_APP_PREFIX) && !isBuilderPreview
+ if (!appId && isViewingProdApp) {
+ appId = confirmAppId(await resolveAppUrl(ctx))
}
- // look in the url - prod app
- if (!appId && ctx.path.startsWith(PROD_APP_PREFIX)) {
- appId = confirmAppId(await resolveAppUrl(ctx))
+ // look in the referer - builder only
+ // make sure this is performed after prod app url resolution, in case the
+ // referer header is present from a builder redirect
+ const referer = ctx.request.headers.referer
+ if (!appId && referer?.includes(BUILDER_REFERER_PREFIX)) {
+ const refererId = parseAppIdFromUrl(ctx.request.headers.referer)
+ appId = confirmAppId(refererId)
}
return appId
diff --git a/packages/backend-core/tests/utilities/structures/apps.ts b/packages/backend-core/tests/utilities/structures/apps.ts
new file mode 100644
index 0000000000..f3743d99b2
--- /dev/null
+++ b/packages/backend-core/tests/utilities/structures/apps.ts
@@ -0,0 +1,21 @@
+import { generator } from "."
+import { App } from "@budibase/types"
+import { DEFAULT_TENANT_ID, DocumentType } from "../../../src/constants"
+
+export function app(id: string): App {
+ return {
+ _id: DocumentType.APP_METADATA,
+ appId: id,
+ type: "",
+ version: "0.0.1",
+ componentLibraries: [],
+ name: generator.name(),
+ url: `/custom-url`,
+ instance: {
+ _id: id,
+ },
+ tenantId: DEFAULT_TENANT_ID,
+ status: "",
+ template: undefined,
+ }
+}
diff --git a/packages/backend-core/tests/utilities/structures/index.ts b/packages/backend-core/tests/utilities/structures/index.ts
index e0ed4df9c4..0d0f0c507f 100644
--- a/packages/backend-core/tests/utilities/structures/index.ts
+++ b/packages/backend-core/tests/utilities/structures/index.ts
@@ -3,7 +3,8 @@ export * from "./common"
import Chance from "chance"
export const generator = new Chance()
-export * as koa from "./koa"
export * as accounts from "./accounts"
+export * as apps from "./apps"
+export * as koa from "./koa"
export * as licenses from "./licenses"
export * as plugins from "./plugins"
diff --git a/packages/backend-core/tests/utilities/structures/koa.ts b/packages/backend-core/tests/utilities/structures/koa.ts
index a33dca1546..102fe029de 100644
--- a/packages/backend-core/tests/utilities/structures/koa.ts
+++ b/packages/backend-core/tests/utilities/structures/koa.ts
@@ -5,9 +5,11 @@ export const newContext = (): BBContext => {
const ctx = createMockContext()
return {
...ctx,
+ path: "/",
cookies: createMockCookies(),
request: {
...ctx.request,
+ headers: {},
body: {},
},
}
diff --git a/packages/backend-core/tsconfig.build.json b/packages/backend-core/tsconfig.build.json
index 25cfb70899..12f8255a7c 100644
--- a/packages/backend-core/tsconfig.build.json
+++ b/packages/backend-core/tsconfig.build.json
@@ -12,6 +12,7 @@
"declaration": true,
"types": [ "node", "jest" ],
"outDir": "dist",
+ "skipLibCheck": true
},
"include": [
"**/*.js",
diff --git a/packages/bbui/package.json b/packages/bbui/package.json
index 856465a0b7..6b67ca77d0 100644
--- a/packages/bbui/package.json
+++ b/packages/bbui/package.json
@@ -1,7 +1,7 @@
{
"name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.",
- "version": "2.2.12-alpha.6",
+ "version": "2.2.12-alpha.32",
"license": "MPL-2.0",
"svelte": "src/index.js",
"module": "dist/bbui.es.js",
@@ -38,7 +38,7 @@
],
"dependencies": {
"@adobe/spectrum-css-workflow-icons": "1.2.1",
- "@budibase/string-templates": "2.2.12-alpha.6",
+ "@budibase/string-templates": "2.2.12-alpha.32",
"@spectrum-css/actionbutton": "1.0.1",
"@spectrum-css/actiongroup": "1.0.1",
"@spectrum-css/avatar": "3.0.2",
diff --git a/packages/bbui/src/Actions/click_outside.js b/packages/bbui/src/Actions/click_outside.js
index 3a08484635..6842b94a32 100644
--- a/packages/bbui/src/Actions/click_outside.js
+++ b/packages/bbui/src/Actions/click_outside.js
@@ -1,11 +1,11 @@
-const ignoredClasses = [".flatpickr-calendar", ".modal-container"]
+const ignoredClasses = [".flatpickr-calendar"]
let clickHandlers = []
/**
* Handle a body click event
*/
const handleClick = event => {
- // Ignore click if needed
+ // Ignore click if this is an ignored class
for (let className of ignoredClasses) {
if (event.target.closest(className)) {
return
@@ -14,9 +14,18 @@ const handleClick = event => {
// Process handlers
clickHandlers.forEach(handler => {
- if (!handler.element.contains(event.target)) {
- handler.callback?.(event)
+ if (handler.element.contains(event.target)) {
+ return
}
+
+ // Ignore clicks for modals, unless the handler is registered from a modal
+ const sourceInModal = handler.element.closest(".spectrum-Modal") != null
+ const clickInModal = event.target.closest(".spectrum-Modal") != null
+ if (clickInModal && !sourceInModal) {
+ return
+ }
+
+ handler.callback?.(event)
})
}
document.documentElement.addEventListener("click", handleClick, true)
diff --git a/packages/bbui/src/Actions/position_dropdown.js b/packages/bbui/src/Actions/position_dropdown.js
index 7570a39c8c..463b69169f 100644
--- a/packages/bbui/src/Actions/position_dropdown.js
+++ b/packages/bbui/src/Actions/position_dropdown.js
@@ -1,75 +1,68 @@
-export default function positionDropdown(element, { anchor, align, maxWidth }) {
- let positionSide = "top"
- let maxHeight = 0
- let dimensions = getDimensions(anchor)
+export default function positionDropdown(
+ element,
+ { anchor, align, maxWidth, useAnchorWidth }
+) {
+ const update = () => {
+ const anchorBounds = anchor.getBoundingClientRect()
+ const elementBounds = element.getBoundingClientRect()
+ let styles = {
+ maxHeight: null,
+ minWidth: null,
+ maxWidth,
+ left: null,
+ top: null,
+ }
- function getDimensions() {
- const {
- bottom,
- top: spaceAbove,
- left,
- width,
- } = anchor.getBoundingClientRect()
- const spaceBelow = window.innerHeight - bottom
- const containerRect = element.getBoundingClientRect()
-
- let y
-
- if (spaceAbove > spaceBelow) {
- positionSide = "bottom"
- maxHeight = spaceAbove - 20
- y = window.innerHeight - spaceAbove + 5
+ // Determine vertical styles
+ if (window.innerHeight - anchorBounds.bottom < 100) {
+ styles.top = anchorBounds.top - elementBounds.height - 5
} else {
- positionSide = "top"
- y = bottom + 5
- maxHeight = spaceBelow - 20
+ styles.top = anchorBounds.bottom + 5
+ styles.maxHeight = window.innerHeight - anchorBounds.bottom - 20
}
- return {
- [positionSide]: y,
- left,
- width,
- containerWidth: containerRect.width,
+ // Determine horizontal styles
+ if (!maxWidth && useAnchorWidth) {
+ styles.maxWidth = anchorBounds.width
}
+ if (useAnchorWidth) {
+ styles.minWidth = anchorBounds.width
+ }
+ if (align === "right") {
+ styles.left = anchorBounds.left + anchorBounds.width - elementBounds.width
+ } else if (align === "right-side") {
+ styles.left = anchorBounds.left + anchorBounds.width
+ } else {
+ styles.left = anchorBounds.left
+ }
+
+ // Apply styles
+ Object.entries(styles).forEach(([style, value]) => {
+ if (value) {
+ element.style[style] = `${value.toFixed(0)}px`
+ } else {
+ element.style[style] = null
+ }
+ })
}
- function calcLeftPosition() {
- let left
-
- if (align == "right") {
- left = dimensions.left + dimensions.width - dimensions.containerWidth
- } else if (align == "right-side") {
- left = dimensions.left + dimensions.width
- } else {
- left = dimensions.left
- }
-
- return left
- }
-
+ // Apply initial styles which don't need to change
element.style.position = "absolute"
element.style.zIndex = "9999"
- if (maxWidth) {
- element.style.maxWidth = `${maxWidth}px`
- }
- element.style.minWidth = `${dimensions.width}px`
- element.style.maxHeight = `${maxHeight.toFixed(0)}px`
- element.style.transformOrigin = `center ${positionSide}`
- element.style[positionSide] = `${dimensions[positionSide]}px`
- element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px`
+ // Observe both anchor and element and resize the popover as appropriate
const resizeObserver = new ResizeObserver(entries => {
- entries.forEach(() => {
- dimensions = getDimensions()
- element.style[positionSide] = `${dimensions[positionSide]}px`
- element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px`
- })
+ entries.forEach(update)
})
resizeObserver.observe(anchor)
resizeObserver.observe(element)
+
+ document.addEventListener("scroll", update, true)
+
return {
destroy() {
resizeObserver.disconnect()
+ document.removeEventListener("scroll", update, true)
},
}
}
diff --git a/packages/bbui/src/Avatar/Avatar.svelte b/packages/bbui/src/Avatar/Avatar.svelte
index 136a4fe24b..1e4cefd8ce 100644
--- a/packages/bbui/src/Avatar/Avatar.svelte
+++ b/packages/bbui/src/Avatar/Avatar.svelte
@@ -58,5 +58,6 @@
overflow: hidden;
user-select: none;
text-transform: uppercase;
+ flex-shrink: 0;
}
diff --git a/packages/bbui/src/Button/Button.svelte b/packages/bbui/src/Button/Button.svelte
index 36abcbf4da..979ec6a728 100644
--- a/packages/bbui/src/Button/Button.svelte
+++ b/packages/bbui/src/Button/Button.svelte
@@ -14,7 +14,7 @@
export let active = false
export let tooltip = undefined
export let dataCy
- export let newStyles = false
+ export let newStyles = true
let showTooltip = false
@@ -28,6 +28,7 @@
class:spectrum-Button--quiet={quiet}
class:new-styles={newStyles}
class:active
+ class:disabled
class="spectrum-Button spectrum-Button--size{size.toUpperCase()}"
{disabled}
data-cy={dataCy}
@@ -108,7 +109,10 @@
border-color: transparent;
color: var(--spectrum-global-color-gray-900);
}
- .spectrum-Button--secondary.new-styles:hover {
+ .spectrum-Button--secondary.new-styles:not(.disabled):hover {
background: var(--spectrum-global-color-gray-300);
}
+ .spectrum-Button--secondary.new-styles.disabled {
+ color: var(--spectrum-global-color-gray-500);
+ }
diff --git a/packages/bbui/src/Drawer/DrawerContent.svelte b/packages/bbui/src/Drawer/DrawerContent.svelte
index 28e0975d82..944a3f4313 100644
--- a/packages/bbui/src/Drawer/DrawerContent.svelte
+++ b/packages/bbui/src/Drawer/DrawerContent.svelte
@@ -34,7 +34,6 @@
display: none;
}
.main {
- font-family: var(--font-sans);
padding: var(--spacing-xl);
}
.main :global(textarea) {
diff --git a/packages/bbui/src/Form/Core/DatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker.svelte
index 6996525a76..10aae67ec6 100644
--- a/packages/bbui/src/Form/Core/DatePicker.svelte
+++ b/packages/bbui/src/Form/Core/DatePicker.svelte
@@ -264,7 +264,7 @@
max-height: 100%;
}
:global(.flatpickr-calendar) {
- font-family: "Source Sans Pro", sans-serif;
+ font-family: var(--font-sans);
}
.is-disabled {
pointer-events: none !important;
diff --git a/packages/bbui/src/Form/Core/Picker.svelte b/packages/bbui/src/Form/Core/Picker.svelte
index 97bd1394b4..32cfcf3310 100644
--- a/packages/bbui/src/Form/Core/Picker.svelte
+++ b/packages/bbui/src/Form/Core/Picker.svelte
@@ -2,12 +2,12 @@
import "@spectrum-css/picker/dist/index-vars.css"
import "@spectrum-css/popover/dist/index-vars.css"
import "@spectrum-css/menu/dist/index-vars.css"
- import { fly } from "svelte/transition"
import { createEventDispatcher } from "svelte"
import clickOutside from "../../Actions/click_outside"
import Search from "./Search.svelte"
import Icon from "../../Icon/Icon.svelte"
import StatusLight from "../../StatusLight/StatusLight.svelte"
+ import Popover from "../../Popover/Popover.svelte"
export let id = null
export let disabled = false
@@ -33,7 +33,10 @@
export let sort = false
const dispatch = createEventDispatcher()
+
let searchTerm = null
+ let button
+ let popover
$: sortedOptions = getSortedOptions(options, getOptionLabel, sort)
$: filteredOptions = getFilteredOptions(
@@ -42,7 +45,9 @@
getOptionLabel
)
- const onClick = () => {
+ const onClick = e => {
+ e.preventDefault()
+ e.stopPropagation()
dispatch("click")
if (readonly) {
return
@@ -76,77 +81,119 @@
}
-
(open = false)}>
-
+
diff --git a/packages/bbui/src/Form/Core/TextField.svelte b/packages/bbui/src/Form/Core/TextField.svelte
index 0a723c140a..6a64876a2c 100644
--- a/packages/bbui/src/Form/Core/TextField.svelte
+++ b/packages/bbui/src/Form/Core/TextField.svelte
@@ -112,8 +112,4 @@
.spectrum-Textfield {
width: 100%;
}
- input:disabled {
- color: var(--spectrum-global-color-gray-600) !important;
- -webkit-text-fill-color: var(--spectrum-global-color-gray-600) !important;
- }
diff --git a/packages/bbui/src/Icon/IconAvatar.svelte b/packages/bbui/src/Icon/IconAvatar.svelte
index b404cdea9f..add51f5bdc 100644
--- a/packages/bbui/src/Icon/IconAvatar.svelte
+++ b/packages/bbui/src/Icon/IconAvatar.svelte
@@ -19,6 +19,7 @@
.icon {
width: 28px;
height: 28px;
+ flex: 0 0 28px;
display: grid;
place-items: center;
border-radius: 50%;
@@ -34,6 +35,7 @@
.icon.size--S {
width: 22px;
height: 22px;
+ flex: 0 0 22px;
}
.icon.size--S :global(.spectrum-Icon) {
width: 16px;
@@ -46,6 +48,7 @@
.icon.size--L {
width: 40px;
height: 40px;
+ flex: 0 0 40px;
}
.icon.size--L :global(.spectrum-Icon) {
width: 28px;
diff --git a/packages/bbui/src/InlineAlert/InlineAlert.svelte b/packages/bbui/src/InlineAlert/InlineAlert.svelte
index 94ac6b2c2a..57e7296234 100644
--- a/packages/bbui/src/InlineAlert/InlineAlert.svelte
+++ b/packages/bbui/src/InlineAlert/InlineAlert.svelte
@@ -56,5 +56,6 @@
--spectrum-semantic-positive-icon-color: #2d9d78;
--spectrum-semantic-negative-icon-color: #e34850;
min-width: 100px;
+ margin: 0;
}
diff --git a/packages/bbui/src/Label/Label.svelte b/packages/bbui/src/Label/Label.svelte
index 6b3392ce2d..ee6d9adf76 100644
--- a/packages/bbui/src/Label/Label.svelte
+++ b/packages/bbui/src/Label/Label.svelte
@@ -21,6 +21,7 @@
label {
padding: 0;
white-space: nowrap;
+ color: var(--spectrum-global-color-gray-600);
}
.muted {
diff --git a/packages/bbui/src/Layout/Page.svelte b/packages/bbui/src/Layout/Page.svelte
index 2996bcc613..15aabd2c61 100644
--- a/packages/bbui/src/Layout/Page.svelte
+++ b/packages/bbui/src/Layout/Page.svelte
@@ -1,32 +1,95 @@
-
-
+
+
+
{
+ sidePanelVisble = false
+ }}
+ >
+
+
diff --git a/packages/bbui/src/List/ListItem.svelte b/packages/bbui/src/List/ListItem.svelte
index 40d3c5541c..28015c4c57 100644
--- a/packages/bbui/src/List/ListItem.svelte
+++ b/packages/bbui/src/List/ListItem.svelte
@@ -30,9 +30,11 @@
{/if}
-
-
-
+ {#if $$slots.default}
+
+
+
+ {/if}
diff --git a/packages/bbui/src/Table/Table.svelte b/packages/bbui/src/Table/Table.svelte
index cec270486a..19d361c8b1 100644
--- a/packages/bbui/src/Table/Table.svelte
+++ b/packages/bbui/src/Table/Table.svelte
@@ -21,6 +21,8 @@
* template: a HBS or JS binding to use as the value
* background: the background color
* color: the text color
+ * borderLeft: show a left border
+ * borderRight: show a right border
*/
export let data = []
export let schema = {}
@@ -31,6 +33,7 @@
export let allowSelectRows
export let allowEditRows = true
export let allowEditColumns = true
+ export let allowClickRows = true
export let selectedRows = []
export let customRenderers = []
export let disableSorting = false
@@ -270,6 +273,17 @@
if (schema[field].align === "Right") {
styles[field] += "justify-content: flex-end; text-align: right;"
}
+ if (schema[field].borderLeft) {
+ styles[field] +=
+ "border-left: 1px solid var(--spectrum-global-color-gray-200);"
+ }
+ if (schema[field].borderLeft) {
+ styles[field] +=
+ "border-right: 1px solid var(--spectrum-global-color-gray-200);"
+ }
+ if (schema[field].minWidth) {
+ styles[field] += `min-width: ${schema[field].minWidth};`
+ }
})
return styles
}
@@ -290,7 +304,11 @@
{:else}
-
+
{#if fields.length}
{#if showEditColumn}
@@ -356,7 +374,7 @@
{/if}
{#if sortedRows?.length}
{#each sortedRows as row, idx}
-
+
{#if showEditColumn}
diff --git a/packages/bbui/src/Tags/Tag.svelte b/packages/bbui/src/Tags/Tag.svelte
index f7089decdb..0cdd6c385d 100644
--- a/packages/bbui/src/Tags/Tag.svelte
+++ b/packages/bbui/src/Tags/Tag.svelte
@@ -37,7 +37,7 @@
diff --git a/packages/bbui/src/Tags/Tags.svelte b/packages/bbui/src/Tags/Tags.svelte
index 36d34507f3..ebca64b774 100644
--- a/packages/bbui/src/Tags/Tags.svelte
+++ b/packages/bbui/src/Tags/Tags.svelte
@@ -5,3 +5,13 @@
+
+
diff --git a/packages/bbui/src/Typography/Heading.svelte b/packages/bbui/src/Typography/Heading.svelte
index 077c0a4878..5f243ad5a6 100644
--- a/packages/bbui/src/Typography/Heading.svelte
+++ b/packages/bbui/src/Typography/Heading.svelte
@@ -15,3 +15,9 @@
>
+
+
diff --git a/packages/bbui/src/bbui.css b/packages/bbui/src/bbui.css
index f98f27bf58..ef3483d3df 100644
--- a/packages/bbui/src/bbui.css
+++ b/packages/bbui/src/bbui.css
@@ -40,12 +40,14 @@
--rounded-medium: 8px;
--rounded-large: 16px;
- --font-sans: Source Sans Pro, -apple-system, BlinkMacSystemFont, Segoe UI, "Inter",
- "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji",
- "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ --font-sans: "Source Sans Pro", -apple-system, BlinkMacSystemFont, Segoe UI, "Inter",
+ "Helvetica Neue", Arial, "Noto Sans", sans-serif;
+ --font-accent: "Source Sans Pro", -apple-system, BlinkMacSystemFont, Segoe UI, "Inter",
+ "Helvetica Neue", Arial, "Noto Sans", sans-serif;
--font-serif: "Georgia", Cambria, Times New Roman, Times, serif;
--font-mono: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
monospace;
+ --spectrum-alias-body-text-font-family: var(--font-sans);
font-size: 16px;
--font-size-xs: 0.75rem;
@@ -89,6 +91,8 @@
--border-light-2: 2px var(--grey-3) solid;
--border-blue: 2px var(--blue) solid;
--border-transparent: 2px transparent solid;
+
+ --spectrum-alias-text-color-disabled: var(--spectrum-global-color-gray-600);
}
a {
diff --git a/packages/bbui/src/context.js b/packages/bbui/src/context.js
index bb95f8b425..919f2140d6 100644
--- a/packages/bbui/src/context.js
+++ b/packages/bbui/src/context.js
@@ -1,3 +1,4 @@
export default {
Modal: "bbui-modal",
+ PopoverRoot: "bbui-popover-root",
}
diff --git a/packages/builder/assets/bb-emblem.svg b/packages/builder/assets/bb-emblem.svg
index 9f4f3690d5..7d499e4862 100644
--- a/packages/builder/assets/bb-emblem.svg
+++ b/packages/builder/assets/bb-emblem.svg
@@ -3,7 +3,7 @@