diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index 730e51bf48..17ecd8f844 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -334,7 +334,7 @@ // Add in defaults and initial definition const definition = fieldDefinitions[type?.toUpperCase()] if (definition?.constraints) { - editableColumn.constraints = definition.constraints + editableColumn.constraints = cloneDeep(definition.constraints) } editableColumn.type = definition.type diff --git a/packages/builder/src/components/common/Dropzone.svelte b/packages/builder/src/components/common/Dropzone.svelte index 37569df0d5..8cd85e2530 100644 --- a/packages/builder/src/components/common/Dropzone.svelte +++ b/packages/builder/src/components/common/Dropzone.svelte @@ -38,4 +38,5 @@ {processFiles} handleFileTooLarge={$admin.cloud ? handleFileTooLarge : null} {fileSizeLimit} + on:change /> diff --git a/packages/client/src/components/app/Button.svelte b/packages/client/src/components/app/Button.svelte index c43face1bb..29bdd46a70 100644 --- a/packages/client/src/components/app/Button.svelte +++ b/packages/client/src/components/app/Button.svelte @@ -5,8 +5,6 @@ const { styleable, builderStore } = getContext("sdk") const component = getContext("component") - let handlingOnClick = false - export let disabled = false export let text = "" export let onClick @@ -19,17 +17,9 @@ // For internal use only for now - not defined in the manifest export let active = false - const handleOnClick = async () => { - handlingOnClick = true - - if (onClick) { - await onClick() - } - - handlingOnClick = false - } - let node + let touched = false + let handlingOnClick = false $: $component.editing && node?.focus() $: componentText = getComponentText(text, $builderStore, $component) @@ -42,7 +32,18 @@ } const updateText = e => { - builderStore.actions.updateProp("text", e.target.textContent) + if (touched) { + builderStore.actions.updateProp("text", e.target.textContent) + } + touched = false + } + + const handleOnClick = async () => { + handlingOnClick = true + if (onClick) { + await onClick() + } + handlingOnClick = false } @@ -57,6 +58,7 @@ on:blur={$component.editing ? updateText : null} bind:this={node} class:active + on:input={() => (touched = true)} > {#if icon} diff --git a/packages/client/src/components/app/GridBlock.svelte b/packages/client/src/components/app/GridBlock.svelte index dbd651b533..7502dc1ba8 100644 --- a/packages/client/src/components/app/GridBlock.svelte +++ b/packages/client/src/components/app/GridBlock.svelte @@ -90,9 +90,11 @@ columns.forEach((column, idx) => { overrides[column.field] = { displayName: column.label, - width: column.width, order: idx, } + if (column.width) { + overrides[column.field].width = column.width + } }) return overrides } diff --git a/packages/client/src/components/app/Heading.svelte b/packages/client/src/components/app/Heading.svelte index 4adec59e2b..103ac31a93 100644 --- a/packages/client/src/components/app/Heading.svelte +++ b/packages/client/src/components/app/Heading.svelte @@ -14,6 +14,7 @@ export let size let node + let touched = false $: $component.editing && node?.focus() $: placeholder = $builderStore.inBuilder && !text && !$component.editing @@ -47,7 +48,10 @@ // Convert contenteditable HTML to text and save const updateText = e => { - builderStore.actions.updateProp("text", e.target.textContent) + if (touched) { + builderStore.actions.updateProp("text", e.target.textContent) + } + touched = false } @@ -62,6 +66,7 @@ class:underline class="spectrum-Heading {sizeClass} {alignClass}" on:blur={$component.editing ? updateText : null} + on:input={() => (touched = true)} > {componentText} diff --git a/packages/client/src/components/app/Link.svelte b/packages/client/src/components/app/Link.svelte index 6cabcec7df..7eddcc6fe5 100644 --- a/packages/client/src/components/app/Link.svelte +++ b/packages/client/src/components/app/Link.svelte @@ -16,6 +16,7 @@ export let size let node + let touched = false $: $component.editing && node?.focus() $: externalLink = url && typeof url === "string" && !url.startsWith("/") @@ -62,7 +63,10 @@ } const updateText = e => { - builderStore.actions.updateProp("text", e.target.textContent) + if (touched) { + builderStore.actions.updateProp("text", e.target.textContent) + } + touched = false } @@ -76,6 +80,7 @@ class:underline class="align--{align || 'left'} size--{size || 'M'}" on:blur={$component.editing ? updateText : null} + on:input={() => (touched = true)} > {componentText} diff --git a/packages/client/src/components/app/Text.svelte b/packages/client/src/components/app/Text.svelte index 1037725ff8..fa15868d0f 100644 --- a/packages/client/src/components/app/Text.svelte +++ b/packages/client/src/components/app/Text.svelte @@ -13,6 +13,7 @@ export let size let node + let touched = false $: $component.editing && node?.focus() $: placeholder = $builderStore.inBuilder && !text && !$component.editing @@ -46,7 +47,10 @@ // Convert contenteditable HTML to text and save const updateText = e => { - builderStore.actions.updateProp("text", e.target.textContent) + if (touched) { + builderStore.actions.updateProp("text", e.target.textContent) + } + touched = false } @@ -61,6 +65,7 @@ class:underline class="spectrum-Body {sizeClass} {alignClass}" on:blur={$component.editing ? updateText : null} + on:input={() => (touched = true)} > {componentText}

