From c948ccfa700bc9ec8043adb0ddecda70487147bb Mon Sep 17 00:00:00 2001 From: Oliver Mesieh Date: Sat, 7 Feb 2026 11:28:18 +0100 Subject: [PATCH 1/3] Re-enable Bard debouncing and fix race condition This adds cancel() method to debounce utility to cancel pending debounced updates before immediate updates to prevent stale content from overwriting fresh changes. This could for instance happen if the user pastes content after deleting content in less than 150ms. --- resources/js/components/fieldtypes/Fieldtype.vue | 12 +++++++----- .../components/fieldtypes/bard/BardFieldtype.vue | 15 ++++++++------- resources/js/util/debounce.js | 9 ++++++++- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/resources/js/components/fieldtypes/Fieldtype.vue b/resources/js/components/fieldtypes/Fieldtype.vue index e4b824a767..2004e662b6 100644 --- a/resources/js/components/fieldtypes/Fieldtype.vue +++ b/resources/js/components/fieldtypes/Fieldtype.vue @@ -4,7 +4,7 @@ import debounce from '@/util/debounce.js'; import props from './props.js'; import emits from './emits.js'; import { publishContextKey } from '@/components/ui'; -import { isRef } from 'vue'; +import { isRef, markRaw } from 'vue'; export default { emits, @@ -24,15 +24,17 @@ export default { this.$emit('update:value', value); }, - updateDebounced: debounce(function (value) { - this.update(value); - }, 150), - updateMeta(value) { this.$emit('update:meta', value); }, }, + created() { + this.updateDebounced = markRaw(debounce((value) => { + this.update(value); + }, 150)); + }, + computed: { publishContainer() { // The injectedPublishContainer contains refs. We'll unwrap everything so that we can do diff --git a/resources/js/components/fieldtypes/bard/BardFieldtype.vue b/resources/js/components/fieldtypes/bard/BardFieldtype.vue index ee2a1fd405..a57e2aac26 100644 --- a/resources/js/components/fieldtypes/bard/BardFieldtype.vue +++ b/resources/js/components/fieldtypes/bard/BardFieldtype.vue @@ -416,14 +416,15 @@ export default { if (JSON.stringify(json) === JSON.stringify(oldJson)) return; - // Temporarily disable debouncing. - this.debounceNextUpdate = false; - - this.debounceNextUpdate - ? this.updateDebounced(json) - : this.update(json); - + const shouldDebounce = this.debounceNextUpdate; this.debounceNextUpdate = true; + + if (shouldDebounce) { + this.updateDebounced(json); + } else { + this.updateDebounced.cancel(); + this.update(json); + } }, value(value, oldValue) { diff --git a/resources/js/util/debounce.js b/resources/js/util/debounce.js index 0165d75d65..1dc726d303 100644 --- a/resources/js/util/debounce.js +++ b/resources/js/util/debounce.js @@ -1,7 +1,7 @@ export default function (func, wait, immediate) { let timeout; - return function () { + const debounced = function () { const context = this, args = arguments; @@ -14,4 +14,11 @@ export default function (func, wait, immediate) { if (!immediate) func.apply(context, args); }, wait); }; + + debounced.cancel = function () { + clearTimeout(timeout); + timeout = null; + }; + + return debounced; } From cbbd428e45743f84b50e4a61098c3ec9e23db038 Mon Sep 17 00:00:00 2001 From: Oliver Mesieh Date: Sat, 7 Feb 2026 12:15:07 +0100 Subject: [PATCH 2/3] Fix type error when moving Bard Sets When moving Sets with Bard fields, the value watcher could fire while the editor is null. --- resources/js/components/fieldtypes/bard/BardFieldtype.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/js/components/fieldtypes/bard/BardFieldtype.vue b/resources/js/components/fieldtypes/bard/BardFieldtype.vue index a57e2aac26..82a2af7832 100644 --- a/resources/js/components/fieldtypes/bard/BardFieldtype.vue +++ b/resources/js/components/fieldtypes/bard/BardFieldtype.vue @@ -428,6 +428,8 @@ export default { }, value(value, oldValue) { + if (!this.editor) return; + const oldContent = this.editor.getJSON(); const content = this.valueToContent(value); From 6fd1e1c2baa2f3cd2542fc672f56b19253f8730b Mon Sep 17 00:00:00 2001 From: Oliver Mesieh Date: Sat, 7 Feb 2026 15:25:15 +0100 Subject: [PATCH 3/3] Fix Firefox scroll jump when focusing nested Bard fields --- resources/css/components/fieldtypes/bard.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/css/components/fieldtypes/bard.css b/resources/css/components/fieldtypes/bard.css index fcaf3ec5d7..d76315499d 100644 --- a/resources/css/components/fieldtypes/bard.css +++ b/resources/css/components/fieldtypes/bard.css @@ -154,6 +154,8 @@ .bard-fixed-toolbar { z-index: var(--z-index-portal); position: sticky; + /* Opt out the toolbar of scroll anchoring. Firefox's scroll anchoring adjusts the scroll position to compensate for the perceived layout change, scrolling up to the parent Bard's top. */ + overflow-anchor: none; @apply sm:-top-2; /* Prevent the sticky toolbar from hitting the very end of container, which causes it to overlap the container's border-radius */ margin-block-end: 8px;