Skip to content

Let Dialog waitUntil accept a transitioner and make its lifecycle events bubble - #635

Merged
titouanmathis merged 2 commits into
mainfrom
feature/dialog-transitioner-waituntil
Aug 13, 2026
Merged

Let Dialog waitUntil accept a transitioner and make its lifecycle events bubble#635
titouanmathis merged 2 commits into
mainfrom
feature/dialog-transitioner-waituntil

Conversation

@titouanmathis

Copy link
Copy Markdown
Contributor

What

Two additions to the extendable open/close lifecycle events introduced in #627:

  • The events now bubble (bubbles: true): ancestors can route them declaratively (e.g. through an Action higher up the tree), and descendants listening at the document level can join the choreography without being a declared child.
  • waitUntil() now also accepts a transitioner: a duck-typed object with enter() and leave() methods. __emitExtendable() knows which phase is dispatching, so it awaits enter() on open and leave() on close — a single handler passing the object itself is enough, no phase-specific expression required. A thenable keeps today's behavior, and the sync-only registration guard and rejection handling (warn + settle, choreography always completes) are unchanged.
<dialog
  data-component="Action Dialog"
  data-on:close="MotionView(#nav)->event.detail.waitUntil(target)"
  data-on:cancel.prevent="Dialog.close()">
  <nav id="nav"></nav>
</dialog>

Why

This is the duck-typed handshake the upcoming ambient MotionView from @studiometa/ui-motion builds on: a transitioner exposing enter()/leave() can follow the dialog lifecycle without knowing anything about Dialog, and Dialog needs to know nothing about Motion. With bubbling events, the ambient MotionView can pick up a dialog's lifecycle from the tree by itself — the common case becomes pure nesting with no attributes, and the Action expression above stays as the explicit escape hatch.

Test plan

  • New specs: the open/close events reach an ancestor listener (document.body); a transitioner given to waitUntil() gets enter() called on open and leave() on close, with the dialog's promises held pending until each phase's promise resolves (same ordering assertions as the existing waitUntil specs); a transitioner whose method rejects behaves like a rejected promise — warned and settled, the dialog still hides and cleans up.
  • All existing Dialog specs pass unchanged.
  • npm run test — 98 files, 806 tests passed.
  • npm run lint — 0 errors, 19 warnings (baseline).
  • cd packages/docs && node scripts/validate-reference.ts — "Documentation validation passed".

🤖 Generated with Claude Code

https://claude.ai/code/session_01FVXrJ8idMfvt667yJadvB8

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown

Export size

Bundled per export with peer dependencies left external, dynamic imports excluded and the output minified; sizes are gzipped.

@studiometa/ui

Export Size (gzip) Diff
Dialog 1.79 kB +42 B (+2.4%)
(barrel) 30.77 kB +29 B (+0.1%)
Unchanged (121)

@studiometa/ui

