Skip to content

Add JavaScript functionality for restoring layouts#863

Open
wibjorn wants to merge 12 commits into
mainfrom
wbb/layout-persistence
Open

Add JavaScript functionality for restoring layouts#863
wibjorn wants to merge 12 commits into
mainfrom
wbb/layout-persistence

Conversation

@wibjorn
Copy link
Copy Markdown
Contributor

@wibjorn wibjorn commented May 18, 2026

changes.

Link: preview

I've been trying to create some code that helps us restore the layout related html classes on the documentElement, then create an API subscribers can listen for changes and also use the same api just to make changes they want to happen on page load. It's been hard to fully figure out, and I've been bouncing between ideas to get the right balance.

I landed on the following:

We put an IIEF directly in the markup of pages on the consumer site. This is a short snippet that essentially checks local storage and uses it to restore the same, if we're on the same page template. This happens quickly, synchronously, before we get into any bundle code. This is a common method for restoring color themes without flashes of different themes too. It just lives in documentation, not source.

Next, (we are in the bundles now), we read the state again (which is keyed on page templates / view names), and create a thing (layoutState) that can both what for changes to the relevant html classes on the documentElement and save the changes to state.

Subscribers and write functions that run when a particular state is true - and they also run right away once if the state matches their conditions.

Put in another set of css helper classes, which are design to hide things if the layout is still restoring.

What this'll do ensure that we never see any jank on load, and give us the flexibility we need to easily write functions that target specific states.

  • todo, write at least one integration test.

Testing

  1. Visit layout page.
  2. Select a relevant expand/collapse toggle, such as toggling the menu, aside, or the flyout.
  3. Refresh page. You should see the same state as you left off.
  4. Note that the Atlas doc site does not support expanding/collapsing of elements off the layout page.

Additional information

[Optional]

Contributor checklist

  • Did you update the description of this pull request with a review link and test steps?
  • Did you submit a changeset? Changesets are required for all code changes.
  • Does your pull request implement complex UI logic (js/ts)? Consider adding an integration test to test your user flow.
  • Does your pull request add a new page to the documentation site? Add your new page for automated accessibility testing in /integration/tests/accessibility.
  • Does your pull request change any transforms? Did you test they work on right to left?

@wibjorn wibjorn requested a review from a team as a code owner May 18, 2026 22:56
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 18, 2026

🦋 Changeset detected

Latest commit: 8ae4ba8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@microsoft/atlas-js Major
@microsoft/atlas-css Minor
@microsoft/parcel-transformer-markdown-html Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment thread js/src/behaviors/layout.ts Fixed
Comment thread js/src/behaviors/layout.ts Fixed
Comment thread js/src/behaviors/layout.ts Fixed
Comment thread js/package.json Outdated
wibjorn and others added 6 commits May 22, 2026 14:01
…lpers

createLayoutState is now live on return — no separate start() ceremony — and sets data-layout-restored=true on the root after its initial subscriber flush. A failure-fallback path (sync setup throw, deferCallbacksUntil rejection, or a queued subscriber throwing) still marks the sentinel so gated CSS rules cant permanently hide content; setup errors are re-thrown.

Adds .display-{value}-until-layout-restored helpers in atlas-css mirroring the existing .display-{value}-{state} family. Pares comments in layout.ts and trims the README; moves the restoration recipe (inline snippet, CSP notes) into site/src/components/layout.md.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
- Add tsconfig.eslint.json (include src + test) and point .eslintrc.js
  parserOptions.project at it. Restores test-file linting that was
  broken when tsconfig.json was constrained to `include: ["src"]`.
- `LayoutStateOptions.viewName` now accepts `string | (() => string)`.
  The function variant is resolved every time a view name is needed so
  a single instance can follow a dynamically changing "current view".
  Unsafe-key guard moved into the new `getViewName()` helper so it
  applies to both the static and function variants.
- 5 new tests for the accessor: static behaviour, dynamic value across
  mutations, event-payload viewName at fire time, unsafe-key coercion
  from the function, unsafe-key coercion from a static string.
- README.md: document the function variant; correct stale
  `deferCallbacksUntil` default (resolved promise, not DOMContentLoaded).
- site/components/layout.md: mention the function variant.
- changeset: mention viewName accessor + failure-fallback sentinel,
  correct `useViewTransitionOnRestore` per-instance flag name, and
  fix the stale `contentLoaded` default claim.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@wibjorn wibjorn force-pushed the wbb/layout-persistence branch from 2cc18c0 to 4d8788f Compare May 22, 2026 21:03
wibjorn and others added 6 commits May 22, 2026 14:31
createLayoutState wrapped restoration and each opted-in subscriber callback in document.startViewTransition without coordinating between them. When two calls landed close together (multiple subscribers for one class change, or a callback firing while restoration was still animating) the second startViewTransition aborted the first, surfacing InvalidStateError in the console.

Each instance now tracks an activeTransitionCount and a same-microtask batch:

- Restoration keeps its synchronous wrap but registers itself in the count.

- Subscriber callbacks queued in the same microtask coalesce into one transition.

- Any callback that would otherwise start a second transition while one is animating runs synchronously instead — no animation for that callback, but no abort either.

Includes 4 new tests covering coalescing, skip-while-active, fresh transitions after settle, and resilience to a batched callback throwing. Updates the existing changeset and the js README to document the coordination.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… /your-bundle.js

The atlas-design site uses @microsoft/parcel-transformer-markdown-html,
which renders fenced ```html blocks as live HTML examples in addition to
a syntax-highlighted code listing. The inline-restore snippet in
layout.md ends with a placeholder
`<script type="module" src="/your-bundle.js"></script>`, so Parcel saw a
real script tag in the rendered page and tried to resolve
/your-bundle.js as an asset, breaking the build / dev server.

Switching the fence to ```html-no-example tells the transformer to
render only the highlighted code listing and skip the live HTML
injection. The snippet still renders correctly in docs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reduce layout-state-subscriptions.md to 3 sentences; fix small typos in busy-planets-like.md and large-snakes-share.md.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Drop the Layout state API-reference section in favor of a one-line mention and link, merge install/import sections, fix typo (Accesssing) and grammar bug (fall into a one of), and remove the stub src-folder section. ~57 lines down to ~27.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Compress 'Display helpers gated on JS restoration' (drop redundant 'Class naming pattern' label, shorten comments, fix awkward 'rules will never disable').

Reorder and compress 'Persisting layout state across page loads': fold 'When to skip' into the opener, drop the no-op/MutationObserver mechanism detail, merge the CSP workarounds into one paragraph, and trim the sentinel subsection so it stops repeating the helpers subsection.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Place this synchronous IIFE in `<head>` before the bundle. It reads the same `localStorage` key that `createLayoutState` uses and applies the saved classes to `<html>` during parse:

```html-no-example
<head>
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.

Here is the relevant snippet.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants