Skip to content

GAUD-10203 & GAUD-10205 - Add divider, panel state controller and keyboard resizing#7151

Merged
svanherk merged 9 commits into
mainfrom
GAUD-10203-GAUD-10205-Add-divider-panel-controller-and-keyboard-resizing
Jul 2, 2026
Merged

GAUD-10203 & GAUD-10205 - Add divider, panel state controller and keyboard resizing#7151
svanherk merged 9 commits into
mainfrom
GAUD-10203-GAUD-10205-Add-divider-panel-controller-and-keyboard-resizing

Conversation

@svanherk

@svanherk svanherk commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

First resizable panels slice! Want to make sure this pattern looks good before fleshing it out more, and will start working on tests.

This PR separates the divider out into its own internal component (followed tab as an example), and adds a lit controller to handle panel state. The responsibility breakdown is:

  • d2l-page-divider-internal
    • Knows about its styles (both panel and drawer mode), mapping RTL + left/right panel values accordingly, setting all the aria stuff, and handling keyboard and mouse interactions
    • Tells the page what the new panel values should be, but page/panel state owns updating it
  • PanelStateController
    • Holds the state of all three panels (set up to make it really easy for switching for demos or if making your screen smaller, but also if we do want to support both panels)
    • The logic around clamping, knowing when to request a Lit update, and more to come (potentially how to grab old values for local storage, etc.)
  • d2l-page
    • Measuring all its content and pieces, and deciding the min and max values depending on the page setup
    • Hiding the panels or updating panel width in response to events

In order to be able to test things, I also added keyboard resizing. Collapsing, dragging, mobile modes, etc will all come later.

@github-actions

Copy link
Copy Markdown
Contributor

Thanks for the PR! 🎉

We've deployed an automatic preview for this PR - you can see your changes here:

URL https://live.d2l.dev/prs/BrightspaceUI/core/pr-7151/

Note

The build needs to finish before your changes are deployed.
Changes to the PR will automatically update the instance.

return html`
<div
class="divider"
role="slider"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The window splitter pattern says to use the "separator" role, but as far as I can tell that doesn't work at all. This is talked about here - it's not a widget and keyboard and mouse events are just swallowed. So I'm using the "slider" role, as primary-secondary does, and following that pattern.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah was just reading about that as well, sounds good.

Comment on lines +144 to +145
// TO DO: Maybe we don't bother clamping here, since the panel state controller will clamp it anyway
const clampedRequestedSize = clampedSize(requestedSize, this.minSize, this.maxSize);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering removing this - we can clamp early because we have all the info we need. But PanelStateController also has to do this clamping, so it's redundant.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't hate it either, since this component does have a "min" and a "max" it's a little bad if it doesn't adhere to its own setup. 🤷

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, we'll keep it! Removed the comment questioning it

Comment thread components/page/page.js
const MAIN_MIN_WIDTH = 600; // TO DO: Confirm
const PANEL_MIN_WIDTH = 320;

class PanelStateController {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went through a few iterations before settling on using a controller here. Simple objects were messy, a Class seemed a bit like overkill by itself, but the controller works really nicely with being able to handle the requested updates smoothly.

Comment thread components/page/page.js
#handleWindowResizeBound;
#resizeObserver;

#getMaxDrawerHeight() {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we can't actually test the drawer stuff yet (I might do that as the next PR), but I wanted to make sure it fit into the pattern well. So added some minimal stuff for that - defaults, setting min and max, and reacting to height updates.

@dlockhart dlockhart left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good to me!

.divider:hover {
background-color: var(--d2l-color-mica);
}
.divider:focus {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably want :focus-visible here as I'm noticing that when I click on it (even though that isn't supported yet) these styles get applied.

@svanherk svanherk Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I asked Glen about this and he wanted it visible, to match primary-secondary, but we can check to confirm. Eventually it'll show arrows that may be helpful to a sighted user, to tell them they've made it as big as it can go 🤷‍♀️

* REQUIRED: aria-label for the divider
* @type {string}
*/
label: { type: String },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably want to use PropertyRequiredMixin so that this throws if it's missing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added!

Comment thread components/page/page-divider-internal.js Outdated
return html`
<div
class="divider"
role="slider"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah was just reading about that as well, sounds good.

Comment on lines +144 to +145
// TO DO: Maybe we don't bother clamping here, since the panel state controller will clamp it anyway
const clampedRequestedSize = clampedSize(requestedSize, this.minSize, this.maxSize);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't hate it either, since this component does have a "min" and a "max" it's a little bad if it doesn't adhere to its own setup. 🤷

