Conversation
|
@oliverfoster Good question, and it is a fair alternative. I went and measured it rather than reasoning about it, because my first instinct turned out to be half wrong. Summary: it would fix trickle, but it changes the meaning of a public getter during a window that any listener can stretch, and it would conflict with this PR rather than sit alongside it. It would work for trickle. Both sites this PR touches only ever ask about popups, so a closing-aware The catch is that the closing window is not short. Core does Measured in a sandbox (Framework 5.56.2, core 6.78.3):
During that window a dialog popup can still be genuinely modal. At 802 ms into the extended window I checked the drawer's element: Where I was wrong: this is not true for notify popups. In notifyPopupView.js the close animation runs, then It is observable in a core primitive, not just in theory.
So the change would let focus traversal walk into background content while a modal dialog is still up. Over 3 ms that is unlikely to matter. Over 803 ms, with content rendering and There is also a11y/scroll.js, which gates on The part that decided it for me: the two fixes conflict, they do not stack. If core later excludes the closing popup from Suggestion. Land this as the fix for the regression, since it is contained to one plugin and the bug currently makes pages impossible to complete. If you want the core-level fix, I would rather it were additive than a redefinition: either pass the closing layer as an argument, One thing worth noting either way: trickle is currently the only listener for Posted via collaboration with Claude Code |
|
@oliverfoster Correcting the last line of my previous comment, where I said "trickle is currently the only listener for I widened the search: 175 local plugin clones, plus GitHub code search across current Listeners for Readers of
That last one is why I was wrong. It does not listen to if (a11y.isPopupOpen) {
this.listenToOnce(Adapt, 'popup:closed', this.onPageScrollTo.bind(this, selector, settings));
return;
}It uses the getter to defer a scroll until the popup has gone, and waits for The general point stands regardless of which plugin it is: Worth saying explicitly, so the list above does not look worse than it is: the hits in hotgraphic, narrative, hotgrid and a few others are all private Readers of That gives a clean comparison of blast radius, which I think is the useful part:
None of that makes the core route wrong, and it is still the more complete fix for the underlying footgun. It does mean it needs that other consumer checked and Posted via collaboration with Claude Code |
|
This issue wasn't actually introduced by #264, as at the time that was written the plugin was still listening to adapt-contrib-trickle/js/TrickleButtonView.js Lines 93 to 94 in 0cb46eb Two different PRs were merged around the same time, with one affecting the other. Could the changes made for #233 instead add |
| @@ -91,7 +97,9 @@ class TrickleButtonView extends ComponentView { | |||
| } | |||
|
|
|||
| async onPopupClosed() { | |||
There was a problem hiding this comment.
As the event for this listener changed in #233 which subsequently caused an issue with #264, which would have otherwise been flagged before the merge, I would recommend this method changes to onPopupClosing to reflect the trigger.
Will require changes to:
adapt-contrib-trickle/js/TrickleButtonView.js
Line 185 in 0cb46eb
| // This fires before the closing popup is removed from the stack, so more | ||
| // than one means another popup is still open behind it | ||
| if (a11y.popupStack.length > 1) return; |
There was a problem hiding this comment.
Extract the guard condition into a named predicate?
| // This fires before the closing popup is removed from the stack, so more | |
| // than one means another popup is still open behind it | |
| if (a11y.popupStack.length > 1) return; | |
| const isAnotherPopupOpen = (a11y.popupStack.length > 1); | |
| if (isAnotherPopupOpen) return; |
| // A popup which is closing is still on the stack, so discount it when | ||
| // deciding whether the button should be disabled by an open popup | ||
| const openPopupCount = a11y.popupStack.length; | ||
| const isDisabledByPopups = this.isPopupClosing |
There was a problem hiding this comment.
You could argue that the wrong value is set here (remains disabled) when this method is reached via finish in the following condition:
adapt-contrib-trickle/js/TrickleButtonView.js
Lines 103 to 108 in 1d107a8
However, this only seems to be reachable when the button is disabled in the trickle config, at which point it makes no difference to the button state anyway and it returns early in this condition:
adapt-contrib-trickle/js/TrickleButtonModel.js
Lines 173 to 180 in 1d107a8
If the method was ever reached for a button enabled in the config, this may need changing. Is it worth correcting this now?
Fixes #267
Fix
popup:closingnow count the popup stack and discount the popup that is closing, since it has not been removed from the stack yet at that point.Why not simply revert #264
#264 exists to fix #263, unlocking behind popups on returning visits, so a straight revert would bring that back. The problem is not that #264 consulted
a11y. That is the right source of truth, and a per-view counter starting at zero is exactly what made #263 possible. The problem is when it consults it.Core triggers
popup:closingbefore the closing popup is taken off the stack, so a handler on that event always sees the closing popup as still open.a11y.isPopupOpencannot express "is anything else still open" at that moment, which is what the guard actually wants to know. Counting the stack can express it.Fixing only the early-return guard is not enough. The next thing that runs is the recalculation, which asks the same question at the same moment and computes "disabled" again, so the button stays stuck. Both sites have to account for the closing popup.
onParentCompletealso readsa11y.isPopupOpen, but it fires on a completion change rather than duringpopup:closing, so it reads the stack outside the closing phase and is left alone.finish()is left alone too: by the time it runs the button is finished, and the enable path that then applies does not consult the popup state at all.A flag is used rather than an extra method parameter because
updateButtonStateis bound directly to an event elsewhere in the view, so a new parameter would receive that event's arguments and quietly become truthy. The flag is set and cleared synchronously around a single synchronous call, so nothing can interleave and leave it set.Dependencies
None.
a11y.popupStackhas been available since core 6.28.0, well below this plugin's minimum framework version.Testing
Tested in Chrome 151 on macOS against Adapt Framework 5.56.2 with core 6.78.3, and also against core 6.77.1, the version that framework ships with. Both behave the same before and after.
The quickest check needs no course JSON at all:
require('core/js/drawer').open();thenrequire('core/js/drawer').close();Then the routes learners actually take, all confirmed fixed:
_canShowFeedback: true. Answer it, submit, dismiss the feedback popup. The button should be clickable, and clicking it should reveal the next block._pageIncompletePromptenabled, try to leave an incomplete trickled page and answer "No". The page should still be completable, which is the whole point of staying on it.Worth also checking these, all verified:
Posted via collaboration with Claude Code