Export Size (gzip) Diff
AbstractCarouselChild 542 B
AbstractCarouselComponent 219 B
AbstractFrameTrigger 1.11 kB
AbstractPrefetch 438 B
AbstractScrollAnimation 651 B
AbstractSliderChild 489 B
Accordion 1.11 kB
AccordionItem 995 B
Action 1.22 kB
AnchorNav 1018 B
AnchorNavLink 867 B
AnchorNavTarget 166 B
AnchorScrollTo 222 B
Carousel 2.89 kB
CarouselBtn 720 B
CarouselDrag 735 B
CarouselItem 786 B
CarouselWrapper 670 B
CircularMarquee 313 B
ClickOutside 219 B
Cursor 676 B
DataBind 3.97 kB
DataComputed 4.06 kB
DataEffect 4.04 kB
DataModel 4.07 kB
DataScope 1.4 kB
Disclosure 2.39 kB
DisclosureGroup 1.16 kB
Draggable 1.14 kB
Fetch 2.45 kB
FetchShopifyPartial 2.98 kB
FetchShopifySection 2.71 kB
Figure 1.07 kB
FigureShopify 1.35 kB
FigureTwicpics 1.6 kB
FigureVideo 1.27 kB
FigureVideoTwicpics 1.85 kB
Frame 2.51 kB
FrameAnchor 1.2 kB
FrameForm 1.32 kB
FrameLoader 788 B
FrameTarget 1.1 kB
FrameTriggerLoader 801 B
Hoverable 624 B
InView 204 B
InViewOnce 245 B
Indexable 1022 B
LargeText 499 B
LazyInclude 421 B
Menu 1.72 kB
MenuBtn 186 B
MenuList 1.28 kB
Modal 1.13 kB
ModalWithTransition 1.26 kB
Panel 1.52 kB
PrefetchWhenOver 485 B
PrefetchWhenVisible 510 B
ScrollAnimation 806 B
ScrollAnimationChild 979 B
ScrollAnimationChildWithEase 1.25 kB
ScrollAnimationParent 1.05 kB
ScrollAnimationTarget 898 B
ScrollAnimationTimeline 983 B
ScrollAnimationWithEase 1.08 kB
ScrollReveal 1005 B
Sentinel 175 B
Slider 2.28 kB
SliderBtn 745 B
SliderCount 549 B
SliderDots 1.15 kB
SliderDrag 348 B
SliderItem 574 B
SliderProgress 612 B
Sticky 929 B
Tabs 663 B
Target 125 B
Timer 589 B
TimerProgress 731 B
Toast 968 B
Toaster 1.3 kB
Track 1.61 kB
TrackContext 416 B
TrackShopify 1.71 kB
Transition 746 B
ViewTransition 658 B
animationScrollWithEase 438 B
autoload 3.04 kB
manifest 2.99 kB
scheduler 324 B
types 20 B
viewTransition 325 B
withDeprecation 250 B
withIndex 986 B
withScrollAnimationDebug 1.63 kB
withTransition 717 B

@studiometa/ui-mapbox

Export Size (gzip) Diff
(barrel) 6.41 kB
AbstractMapboxControl 1.02 kB
AbstractMapboxMapChild 848 B
MAPBOX_CLUSTER_CONNECTED 143 B
MAPBOX_MAP_CONNECTED 128 B
MapboxCluster 2.13 kB
MapboxClusterItem 644 B
MapboxFullscreenControl 1.17 kB
MapboxGeocoder 1.21 kB
MapboxGeolocateControl 1.23 kB
MapboxImage 1.34 kB
MapboxImages 1.38 kB
MapboxLayer 1.36 kB
MapboxMap 723 B
MapboxMarker 1.11 kB
MapboxNavigationControl 1.21 kB
MapboxPopup 1.17 kB
MapboxSource 1.32 kB
StoreLocator 1.96 kB
autoload 681 B
dependencies 342 B
manifest 639 B
provideMapboxGeocoder 107 B
provideMapboxGl 104 B
resolveMapboxGeocoder 148 B
resolveMapboxGl 139 B

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown

Code Review

Risk: Medium — issues that should be addressed before merge.

This change makes Dialog lifecycle events bubble and extends waitUntil() to accept transitioners whose phase-specific method is awaited. It adds documentation and coverage for bubbling, transitioner sequencing, and rejected promises.

1 issue found:

  • issuepackages/ui/src/Dialog/Dialog.ts:187 — Handle synchronously thrown transitioner methods

Still open from earlier reviews (2 findings):

  • packages/ui/src/Dialog/Dialog.ts:183issue: Catch synchronous transitioner method failures
  • packages/ui/src/Dialog/Dialog.ts:184issue: Preserve thenable handling before transitioner detection

Review usage: 47,214 in (29,361 cached) / 1,214 out tokens — $0.0163 (openrouter/openai/gpt-5.6-luna, thinking: low)

Reviewed by @weareikko/code-review v0.9.5 for commit f186010.

Previous review runs

Previous run archived 2026-08-13T12:46:57Z

Code Review

Risk: Medium — issues that should be addressed before merge.

This change makes Dialog lifecycle events bubble and extends waitUntil() to accept duck-typed transitioners whose phase-specific method is awaited. It also adds documentation and coverage for bubbling, transitioner lifecycle handling, and rejected extensions.