diff --git a/packages/client/src/components/app/forms/Field.svelte b/packages/client/src/components/app/forms/Field.svelte index 74ff5442a9..9210b6ea8f 100644 --- a/packages/client/src/components/app/forms/Field.svelte +++ b/packages/client/src/components/app/forms/Field.svelte @@ -26,6 +26,10 @@ // Register field with form const formApi = formContext?.formApi const labelPos = fieldGroupContext?.labelPosition || "above" + + let touched = false + let labelNode + $: formStep = formStepContext ? $formStepContext || 1 : 1 $: formField = formApi?.registerField( field, @@ -36,14 +40,12 @@ validation, formStep ) - $: schemaType = fieldSchema?.type !== "formula" && fieldSchema?.type !== "bigint" ? fieldSchema?.type : "string" // Focus label when editing - let labelNode $: $component.editing && labelNode?.focus() // Update form properties in parent component on every store change @@ -57,7 +59,10 @@ $: labelClass = labelPos === "above" ? "" : `spectrum-FieldLabel--${labelPos}` const updateLabel = e => { - builderStore.actions.updateProp("label", e.target.textContent) + if (touched) { + builderStore.actions.updateProp("label", e.target.textContent) + } + touched = false } onDestroy(() => { @@ -79,6 +84,7 @@ bind:this={labelNode} contenteditable={$component.editing} on:blur={$component.editing ? updateLabel : null} + on:input={() => (touched = true)} class:hidden={!label} class:readonly for={fieldState?.fieldId} diff --git a/packages/frontend-core/src/components/grid/cells/TextCell.svelte b/packages/frontend-core/src/components/grid/cells/TextCell.svelte index d2ee0189fe..0cf0ab2004 100644 --- a/packages/frontend-core/src/components/grid/cells/TextCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/TextCell.svelte @@ -81,6 +81,7 @@ } input { flex: 1 1 auto; + width: 0; border: none; padding: var(--cell-padding); overflow: hidden; diff --git a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte index ed94a01e56..f16a1183a4 100644 --- a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte +++ b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte @@ -116,7 +116,9 @@ {#each displayColumns as column}
- {column.label} +
+ {column.label} +
toggleColumn(column, e.detail)} @@ -139,7 +141,8 @@ display: grid; align-items: center; grid-template-columns: 1fr auto; - gap: 8px; + grid-row-gap: 8px; + grid-column-gap: 24px; } .columns :global(.spectrum-Switch) { margin-right: 0; @@ -148,4 +151,11 @@ display: flex; gap: 8px; } + .column-label { + min-width: 80px; + max-width: 200px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } diff --git a/packages/frontend-core/src/components/grid/controls/ToggleActionButtonGroup.svelte b/packages/frontend-core/src/components/grid/controls/ToggleActionButtonGroup.svelte index 2e62c593d1..497e77c2c9 100644 --- a/packages/frontend-core/src/components/grid/controls/ToggleActionButtonGroup.svelte +++ b/packages/frontend-core/src/components/grid/controls/ToggleActionButtonGroup.svelte @@ -29,7 +29,6 @@ .permissionPicker { display: flex; gap: var(--spacing-xs); - padding-left: calc(var(--spacing-xl) * 2); } .permissionPicker :global(.spectrum-Icon) { diff --git a/packages/frontend-core/src/components/grid/layout/GridBody.svelte b/packages/frontend-core/src/components/grid/layout/GridBody.svelte index 87fcfe2d32..8be56674be 100644 --- a/packages/frontend-core/src/components/grid/layout/GridBody.svelte +++ b/packages/frontend-core/src/components/grid/layout/GridBody.svelte @@ -23,14 +23,24 @@ 0 ) + const updateBounds = () => { + bounds.set(body.getBoundingClientRect()) + } + onMount(() => { // Observe and record the height of the body - const observer = new ResizeObserver(() => { - bounds.set(body.getBoundingClientRect()) - }) - observer.observe(body) + const resizeObserver = new ResizeObserver(updateBounds) + resizeObserver.observe(body) + + // Capture any wheel events on the page to ensure our scroll offset is + // correct. We don't care about touch events as we only need this for + // hovering over rows with a mouse. + window.addEventListener("wheel", updateBounds, true) + + // Clean up listeners return () => { - observer.disconnect() + resizeObserver.disconnect() + window.removeEventListener("wheel", updateBounds, true) } }) diff --git a/packages/frontend-core/src/components/grid/stores/datasource.js b/packages/frontend-core/src/components/grid/stores/datasource.js index 09b8be4868..8b8ffdf2cf 100644 --- a/packages/frontend-core/src/components/grid/stores/datasource.js +++ b/packages/frontend-core/src/components/grid/stores/datasource.js @@ -94,6 +94,7 @@ export const createActions = context => { nonPlus, schemaMutations, schema, + notifications, } = context // Gets the appropriate API for the configured datasource type @@ -125,16 +126,25 @@ export const createActions = context => { // Saves the datasource definition const saveDefinition = async newDefinition => { // Update local state + const originalDefinition = get(definition) definition.set(newDefinition) // Update server if (get(config).canSaveSchema) { - await getAPI()?.actions.saveDefinition(newDefinition) + try { + await getAPI()?.actions.saveDefinition(newDefinition) - // Broadcast change so external state can be updated, as this change - // will not be received by the builder websocket because we caused it - // ourselves - dispatch("updatedatasource", newDefinition) + // Broadcast change so external state can be updated, as this change + // will not be received by the builder websocket because we caused it + // ourselves + dispatch("updatedatasource", newDefinition) + } catch (error) { + const msg = error?.message || error || "Unknown error" + get(notifications).error(`Error saving schema: ${msg}`) + + // Reset the definition if saving failed + definition.set(originalDefinition) + } } }