<div
class="divider"
role="slider"
tabindex="0"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm seeing something weird where I can only shift-tab to the divider from the main panel... if I tab from the side nav panel it skips right past it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I'm pretty sure this is a list issue/defect. If you remove d2l-list or put something focusable after the list, it's fine. But I haven't dug in too much yet, maybe I need to do something to the divider for list to consider it an appropriate "next focusable" item.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see, yeah likely an issue with getNextFocusable not seeing it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a story for this, will probably add a broken vdiff for it that it can fix lol

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this list in grid mode? I could be wrong but unless it's grid mode (which we don't like), I think d2l-list should just be relying on normal tab focus interaction.

I think there is a known issue with focus order due to a subtle difference in how the browser determines the next focusable vs our focus helpers when moving focus in/out of slotted content. I've only ever seen it in d2l-template-primary-secondary, and it's divider was also part of the scenario. Not sure if it's the same.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is in grid mode - all of out "nav" list demos have grid mode on, so I figured that's how we recommend/support it currently?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We received some feedback that it's unhelpful and sometimes confusing. It has come up a couple times in conversation - at this point I think we're uncertain as to whether we want to keep it since the UX improvement has been questioned, it adds complexity to the code, and presents extra challenges when other component behaviour conflicts inside list items. Having said that, I think it's good to test with both modes since we still support it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So anyway... I'd be curious to turn off grid mode on the list for this case, just to see if it makes a difference with the behaviour you are observing. Might help narrow the culprit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah good idea, removing grid does fix it

Base automatically changed from GAUD-10203-Add-aria-label-for-supporting-panel to main July 2, 2026 20:02
svanherk added 2 commits July 2, 2026 16:10
…roller-and-keyboard-resizing

# Conflicts:
#	components/page/page.js
#	lang/ar.js
#	lang/ca.js
#	lang/cy.js
#	lang/da.js
#	lang/de.js
#	lang/en-gb.js
#	lang/en.js
#	lang/es-es.js
#	lang/es.js
#	lang/fr-fr.js
#	lang/fr.js
#	lang/haw.js
#	lang/hi.js
#	lang/ja.js
#	lang/ko.js
#	lang/mi.js
#	lang/nl.js
#	lang/pt.js
#	lang/sv.js
#	lang/th.js
#	lang/tr.js
#	lang/vi.js
#	lang/zh-cn.js
#	lang/zh-tw.js
@svanherk svanherk marked this pull request as ready for review July 2, 2026 20:30
@svanherk svanherk requested a review from a team as a code owner July 2, 2026 20:30
@svanherk svanherk merged commit 3472d8e into main Jul 2, 2026
10 checks passed
@svanherk svanherk deleted the GAUD-10203-GAUD-10205-Add-divider-panel-controller-and-keyboard-resizing branch July 2, 2026 21:39
@d2l-github-release-tokens

Copy link
Copy Markdown

🎉 This PR is included in version 3.267.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Comment thread components/page/page.js
</nav>
${!this._slotVisibility['side-nav'] ? nothing :
html`<div class="divider"></div>`}
this.#renderDivider('side-nav', this.localize('components.page.side-nav-divider-label'), 'start')}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A test dev on my team pointed out to me that we're getting playwright axe test failures now that the divider is more fleshed out but is outside of a landmark. I don't expect a quick resolution because the page component is in progress, but we thought it'd be worth point out.

Failure: https://test-reporting.d2l.dev/artifacts/Brightspace/playwright-ui-automation/28670352275/1/test/playwright/playwright-report/#?testId=723f70fe57f73a0b2d6e-b0b6d6d674acf2bcb2ba
Our use of the component: https://github.com/Brightspace/communications-hub-ui/blob/2a33e0038edd544d21a6231e560271581f67d8b2/src/components/communications-hub-home.js#L11

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol but... the divider is literally the thing that separates the two landmarks. 🤦

Thanks Tristan -- at the very least it means we need an axe test here that would have caught this!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah Mark mentioned this and I was looking a bit on Friday. I'm going to put up a change soon and would be interested if it fixes it. In terms of our axe tests not catching it - I think it might be because we wrap our tests, d2l-page is not the top-level component like it might be in a real LMS usage.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is a jira story to track this issue, we can skip this check in our test and can enable the check once story is complete.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm hopeful it may be fixed as a side effect of https://desire2learn.atlassian.net/browse/GAUD-10269, but that story doesn't need to be fully completed for that to happen. Can I reach out to you or Tristan when the change is in and you can unskip the check and test it?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure! thanks

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, this should be resolved by #7200 with core version 3.270.0. Once you've pulled latest core, can you un-skip the test and let me know if it fixes things for you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants