@blakesteve/roster 5.18.0 → 5.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -186,6 +186,72 @@ Tokens Roster never declares, such as the [shape tokens](#shape) and
|
|
|
186
186
|
`--roster-elevation-control`, have no such rule to lose to, so `:root` alone
|
|
187
187
|
covers both schemes.
|
|
188
188
|
|
|
189
|
+
#### A region with its own palette
|
|
190
|
+
|
|
191
|
+
Roster's semantic tokens, such as `--roster-tabs-indicator` and
|
|
192
|
+
`--roster-card-bg`, derive from the ramps at `:root`. A custom property resolves
|
|
193
|
+
where it's declared, so they reach the page as finished values, and a ramp
|
|
194
|
+
overridden on a wrapper changes nothing below it: inside that wrapper, tab
|
|
195
|
+
underlines and timeline rails keep the page's color. Add `data-roster-scope` and
|
|
196
|
+
they derive again from the ramps in force on that element:
|
|
197
|
+
|
|
198
|
+
```css
|
|
199
|
+
.region {
|
|
200
|
+
--roster-primary-400: #a78bfa;
|
|
201
|
+
--roster-primary-500: #7c3aed;
|
|
202
|
+
--roster-primary-600: #6d28d9;
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
<section className="region" data-roster-scope>
|
|
208
|
+
<Tabs tabs={tabs} activeTab={active} onChange={setActive} />
|
|
209
|
+
</section>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
A page that never sets the attribute computes exactly what it did before.
|
|
213
|
+
|
|
214
|
+
**A scope owns its state.** The attribute matches the light tokens directly, so
|
|
215
|
+
a scope inside a dark page, with no `.dark` of its own, resets to light while
|
|
216
|
+
everything around it stays dark. To stay dark, a scope sets `.dark` too: an
|
|
217
|
+
element with both gets the dark values, from its own ramps.
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
<section className="region dark" data-roster-scope>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
A scope that follows the page's theme has to do that itself, at two costs:
|
|
224
|
+
|
|
225
|
+
- `ThemeToggle` and the blocking script above reach `<html>` and nothing else.
|
|
226
|
+
Until your code sets `.dark` on the scope, a server-rendered scope paints
|
|
227
|
+
light on a dark page.
|
|
228
|
+
- `Select`, `Combobox`, `MultiSelect`, `Sheet` and the `Toaster` read a scoped
|
|
229
|
+
`.dark` once, when they mount. Give the scope a `key` that changes with the
|
|
230
|
+
theme, so they mount again and their panels follow it.
|
|
231
|
+
|
|
232
|
+
A few more things follow from the same rule:
|
|
233
|
+
|
|
234
|
+
- Scopes nest. An inner scope derives from the ramps in force at the inner
|
|
235
|
+
element, its own if it sets any and the outer scope's if it doesn't.
|
|
236
|
+
- A semantic token you set at `:root` stops at a scope, which declares its own.
|
|
237
|
+
Set it on the scope as well. `[data-roster-scope]` weighs the same as a class
|
|
238
|
+
and sits outside any layer, so a class on the scope has to load after Roster's
|
|
239
|
+
stylesheet, unlayered, or it's discarded, as in the rule above.
|
|
240
|
+
- Sheet timing and elevation read no ramp, so a scope doesn't re-declare them.
|
|
241
|
+
It inherits them, so a light scope in a dark page keeps the dark shadows and
|
|
242
|
+
the dark Sheet backdrop.
|
|
243
|
+
|
|
244
|
+
Two things it doesn't do:
|
|
245
|
+
|
|
246
|
+
- **It doesn't cross a portal.** `Select`'s menu, `Menu`, `Dialog` and `Tooltip`
|
|
247
|
+
render at `<body>`, outside the scope, so the scope's ramps don't reach them.
|
|
248
|
+
A `.dark` on the scope is carried across by the five surfaces above and by
|
|
249
|
+
`Menu`, which then open on Roster's own dark tokens rather than your page's.
|
|
250
|
+
`Dialog` and `Tooltip` don't carry it, so they open in the page's scheme.
|
|
251
|
+
- **It doesn't stop `dark:` variants.** They fire under any `.dark` ancestor,
|
|
252
|
+
and a scope can't undo one, so a light scope in a dark page gets light tokens
|
|
253
|
+
and dark variants together.
|
|
254
|
+
|
|
189
255
|
### Elevation
|
|
190
256
|
|
|
191
257
|
Three levels, each a themeable box-shadow, and the only way a surface should
|
|
@@ -754,8 +820,9 @@ something different in each would not be a token.
|
|
|
754
820
|
One caveat worth knowing before you lean on these: like `--roster-card-*`,
|
|
755
821
|
`--roster-nav-*` and `--roster-footer-*`, they are declared outside
|
|
756
822
|
`@layer roster` and resolved at `:root`. A host that declares its override
|
|
757
|
-
inside `@layer base` is outranked, and
|
|
758
|
-
than the document root will not reach them
|
|
823
|
+
inside `@layer base` is outranked, and a ramp overridden on a subtree rather
|
|
824
|
+
than the document root will not reach them unless that subtree carries
|
|
825
|
+
[`data-roster-scope`](#a-region-with-its-own-palette).
|
|
759
826
|
|
|
760
827
|
### Floating surface colors
|
|
761
828
|
|
|
@@ -1435,6 +1502,23 @@ and checks it's visible (Playwright's `toBeVisible`, say) finds it open. In
|
|
|
1435
1502
|
read as hidden; `Sheet`'s was too, and is fixed the same way. Nothing on
|
|
1436
1503
|
screen moves, and a click outside the panel still closes it.
|
|
1437
1504
|
|
|
1505
|
+
**One opened as another leaves.** A `Dialog` or `Sheet` opened while another
|
|
1506
|
+
is still leaving holds the page from its first frame, so the other one's focus
|
|
1507
|
+
restore can't move focus out onto the page. An opener in the toast host is the
|
|
1508
|
+
exception: the host stays live, so one opened from a toast still gives focus
|
|
1509
|
+
back to it as it leaves, out of whatever opened in its place. In 5.19.0 and
|
|
1510
|
+
before, a `Sheet` that mounted already
|
|
1511
|
+
open, as one whose code loads on its first open does, never held the part of
|
|
1512
|
+
the page it was written in. That part stayed live behind it, and a sheet or
|
|
1513
|
+
dialog leaving at the same time restored focus onto it as its leave ended. A
|
|
1514
|
+
`Sheet` now opens a render later, before anything is painted, as `Dialog`
|
|
1515
|
+
already did, so on that first open it also slides in rather than appearing in
|
|
1516
|
+
place. Nothing to change in your code.
|
|
1517
|
+
|
|
1518
|
+
Such a sheet may also have no opener to give focus back to: Headless UI keeps
|
|
1519
|
+
the record of what was pressed only once its code has loaded, after the press
|
|
1520
|
+
that opened the sheet. Remember the press and pass it as `returnFocus`.
|
|
1521
|
+
|
|
1438
1522
|
**Full screen.** `layout="fullscreen"` makes a bare dialog over the whole
|
|
1439
1523
|
viewport, for a photo viewer or a full-screen card: no box, no padding, and no
|
|
1440
1524
|
header or close button of Roster's. Everything inside is yours, a close
|
|
@@ -2794,7 +2878,7 @@ Output in `dist/`:
|
|
|
2794
2878
|
| `tokens.css` | Design token CSS variables |
|
|
2795
2879
|
| `preflight.css` | Optional global reset (see Setup) |
|
|
2796
2880
|
| `index.d.ts` | TypeScript definitions |
|
|
2797
|
-
| `meta.json` | Test count and
|
|
2881
|
+
| `meta.json` | Test count, version and passes on retry, written when the tarball is built |
|
|
2798
2882
|
|
|
2799
2883
|
### What `meta.json` is for
|
|
2800
2884
|
|
|
@@ -2828,6 +2912,59 @@ rather than failing, and "live · 0 tests" would look authoritative while being
|
|
|
2828
2912
|
the worst possible answer. `src/write-meta.test.ts` pins that guard and the
|
|
2829
2913
|
field selection, including the one that bit first: `numTotalTestSuites` reads
|
|
2830
2914
|
like a file count but counts `describe` blocks — 166 against 92 real files.
|
|
2915
|
+
`testFiles` counts each test file that ran, once: vitest's report has an entry
|
|
2916
|
+
for every file in every project and browser that collects it, skipped or not:
|
|
2917
|
+
699 entries for 219 files on 2 Nov 2026.
|
|
2918
|
+
|
|
2919
|
+
It never writes from a failed run, and vitest's exit code decides what failed.
|
|
2920
|
+
Each failing test gets one retry (`vitest run --retry=1`): a real-browser test
|
|
2921
|
+
can time out once on a busy machine and pass on its second try, and a publish
|
|
2922
|
+
shouldn't stop for that. Anything else that fails the run stops it: a test that
|
|
2923
|
+
fails twice, a file that throws as it loads, an error thrown after its test has
|
|
2924
|
+
passed. Then `prepack` exits non-zero, what failed is printed (vitest's own
|
|
2925
|
+
output is hidden there), and no `meta.json` is written. A test that passed only
|
|
2926
|
+
on its retry is named in the output too, and `meta.json` records how many there
|
|
2927
|
+
were, as `passedOnRetry`.
|
|
2928
|
+
|
|
2929
|
+
## Discord notices
|
|
2930
|
+
|
|
2931
|
+
`.github/workflows/discord.yml` posts to Roster's channel on Blake's Discord,
|
|
2932
|
+
through the webhook in the `DISCORD_WEBHOOK_URL` repository secret. It's a
|
|
2933
|
+
separate workflow, so nothing it does can fail or delay CI or a deploy. Without
|
|
2934
|
+
the secret, as in a fork, it skips cleanly and posts nothing.
|
|
2935
|
+
|
|
2936
|
+
| Event | Posts when | Which copy of the file runs |
|
|
2937
|
+
| --- | --- | --- |
|
|
2938
|
+
| Storybook deployed, or its deploy failed | `deployment_status`, environment `Production`, state `success`, `failure` or `error` | The one in the deployed commit |
|
|
2939
|
+
| A release | `release: published` | The one in the tagged commit |
|
|
2940
|
+
| CI red | `workflow_run` on `CI`, `failure`, on `main`, from a push | The one on `main` |
|
|
2941
|
+
|
|
2942
|
+
A deploy here is Storybook, at `https://roster-tan.vercel.app`, so the post
|
|
2943
|
+
says Storybook was deployed. The package itself ships on `npm publish`, which
|
|
2944
|
+
isn't a GitHub event, so nothing posts it; the GitHub release made beside it
|
|
2945
|
+
does. Previews and pull requests don't post, and neither does a fork's: CI on a
|
|
2946
|
+
pull request isn't a push. Nor does a CI run that ends canceled, which is how a
|
|
2947
|
+
job stopped at its own time limit ends it. The Storybook job's steps have their
|
|
2948
|
+
own limits, so a stalled step there fails and posts, but Unit Tests and Build
|
|
2949
|
+
have only the job's, so a hung step in either is silent.
|
|
2950
|
+
|
|
2951
|
+
Each post has the version, the commit and a link. With `DETAILS: "on"`, a
|
|
2952
|
+
deploy lists the PRs merged since the last good deploy (not the `release/`
|
|
2953
|
+
PRs), a release carries its notes without the authors and links, and red CI
|
|
2954
|
+
names each failed job and its failed step, such as "Storybook Tests: Run
|
|
2955
|
+
Storybook tests with real input". All of that text is escaped, so a title can't
|
|
2956
|
+
ping anyone or draw a link.
|
|
2957
|
+
|
|
2958
|
+
`src/discord-workflow.test.ts` runs the job's real condition and its real
|
|
2959
|
+
script, with stand-ins for `curl` and `gh`, in the `unit` project. To send each
|
|
2960
|
+
message as a test, from `main`:
|
|
2961
|
+
|
|
2962
|
+
```bash
|
|
2963
|
+
gh workflow run discord.yml --repo blakesteve/roster -f message=deploy
|
|
2964
|
+
```
|
|
2965
|
+
|
|
2966
|
+
The other three are `deploy-failed`, `release` and `ci-failed`. A `ci-failed`
|
|
2967
|
+
test names the steps of CI's latest failed push to `main`.
|
|
2831
2968
|
|
|
2832
2969
|
## Contributing
|
|
2833
2970
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("react/jsx-runtime"),
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("react/jsx-runtime"),re=require("react"),se=require("../../../node_modules/@headlessui/react/dist/components/description/description.cjs"),x=require("../../../node_modules/@headlessui/react/dist/components/dialog/dialog.cjs"),ne=require("../../../node_modules/@headlessui/react/dist/components/transition/transition.cjs"),p=require("../../../lib/utils.cjs"),oe=require("../../../lib/labels.cjs"),ie=require("../../../internal/popup.cjs"),ae=require("../../../internal/motion.cjs"),ce=require("../../../internal/page-inert.cjs"),ue=require("../../../internal/leave-watchdog.cjs"),D=require("./sheet-drag.cjs"),le=require("./sheet-title.cjs"),de=require("../../../internal/key-focus.cjs");function fe(o){const u=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(o){for(const l in o)if(l!=="default"){const d=Object.getOwnPropertyDescriptor(o,l);Object.defineProperty(u,l,d.get?d:{enumerable:!0,get:()=>o[l]})}}return u.default=o,Object.freeze(u)}const r=fe(re),pe={close:"Close"},he="rst:transition-transform rst:duration-(--roster-sheet-enter-duration) rst:ease-(--roster-sheet-enter-easing) rst:data-leave:duration-(--roster-sheet-leave-duration) rst:data-leave:ease-(--roster-sheet-leave-easing) rst:data-closed:translate-y-full",ge="rst:transition-opacity rst:duration-(--roster-sheet-fade-duration) rst:ease-out rst:data-closed:opacity-0",me="rst:transition-opacity rst:duration-(--roster-sheet-enter-duration) rst:ease-out rst:data-leave:duration-(--roster-sheet-leave-duration) rst:data-closed:opacity-0",ye="rst:transition-opacity rst:duration-(--roster-sheet-fade-duration) rst:ease-out rst:data-closed:opacity-0",z={slide:200,fade:150},be=["input:not([type=checkbox]):not([type=radio]):not([type=button]):not([type=submit]):not([type=reset]):not([type=range]):not([type=color]):not([type=file]):not([type=image])","textarea","select","[contenteditable]:not([contenteditable=false])"].join(",");function xe(o){return o?"current"in o?o.current:o:null}function ve({target:o}){const u=r.useRef(o),l=r.useRef(!1);return r.useEffect(()=>{u.current=o}),r.useEffect(()=>(l.current=!1,()=>{l.current=!0,queueMicrotask(()=>{if(!l.current)return;const d=xe(u.current);d?.isConnected&&d.focus()})}),[]),null}function je(){return s.jsx("svg",{"aria-hidden":"true",focusable:"false",viewBox:"0 0 20 20",className:"rst:h-5 rst:w-5",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",children:s.jsx("path",{d:"M5 5l10 10M15 5L5 15"})})}const Ee=({isOpen:o,onClose:u,onAfterClose:l,title:d,hideTitle:B=!1,description:N,actions:q,maxWidth:v=560,initialFocus:j,returnFocus:Y,busy:h=!1,dragToDismiss:E=!0,contentKey:g,labels:V,className:K,children:U})=>{de.useKeyFocus();const X=oe.withLabels(pe,V),{ref:R,inDarkScope:$}=ie.useDarkScope(),k=ae.useReducedMotion(),[G,W]=r.useState(!1);r.useLayoutEffect(()=>{W(!0)},[]);const n=o&&G,C=r.useRef(null),c=r.useRef(null),I=ue.useLeaveWatchdog(C,c,n,k?z.fade:z.slide,l);r.useLayoutEffect(()=>{if(n)return ce.lockPage(R.current)},[n,R]);const w=r.useRef(null),P=r.useRef(null),L=r.useRef(null),S=r.useRef(null),M=r.useId(),m=le.titleSignature(d),O=r.useRef({signature:m,contentKey:g,open:n,busy:h});r.useEffect(()=>{const e=O.current;if(O.current={signature:m,contentKey:g,open:n,busy:h},!e.open||!n)return;const t=e.contentKey!==g,i=e.signature!==m&&!e.busy;!t&&!i||(P.current?.focus(),S.current&&(S.current.scrollTop=0))},[m,g,n,h]),r.useEffect(()=>{if(!n)return;const e=requestAnimationFrame(()=>{const t=c.current,i=document.activeElement;!t||i&&t.contains(i)||(j?.current??L.current)?.focus()});return()=>cancelAnimationFrame(e)},[n,j]);const[A,F]=r.useState(null);r.useEffect(()=>{if(!n)return;const e=window.visualViewport;if(!e)return;const t=()=>{const i=document.activeElement,f=!!i&&!!c.current?.contains(i)&&i.matches(be);F(f?Math.round(e.height):null)};return e.addEventListener("resize",t),document.addEventListener("focusin",t),document.addEventListener("focusout",t),()=>{e.removeEventListener("resize",t),document.removeEventListener("focusin",t),document.removeEventListener("focusout",t),F(null)}},[n]);const a=r.useRef(null),y=r.useRef(!1),_=r.useRef(n);r.useEffect(()=>{_.current=n},[n]);const b=e=>{c.current?.style.setProperty("--rst-sheet-drag",`${e}px`)},T=e=>{const t=c.current;a.current=null,t&&(delete t.dataset.dragging,e==="close"?(u(),requestAnimationFrame(()=>{_.current&&!a.current&&b(0)})):b(0))};r.useEffect(()=>{!n&&a.current&&(a.current=null,c.current&&delete c.current.dataset.dragging),n&&!a.current&&b(0)},[n]);const H=e=>{a.current||(y.current=!1,!(!E||!n)&&(e.pointerType==="mouse"&&(e.button!==0||!w.current?.contains(e.target))||(a.current={pointerId:e.pointerId,x0:e.clientX,y0:e.clientY,dy:0,phase:"pending",samples:[{t:e.timeStamp,y:e.clientY}]})))},J=e=>{const t=a.current;if(!t||e.pointerId!==t.pointerId)return;const i=e.clientX-t.x0,f=e.clientY-t.y0;if(t.phase==="pending"){if(!D.exceedsSheetTapSlop(i,f))return;if(Math.abs(i)>Math.abs(f)){t.phase="ignored";return}t.phase="dragging",y.current=!0;try{e.currentTarget.setPointerCapture(e.pointerId)}catch{}c.current&&(c.current.dataset.dragging="")}t.phase==="dragging"&&(t.dy=f,t.samples.push({t:e.timeStamp,y:e.clientY}),t.samples.length>32&&t.samples.shift(),b(Math.max(0,f)))},Q=e=>{const t=a.current;if(!t||e.pointerId!==t.pointerId)return;if(t.phase!=="dragging"){a.current=null;return}t.samples.push({t:e.timeStamp,y:e.clientY});const i=e.clientY-t.y0,f=c.current?.getBoundingClientRect().height??0;T(D.sheetDragOutcome(i,f,D.sheetReleaseVelocity(t.samples)))},Z=e=>{const t=a.current;!t||e.pointerId!==t.pointerId||(t.phase==="dragging"?T("stay"):a.current=null)},ee=e=>{!y.current||e.detail===0||(y.current=!1,e.preventDefault(),e.stopPropagation())},te={"--rst-sheet-max-width":typeof v=="number"?`${v}px`:v,...A!==null&&{"--rst-sheet-keyboard-cap":`${A}px`}};return s.jsxs(s.Fragment,{children:[s.jsx("span",{ref:R,hidden:!0}),s.jsxs(x.Dialog,{ref:C,open:n,as:"div",onClose:u,initialFocus:j??L,className:p.cn("rst:font-ui rst:fixed rst:inset-0 rst:z-50 rst:focus:outline-hidden",$&&"dark"),children:[s.jsx(x.DialogBackdrop,{transition:!0,"data-testid":"sheet-backdrop",className:p.cn("rst:fixed rst:inset-0 rst:bg-[var(--roster-backdrop,var(--roster-sheet-backdrop))]",k?ye:me)}),s.jsx("div",{className:"rst:fixed rst:inset-0 rst:flex rst:items-end rst:justify-center",children:s.jsx(ne.TransitionChild,{afterLeave:I.afterLeave,children:s.jsxs(x.DialogPanel,{ref:c,style:te,"data-testid":"sheet-panel",className:p.cn("rst:relative rst:flex rst:max-h-[min(92dvh,var(--rst-sheet-keyboard-cap,92dvh))] rst:w-full rst:flex-col","rst:md:max-w-(--rst-sheet-max-width)","rst:rounded-t-2xl rst:border rst:border-b-0 rst:elevation-overlay","rst:bg-[var(--roster-popover-bg)] rst:border-[var(--roster-popover-border)] rst:text-[var(--roster-popover-text)]","rst:[--roster-ring-offset:var(--roster-popover-bg)]","rst:pb-[env(safe-area-inset-bottom)]","rst:translate-y-[var(--rst-sheet-drag,0px)]",k?ge:he,"rst:data-dragging:transition-none",K),children:[s.jsxs("div",{"data-testid":"sheet-drag-zone",className:p.cn("rst:shrink-0",E&&"rst:touch-none"),onPointerDown:H,onPointerMove:J,onPointerUp:Q,onPointerCancel:Z,onClickCapture:ee,children:[s.jsx("div",{ref:w,"aria-hidden":"true","data-testid":"sheet-handle",className:p.cn("rst:flex rst:h-11 rst:items-start rst:justify-center rst:pt-2",E&&"rst:cursor-grab rst:active:cursor-grabbing"),children:s.jsx("span",{className:"rst:h-1 rst:w-10 rst:rounded-full rst:bg-current rst:opacity-30"})}),s.jsxs("div",{className:"rst:-mt-3 rst:flex rst:items-start rst:gap-2 rst:px-5 rst:pb-4",children:[s.jsxs("div",{className:"rst:min-w-0 rst:flex-1 rst:pt-2",children:[s.jsx(x.DialogTitle,{as:"h2",id:M,ref:P,tabIndex:-1,className:p.cn("rst:text-lg rst:font-semibold rst:text-inherit rst:focus:outline-hidden",B&&"rst:sr-only"),children:d}),N&&s.jsx(se.Description,{as:"p",className:"rst:mt-1 rst:text-sm rst:text-inherit rst:opacity-75",children:N})]}),q&&s.jsx("div",{className:"rst:flex rst:h-11 rst:shrink-0 rst:items-center rst:gap-1",children:q}),s.jsx("button",{ref:L,type:"button",onClick:u,"aria-label":X.close,className:"rst:inline-flex rst:size-11 rst:shrink-0 rst:-mr-2 rst:cursor-pointer rst:items-center rst:justify-center rst:rounded-md rst:bg-transparent rst:text-inherit rst:opacity-60 rst:transition-opacity rst:hover:opacity-100 rst:focus:outline-hidden rst:focus-visible:opacity-100 rst:focus-visible:ring-2 rst:focus-visible:ring-ring rst:focus-visible:ring-offset-2 rst:ring-offset-background",children:s.jsx(je,{})})]})]}),s.jsx("div",{ref:S,role:"region","aria-labelledby":M,tabIndex:0,"data-testid":"sheet-content","aria-busy":h||void 0,className:"rst:min-h-0 rst:flex-1 rst:overflow-y-auto rst:overscroll-contain rst:px-5 rst:pb-4 rst:focus:outline-hidden rst:ring-inset rst:focus-visible:ring-2 rst:focus-visible:ring-ring",children:U}),s.jsx(ve,{target:Y})]})})})]},I.key)]})};exports.Sheet=Ee;
|
|
@@ -46,7 +46,10 @@ export interface SheetProps {
|
|
|
46
46
|
* Where focus goes on close, over whatever opened the sheet.
|
|
47
47
|
*
|
|
48
48
|
* A sheet opened on page load from a deep link has no opener, so without
|
|
49
|
-
* this focus falls to `<body>`.
|
|
49
|
+
* this focus falls to `<body>`. A sheet whose code loads on its first open
|
|
50
|
+
* can have none either: Headless UI, which keeps the record of what was
|
|
51
|
+
* pressed, starts keeping it only once its code has loaded, after the press
|
|
52
|
+
* that opened the sheet. Remember the press yourself and pass it here.
|
|
50
53
|
*/
|
|
51
54
|
returnFocus?: React.RefObject<HTMLElement | null> | HTMLElement | null;
|
|
52
55
|
/** Sets `aria-busy` on the content region while its content loads. */
|