1 issue found:

  • issuepackages/ui/src/Dialog/Dialog.ts:184 — Preserve thenable handling before transitioner detection

Still open from earlier reviews (1 finding):

  • packages/ui/src/Dialog/Dialog.ts:183issue: Catch synchronous transitioner method failures

Review usage: 59,879 in (45,780 cached) / 1,368 out tokens — $0.0152 (openrouter/openai/gpt-5.6-luna, thinking: low)

Reviewed by @weareikko/code-review v0.9.5 for commit 506d359.

Previous run archived 2026-08-13T12:09:55Z

Code Review

Risk: Medium — issues that should be addressed before merge.

This change makes Dialog lifecycle events bubble and extends waitUntil() to invoke phase-specific methods on transitioner objects. It also adds documentation and coverage for bubbling, pending transitioners, and asynchronously rejected transitioner methods.

1 issue found:

  • issuepackages/ui/src/Dialog/Dialog.ts:183 — Catch synchronous transitioner method failures

Review usage: 47,623 in (33,697 cached) / 1,907 out tokens — $0.0161 (openrouter/openai/gpt-5.6-luna, thinking: low)

Reviewed by @weareikko/code-review v0.9.5 for commit 506d359.

this.$warn(
`\`waitUntil\` must be called synchronously while the \`${name}\` event dispatches.`,
);
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue: Catch synchronous transitioner method failures

Calling enter() or leave() occurs before Promise.resolve(value), so a transitioner that throws synchronously escapes waitUntil() entirely. The open() or close() promise then rejects without the warning-and-settle behavior documented for failed extensions, and a failed open() can leave the dialog shown with scroll locking active. Invoke the method inside a promise or try/catch so synchronous throws are handled like rejected results.

Confidence: high.


Reviewed by @weareikko/code-review v0.9.5 for commit 506d359.

@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.24%. Comparing base (0872e90) to head (f186010).

Additional details and impacted files
@@            Coverage Diff            @@
##               main     #635   +/-   ##
=========================================
  Coverage     87.23%   87.24%           
  Complexity      145      145           
=========================================
  Files           147      147           
  Lines          5124     5128    +4     
  Branches        965      969    +4     
=========================================
+ Hits           4470     4474    +4     
  Misses          571      571           
  Partials         83       83           
Flag Coverage Δ
unittests 87.24% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
packages/ui/src/Dialog/Dialog.ts 89.83% <100.00%> (+0.73%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

`\`waitUntil\` must be called synchronously while the \`${name}\` event dispatches.`,
);
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue: Preserve thenable handling before transitioner detection

The method check runs before thenable normalization, so an object implementing both then() and enter()/leave() is treated as a transitioner and its phase method is invoked instead of awaiting the thenable. This breaks the documented compatibility that a thenable keeps the existing behavior; classify thenables first or explicitly prefer then() when present.

Confidence: high.


Reviewed by @weareikko/code-review v0.9.5 for commit 506d359.

titouanmathis and others added 2 commits August 13, 2026 12:45
…nts bubble

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVXrJ8idMfvt667yJadvB8
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVXrJ8idMfvt667yJadvB8
@titouanmathis
titouanmathis force-pushed the feature/dialog-transitioner-waituntil branch from 506d359 to f186010 Compare August 13, 2026 12:46
}
const value =
typeof (extension as DialogTransitioner)?.[method] === 'function'
? (extension as DialogTransitioner)[method]()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue: Handle synchronously thrown transitioner methods

Calling [method]() occurs before Promise.resolve(), so a transitioner whose enter() or leave() throws synchronously escapes waitUntil() and interrupts event dispatch. This violates the documented rejection-handling guarantee that a failing extension is warned about and the dialog choreography still completes; invoke the method inside a promise boundary or catch the synchronous error and register it as a rejected extension.

Confidence: high.


Reviewed by @weareikko/code-review v0.9.5 for commit f186010.

@titouanmathis
titouanmathis merged commit ab1be79 into main Aug 13, 2026
19 of 20 checks passed
@titouanmathis
titouanmathis deleted the feature/dialog-transitioner-waituntil branch August 13, 2026 12:52
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.

1 participant