gridsmith-ui 0.31.0 → 0.31.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/CHANGELOG.md +19 -1
- package/dist/charts.js +8 -8
- package/dist/chat.js +6 -6
- package/dist/{chunk-YFHTREH5.js → chunk-2TCCSKL3.js} +5 -5
- package/dist/{chunk-YFHTREH5.js.map → chunk-2TCCSKL3.js.map} +1 -1
- package/dist/{chunk-U7WBI4AV.js → chunk-BHTAVJIG.js} +5 -5
- package/dist/{chunk-U7WBI4AV.js.map → chunk-BHTAVJIG.js.map} +1 -1
- package/dist/{chunk-XSQD3UGY.js → chunk-DR6WEI3A.js} +17 -4
- package/dist/chunk-DR6WEI3A.js.map +1 -0
- package/dist/{chunk-OWQ7M6R7.js → chunk-EZ3ZGFGA.js} +132 -23
- package/dist/chunk-EZ3ZGFGA.js.map +1 -0
- package/dist/{chunk-2Q4HOGEI.js → chunk-HQFTP7Y5.js} +15 -5
- package/dist/chunk-HQFTP7Y5.js.map +1 -0
- package/dist/{chunk-3J67NISC.js → chunk-I6JJ7KKP.js} +8 -8
- package/dist/{chunk-3J67NISC.js.map → chunk-I6JJ7KKP.js.map} +1 -1
- package/dist/{chunk-V2ARGHJN.js → chunk-IOHGE4EW.js} +7 -7
- package/dist/{chunk-V2ARGHJN.js.map → chunk-IOHGE4EW.js.map} +1 -1
- package/dist/{chunk-BKYWKMUN.js → chunk-IZW5XHKA.js} +7 -7
- package/dist/{chunk-BKYWKMUN.js.map → chunk-IZW5XHKA.js.map} +1 -1
- package/dist/{chunk-LE72KHIE.js → chunk-K3NAFQ4S.js} +3 -3
- package/dist/{chunk-LE72KHIE.js.map → chunk-K3NAFQ4S.js.map} +1 -1
- package/dist/{chunk-GP2COVJC.js → chunk-KSJTMSYA.js} +4 -4
- package/dist/{chunk-GP2COVJC.js.map → chunk-KSJTMSYA.js.map} +1 -1
- package/dist/{chunk-OFACAHRV.js → chunk-NDL2JGQ7.js} +5 -5
- package/dist/{chunk-OFACAHRV.js.map → chunk-NDL2JGQ7.js.map} +1 -1
- package/dist/{chunk-GT6M74IZ.js → chunk-SLGGZ3MU.js} +5 -5
- package/dist/{chunk-GT6M74IZ.js.map → chunk-SLGGZ3MU.js.map} +1 -1
- package/dist/{chunk-EKWXPSTI.js → chunk-TH272DIC.js} +7 -7
- package/dist/{chunk-EKWXPSTI.js.map → chunk-TH272DIC.js.map} +1 -1
- package/dist/{chunk-HUNEJIKV.js → chunk-W3GY5ROH.js} +3 -3
- package/dist/{chunk-HUNEJIKV.js.map → chunk-W3GY5ROH.js.map} +1 -1
- package/dist/data-grid.js +8 -8
- package/dist/editor-mentions.js +2 -2
- package/dist/editor.js +3 -3
- package/dist/index.d.ts +9 -0
- package/dist/index.js +149 -47
- package/dist/index.js.map +1 -1
- package/dist/kanban.js +8 -7
- package/dist/kanban.js.map +1 -1
- package/dist/resizable.js +7 -6
- package/dist/resizable.js.map +1 -1
- package/dist/virtualized.js +8 -8
- package/package.json +1 -1
- package/dist/chunk-2Q4HOGEI.js.map +0 -1
- package/dist/chunk-OWQ7M6R7.js.map +0 -1
- package/dist/chunk-XSQD3UGY.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
# gridsmith-ui changelog
|
|
2
2
|
|
|
3
|
-
## Unreleased (0.31.
|
|
3
|
+
## Unreleased (0.31.1)
|
|
4
|
+
|
|
5
|
+
**Fixed.**
|
|
6
|
+
- **A closed Collapsible is out of the Tab order.** Its content was only out of sight, so a keyboard Tabbed into fields no one could see, and focus vanished: an event form's "More options" held its Location and Description. Closed, the content is now `inert`, out of the Tab order and the accessibility tree. Focus inside it when it closes goes back to its trigger; with no trigger that takes focus (the page's own button opens it, or the trigger is plain text), to the control before it, else the nearest place the page still holds. When a form sends focus to a field in a closed section (its only error, or an error summary's link), the section opens first, and the field takes focus in view.
|
|
7
|
+
- **An open Collapsible clips nothing.** It kept the height it had when it opened and hid what overflowed, so a field's outline at its edge was cut, and so was an error message that appeared once it was open. Open and settled, its height follows its content; it clips only while it moves. A Collapsible open from the start is drawn open, with no movement.
|
|
8
|
+
- **A Button whose own keyboard press takes it away hands focus on.** A button drawn only in one state (a card's Reconnect, gone once the app reconnects) left focus on the page after Enter. Focus now goes where the browser's Tab would go on from where the button stood, within the page, or the dialog, it was in: the first control after that place, what's drawn there now included, else the last before it. A destructive control is no exception, as Tab reaches it: a red button belongs in a confirmation dialog, which opens with focus on its safe action. A deleted row's Delete hands focus to the next row; a row that brings its own divider, or a tip drawn with its heading, is followed the same way. Focus never goes into another layer, such as a toast's Undo or a popover. It waits 0.4 s once the button has gone, so the library's own rules go first, and the page's if it moves focus by then. It never acts once the press has gone to another page, a hash router's included. A click leaves focus where the browser puts it, and so does a disable while the press is pending (`disabled={saving}`): the handover takes neither for the button going.
|
|
9
|
+
- **A handover of focus after a removal ends once you've moved on.** The library's handovers end when focus goes elsewhere or is let go on the page, at a pointer's press, or at a scroll by wheel, touch or key: a Tag's remove, an Alert's Dismiss, a bulk action bar's Clear selection, a chat's suggestions, TransferList's moves, Pagination's Previous, Next and Load more, the Button's above, and an overlay's way back to the row it was opened from. A tag removed once a server answered, after a click on plain text meanwhile, pulled focus onto the next tag.
|
|
10
|
+
- **A snapping PageHeader keeps a short page where Tab took it.** On a page that scrolled only a little past the header's collapse offset, Tab to a control near the foot collapsed the header, and the page's scroll range shrank for a moment: the header opened again and took the page back to the top, with the control under the fold. The space the header gives up is held from the moment it collapses.
|
|
11
|
+
- **One Tooltip on screen at a time.** A relative time's full date, shown while it had keyboard focus, stayed up while the pointer rested on an icon button, and both labels showed at once. A Tooltip that opens now closes any other Tooltip, before either is drawn.
|
|
12
|
+
- **Focus put back after a calendar event's deletion stands down once focus has had a place.** After an event was deleted from its card, focus went to its day; a click on plain text a moment later let it go, and a check made a moment later took focus back to the day. The Calendar's own watch now stands down once focus leaves the page's content, and an overlay's way back to a control that has gone acts only when focus stayed on nothing for its whole wait.
|
|
13
|
+
|
|
14
|
+
### Upgrade note for agents (0.31.0 → 0.31.1)
|
|
15
|
+
|
|
16
|
+
`^0.31.0` covers 0.31.1, so `npm update gridsmith-ui` brings it in. Nothing in an app's code changes, and the theme's tokens need no regenerating. What to check:
|
|
17
|
+
- **The Tab order.** A closed Collapsible's fields are no Tab stops. A test that Tabs through a page with a closed section counts no stop for them; open the section first to reach them.
|
|
18
|
+
- **Where focus goes.** After Enter on a Button that its own press takes away, focus moves 0.4 s after the button goes, to the first control after where it stood (what's drawn there now included, a destructive one too), else the last before it, within the page or the dialog. After a Collapsible closes with focus inside it, focus is on its trigger, else the control before it. After a form sends focus to an error in a closed Collapsible, the section opens and the field has focus. A test that expected focus on the page in these cases now finds it on that control, once it waits; in a dialog with nothing left in it, or a section its page keeps closed, nothing moves.
|
|
19
|
+
- **Tooltips.** One shows at a time. A test that expected a tooltip shown by focus to stay while another opened by hover finds only the second.
|
|
20
|
+
|
|
21
|
+
## 0.31.0 — 16 October 2026
|
|
4
22
|
|
|
5
23
|
**Added.**
|
|
6
24
|
- **A chart says what it shows, in a line above the drawing.** Every chart but Sparkline draws a sentence worked out from its data, so it stays true as the data changes: what it plots and over what; for each series (three at most, the rest counted), over dates or numbers its first and last values, "up" or "down" only when every step goes that way, and its highest and lowest when it goes both ways, and over names its highest and lowest; a pie's largest and smallest shares, a funnel's carry-through, a scatter chart's ranges, a Sankey chart's largest flow; any reference, and where a forecast runs. A screen reader hears it as the drawing's description once the chart is named (its `aria-label`, or its ChartContainer's title). `description` replaces it with the page's own words; a blank one keeps it. In a ChartContainer's fixed area it's shown with the table, so the drawing keeps its height.
|
package/dist/charts.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { SegmentedControl, TITLE_TEXT, Skeleton } from './chunk-
|
|
3
|
-
import { useKeyboardScroll } from './chunk-
|
|
2
|
+
import { SegmentedControl, TITLE_TEXT, Skeleton } from './chunk-BHTAVJIG.js';
|
|
3
|
+
import { useKeyboardScroll } from './chunk-KSJTMSYA.js';
|
|
4
4
|
import { useFormatLocale, useHydrated } from './chunk-LJYSVZMI.js';
|
|
5
|
-
import './chunk-
|
|
5
|
+
import './chunk-IZW5XHKA.js';
|
|
6
6
|
import { formatLocale } from './chunk-7QLXYRPD.js';
|
|
7
|
-
import './chunk-7RKC5C7G.js';
|
|
8
7
|
import { FormChangeCtx } from './chunk-72SDAX54.js';
|
|
9
|
-
import { elementType } from './chunk-
|
|
10
|
-
import './chunk-
|
|
11
|
-
import
|
|
8
|
+
import { elementType } from './chunk-HQFTP7Y5.js';
|
|
9
|
+
import './chunk-7RKC5C7G.js';
|
|
10
|
+
import './chunk-W3GY5ROH.js';
|
|
11
|
+
import { mergeRefs } from './chunk-DR6WEI3A.js';
|
|
12
12
|
import { warnOnce } from './chunk-53F7GVBQ.js';
|
|
13
|
-
import { cn, FOCUS_INSET, GLASS_SURFACE } from './chunk-
|
|
13
|
+
import { cn, FOCUS_INSET, GLASS_SURFACE } from './chunk-EZ3ZGFGA.js';
|
|
14
14
|
import { createContext, forwardRef, useContext, useRef, useState, useId, useMemo, useLayoutEffect, useEffect, useCallback, useSyncExternalStore, Children, isValidElement } from 'react';
|
|
15
15
|
import { ResponsiveContainer, BarChart as BarChart$1, CartesianGrid, XAxis, YAxis, Tooltip, Bar, LineChart as LineChart$1, Line, AreaChart as AreaChart$1, Area, PieChart as PieChart$1, Pie, Cell, ComposedChart, ScatterChart as ScatterChart$1, ZAxis, Scatter, Symbols, RadarChart as RadarChart$1, PolarGrid, PolarAngleAxis, PolarRadiusAxis, Radar, RadialBarChart as RadialBarChart$1, RadialBar, FunnelChart as FunnelChart$1, Funnel, LabelList, Treemap, Sankey, Text, Legend, ReferenceArea, ReferenceLine, getNiceTickValues, Rectangle, usePlotArea, ZIndexLayer, DefaultZIndexes } from 'recharts';
|
|
16
16
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
package/dist/chat.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { Divider, CodeBlock, Link } from './chunk-
|
|
3
|
-
import './chunk-
|
|
4
|
-
import { useKeyboardScroll } from './chunk-
|
|
5
|
-
import './chunk-
|
|
6
|
-
import './chunk-
|
|
2
|
+
import { Divider, CodeBlock, Link } from './chunk-NDL2JGQ7.js';
|
|
3
|
+
import './chunk-SLGGZ3MU.js';
|
|
4
|
+
import { useKeyboardScroll } from './chunk-KSJTMSYA.js';
|
|
5
|
+
import './chunk-W3GY5ROH.js';
|
|
6
|
+
import './chunk-DR6WEI3A.js';
|
|
7
7
|
import './chunk-53F7GVBQ.js';
|
|
8
|
-
import { cn, FOCUS_INSET } from './chunk-
|
|
8
|
+
import { cn, FOCUS_INSET } from './chunk-EZ3ZGFGA.js';
|
|
9
9
|
import { memo, useState, Children, isValidElement } from 'react';
|
|
10
10
|
import Markdown from 'react-markdown';
|
|
11
11
|
import remarkGfm from 'remark-gfm';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { triggerType } from './chunk-
|
|
2
|
+
import { triggerType } from './chunk-HQFTP7Y5.js';
|
|
3
3
|
import { useBreakpoint } from './chunk-ZHUVLBCC.js';
|
|
4
|
-
import { Slot } from './chunk-
|
|
4
|
+
import { Slot } from './chunk-DR6WEI3A.js';
|
|
5
5
|
import { Icon } from './chunk-53F7GVBQ.js';
|
|
6
|
-
import { cn, GLASS_DROPDOWN, usePosition, usePopupFocus, FOCUS_RING } from './chunk-
|
|
6
|
+
import { cn, GLASS_DROPDOWN, usePosition, usePopupFocus, FOCUS_RING } from './chunk-EZ3ZGFGA.js';
|
|
7
7
|
import { useState, useRef, useEffect, useLayoutEffect } from 'react';
|
|
8
8
|
import { createPortal } from 'react-dom';
|
|
9
9
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
@@ -310,5 +310,5 @@ function DropdownMenu({ trigger, items, align = "end", className, container }) {
|
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
export { DropdownMenu };
|
|
313
|
-
//# sourceMappingURL=chunk-
|
|
314
|
-
//# sourceMappingURL=chunk-
|
|
313
|
+
//# sourceMappingURL=chunk-2TCCSKL3.js.map
|
|
314
|
+
//# sourceMappingURL=chunk-2TCCSKL3.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../showcase/src/components/DropdownMenu.tsx"],"names":[],"mappings":";;;;;;;;;;AAoCA,SAAS,UAAU,KAAA,EAAwD;AACzE,EAAA,OAAO,MAAA,IAAU,KAAA,IAAS,KAAA,CAAM,IAAA,KAAS,SAAA;AAC3C;AACA,SAAS,UAAU,KAAA,EAAwD;AACzE,EAAA,OAAO,OAAA,IAAW,KAAA;AACpB;AAEA,IAAM,OAAA,GAAU,CAAC,OAAA,KAA2C,OAAA,CAAQ,OAAiB,CAAC,GAAA,EAAK,GAAG,CAAA,KAAM;AAClG,EAAA,IAAI,CAAC,SAAA,CAAU,CAAC,CAAA,IAAK,CAAC,cAAc,CAAC,CAAA,EAAG,GAAA,CAAI,IAAA,CAAK,CAAC,CAAA;AAClD,EAAA,OAAO,GAAA;AACT,CAAA,EAAG,EAAE,CAAA;AAEL,SAAS,cAAc,KAAA,EAAwD;AAC7E,EAAA,OAAO,CAAC,CAAC,KAAA,CAAM,QAAA,IAAa,SAAA,CAAU,KAAK,CAAA,IAAK,OAAA,CAAQ,KAAA,CAAM,KAAK,CAAA,CAAE,MAAA,KAAW,CAAA;AAClF;AAKA,IAAM,gBAAA,GAAmB,GAAA;AAEzB,IAAM,IAAA,GAAO,oIAAA;AACb,IAAM,QAAA,GAAW,CAAC,KAAA,EAA+C,IAAA,GAAO,KAAA,KAAU,EAAA;AAAA,EAChF,IAAA;AAAA,EACA,UAAA;AAAA,EACA,aAAA,CAAc,KAAK,CAAA,GACf,qCAAA,GACA,SAAA,IAAa,KAAA,IAAS,KAAA,CAAM,OAAA,KAAY,aAAA,GACtC,sHAAA,GACA,EAAA,CAAG,yGAAA,EAA2G,QAAQ,4BAA4B;AAC1J,CAAA;AACA,IAAM,KAAA,GAAQ,EAAA;AAAA;AAAA,EAEZ,4EAAA;AAAA,EACA,oCAAA;AAAA,EACA,cAAA;AAAA,EACA,0EAAA;AAAA,EACA;AACF,CAAA;AACA,IAAM,UAAU,sBAAM,GAAA,CAAC,SAAI,IAAA,EAAK,WAAA,EAAY,WAAU,4CAAA,EAA6C,CAAA;AAU5F,SAAS,YAAA,CAAa,EAAE,OAAA,EAAS,KAAA,EAAO,QAAQ,KAAA,EAAO,SAAA,EAAW,WAAU,EAAsB;AACvG,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAS,KAAK,CAAA;AACtC,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,SAAS,EAAE,CAAA;AAE/C,EAAA,MAAM,CAAC,GAAA,EAAK,MAAM,CAAA,GAAI,SAAwB,IAAI,CAAA;AAClD,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAI,SAAS,EAAE,CAAA;AACrD,EAAA,MAAM,UAAA,GAAa,OAAoB,IAAI,CAAA;AAE3C,EAAA,MAAM,SAAA,GAAY,OAAuB,IAAI,CAAA;AAC7C,EAAA,MAAM,OAAA,GAAU,OAAuB,IAAI,CAAA;AAC3C,EAAA,MAAM,MAAA,GAAS,OAAuB,IAAI,CAAA;AAC1C,EAAA,MAAM,UAAA,GAAa,OAAoB,IAAI,CAAA;AAC3C,EAAA,MAAM,OAAA,GAAU,cAAc,IAAI,CAAA;AAElC,EAAA,MAAM,UAAA,GAAa,OAA6C,IAAI,CAAA;AACpE,EAAA,MAAM,cAAc,MAAM;AACxB,IAAA,IAAI,WAAW,OAAA,EAAS;AAAE,MAAA,YAAA,CAAa,WAAW,OAAO,CAAA;AAAG,MAAA,UAAA,CAAW,OAAA,GAAU,IAAA;AAAA,IAAM;AAAA,EACzF,CAAA;AACA,EAAA,MAAM,UAAA,GAAa,CAAC,IAAA,KAAqB;AACvC,IAAA,WAAA,EAAY;AACZ,IAAA,UAAA,CAAW,OAAA,GAAU,WAAW,MAAM;AAAE,MAAA,UAAA,CAAW,OAAA,GAAU,IAAA;AAAM,MAAA,IAAA,EAAK;AAAA,IAAG,GAAG,gBAAgB,CAAA;AAAA,EAChG,CAAA;AAEA,EAAA,SAAA,CAAU,MAAM;AAAE,IAAA,IAAI,CAAC,MAAM,WAAA,EAAY;AAAA,EAAG,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AACrD,EAAA,SAAA,CAAU,MAAM,WAAA,EAAa,EAAE,CAAA;AAE/B,EAAA,MAAM,GAAA,GAAM,WAAA,CAAY,UAAA,EAAY,OAAA,EAAS,IAAA,EAAM,EAAE,IAAA,EAAM,QAAA,EAAU,KAAA,EAAO,MAAA,EAAQ,CAAA,EAAG,IAAA,EAAM,MAAM,CAAA;AAGnG,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,QAAA,CAAS,EAAE,GAAA,EAAK,CAAA,EAAG,IAAA,EAAM,CAAA,EAAG,CAAA;AACxD,EAAA,eAAA,CAAgB,MAAM;AACpB,IAAA,IAAI,CAAC,IAAA,IAAQ,GAAA,KAAQ,IAAA,IAAQ,OAAA,EAAS;AACtC,IAAA,MAAM,QAAQ,MAAM;AAClB,MAAA,MAAM,OAAO,OAAA,CAAQ,OAAA;AACrB,MAAA,MAAM,QAAQ,UAAA,CAAW,OAAA;AACzB,MAAA,MAAM,QAAQ,MAAA,CAAO,OAAA;AACrB,MAAA,IAAI,CAAC,IAAA,IAAQ,CAAC,KAAA,IAAS,CAAC,KAAA,EAAO;AAC/B,MAAA,MAAM,CAAA,GAAI,KAAK,qBAAA,EAAsB;AACrC,MAAA,MAAM,CAAA,GAAI,MAAM,qBAAA,EAAsB;AACtC,MAAA,MAAM,CAAA,GAAI,MAAM,qBAAA,EAAsB;AACtC,MAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,aAAA,CAAc,kBAAkB,GAAG,qBAAA,EAAsB;AAC7E,MAAA,IAAI,IAAA,GAAO,EAAE,KAAA,GAAQ,CAAA;AACrB,MAAA,IAAI,IAAA,GAAO,CAAA,CAAE,KAAA,GAAQ,MAAA,CAAO,aAAa,CAAA,EAAG,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,CAAA,CAAE,IAAA,GAAO,CAAA,GAAI,EAAE,KAAK,CAAA;AACnF,MAAA,MAAM,MAAM,IAAA,CAAK,GAAA,CAAI,GAAG,IAAA,CAAK,GAAA,CAAI,EAAE,GAAA,IAAO,KAAA,GAAQ,MAAM,GAAA,GAAM,CAAA,CAAE,MAAM,CAAA,CAAA,EAAI,MAAA,CAAO,cAAc,CAAA,CAAE,MAAA,GAAS,CAAC,CAAC,CAAA;AAC5G,MAAA,SAAA,CAAU,CAAC,GAAA,KAAS,GAAA,CAAI,GAAA,KAAQ,GAAA,IAAO,GAAA,CAAI,IAAA,KAAS,IAAA,GAAO,GAAA,GAAM,EAAE,GAAA,EAAK,IAAA,EAAO,CAAA;AAAA,IACjF,CAAA;AACA,IAAA,KAAA,EAAM;AACN,IAAA,MAAA,CAAO,gBAAA,CAAiB,UAAU,KAAK,CAAA;AACvC,IAAA,MAAA,CAAO,gBAAA,CAAiB,QAAA,EAAU,KAAA,EAAO,IAAI,CAAA;AAC7C,IAAA,OAAO,MAAM;AACX,MAAA,MAAA,CAAO,mBAAA,CAAoB,UAAU,KAAK,CAAA;AAC1C,MAAA,MAAA,CAAO,mBAAA,CAAoB,QAAA,EAAU,KAAA,EAAO,IAAI,CAAA;AAAA,IAClD,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,IAAA,EAAM,GAAA,EAAK,OAAO,CAAC,CAAA;AAEvB,EAAA,MAAM,cAAA,GAAiB,QAAQ,KAAK,CAAA;AACpC,EAAA,MAAM,QAAA,GAAW,GAAA,KAAQ,IAAA,IAAQ,SAAA,CAAU,KAAA,CAAM,GAAG,CAAC,CAAA,GAAK,KAAA,CAAM,GAAG,CAAA,GAA4B,IAAA;AAC/F,EAAA,MAAM,aAAa,QAAA,GAAW,OAAA,CAAQ,QAAA,CAAS,KAAK,IAAI,EAAC;AAGzD,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAS,IAAI,CAAA;AAC7C,EAAA,IAAI,SAAS,QAAA,EAAU;AACrB,IAAA,WAAA,CAAY,IAAI,CAAA;AAChB,IAAA,IAAI,CAAC,IAAA,EAAM;AAAE,MAAA,aAAA,CAAc,EAAE,CAAA;AAAG,MAAA,MAAA,CAAO,IAAI,CAAA;AAAG,MAAA,gBAAA,CAAiB,EAAE,CAAA;AAAA,IAAG;AAAA,EACtE;AAGA,EAAA,MAAM,EAAE,KAAA,EAAO,UAAA,EAAY,aAAY,GAAI,aAAA,CAAc,MAAM,EAAE,OAAA,EAAS,YAAY,KAAA,EAAO,SAAA,EAAW,OAAO,MAAM,OAAA,CAAQ,KAAK,CAAA,EAAG,QAAA,EAAU,OAAO,CAAA;AAGtJ,EAAA,MAAM,WAAW,MAAM;AACrB,IAAA,IAAI,QAAQ,IAAA,EAAM;AAClB,IAAA,aAAA,CAAc,GAAG,CAAA;AACjB,IAAA,MAAA,CAAO,IAAI,CAAA;AACX,IAAA,gBAAA,CAAiB,EAAE,CAAA;AAAA,EACrB,CAAA;AACA,EAAA,MAAM,OAAA,GAAU,CAAC,CAAA,EAAW,UAAA,KAAwB;AAClD,IAAA,MAAM,KAAA,GAAQ,MAAM,CAAC,CAAA;AACrB,IAAA,IAAI,CAAC,SAAA,CAAU,KAAK,CAAA,IAAK,aAAA,CAAc,KAAK,CAAA,EAAG;AAC/C,IAAA,WAAA,EAAY;AACZ,IAAA,MAAA,CAAO,CAAC,CAAA;AACR,IAAA,aAAA,CAAc,CAAC,CAAA;AACf,IAAA,gBAAA,CAAiB,UAAA,GAAa,QAAQ,KAAA,CAAM,KAAK,EAAE,CAAC,CAAA,IAAK,KAAK,EAAE,CAAA;AAAA,EAClE,CAAA;AAEA,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,IAAA,EAAM;AAIX,IAAA,MAAM,SAAA,GAAY,CAAC,CAAA,KAAgC;AACjD,MAAA,IAAI,EAAE,GAAA,KAAQ,QAAA,IAAY,EAAE,gBAAA,IAAoB,CAAC,YAAW,EAAG;AAC/D,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,IAAI,QAAQ,IAAA,EAAM;AAAE,QAAA,QAAA,EAAS;AAAG,QAAA;AAAA,MAAQ;AACxC,MAAA,WAAA,EAAY;AACZ,MAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,IACf,CAAA;AAEA,IAAA,MAAM,OAAA,GAAU,CAAC,CAAA,KAAkB;AACjC,MAAA,MAAM,SAAS,CAAA,CAAE,MAAA;AACjB,MAAA,IAAI,UAAA,CAAW,SAAS,QAAA,CAAS,MAAM,KAAK,SAAA,CAAU,OAAA,EAAS,QAAA,CAAS,MAAM,CAAA,EAAG;AACjF,MAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,IACf,CAAA;AAEA,IAAA,QAAA,CAAS,gBAAA,CAAiB,WAAW,SAAS,CAAA;AAC9C,IAAA,QAAA,CAAS,gBAAA,CAAiB,aAAa,OAAO,CAAA;AAC9C,IAAA,OAAO,MAAM;AACX,MAAA,QAAA,CAAS,mBAAA,CAAoB,WAAW,SAAS,CAAA;AACjD,MAAA,QAAA,CAAS,mBAAA,CAAoB,aAAa,OAAO,CAAA;AAAA,IACnD,CAAA;AAAA,EACF,CAAC,CAAA;AAED,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,IAAA,EAAM;AAGX,IAAA,IAAI,GAAA,KAAQ,IAAA,IAAQ,aAAA,IAAiB,CAAA,EAAG,SAAA,CAAU,OAAA,EAAS,aAAA,CAA2B,CAAA,iBAAA,EAAoB,aAAa,CAAA,EAAA,CAAI,CAAA,EAAG,KAAA,EAAM;AAAA,SAAA,IAC3H,GAAA,KAAQ,QAAQ,OAAA,EAAS,SAAA,CAAU,SAAS,aAAA,CAA2B,iBAAiB,GAAG,KAAA,EAAM;AAAA,SAAA,IACjG,UAAA,IAAc,GAAG,OAAA,CAAQ,OAAA,EAAS,cAA2B,CAAA,aAAA,EAAgB,UAAU,CAAA,EAAA,CAAI,CAAA,EAAG,KAAA,EAAM;AAAA,EAC/G,GAAG,CAAC,IAAA,EAAM,YAAY,GAAA,EAAK,aAAA,EAAe,OAAO,CAAC,CAAA;AAElD,EAAA,MAAM,OAAO,CAAC,KAAA,EAAiB,EAAA,EAAY,EAAA,KAAe,QAAQ,KAAA,CAAM,OAAA,CAAQ,EAAE,CAAA,GAAI,MAAM,KAAA,CAAM,MAAA,GAAS,KAAA,CAAM,MAAA,IAAU,MAAM,MAAM,CAAA;AACvI,EAAA,SAAS,kBAAkB,CAAA,EAAuB;AAChD,IAAA,MAAM,QAAQ,GAAA,KAAQ,IAAA;AACtB,IAAA,MAAM,KAAA,GAAQ,QAAQ,UAAA,GAAa,cAAA;AACnC,IAAA,MAAM,EAAA,GAAK,QAAQ,aAAA,GAAgB,UAAA;AACnC,IAAA,MAAM,EAAA,GAAK,CAAC,CAAA,KAA0B;AAAE,MAAA,IAAI,MAAM,MAAA,EAAW;AAAQ,MAAA,IAAI,KAAA,mBAAwB,CAAC,CAAA;AAAA,yBAAsB,CAAC,CAAA;AAAA,IAAG,CAAA;AAC5H,IAAA,IAAI,CAAA,CAAE,QAAQ,WAAA,EAAa;AAAE,MAAA,CAAA,CAAE,cAAA,EAAe;AAAG,MAAA,EAAA,CAAG,EAAA,GAAK,IAAI,KAAA,CAAM,CAAC,IAAI,IAAA,CAAK,KAAA,EAAO,EAAA,EAAI,CAAC,CAAC,CAAA;AAAA,IAAG,CAAA,MAAA,IACpF,CAAA,CAAE,GAAA,KAAQ,SAAA,EAAW;AAAE,MAAA,CAAA,CAAE,cAAA,EAAe;AAAG,MAAA,EAAA,CAAG,EAAA,GAAK,CAAA,GAAI,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA,GAAI,IAAA,CAAK,KAAA,EAAO,EAAA,EAAI,EAAE,CAAC,CAAA;AAAA,IAAG,CAAA,MAAA,IACvG,CAAA,CAAE,GAAA,KAAQ,MAAA,EAAQ;AAAE,MAAA,CAAA,CAAE,cAAA,EAAe;AAAG,MAAA,EAAA,CAAG,KAAA,CAAM,CAAC,CAAC,CAAA;AAAA,IAAG,CAAA,MAAA,IACtD,CAAA,CAAE,GAAA,KAAQ,KAAA,EAAO;AAAE,MAAA,CAAA,CAAE,cAAA,EAAe;AAAG,MAAA,EAAA,CAAG,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,CAAC,CAAA;AAAA,IAAG,CAAA,MAAA,IACpE,CAAA,CAAE,GAAA,KAAQ,YAAA,IAAgB,CAAC,KAAA,IAAS,UAAA,IAAc,CAAA,IAAK,SAAA,CAAU,KAAA,CAAM,UAAU,CAAC,CAAA,EAAG;AAAE,MAAA,CAAA,CAAE,cAAA,EAAe;AAAG,MAAA,OAAA,CAAQ,YAAY,IAAI,CAAA;AAAA,IAAG,CAAA,MAAA,IACtI,CAAA,CAAE,GAAA,KAAQ,WAAA,IAAe,KAAA,EAAO;AAAE,MAAA,CAAA,CAAE,cAAA,EAAe;AAAG,MAAA,QAAA,EAAS;AAAA,IAAG;AAAA,EAC7E;AAGA,EAAA,MAAM,MAAA,GAAS,CAAC,IAAA,KAA2B;AACzC,IAAA,IAAI,KAAK,QAAA,EAAU;AACnB,IAAA,WAAA,EAAY;AACZ,IAAA,IAAA,CAAK,OAAA,EAAQ;AACb,IAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,EACf,CAAA;AAEA,EAAA,MAAM,QAAA,GAAW,CAAC,OAAA,KAAwD,OAAA,CAAQ,IAAI,CAAC,IAAA,EAAM,MAAO,SAAA,CAAU,IAAI,oBAAI,GAAA,CAAC,OAAA,EAAA,EAAA,EAAa,CAAG,CAAA,mBACrI,IAAA,CAAC,YAAO,IAAA,EAAK,QAAA,EAAiB,kBAAgB,CAAA,EAAG,IAAA,EAAK,YAAW,QAAA,EAAU,IAAA,CAAK,UAAU,QAAA,EAAU,aAAA,KAAkB,IAAI,CAAA,GAAI,EAAA,EAAI,SAAS,MAAM,gBAAA,CAAiB,CAAC,CAAA,EAAG,OAAA,EAAS,MAAM,MAAA,CAAO,IAAI,GAAG,SAAA,EAAW,QAAA,CAAS,IAAI,CAAA,EACxN,QAAA,EAAA;AAAA,IAAA,IAAA,CAAK,IAAA,wBAAS,IAAA,EAAA,EAAK,IAAA,EAAM,KAAK,IAAA,EAAM,IAAA,EAAM,EAAA,EAAI,SAAA,EAAU,UAAA,EAAW,CAAA;AAAA,oBACpE,GAAA,CAAC,MAAA,EAAA,EAAM,QAAA,EAAA,IAAA,CAAK,KAAA,EAAM;AAAA,GAAA,EAAA,EAFO,CAG3B,CACA,CAAA;AAEF,EAAA,uBACE,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,oBAAA,GAAA;AAAA,MAAC,IAAA;AAAA,MAAA;AAAA,QACE,GAAG,YAAY,OAAO,CAAA;AAAA,QAEvB,eAAA,EAAc,MAAA;AAAA,QACd,eAAA,EAAe,IAAA;AAAA,QACf,GAAA,EAAK,UAAA;AAAA,QACL,SAAS,MAAM;AACb,UAAA,OAAA,CAAQ,CAAC,IAAI,CAAA;AACb,UAAA,IAAI,CAAC,IAAA,IAAQ,cAAA,CAAe,QAAQ,aAAA,CAAc,cAAA,CAAe,CAAC,CAAC,CAAA;AAAA,QACrE,CAAA;AAAA,QAEC,QAAA,EAAA;AAAA;AAAA,KACH;AAAA,IACC,IAAA,IAAQ,YAAA;AAAA,sBACP,IAAA,CAAC,KAAA,EAAA,EAAI,GAAA,EAAK,SAAA,EAAW,WAAU,UAAA,EAC7B,QAAA,EAAA;AAAA,wBAAA,GAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,GAAA,EAAK,OAAA;AAAA,YACL,IAAA,EAAK,MAAA;AAAA,YACL,SAAA,EAAQ,MAAA;AAAA,YACR,YAAA,EAAY,OAAA,IAAW,QAAA,GAAW,QAAA,CAAS,KAAA,GAAQ,MAAA;AAAA,YACnD,SAAA,EAAW,iBAAA;AAAA,YACX,SAAA,EAAW,EAAA,CAAG,KAAA,EAAO,SAAS,CAAA;AAAA,YAC9B,OAAO,EAAE,GAAA,EAAK,IAAI,GAAA,EAAK,IAAA,EAAM,IAAI,IAAA,EAAK;AAAA,YAGtC,OAAA,EAAS,CAAC,CAAA,KAAM,CAAA,CAAE,eAAA,EAAgB;AAAA,YAEjC,QAAA,EAAA,OAAA,IAAW,QAAA;AAAA;AAAA,8BAEV,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,gCAAA,IAAA,CAAC,QAAA,EAAA,EAAO,MAAK,QAAA,EAAS,eAAA,EAAc,IAAG,IAAA,EAAK,UAAA,EAAW,YAAA,EAAY,CAAA,UAAA,EAAa,QAAA,CAAS,KAAK,IAAI,QAAA,EAAU,EAAA,EAAI,SAAS,QAAA,EAAU,SAAA,EAAW,GAAG,QAAA,CAAS,QAAQ,CAAA,EAAG,iEAAiE,CAAA,EACpO,QAAA,EAAA;AAAA,kCAAA,GAAA,CAAC,QAAK,IAAA,EAAK,cAAA,EAAe,IAAA,EAAM,EAAA,EAAI,WAAU,UAAA,EAAW,CAAA;AAAA,kCACzD,GAAA,CAAC,MAAA,EAAA,EAAM,QAAA,EAAA,QAAA,CAAS,KAAA,EAAM;AAAA,iBAAA,EACxB,CAAA;AAAA,oCACC,OAAA,EAAA,EAAQ,CAAA;AAAA,gBACR,QAAA,CAAS,SAAS,KAAK;AAAA,eAAA,EAC1B;AAAA,gBACE,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,EAAM,CAAA,KAAM;AACzB,cAAA,IAAI,UAAU,IAAI,CAAA,EAAG,uBAAO,GAAA,CAAC,aAAa,CAAG,CAAA;AAC7C,cAAA,IAAI,SAAA,CAAU,IAAI,CAAA,EAAG;AACnB,gBAAA,MAAM,SAAS,GAAA,KAAQ,CAAA;AACvB,gBAAA,uBACE,IAAA;AAAA,kBAAC,QAAA;AAAA,kBAAA;AAAA,oBAAO,IAAA,EAAK,QAAA;AAAA,oBAEX,GAAA,EAAK,MAAA,GAAS,CAAC,EAAA,KAAO;AAAE,sBAAA,UAAA,CAAW,OAAA,GAAU,EAAA;AAAA,oBAAI,CAAA,GAAI,MAAA;AAAA,oBACrD,YAAA,EAAY,CAAA;AAAA,oBACZ,IAAA,EAAK,UAAA;AAAA,oBACL,eAAA,EAAc,MAAA;AAAA,oBACd,eAAA,EAAe,MAAA;AAAA,oBACf,QAAA,EAAU,cAAc,IAAI,CAAA;AAAA,oBAC5B,QAAA,EAAU,UAAA,KAAe,CAAA,GAAI,CAAA,GAAI,EAAA;AAAA,oBAEjC,OAAA,EAAS,MAAM,aAAA,CAAc,CAAC,CAAA;AAAA,oBAI9B,OAAA,EAAS,MAAM,OAAA,CAAQ,CAAA,EAAG,IAAI,CAAA;AAAA,oBAG9B,YAAA,EAAc,CAAC,CAAA,KAAM;AACnB,sBAAA,IAAI,OAAA,IAAW,CAAA,CAAE,OAAA,KAAY,CAAA,EAAG;AAChC,sBAAA,IAAI,QAAQ,WAAA,EAAY;AAAA,2BAAA,IACf,GAAA,KAAQ,IAAA,EAAM,OAAA,CAAQ,CAAA,EAAG,KAAK,CAAA;AAAA,2BAClC,UAAA,CAAW,MAAM,OAAA,CAAQ,CAAA,EAAG,KAAK,CAAC,CAAA;AAAA,oBACzC,CAAA;AAAA,oBACA,SAAA,EAAW,QAAA,CAAS,IAAA,EAAM,MAAM,CAAA;AAAA,oBAE/B,QAAA,EAAA;AAAA,sBAAA,IAAA,CAAK,IAAA,wBAAS,IAAA,EAAA,EAAK,IAAA,EAAM,KAAK,IAAA,EAAM,IAAA,EAAM,EAAA,EAAI,SAAA,EAAU,UAAA,EAAW,CAAA;AAAA,sCACpE,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,QAAA,EAAU,eAAK,KAAA,EAAM,CAAA;AAAA,0CACpC,IAAA,EAAA,EAAK,IAAA,EAAK,iBAAgB,IAAA,EAAM,EAAA,EAAI,WAAU,sCAAA,EAAuC;AAAA;AAAA,mBAAA;AAAA,kBA1BjF;AAAA,iBA2BP;AAAA,cAEJ;AACA,cAAA,uBACE,IAAA;AAAA,gBAAC,QAAA;AAAA,gBAAA;AAAA,kBAAO,IAAA,EAAK,QAAA;AAAA,kBAEX,YAAA,EAAY,CAAA;AAAA,kBACZ,IAAA,EAAK,UAAA;AAAA,kBACL,UAAU,IAAA,CAAK,QAAA;AAAA,kBACf,QAAA,EAAU,UAAA,KAAe,CAAA,GAAI,CAAA,GAAI,EAAA;AAAA,kBACjC,OAAA,EAAS,MAAM,aAAA,CAAc,CAAC,CAAA;AAAA,kBAC9B,OAAA,EAAS,MAAM,MAAA,CAAO,IAAI,CAAA;AAAA,kBAG1B,cAAc,MAAM;AAAE,oBAAA,IAAI,CAAC,OAAA,IAAW,GAAA,KAAQ,IAAA,aAAiB,MAAM;AAAE,sBAAA,MAAA,CAAO,IAAI,CAAA;AAAG,sBAAA,gBAAA,CAAiB,EAAE,CAAA;AAAA,oBAAG,CAAC,CAAA;AAAA,kBAAG,CAAA;AAAA,kBAC/G,SAAA,EAAW,SAAS,IAAI,CAAA;AAAA,kBAEvB,QAAA,EAAA;AAAA,oBAAA,IAAA,CAAK,IAAA,wBAAS,IAAA,EAAA,EAAK,IAAA,EAAM,KAAK,IAAA,EAAM,IAAA,EAAM,EAAA,EAAI,SAAA,EAAU,UAAA,EAAW,CAAA;AAAA,oCACpE,GAAA,CAAC,MAAA,EAAA,EAAM,QAAA,EAAA,IAAA,CAAK,KAAA,EAAM;AAAA;AAAA,iBAAA;AAAA,gBAbb;AAAA,eAcP;AAAA,YAEJ,CAAC;AAAA;AAAA,SACH;AAAA,QACC,CAAC,WAAW,QAAA,oBACX,GAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,GAAA,EAAK,MAAA;AAAA,YACL,IAAA,EAAK,MAAA;AAAA,YACL,cAAY,QAAA,CAAS,KAAA;AAAA,YACrB,SAAA,EAAW,iBAAA;AAAA,YACX,SAAA,EAAW,KAAA;AAAA,YACX,OAAO,EAAE,GAAA,EAAK,OAAO,GAAA,EAAK,IAAA,EAAM,OAAO,IAAA,EAAK;AAAA,YAC5C,OAAA,EAAS,CAAC,CAAA,KAAM,CAAA,CAAE,eAAA,EAAgB;AAAA,YAElC,YAAA,EAAc,WAAA;AAAA,YAEb,QAAA,EAAA,QAAA,CAAS,SAAS,KAAK;AAAA;AAAA;AAC1B,OAAA,EAEJ,CAAA;AAAA,MACA,aAAa,QAAA,CAAS;AAAA;AACxB,GAAA,EACF,CAAA;AAEJ","file":"chunk-YFHTREH5.js","sourcesContent":["import { useRef, useEffect, useLayoutEffect, useState, type ReactNode, type KeyboardEvent as ReactKeyboardEvent } from \"react\";\nimport { useBreakpoint } from \"../lib/use-breakpoint\";\nimport { createPortal } from \"react-dom\";\nimport { cn, FOCUS_RING, GLASS_DROPDOWN } from \"../lib/utils\";\nimport { Icon, type IconName } from \"../lib/icons\";\nimport { Slot } from \"../lib/slot\";\nimport { triggerType } from \"./Button\";\nimport { usePosition, type Align } from \"../lib/use-position\";\nimport { usePopupFocus } from \"../lib/focus\";\n\nexport interface DropdownMenuItem {\n label: string;\n icon?: IconName;\n onClick: () => void;\n variant?: \"default\" | \"destructive\";\n disabled?: boolean;\n}\n\nexport interface DropdownMenuDivider {\n type: \"divider\";\n}\n\n/**\n * An entry that opens a submenu of its own (Sprint 25.8.7's look check: a card's Move among its other actions), one\n * level deep. On a desktop it opens beside the menu: → or Enter opens it, its first item focused; ← or Escape closes it,\n * focus back on this entry; a hover opens it too. On a phone its items take the menu's place, under a Back item.\n */\nexport interface DropdownMenuSubmenu {\n label: string;\n icon?: IconName;\n items: (DropdownMenuItem | DropdownMenuDivider)[];\n disabled?: boolean;\n}\n\nexport type DropdownMenuEntry = DropdownMenuItem | DropdownMenuDivider | DropdownMenuSubmenu;\n\nfunction isDivider(entry: DropdownMenuEntry): entry is DropdownMenuDivider {\n return \"type\" in entry && entry.type === \"divider\";\n}\nfunction isSubmenu(entry: DropdownMenuEntry): entry is DropdownMenuSubmenu {\n return \"items\" in entry;\n}\n/** The indices of a list's items the arrows stop at: no divider, nothing unavailable. */\nconst stopsOf = (entries: DropdownMenuEntry[]): number[] => entries.reduce<number[]>((acc, e, i) => {\n if (!isDivider(e) && !isUnavailable(e)) acc.push(i);\n return acc;\n}, []);\n/** An entry that can't be chosen: disabled, or a submenu with nothing in it to choose, which never opens empty. */\nfunction isUnavailable(entry: DropdownMenuItem | DropdownMenuSubmenu): boolean {\n return !!entry.disabled || (isSubmenu(entry) && stopsOf(entry.items).length === 0);\n}\n/**\n * How long a hover on another entry waits before it closes the open submenu (or opens the other's): a pointer on its way\n * to the submenu crosses the entries below its own, and reaching the submenu in that time keeps it open.\n */\nconst SUBMENU_GRACE_MS = 300;\n\nconst ITEM = \"flex items-center gap-ds-2 w-full text-left px-ds-3 py-ds-2 text-sm cursor-pointer transition-colors rounded-[var(--ds-radius-sm)]\";\nconst itemLook = (entry: DropdownMenuItem | DropdownMenuSubmenu, open = false) => cn(\n ITEM,\n FOCUS_RING,\n isUnavailable(entry)\n ? \"opacity-disabled cursor-not-allowed\"\n : \"variant\" in entry && entry.variant === \"destructive\"\n ? \"text-[var(--ds-status-error-text)] hover:bg-[var(--ds-status-error-bg)] focus-visible:bg-[var(--ds-status-error-bg)]\"\n : cn(\"text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-emphasis)] focus-visible:bg-[var(--ds-bg-emphasis)]\", open && \"bg-[var(--ds-bg-emphasis)]\"),\n);\nconst PANEL = cn(\n // the items' outline has its 4 px of room at any density (Sprint 30, P3)\n \"fixed z-[var(--ds-z-popover)] min-w-[180px] p-[max(var(--ds-space-1),4px)]\",\n \"bg-[var(--ds-bg-surface-elevated)]\",\n GLASS_DROPDOWN,\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n \"rounded-[var(--ds-radius-md)] overflow-hidden\",\n);\nconst Divider = () => <div role=\"separator\" className=\"my-ds-1 h-px bg-[var(--ds-border-default)]\" />;\n\ninterface DropdownMenuProps {\n trigger: ReactNode;\n items: DropdownMenuEntry[];\n align?: Align;\n className?: string;\n container?: Element | DocumentFragment;\n}\n\nexport function DropdownMenu({ trigger, items, align = \"end\", className, container }: DropdownMenuProps) {\n const [open, setOpen] = useState(false);\n const [focusedIdx, setFocusedIdx] = useState(-1);\n // the submenu open, by its entry's index, and the focus inside it\n const [sub, setSub] = useState<number | null>(null);\n const [subFocusedIdx, setSubFocusedIdx] = useState(-1);\n const triggerRef = useRef<HTMLElement>(null);\n // both panels, the menu and its submenu, sit in one box (display: contents) that is the popup's for focus and presses\n const panelsRef = useRef<HTMLDivElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const subRef = useRef<HTMLDivElement>(null);\n const subItemRef = useRef<HTMLElement>(null);\n const isPhone = useBreakpoint(\"sm\");\n // a hover elsewhere, waiting before it closes the open submenu (SUBMENU_GRACE_MS)\n const graceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const cancelGrace = () => {\n if (graceTimer.current) { clearTimeout(graceTimer.current); graceTimer.current = null; }\n };\n const afterGrace = (then: () => void) => {\n cancelGrace();\n graceTimer.current = setTimeout(() => { graceTimer.current = null; then(); }, SUBMENU_GRACE_MS);\n };\n // closing the menu, or leaving the page, drops a hover's wait\n useEffect(() => { if (!open) cancelGrace(); }, [open]);\n useEffect(() => cancelGrace, []);\n\n const pos = usePosition(triggerRef, menuRef, open, { side: \"bottom\", align, offset: 4, flip: true });\n // beside the menu, 4 px from its edge, its first item level with its entry; on the other side where the screen has no\n // room, and inside the screen (measured against the menu, not the entry: the menu's padding and edge sit between them)\n const [subPos, setSubPos] = useState({ top: 0, left: 0 });\n useLayoutEffect(() => {\n if (!open || sub === null || isPhone) return;\n const place = () => {\n const menu = menuRef.current;\n const entry = subItemRef.current;\n const panel = subRef.current;\n if (!menu || !entry || !panel) return;\n const m = menu.getBoundingClientRect();\n const e = entry.getBoundingClientRect();\n const p = panel.getBoundingClientRect();\n const first = panel.querySelector(\"[data-sub-index]\")?.getBoundingClientRect();\n let left = m.right + 4;\n if (left + p.width > window.innerWidth - 8) left = Math.max(8, m.left - 4 - p.width);\n const top = Math.max(8, Math.min(e.top - (first ? first.top - p.top : 0), window.innerHeight - p.height - 8));\n setSubPos((cur) => (cur.top === top && cur.left === left ? cur : { top, left }));\n };\n place();\n window.addEventListener(\"resize\", place);\n window.addEventListener(\"scroll\", place, true);\n return () => {\n window.removeEventListener(\"resize\", place);\n window.removeEventListener(\"scroll\", place, true);\n };\n }, [open, sub, isPhone]);\n\n const enabledIndices = stopsOf(items);\n const subEntry = sub !== null && isSubmenu(items[sub]) ? (items[sub] as DropdownMenuSubmenu) : null;\n const subIndices = subEntry ? stopsOf(subEntry.items) : [];\n\n // Closing clears the keyboard focus index and the submenu (render-time state adjustment).\n const [prevOpen, setPrevOpen] = useState(open);\n if (open !== prevOpen) {\n setPrevOpen(open);\n if (!open) { setFocusedIdx(-1); setSub(null); setSubFocusedIdx(-1); }\n }\n\n // Sprint 25.7: focus hands back on close, and Tab out carries on from the trigger (lib/focus usePopupFocus).\n const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: panelsRef, close: () => setOpen(false), tabExits: \"any\" });\n\n /** The submenu closes, and focus goes back to the entry that opened it. */\n const closeSub = () => {\n if (sub === null) return;\n setFocusedIdx(sub);\n setSub(null);\n setSubFocusedIdx(-1);\n };\n const openSub = (i: number, focusFirst: boolean) => {\n const entry = items[i];\n if (!isSubmenu(entry) || isUnavailable(entry)) return;\n cancelGrace();\n setSub(i);\n setFocusedIdx(i);\n setSubFocusedIdx(focusFirst ? stopsOf(entry.items)[0] ?? -1 : -1);\n };\n\n useEffect(() => {\n if (!open) return;\n\n // An Escape a control inside already handled is its own; the one this acts on is marked for overlays around it.\n // In a submenu it closes the submenu alone.\n const onKeyDown = (e: globalThis.KeyboardEvent) => {\n if (e.key !== \"Escape\" || e.defaultPrevented || !isTopLayer()) return;\n e.preventDefault();\n if (sub !== null) { closeSub(); return; }\n returnFocus();\n setOpen(false);\n };\n\n const onClick = (e: MouseEvent) => {\n const target = e.target as Node;\n if (triggerRef.current?.contains(target) || panelsRef.current?.contains(target)) return;\n setOpen(false);\n };\n\n document.addEventListener(\"keydown\", onKeyDown);\n document.addEventListener(\"mousedown\", onClick);\n return () => {\n document.removeEventListener(\"keydown\", onKeyDown);\n document.removeEventListener(\"mousedown\", onClick);\n };\n });\n\n useEffect(() => {\n if (!open) return;\n // By the item's own index: items and dividers share one numbering, so indexing the buttons alone left every\n // item after a divider unreachable by the arrows (the shell's user menu: Settings, divider, Log out).\n if (sub !== null && subFocusedIdx >= 0) panelsRef.current?.querySelector<HTMLElement>(`[data-sub-index=\"${subFocusedIdx}\"]`)?.focus();\n else if (sub !== null && isPhone) panelsRef.current?.querySelector<HTMLElement>(\"[data-sub-back]\")?.focus();\n else if (focusedIdx >= 0) menuRef.current?.querySelector<HTMLElement>(`[data-index=\"${focusedIdx}\"]`)?.focus();\n }, [open, focusedIdx, sub, subFocusedIdx, isPhone]);\n\n const step = (stops: number[], at: number, by: number) => stops[((stops.indexOf(at) + by) % stops.length + stops.length) % stops.length];\n function handleMenuKeyDown(e: ReactKeyboardEvent) {\n const inSub = sub !== null;\n const stops = inSub ? subIndices : enabledIndices;\n const at = inSub ? subFocusedIdx : focusedIdx;\n const go = (i: number | undefined) => { if (i === undefined) return; if (inSub) setSubFocusedIdx(i); else setFocusedIdx(i); };\n if (e.key === \"ArrowDown\") { e.preventDefault(); go(at < 0 ? stops[0] : step(stops, at, 1)); }\n else if (e.key === \"ArrowUp\") { e.preventDefault(); go(at < 0 ? stops[stops.length - 1] : step(stops, at, -1)); }\n else if (e.key === \"Home\") { e.preventDefault(); go(stops[0]); }\n else if (e.key === \"End\") { e.preventDefault(); go(stops[stops.length - 1]); }\n else if (e.key === \"ArrowRight\" && !inSub && focusedIdx >= 0 && isSubmenu(items[focusedIdx])) { e.preventDefault(); openSub(focusedIdx, true); }\n else if (e.key === \"ArrowLeft\" && inSub) { e.preventDefault(); closeSub(); }\n }\n\n /** A choice: focus back on the trigger first, so a dialog the item opens returns there (Sprint 25.7). */\n const choose = (item: DropdownMenuItem) => {\n if (item.disabled) return;\n returnFocus();\n item.onClick();\n setOpen(false);\n };\n\n const subItems = (entries: (DropdownMenuItem | DropdownMenuDivider)[]) => entries.map((item, i) => (isDivider(item) ? <Divider key={i} /> : (\n <button type=\"button\" key={i} data-sub-index={i} role=\"menuitem\" disabled={item.disabled} tabIndex={subFocusedIdx === i ? 0 : -1} onFocus={() => setSubFocusedIdx(i)} onClick={() => choose(item)} className={itemLook(item)}>\n {item.icon && <Icon name={item.icon} size={16} className=\"shrink-0\" />}\n <span>{item.label}</span>\n </button>\n )));\n\n return (\n <>\n <Slot\n {...triggerType(trigger)}\n // the APG's menu button: the trigger says it opens a menu, and whether it is open\n aria-haspopup=\"menu\"\n aria-expanded={open}\n ref={triggerRef}\n onClick={() => {\n setOpen(!open);\n if (!open && enabledIndices.length) setFocusedIdx(enabledIndices[0]);\n }}\n >\n {trigger}\n </Slot>\n {open && createPortal(\n <div ref={panelsRef} className=\"contents\">\n <div\n ref={menuRef}\n role=\"menu\"\n data-gs=\"menu\"\n aria-label={isPhone && subEntry ? subEntry.label : undefined}\n onKeyDown={handleMenuKeyDown}\n className={cn(PANEL, className)}\n style={{ top: pos.top, left: pos.left }}\n // A choice is the menu's own: the click must not also reach a clickable row or card the menu sits in\n // (React carries events from a portal to its React parents).\n onClick={(e) => e.stopPropagation()}\n >\n {isPhone && subEntry ? (\n // on a phone the submenu takes the menu's place: Back first, its entry's name on it\n <>\n <button type=\"button\" data-sub-back=\"\" role=\"menuitem\" aria-label={`Back from ${subEntry.label}`} tabIndex={-1} onClick={closeSub} className={cn(itemLook(subEntry), \"text-[var(--ds-text-secondary)] font-[var(--ds-weight-heading)]\")}>\n <Icon name=\"chevron-left\" size={16} className=\"shrink-0\" />\n <span>{subEntry.label}</span>\n </button>\n <Divider />\n {subItems(subEntry.items)}\n </>\n ) : items.map((item, i) => {\n if (isDivider(item)) return <Divider key={i} />;\n if (isSubmenu(item)) {\n const isOpen = sub === i;\n return (\n <button type=\"button\"\n key={i}\n ref={isOpen ? (el) => { subItemRef.current = el; } : undefined}\n data-index={i}\n role=\"menuitem\"\n aria-haspopup=\"menu\"\n aria-expanded={isOpen}\n disabled={isUnavailable(item)}\n tabIndex={focusedIdx === i ? 0 : -1}\n // the arrows go on from wherever focus is, however it got there (a click, a screen reader's command)\n onFocus={() => setFocusedIdx(i)}\n // Enter (its click) or a press goes into the submenu, its first item focused, whether a hover had\n // opened it or not (the APG's menu: Enter opens it and focuses its first item); ←, Escape or Back\n // closes it\n onClick={() => openSub(i, true)}\n // a hover opens it at once where no other is open; with another open it waits, as the pointer may be\n // on its way to that one (SUBMENU_GRACE_MS); back on its own entry, the wait for a close is dropped\n onMouseEnter={(e) => {\n if (isPhone || e.buttons !== 0) return;\n if (isOpen) cancelGrace();\n else if (sub === null) openSub(i, false);\n else afterGrace(() => openSub(i, false));\n }}\n className={itemLook(item, isOpen)}\n >\n {item.icon && <Icon name={item.icon} size={16} className=\"shrink-0\" />}\n <span className=\"flex-1\">{item.label}</span>\n <Icon name=\"chevron-right\" size={14} className=\"shrink-0 text-[var(--ds-text-muted)]\" />\n </button>\n );\n }\n return (\n <button type=\"button\"\n key={i}\n data-index={i}\n role=\"menuitem\"\n disabled={item.disabled}\n tabIndex={focusedIdx === i ? 0 : -1}\n onFocus={() => setFocusedIdx(i)}\n onClick={() => choose(item)}\n // a hover on another entry closes an open submenu, as moving on from it does, after a moment: the\n // pointer may only be crossing it on its way to the submenu (SUBMENU_GRACE_MS)\n onMouseEnter={() => { if (!isPhone && sub !== null) afterGrace(() => { setSub(null); setSubFocusedIdx(-1); }); }}\n className={itemLook(item)}\n >\n {item.icon && <Icon name={item.icon} size={16} className=\"shrink-0\" />}\n <span>{item.label}</span>\n </button>\n );\n })}\n </div>\n {!isPhone && subEntry && (\n <div\n ref={subRef}\n role=\"menu\"\n aria-label={subEntry.label}\n onKeyDown={handleMenuKeyDown}\n className={PANEL}\n style={{ top: subPos.top, left: subPos.left }}\n onClick={(e) => e.stopPropagation()}\n // the pointer has reached the submenu: a hover it crossed on the way closes nothing\n onMouseEnter={cancelGrace}\n >\n {subItems(subEntry.items)}\n </div>\n )}\n </div>,\n container ?? document.body\n )}\n </>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../showcase/src/components/DropdownMenu.tsx"],"names":[],"mappings":"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","file":"chunk-2TCCSKL3.js","sourcesContent":["import { useRef, useEffect, useLayoutEffect, useState, type ReactNode, type KeyboardEvent as ReactKeyboardEvent } from \"react\";\nimport { useBreakpoint } from \"../lib/use-breakpoint\";\nimport { createPortal } from \"react-dom\";\nimport { cn, FOCUS_RING, GLASS_DROPDOWN } from \"../lib/utils\";\nimport { Icon, type IconName } from \"../lib/icons\";\nimport { Slot } from \"../lib/slot\";\nimport { triggerType } from \"./Button\";\nimport { usePosition, type Align } from \"../lib/use-position\";\nimport { usePopupFocus } from \"../lib/focus\";\n\nexport interface DropdownMenuItem {\n label: string;\n icon?: IconName;\n onClick: () => void;\n variant?: \"default\" | \"destructive\";\n disabled?: boolean;\n}\n\nexport interface DropdownMenuDivider {\n type: \"divider\";\n}\n\n/**\n * An entry that opens a submenu of its own (Sprint 25.8.7's look check: a card's Move among its other actions), one\n * level deep. On a desktop it opens beside the menu: → or Enter opens it, its first item focused; ← or Escape closes it,\n * focus back on this entry; a hover opens it too. On a phone its items take the menu's place, under a Back item.\n */\nexport interface DropdownMenuSubmenu {\n label: string;\n icon?: IconName;\n items: (DropdownMenuItem | DropdownMenuDivider)[];\n disabled?: boolean;\n}\n\nexport type DropdownMenuEntry = DropdownMenuItem | DropdownMenuDivider | DropdownMenuSubmenu;\n\nfunction isDivider(entry: DropdownMenuEntry): entry is DropdownMenuDivider {\n return \"type\" in entry && entry.type === \"divider\";\n}\nfunction isSubmenu(entry: DropdownMenuEntry): entry is DropdownMenuSubmenu {\n return \"items\" in entry;\n}\n/** The indices of a list's items the arrows stop at: no divider, nothing unavailable. */\nconst stopsOf = (entries: DropdownMenuEntry[]): number[] => entries.reduce<number[]>((acc, e, i) => {\n if (!isDivider(e) && !isUnavailable(e)) acc.push(i);\n return acc;\n}, []);\n/** An entry that can't be chosen: disabled, or a submenu with nothing in it to choose, which never opens empty. */\nfunction isUnavailable(entry: DropdownMenuItem | DropdownMenuSubmenu): boolean {\n return !!entry.disabled || (isSubmenu(entry) && stopsOf(entry.items).length === 0);\n}\n/**\n * How long a hover on another entry waits before it closes the open submenu (or opens the other's): a pointer on its way\n * to the submenu crosses the entries below its own, and reaching the submenu in that time keeps it open.\n */\nconst SUBMENU_GRACE_MS = 300;\n\nconst ITEM = \"flex items-center gap-ds-2 w-full text-left px-ds-3 py-ds-2 text-sm cursor-pointer transition-colors rounded-[var(--ds-radius-sm)]\";\nconst itemLook = (entry: DropdownMenuItem | DropdownMenuSubmenu, open = false) => cn(\n ITEM,\n FOCUS_RING,\n isUnavailable(entry)\n ? \"opacity-disabled cursor-not-allowed\"\n : \"variant\" in entry && entry.variant === \"destructive\"\n ? \"text-[var(--ds-status-error-text)] hover:bg-[var(--ds-status-error-bg)] focus-visible:bg-[var(--ds-status-error-bg)]\"\n : cn(\"text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-emphasis)] focus-visible:bg-[var(--ds-bg-emphasis)]\", open && \"bg-[var(--ds-bg-emphasis)]\"),\n);\nconst PANEL = cn(\n // the items' outline has its 4 px of room at any density (Sprint 30, P3)\n \"fixed z-[var(--ds-z-popover)] min-w-[180px] p-[max(var(--ds-space-1),4px)]\",\n \"bg-[var(--ds-bg-surface-elevated)]\",\n GLASS_DROPDOWN,\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n \"rounded-[var(--ds-radius-md)] overflow-hidden\",\n);\nconst Divider = () => <div role=\"separator\" className=\"my-ds-1 h-px bg-[var(--ds-border-default)]\" />;\n\ninterface DropdownMenuProps {\n trigger: ReactNode;\n items: DropdownMenuEntry[];\n align?: Align;\n className?: string;\n container?: Element | DocumentFragment;\n}\n\nexport function DropdownMenu({ trigger, items, align = \"end\", className, container }: DropdownMenuProps) {\n const [open, setOpen] = useState(false);\n const [focusedIdx, setFocusedIdx] = useState(-1);\n // the submenu open, by its entry's index, and the focus inside it\n const [sub, setSub] = useState<number | null>(null);\n const [subFocusedIdx, setSubFocusedIdx] = useState(-1);\n const triggerRef = useRef<HTMLElement>(null);\n // both panels, the menu and its submenu, sit in one box (display: contents) that is the popup's for focus and presses\n const panelsRef = useRef<HTMLDivElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const subRef = useRef<HTMLDivElement>(null);\n const subItemRef = useRef<HTMLElement>(null);\n const isPhone = useBreakpoint(\"sm\");\n // a hover elsewhere, waiting before it closes the open submenu (SUBMENU_GRACE_MS)\n const graceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const cancelGrace = () => {\n if (graceTimer.current) { clearTimeout(graceTimer.current); graceTimer.current = null; }\n };\n const afterGrace = (then: () => void) => {\n cancelGrace();\n graceTimer.current = setTimeout(() => { graceTimer.current = null; then(); }, SUBMENU_GRACE_MS);\n };\n // closing the menu, or leaving the page, drops a hover's wait\n useEffect(() => { if (!open) cancelGrace(); }, [open]);\n useEffect(() => cancelGrace, []);\n\n const pos = usePosition(triggerRef, menuRef, open, { side: \"bottom\", align, offset: 4, flip: true });\n // beside the menu, 4 px from its edge, its first item level with its entry; on the other side where the screen has no\n // room, and inside the screen (measured against the menu, not the entry: the menu's padding and edge sit between them)\n const [subPos, setSubPos] = useState({ top: 0, left: 0 });\n useLayoutEffect(() => {\n if (!open || sub === null || isPhone) return;\n const place = () => {\n const menu = menuRef.current;\n const entry = subItemRef.current;\n const panel = subRef.current;\n if (!menu || !entry || !panel) return;\n const m = menu.getBoundingClientRect();\n const e = entry.getBoundingClientRect();\n const p = panel.getBoundingClientRect();\n const first = panel.querySelector(\"[data-sub-index]\")?.getBoundingClientRect();\n let left = m.right + 4;\n if (left + p.width > window.innerWidth - 8) left = Math.max(8, m.left - 4 - p.width);\n const top = Math.max(8, Math.min(e.top - (first ? first.top - p.top : 0), window.innerHeight - p.height - 8));\n setSubPos((cur) => (cur.top === top && cur.left === left ? cur : { top, left }));\n };\n place();\n window.addEventListener(\"resize\", place);\n window.addEventListener(\"scroll\", place, true);\n return () => {\n window.removeEventListener(\"resize\", place);\n window.removeEventListener(\"scroll\", place, true);\n };\n }, [open, sub, isPhone]);\n\n const enabledIndices = stopsOf(items);\n const subEntry = sub !== null && isSubmenu(items[sub]) ? (items[sub] as DropdownMenuSubmenu) : null;\n const subIndices = subEntry ? stopsOf(subEntry.items) : [];\n\n // Closing clears the keyboard focus index and the submenu (render-time state adjustment).\n const [prevOpen, setPrevOpen] = useState(open);\n if (open !== prevOpen) {\n setPrevOpen(open);\n if (!open) { setFocusedIdx(-1); setSub(null); setSubFocusedIdx(-1); }\n }\n\n // Sprint 25.7: focus hands back on close, and Tab out carries on from the trigger (lib/focus usePopupFocus).\n const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: panelsRef, close: () => setOpen(false), tabExits: \"any\" });\n\n /** The submenu closes, and focus goes back to the entry that opened it. */\n const closeSub = () => {\n if (sub === null) return;\n setFocusedIdx(sub);\n setSub(null);\n setSubFocusedIdx(-1);\n };\n const openSub = (i: number, focusFirst: boolean) => {\n const entry = items[i];\n if (!isSubmenu(entry) || isUnavailable(entry)) return;\n cancelGrace();\n setSub(i);\n setFocusedIdx(i);\n setSubFocusedIdx(focusFirst ? stopsOf(entry.items)[0] ?? -1 : -1);\n };\n\n useEffect(() => {\n if (!open) return;\n\n // An Escape a control inside already handled is its own; the one this acts on is marked for overlays around it.\n // In a submenu it closes the submenu alone.\n const onKeyDown = (e: globalThis.KeyboardEvent) => {\n if (e.key !== \"Escape\" || e.defaultPrevented || !isTopLayer()) return;\n e.preventDefault();\n if (sub !== null) { closeSub(); return; }\n returnFocus();\n setOpen(false);\n };\n\n const onClick = (e: MouseEvent) => {\n const target = e.target as Node;\n if (triggerRef.current?.contains(target) || panelsRef.current?.contains(target)) return;\n setOpen(false);\n };\n\n document.addEventListener(\"keydown\", onKeyDown);\n document.addEventListener(\"mousedown\", onClick);\n return () => {\n document.removeEventListener(\"keydown\", onKeyDown);\n document.removeEventListener(\"mousedown\", onClick);\n };\n });\n\n useEffect(() => {\n if (!open) return;\n // By the item's own index: items and dividers share one numbering, so indexing the buttons alone left every\n // item after a divider unreachable by the arrows (the shell's user menu: Settings, divider, Log out).\n if (sub !== null && subFocusedIdx >= 0) panelsRef.current?.querySelector<HTMLElement>(`[data-sub-index=\"${subFocusedIdx}\"]`)?.focus();\n else if (sub !== null && isPhone) panelsRef.current?.querySelector<HTMLElement>(\"[data-sub-back]\")?.focus();\n else if (focusedIdx >= 0) menuRef.current?.querySelector<HTMLElement>(`[data-index=\"${focusedIdx}\"]`)?.focus();\n }, [open, focusedIdx, sub, subFocusedIdx, isPhone]);\n\n const step = (stops: number[], at: number, by: number) => stops[((stops.indexOf(at) + by) % stops.length + stops.length) % stops.length];\n function handleMenuKeyDown(e: ReactKeyboardEvent) {\n const inSub = sub !== null;\n const stops = inSub ? subIndices : enabledIndices;\n const at = inSub ? subFocusedIdx : focusedIdx;\n const go = (i: number | undefined) => { if (i === undefined) return; if (inSub) setSubFocusedIdx(i); else setFocusedIdx(i); };\n if (e.key === \"ArrowDown\") { e.preventDefault(); go(at < 0 ? stops[0] : step(stops, at, 1)); }\n else if (e.key === \"ArrowUp\") { e.preventDefault(); go(at < 0 ? stops[stops.length - 1] : step(stops, at, -1)); }\n else if (e.key === \"Home\") { e.preventDefault(); go(stops[0]); }\n else if (e.key === \"End\") { e.preventDefault(); go(stops[stops.length - 1]); }\n else if (e.key === \"ArrowRight\" && !inSub && focusedIdx >= 0 && isSubmenu(items[focusedIdx])) { e.preventDefault(); openSub(focusedIdx, true); }\n else if (e.key === \"ArrowLeft\" && inSub) { e.preventDefault(); closeSub(); }\n }\n\n /** A choice: focus back on the trigger first, so a dialog the item opens returns there (Sprint 25.7). */\n const choose = (item: DropdownMenuItem) => {\n if (item.disabled) return;\n returnFocus();\n item.onClick();\n setOpen(false);\n };\n\n const subItems = (entries: (DropdownMenuItem | DropdownMenuDivider)[]) => entries.map((item, i) => (isDivider(item) ? <Divider key={i} /> : (\n <button type=\"button\" key={i} data-sub-index={i} role=\"menuitem\" disabled={item.disabled} tabIndex={subFocusedIdx === i ? 0 : -1} onFocus={() => setSubFocusedIdx(i)} onClick={() => choose(item)} className={itemLook(item)}>\n {item.icon && <Icon name={item.icon} size={16} className=\"shrink-0\" />}\n <span>{item.label}</span>\n </button>\n )));\n\n return (\n <>\n <Slot\n {...triggerType(trigger)}\n // the APG's menu button: the trigger says it opens a menu, and whether it is open\n aria-haspopup=\"menu\"\n aria-expanded={open}\n ref={triggerRef}\n onClick={() => {\n setOpen(!open);\n if (!open && enabledIndices.length) setFocusedIdx(enabledIndices[0]);\n }}\n >\n {trigger}\n </Slot>\n {open && createPortal(\n <div ref={panelsRef} className=\"contents\">\n <div\n ref={menuRef}\n role=\"menu\"\n data-gs=\"menu\"\n aria-label={isPhone && subEntry ? subEntry.label : undefined}\n onKeyDown={handleMenuKeyDown}\n className={cn(PANEL, className)}\n style={{ top: pos.top, left: pos.left }}\n // A choice is the menu's own: the click must not also reach a clickable row or card the menu sits in\n // (React carries events from a portal to its React parents).\n onClick={(e) => e.stopPropagation()}\n >\n {isPhone && subEntry ? (\n // on a phone the submenu takes the menu's place: Back first, its entry's name on it\n <>\n <button type=\"button\" data-sub-back=\"\" role=\"menuitem\" aria-label={`Back from ${subEntry.label}`} tabIndex={-1} onClick={closeSub} className={cn(itemLook(subEntry), \"text-[var(--ds-text-secondary)] font-[var(--ds-weight-heading)]\")}>\n <Icon name=\"chevron-left\" size={16} className=\"shrink-0\" />\n <span>{subEntry.label}</span>\n </button>\n <Divider />\n {subItems(subEntry.items)}\n </>\n ) : items.map((item, i) => {\n if (isDivider(item)) return <Divider key={i} />;\n if (isSubmenu(item)) {\n const isOpen = sub === i;\n return (\n <button type=\"button\"\n key={i}\n ref={isOpen ? (el) => { subItemRef.current = el; } : undefined}\n data-index={i}\n role=\"menuitem\"\n aria-haspopup=\"menu\"\n aria-expanded={isOpen}\n disabled={isUnavailable(item)}\n tabIndex={focusedIdx === i ? 0 : -1}\n // the arrows go on from wherever focus is, however it got there (a click, a screen reader's command)\n onFocus={() => setFocusedIdx(i)}\n // Enter (its click) or a press goes into the submenu, its first item focused, whether a hover had\n // opened it or not (the APG's menu: Enter opens it and focuses its first item); ←, Escape or Back\n // closes it\n onClick={() => openSub(i, true)}\n // a hover opens it at once where no other is open; with another open it waits, as the pointer may be\n // on its way to that one (SUBMENU_GRACE_MS); back on its own entry, the wait for a close is dropped\n onMouseEnter={(e) => {\n if (isPhone || e.buttons !== 0) return;\n if (isOpen) cancelGrace();\n else if (sub === null) openSub(i, false);\n else afterGrace(() => openSub(i, false));\n }}\n className={itemLook(item, isOpen)}\n >\n {item.icon && <Icon name={item.icon} size={16} className=\"shrink-0\" />}\n <span className=\"flex-1\">{item.label}</span>\n <Icon name=\"chevron-right\" size={14} className=\"shrink-0 text-[var(--ds-text-muted)]\" />\n </button>\n );\n }\n return (\n <button type=\"button\"\n key={i}\n data-index={i}\n role=\"menuitem\"\n disabled={item.disabled}\n tabIndex={focusedIdx === i ? 0 : -1}\n onFocus={() => setFocusedIdx(i)}\n onClick={() => choose(item)}\n // a hover on another entry closes an open submenu, as moving on from it does, after a moment: the\n // pointer may only be crossing it on its way to the submenu (SUBMENU_GRACE_MS)\n onMouseEnter={() => { if (!isPhone && sub !== null) afterGrace(() => { setSub(null); setSubFocusedIdx(-1); }); }}\n className={itemLook(item)}\n >\n {item.icon && <Icon name={item.icon} size={16} className=\"shrink-0\" />}\n <span>{item.label}</span>\n </button>\n );\n })}\n </div>\n {!isPhone && subEntry && (\n <div\n ref={subRef}\n role=\"menu\"\n aria-label={subEntry.label}\n onKeyDown={handleMenuKeyDown}\n className={PANEL}\n style={{ top: subPos.top, left: subPos.left }}\n onClick={(e) => e.stopPropagation()}\n // the pointer has reached the submenu: a hover it crossed on the way closes nothing\n onMouseEnter={cancelGrace}\n >\n {subItems(subEntry.items)}\n </div>\n )}\n </div>,\n container ?? document.body\n )}\n </>\n );\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useFieldControl, Dropdown } from './chunk-
|
|
2
|
+
import { useFieldControl, Dropdown } from './chunk-IZW5XHKA.js';
|
|
3
3
|
import { useReportedChange } from './chunk-72SDAX54.js';
|
|
4
|
-
import { ControlSizeContext } from './chunk-
|
|
5
|
-
import { cn, rovingTabStop, FOCUS_RING } from './chunk-
|
|
4
|
+
import { ControlSizeContext } from './chunk-HQFTP7Y5.js';
|
|
5
|
+
import { cn, rovingTabStop, FOCUS_RING } from './chunk-EZ3ZGFGA.js';
|
|
6
6
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
7
7
|
import { useContext, useRef, useState, useCallback, useLayoutEffect, useEffect } from 'react';
|
|
8
8
|
|
|
@@ -281,5 +281,5 @@ function SegmentedControl({
|
|
|
281
281
|
var TITLE_TEXT = "font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] break-words";
|
|
282
282
|
|
|
283
283
|
export { SegmentedControl, Skeleton, TITLE_TEXT };
|
|
284
|
-
//# sourceMappingURL=chunk-
|
|
285
|
-
//# sourceMappingURL=chunk-
|
|
284
|
+
//# sourceMappingURL=chunk-BHTAVJIG.js.map
|
|
285
|
+
//# sourceMappingURL=chunk-BHTAVJIG.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../showcase/src/components/Skeleton.tsx","../../../showcase/src/components/SegmentedControl.tsx","../../../showcase/src/lib/title-text.ts"],"names":["jsx","jsxs"],"mappings":"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","file":"chunk-U7WBI4AV.js","sourcesContent":["import { cn } from \"../lib/utils\";\n\ninterface SkeletonProps {\n variant?: \"text\" | \"avatar\" | \"card\" | \"table-row\" | \"custom\";\n lines?: number;\n width?: string;\n height?: string;\n className?: string;\n}\n\nfunction Bone({ className, style, \"data-gs\": marker }: { className?: string; style?: React.CSSProperties; \"data-gs\"?: string }) {\n return (\n <div\n data-gs={marker}\n className={cn(\"rounded-[var(--ds-radius-sm)] bg-[var(--ds-bg-emphasis)] animate-pulse\", className)}\n style={style}\n />\n );\n}\n\nexport function Skeleton({ variant = \"text\", lines = 3, width, height, className }: SkeletonProps) {\n if (variant === \"custom\") {\n return <Bone data-gs=\"skeleton\" className={className} style={{ width, height }} />;\n }\n\n if (variant === \"avatar\") {\n return <Bone data-gs=\"skeleton\" className={cn(\"h-10 w-10 rounded-[var(--ds-radius-full)]\", className)} />;\n }\n\n if (variant === \"card\") {\n return (\n <div data-gs=\"skeleton\" className={cn(\"space-y-ds-3\", className)}>\n <Bone className=\"h-32 w-full rounded-[var(--ds-radius-md)]\" />\n <Bone className=\"h-4 w-3/4\" />\n <Bone className=\"h-4 w-1/2\" />\n </div>\n );\n }\n\n if (variant === \"table-row\") {\n return (\n <div data-gs=\"skeleton\" className={cn(\"flex gap-ds-4\", className)}>\n <Bone className=\"h-4 flex-[3]\" />\n <Bone className=\"h-4 flex-[2]\" />\n <Bone className=\"h-4 flex-1\" />\n <Bone className=\"h-4 flex-1\" />\n </div>\n );\n }\n\n // text variant\n const widths = [\"w-3/4\", \"w-1/2\", \"w-5/6\", \"w-2/3\", \"w-4/5\"];\n return (\n <div data-gs=\"skeleton\" className={cn(\"space-y-ds-3\", className)}>\n {Array.from({ length: lines }, (_, i) => (\n <Bone key={i} className={cn(\"h-4\", widths[i % widths.length])} />\n ))}\n </div>\n );\n}\n","import { useRef, useState, useLayoutEffect, useEffect, useCallback, useContext } from \"react\";\nimport { cn, FOCUS_RING } from \"../lib/utils\";\nimport { rovingTabStop } from \"../lib/focus\";\nimport { Dropdown } from \"./Dropdown\";\nimport { ControlSizeContext } from \"./Button\";\nimport { useFieldControl, type FieldControlWiring } from \"./Field\";\nimport { useReportedChange } from \"../lib/form-context\";\n\ninterface SegmentedControlOption {\n value: string;\n label: string;\n disabled?: boolean;\n}\n\ntype SegmentedControlSize = \"xs\" | \"sm\" | \"md\" | \"lg\";\n\ninterface SegmentedControlProps extends FieldControlWiring {\n value: string;\n onChange: (value: string) => void;\n options: SegmentedControlOption[];\n /** xs is for dense tool chrome (toolbars, inspectors): 24px tall. */\n size?: SegmentedControlSize;\n variant?: \"default\" | \"ghost\";\n /** Stretch to the container and share the width equally between options. */\n fullWidth?: boolean;\n /**\n * Called with an option's value while the pointer is over it and with null\n * when it leaves, so a picker can preview a choice before committing it.\n */\n onHoverChange?: (value: string | null) => void;\n className?: string;\n /**\n * Accessible name for the radiogroup, for when there is no visible label (e.g. in a toolbar). A control with a visible\n * label sits in a Field instead, which names it and stacks the label above: `<Field label=\"Status\"><SegmentedControl …/></Field>`.\n */\n \"aria-label\"?: string;\n /**\n * Above this many options the control renders a Dropdown with the same value, onChange and options — a row of\n * eleven segments runs off the page (bench round 3). Default 6; raise it only where the row is known to fit. A row\n * that does not fit its container (a four-option range on a phone) renders the Dropdown too, and comes back as a\n * row when the container widens (round 10).\n */\n maxOptions?: number;\n}\n\n/** The options as one string, so the fit re-measures when the labels change. */\nfunction optionsKeyForFit(options: SegmentedControlOption[]): string { return options.map((o) => `${o.value}:${o.label}`).join(\"|\"); }\n\n// Option padding is part of the size (tailwind-merge cannot resolve two ds-\n// spacing classes against each other, so it is not appended as an override).\nconst sizes: Record<SegmentedControlSize, string> = {\n xs: \"text-xs px-ds-2\",\n sm: \"text-xs px-ds-3\",\n md: \"text-sm px-ds-3\",\n lg: \"text-base px-ds-3\",\n};\n\n// xs uses a 2px inset so the whole control stays 24px tall.\nconst containerPadding: Record<SegmentedControlSize, string> = {\n xs: \"p-[2px] gap-[2px]\",\n sm: \"p-ds-1 gap-ds-0.5\",\n md: \"p-ds-1 gap-ds-0.5\",\n lg: \"p-ds-1 gap-ds-0.5\",\n};\n\nconst indicatorInset: Record<SegmentedControlSize, string> = {\n xs: \"2px\",\n sm: \"4px\",\n md: \"4px\",\n lg: \"4px\",\n};\n\n/**\n * The segments' corner: the frame's (a field's, --ds-radius-md) less the gap between the two edges, the selected\n * segment's inset and the frame's border when it draws one, so the curves sit evenly inside each other; never sharper\n * than the theme's small radius, so on a theme of small corners the segments stay slightly rounded, not square (a\n * square theme stays square).\n */\nfunction segmentRadius(inset: string, bordered: boolean): string {\n return `max(var(--ds-radius-sm), calc(var(--ds-radius-md) - ${inset}${bordered ? \" - var(--ds-border-width)\" : \"\"}))`;\n}\n\nexport function SegmentedControl({\n value,\n onChange: onChangeProp,\n options,\n size: sizeProp,\n variant = \"default\",\n fullWidth = false,\n onHoverChange,\n className,\n \"aria-label\": ariaLabel,\n maxOptions = 6,\n ...wiring\n}: SegmentedControlProps) {\n // Sprint 25.12 A15: a new value the user gives this control marks the form's overlay; the page's onChange runs as given\n const onChange = useReportedChange(onChangeProp, value);\n const contextSize = useContext(ControlSizeContext);\n // Inside a Field it takes the Field's label (round 27, R27-10): a radio group is named by aria-labelledby, since\n // <label for> cannot name it, and the Field stacks its label above. It takes the Field's id, hint, error and\n // required state too (round 27 review: without them the summary had no link and focus skipped it); a radio group\n // carries aria-invalid and aria-required itself. As a Dropdown, the Dropdown reads the same Field. The name follows the\n // one rule (useFieldControl; the final review of Sprint 25.31, 2): the Field's label on screen leads it, where a\n // page's aria-label won before (\"Calendar view\" over \"View\"), and a page's name beside it is left out, with a warning\n const control = useFieldControl({ ...wiring, \"aria-label\": ariaLabel }, { component: \"SegmentedControl\" });\n // as a Dropdown, only the names the rule left: the page gave them to this control, not to a Dropdown, which would warn\n const { \"aria-labelledby\": _pageLabelledBy, ...dropdownWiring } = wiring;\n const size: SegmentedControlSize = sizeProp ?? (contextSize as SegmentedControlSize | null) ?? \"md\";\n const containerRef = useRef<HTMLDivElement>(null);\n const [indicator, setIndicator] = useState({ left: 0, width: 0 });\n\n // Round 10 (the owner's review of the analytics overview on a phone): a row that does not fit its container renders\n // the Dropdown too, not only a row of too many options. The row's natural width is remembered from the last time\n // it was laid out; the container (the filters row, the toolbar) is observed in both states, so the row comes back\n // when the container widens. 8 px of hysteresis keeps a row sitting on the edge from flapping.\n const parentRef = useRef<HTMLElement | null>(null);\n const neededRef = useRef(0);\n const [overflows, setOverflows] = useState(false);\n const measureFit = useCallback(() => {\n const row = containerRef.current;\n if (row) {\n parentRef.current = row.parentElement;\n if (row.scrollWidth > 0) neededRef.current = row.scrollWidth;\n }\n const parent = parentRef.current;\n const needed = neededRef.current;\n if (!parent || !needed) return;\n const style = getComputedStyle(parent);\n const available = parent.clientWidth - (parseFloat(style.paddingLeft) || 0) - (parseFloat(style.paddingRight) || 0);\n if (available <= 0) return;\n setOverflows((prev) => (prev ? needed > available - 8 : needed > available));\n }, []);\n useLayoutEffect(() => {\n measureFit();\n const parent = parentRef.current;\n if (!parent || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measureFit);\n observer.observe(parent);\n return () => observer.disconnect();\n }, [measureFit, optionsKeyForFit(options)]); // eslint-disable-line react-hooks/exhaustive-deps\n const collapsed = options.length > maxOptions || overflows;\n\n const updateIndicator = useCallback(() => {\n if (!containerRef.current) return;\n const active = containerRef.current.querySelector<HTMLElement>(\"[data-active='true']\");\n if (active) {\n setIndicator({\n left: active.offsetLeft,\n width: active.offsetWidth,\n });\n }\n }, []);\n\n // The option set can change under the control (a configurator hiding an\n // option), which moves the active segment without changing `value`: re-measure\n // on that too, and watch every segment, not just the container, so a label\n // that changes width also moves the thumb.\n const optionsKey = options.map((o) => `${o.value}:${o.label}`).join(\"|\");\n useLayoutEffect(() => {\n updateIndicator();\n }, [value, optionsKey, updateIndicator]);\n\n // The web font can land after the first measurement and widen the segments (round 5: the thumb was caught\n // mid-slide on every load). Re-measure when the fonts are ready, and animate only after the user's first change:\n // the initial position and the font re-measure snap into place.\n useEffect(() => {\n const fonts = (document as Document & { fonts?: { ready?: Promise<unknown> } }).fonts;\n if (!fonts?.ready) return;\n let live = true;\n fonts.ready.then(() => { if (live) updateIndicator(); });\n return () => { live = false; };\n }, [updateIndicator]);\n const [animate, setAnimate] = useState(false);\n const select = useCallback((v: string) => { setAnimate(true); onChange(v); }, [onChange]);\n\n useLayoutEffect(() => {\n if (collapsed || !containerRef.current || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(updateIndicator);\n observer.observe(containerRef.current);\n containerRef.current.querySelectorAll<HTMLElement>(\"button\").forEach((b) => observer.observe(b));\n return () => observer.disconnect();\n }, [updateIndicator, optionsKey, collapsed]);\n\n const containerHeight: Record<SegmentedControlSize, string> = {\n xs: \"h-ds-6\",\n sm: \"h-[var(--ds-control-height-sm)]\",\n md: \"h-[var(--ds-control-height-md)]\",\n lg: \"h-[var(--ds-control-height-lg)]\",\n };\n\n // Radio-group keyboard pattern: one tab stop (the checked option, or the\n // first enabled one when nothing is checked), arrows move selection between\n // enabled options, Home/End jump to the ends.\n const tabStop = rovingTabStop(options, (o) => o.value === value, (o) => !!o.disabled);\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n const enabled = options.filter((o) => !o.disabled);\n if (enabled.length === 0) return;\n const idx = Math.max(0, enabled.findIndex((o) => o.value === value));\n let next: number | null = null;\n if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\") next = (idx + 1) % enabled.length;\n else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\") next = (idx - 1 + enabled.length) % enabled.length;\n else if (e.key === \"Home\") next = 0;\n else if (e.key === \"End\") next = enabled.length - 1;\n if (next === null) return;\n e.preventDefault();\n select(enabled[next].value);\n const buttons = containerRef.current?.querySelectorAll<HTMLButtonElement>('[role=\"radio\"]');\n buttons?.forEach((b) => { if (b.dataset.value === enabled[next!].value) b.focus(); });\n };\n\n if (collapsed) {\n return (\n <Dropdown\n {...dropdownWiring}\n aria-label={control.shown ? undefined : control.label}\n aria-labelledby={control.shown ? undefined : wiring[\"aria-labelledby\"]}\n size={size === \"md\" || size === \"lg\" ? \"md\" : \"sm\"}\n options={options.filter((o) => !o.disabled).map((o) => ({ id: o.value, label: o.label }))}\n value={value}\n onChange={(v) => onChange(v as string)}\n className={cn(\"inline-block\", fullWidth ? \"w-full\" : \"w-auto\", className)}\n />\n );\n }\n\n return (\n <div\n ref={containerRef}\n role=\"radiogroup\"\n id={control.id}\n aria-label={control.labelId ? undefined : control.label}\n aria-labelledby={control.labelId}\n aria-describedby={control.describedBy}\n aria-invalid={control.invalid || undefined}\n aria-required={control.required || undefined}\n onKeyDown={handleKeyDown}\n className={cn(\n \"relative items-center\",\n // Sprint 25.14 A21: in a Field's column a track that isn't full width keeps its own width; w-fit, not self-start,\n // so a row or a centred column still aligns it (the review of 30 September)\n fullWidth ? \"flex w-full\" : \"inline-flex w-fit\",\n containerPadding[size],\n containerHeight[size],\n \"bg-[var(--ds-bg-muted)]\",\n // The owner's review of 1 October 2026: the frame's corner is a field's, so the two change together with the\n // theme's radius; the segments inside sit concentric with it (SEGMENT_RADIUS)\n \"rounded-[var(--ds-radius-md)]\",\n // The frame's edge is a button's: each segment is a labelled button, so the frame isn't what identifies the\n // control, and WCAG 1.4.11 asks no 3:1 of it\n variant === \"default\" && \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n control.invalid && \"border-[length:var(--ds-border-width)] border-[var(--ds-status-error)]\",\n className,\n )}\n style={{ \"--ds-segment-radius\": segmentRadius(indicatorInset[size], variant === \"default\" || !!control.invalid) } as React.CSSProperties}\n >\n <div\n className={cn(\n \"absolute rounded-[var(--ds-segment-radius)]\",\n \"bg-[var(--ds-bg-surface)]\",\n // The selected segment is a kind of button, so it takes a secondary button's surface and edge, as the frame\n // takes a button's edge (the owner, 3 October 2026: the 3:1 strong edge it kept from Sprint 25.14 drew the\n // darker outline 25.19 took off the fields). It shows its state as the raised surface on the muted track, with\n // the default variant's shadow and its label's stronger colour. While its segment has keyboard focus the shadow\n // steps aside, as a button's does, so the segment's outline sits clear (Sprint 30, D7)\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n variant === \"default\" && \"shadow-[var(--ds-shadow-sm)]\",\n \"[[role=radiogroup]:has([aria-checked=true]:focus-visible)_&]:shadow-none\",\n \"transition-all motion-reduce:transition-none\",\n )}\n data-animate={animate ? \"true\" : \"false\"}\n style={{\n left: indicator.left,\n width: indicator.width,\n top: indicatorInset[size],\n bottom: indicatorInset[size],\n transitionDuration: animate ? \"var(--ds-transition-speed)\" : \"0ms\",\n transitionTimingFunction: \"var(--ds-transition-ease)\",\n }}\n />\n {options.map((opt, i) => (\n <button\n key={opt.value}\n type=\"button\"\n role=\"radio\"\n aria-checked={opt.value === value}\n data-active={opt.value === value}\n data-value={opt.value}\n tabIndex={i === tabStop ? 0 : -1}\n disabled={opt.disabled}\n onClick={() => !opt.disabled && select(opt.value)}\n onMouseEnter={onHoverChange && !opt.disabled ? () => onHoverChange(opt.value) : undefined}\n onMouseLeave={onHoverChange ? () => onHoverChange(null) : undefined}\n className={cn(\n \"relative z-10 inline-flex items-center justify-center self-stretch font-medium whitespace-nowrap\",\n fullWidth && \"flex-1\",\n \"rounded-[var(--ds-segment-radius)]\",\n \"transition-colors cursor-pointer\",\n FOCUS_RING,\n sizes[size],\n opt.value === value\n ? \"text-[var(--ds-text-primary)]\"\n : \"text-[var(--ds-text-muted)] hover:text-[var(--ds-text-secondary)]\",\n opt.disabled && \"opacity-disabled cursor-not-allowed\",\n )}\n >\n {opt.label}\n </button>\n ))}\n </div>\n );\n}\n","/**\n * A title's text classes: CardTitle's, which a titled Table's band and a ChartContainer's title share (Sprint 25.9, A1),\n * one definition so they cannot drift. A word or a name longer than its line breaks at the line's edge (round 27,\n * R27-16: run 70's \"primary-logo-navy.svg\" ran out of its card); break-word, not anywhere, so a title's words still\n * set its minimum and the actions beside it wrap under it before a word is broken (HEADER_LAYOUT.floor).\n */\nexport const TITLE_TEXT = \"font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] break-words\";\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../showcase/src/components/Skeleton.tsx","../../../showcase/src/components/SegmentedControl.tsx","../../../showcase/src/lib/title-text.ts"],"names":["jsx","jsxs"],"mappings":"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","file":"chunk-BHTAVJIG.js","sourcesContent":["import { cn } from \"../lib/utils\";\n\ninterface SkeletonProps {\n variant?: \"text\" | \"avatar\" | \"card\" | \"table-row\" | \"custom\";\n lines?: number;\n width?: string;\n height?: string;\n className?: string;\n}\n\nfunction Bone({ className, style, \"data-gs\": marker }: { className?: string; style?: React.CSSProperties; \"data-gs\"?: string }) {\n return (\n <div\n data-gs={marker}\n className={cn(\"rounded-[var(--ds-radius-sm)] bg-[var(--ds-bg-emphasis)] animate-pulse\", className)}\n style={style}\n />\n );\n}\n\nexport function Skeleton({ variant = \"text\", lines = 3, width, height, className }: SkeletonProps) {\n if (variant === \"custom\") {\n return <Bone data-gs=\"skeleton\" className={className} style={{ width, height }} />;\n }\n\n if (variant === \"avatar\") {\n return <Bone data-gs=\"skeleton\" className={cn(\"h-10 w-10 rounded-[var(--ds-radius-full)]\", className)} />;\n }\n\n if (variant === \"card\") {\n return (\n <div data-gs=\"skeleton\" className={cn(\"space-y-ds-3\", className)}>\n <Bone className=\"h-32 w-full rounded-[var(--ds-radius-md)]\" />\n <Bone className=\"h-4 w-3/4\" />\n <Bone className=\"h-4 w-1/2\" />\n </div>\n );\n }\n\n if (variant === \"table-row\") {\n return (\n <div data-gs=\"skeleton\" className={cn(\"flex gap-ds-4\", className)}>\n <Bone className=\"h-4 flex-[3]\" />\n <Bone className=\"h-4 flex-[2]\" />\n <Bone className=\"h-4 flex-1\" />\n <Bone className=\"h-4 flex-1\" />\n </div>\n );\n }\n\n // text variant\n const widths = [\"w-3/4\", \"w-1/2\", \"w-5/6\", \"w-2/3\", \"w-4/5\"];\n return (\n <div data-gs=\"skeleton\" className={cn(\"space-y-ds-3\", className)}>\n {Array.from({ length: lines }, (_, i) => (\n <Bone key={i} className={cn(\"h-4\", widths[i % widths.length])} />\n ))}\n </div>\n );\n}\n","import { useRef, useState, useLayoutEffect, useEffect, useCallback, useContext } from \"react\";\nimport { cn, FOCUS_RING } from \"../lib/utils\";\nimport { rovingTabStop } from \"../lib/focus\";\nimport { Dropdown } from \"./Dropdown\";\nimport { ControlSizeContext } from \"./Button\";\nimport { useFieldControl, type FieldControlWiring } from \"./Field\";\nimport { useReportedChange } from \"../lib/form-context\";\n\ninterface SegmentedControlOption {\n value: string;\n label: string;\n disabled?: boolean;\n}\n\ntype SegmentedControlSize = \"xs\" | \"sm\" | \"md\" | \"lg\";\n\ninterface SegmentedControlProps extends FieldControlWiring {\n value: string;\n onChange: (value: string) => void;\n options: SegmentedControlOption[];\n /** xs is for dense tool chrome (toolbars, inspectors): 24px tall. */\n size?: SegmentedControlSize;\n variant?: \"default\" | \"ghost\";\n /** Stretch to the container and share the width equally between options. */\n fullWidth?: boolean;\n /**\n * Called with an option's value while the pointer is over it and with null\n * when it leaves, so a picker can preview a choice before committing it.\n */\n onHoverChange?: (value: string | null) => void;\n className?: string;\n /**\n * Accessible name for the radiogroup, for when there is no visible label (e.g. in a toolbar). A control with a visible\n * label sits in a Field instead, which names it and stacks the label above: `<Field label=\"Status\"><SegmentedControl …/></Field>`.\n */\n \"aria-label\"?: string;\n /**\n * Above this many options the control renders a Dropdown with the same value, onChange and options — a row of\n * eleven segments runs off the page (bench round 3). Default 6; raise it only where the row is known to fit. A row\n * that does not fit its container (a four-option range on a phone) renders the Dropdown too, and comes back as a\n * row when the container widens (round 10).\n */\n maxOptions?: number;\n}\n\n/** The options as one string, so the fit re-measures when the labels change. */\nfunction optionsKeyForFit(options: SegmentedControlOption[]): string { return options.map((o) => `${o.value}:${o.label}`).join(\"|\"); }\n\n// Option padding is part of the size (tailwind-merge cannot resolve two ds-\n// spacing classes against each other, so it is not appended as an override).\nconst sizes: Record<SegmentedControlSize, string> = {\n xs: \"text-xs px-ds-2\",\n sm: \"text-xs px-ds-3\",\n md: \"text-sm px-ds-3\",\n lg: \"text-base px-ds-3\",\n};\n\n// xs uses a 2px inset so the whole control stays 24px tall.\nconst containerPadding: Record<SegmentedControlSize, string> = {\n xs: \"p-[2px] gap-[2px]\",\n sm: \"p-ds-1 gap-ds-0.5\",\n md: \"p-ds-1 gap-ds-0.5\",\n lg: \"p-ds-1 gap-ds-0.5\",\n};\n\nconst indicatorInset: Record<SegmentedControlSize, string> = {\n xs: \"2px\",\n sm: \"4px\",\n md: \"4px\",\n lg: \"4px\",\n};\n\n/**\n * The segments' corner: the frame's (a field's, --ds-radius-md) less the gap between the two edges, the selected\n * segment's inset and the frame's border when it draws one, so the curves sit evenly inside each other; never sharper\n * than the theme's small radius, so on a theme of small corners the segments stay slightly rounded, not square (a\n * square theme stays square).\n */\nfunction segmentRadius(inset: string, bordered: boolean): string {\n return `max(var(--ds-radius-sm), calc(var(--ds-radius-md) - ${inset}${bordered ? \" - var(--ds-border-width)\" : \"\"}))`;\n}\n\nexport function SegmentedControl({\n value,\n onChange: onChangeProp,\n options,\n size: sizeProp,\n variant = \"default\",\n fullWidth = false,\n onHoverChange,\n className,\n \"aria-label\": ariaLabel,\n maxOptions = 6,\n ...wiring\n}: SegmentedControlProps) {\n // Sprint 25.12 A15: a new value the user gives this control marks the form's overlay; the page's onChange runs as given\n const onChange = useReportedChange(onChangeProp, value);\n const contextSize = useContext(ControlSizeContext);\n // Inside a Field it takes the Field's label (round 27, R27-10): a radio group is named by aria-labelledby, since\n // <label for> cannot name it, and the Field stacks its label above. It takes the Field's id, hint, error and\n // required state too (round 27 review: without them the summary had no link and focus skipped it); a radio group\n // carries aria-invalid and aria-required itself. As a Dropdown, the Dropdown reads the same Field. The name follows the\n // one rule (useFieldControl; the final review of Sprint 25.31, 2): the Field's label on screen leads it, where a\n // page's aria-label won before (\"Calendar view\" over \"View\"), and a page's name beside it is left out, with a warning\n const control = useFieldControl({ ...wiring, \"aria-label\": ariaLabel }, { component: \"SegmentedControl\" });\n // as a Dropdown, only the names the rule left: the page gave them to this control, not to a Dropdown, which would warn\n const { \"aria-labelledby\": _pageLabelledBy, ...dropdownWiring } = wiring;\n const size: SegmentedControlSize = sizeProp ?? (contextSize as SegmentedControlSize | null) ?? \"md\";\n const containerRef = useRef<HTMLDivElement>(null);\n const [indicator, setIndicator] = useState({ left: 0, width: 0 });\n\n // Round 10 (the owner's review of the analytics overview on a phone): a row that does not fit its container renders\n // the Dropdown too, not only a row of too many options. The row's natural width is remembered from the last time\n // it was laid out; the container (the filters row, the toolbar) is observed in both states, so the row comes back\n // when the container widens. 8 px of hysteresis keeps a row sitting on the edge from flapping.\n const parentRef = useRef<HTMLElement | null>(null);\n const neededRef = useRef(0);\n const [overflows, setOverflows] = useState(false);\n const measureFit = useCallback(() => {\n const row = containerRef.current;\n if (row) {\n parentRef.current = row.parentElement;\n if (row.scrollWidth > 0) neededRef.current = row.scrollWidth;\n }\n const parent = parentRef.current;\n const needed = neededRef.current;\n if (!parent || !needed) return;\n const style = getComputedStyle(parent);\n const available = parent.clientWidth - (parseFloat(style.paddingLeft) || 0) - (parseFloat(style.paddingRight) || 0);\n if (available <= 0) return;\n setOverflows((prev) => (prev ? needed > available - 8 : needed > available));\n }, []);\n useLayoutEffect(() => {\n measureFit();\n const parent = parentRef.current;\n if (!parent || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measureFit);\n observer.observe(parent);\n return () => observer.disconnect();\n }, [measureFit, optionsKeyForFit(options)]); // eslint-disable-line react-hooks/exhaustive-deps\n const collapsed = options.length > maxOptions || overflows;\n\n const updateIndicator = useCallback(() => {\n if (!containerRef.current) return;\n const active = containerRef.current.querySelector<HTMLElement>(\"[data-active='true']\");\n if (active) {\n setIndicator({\n left: active.offsetLeft,\n width: active.offsetWidth,\n });\n }\n }, []);\n\n // The option set can change under the control (a configurator hiding an\n // option), which moves the active segment without changing `value`: re-measure\n // on that too, and watch every segment, not just the container, so a label\n // that changes width also moves the thumb.\n const optionsKey = options.map((o) => `${o.value}:${o.label}`).join(\"|\");\n useLayoutEffect(() => {\n updateIndicator();\n }, [value, optionsKey, updateIndicator]);\n\n // The web font can land after the first measurement and widen the segments (round 5: the thumb was caught\n // mid-slide on every load). Re-measure when the fonts are ready, and animate only after the user's first change:\n // the initial position and the font re-measure snap into place.\n useEffect(() => {\n const fonts = (document as Document & { fonts?: { ready?: Promise<unknown> } }).fonts;\n if (!fonts?.ready) return;\n let live = true;\n fonts.ready.then(() => { if (live) updateIndicator(); });\n return () => { live = false; };\n }, [updateIndicator]);\n const [animate, setAnimate] = useState(false);\n const select = useCallback((v: string) => { setAnimate(true); onChange(v); }, [onChange]);\n\n useLayoutEffect(() => {\n if (collapsed || !containerRef.current || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(updateIndicator);\n observer.observe(containerRef.current);\n containerRef.current.querySelectorAll<HTMLElement>(\"button\").forEach((b) => observer.observe(b));\n return () => observer.disconnect();\n }, [updateIndicator, optionsKey, collapsed]);\n\n const containerHeight: Record<SegmentedControlSize, string> = {\n xs: \"h-ds-6\",\n sm: \"h-[var(--ds-control-height-sm)]\",\n md: \"h-[var(--ds-control-height-md)]\",\n lg: \"h-[var(--ds-control-height-lg)]\",\n };\n\n // Radio-group keyboard pattern: one tab stop (the checked option, or the\n // first enabled one when nothing is checked), arrows move selection between\n // enabled options, Home/End jump to the ends.\n const tabStop = rovingTabStop(options, (o) => o.value === value, (o) => !!o.disabled);\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n const enabled = options.filter((o) => !o.disabled);\n if (enabled.length === 0) return;\n const idx = Math.max(0, enabled.findIndex((o) => o.value === value));\n let next: number | null = null;\n if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\") next = (idx + 1) % enabled.length;\n else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\") next = (idx - 1 + enabled.length) % enabled.length;\n else if (e.key === \"Home\") next = 0;\n else if (e.key === \"End\") next = enabled.length - 1;\n if (next === null) return;\n e.preventDefault();\n select(enabled[next].value);\n const buttons = containerRef.current?.querySelectorAll<HTMLButtonElement>('[role=\"radio\"]');\n buttons?.forEach((b) => { if (b.dataset.value === enabled[next!].value) b.focus(); });\n };\n\n if (collapsed) {\n return (\n <Dropdown\n {...dropdownWiring}\n aria-label={control.shown ? undefined : control.label}\n aria-labelledby={control.shown ? undefined : wiring[\"aria-labelledby\"]}\n size={size === \"md\" || size === \"lg\" ? \"md\" : \"sm\"}\n options={options.filter((o) => !o.disabled).map((o) => ({ id: o.value, label: o.label }))}\n value={value}\n onChange={(v) => onChange(v as string)}\n className={cn(\"inline-block\", fullWidth ? \"w-full\" : \"w-auto\", className)}\n />\n );\n }\n\n return (\n <div\n ref={containerRef}\n role=\"radiogroup\"\n id={control.id}\n aria-label={control.labelId ? undefined : control.label}\n aria-labelledby={control.labelId}\n aria-describedby={control.describedBy}\n aria-invalid={control.invalid || undefined}\n aria-required={control.required || undefined}\n onKeyDown={handleKeyDown}\n className={cn(\n \"relative items-center\",\n // Sprint 25.14 A21: in a Field's column a track that isn't full width keeps its own width; w-fit, not self-start,\n // so a row or a centred column still aligns it (the review of 30 September)\n fullWidth ? \"flex w-full\" : \"inline-flex w-fit\",\n containerPadding[size],\n containerHeight[size],\n \"bg-[var(--ds-bg-muted)]\",\n // The owner's review of 1 October 2026: the frame's corner is a field's, so the two change together with the\n // theme's radius; the segments inside sit concentric with it (SEGMENT_RADIUS)\n \"rounded-[var(--ds-radius-md)]\",\n // The frame's edge is a button's: each segment is a labelled button, so the frame isn't what identifies the\n // control, and WCAG 1.4.11 asks no 3:1 of it\n variant === \"default\" && \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n control.invalid && \"border-[length:var(--ds-border-width)] border-[var(--ds-status-error)]\",\n className,\n )}\n style={{ \"--ds-segment-radius\": segmentRadius(indicatorInset[size], variant === \"default\" || !!control.invalid) } as React.CSSProperties}\n >\n <div\n className={cn(\n \"absolute rounded-[var(--ds-segment-radius)]\",\n \"bg-[var(--ds-bg-surface)]\",\n // The selected segment is a kind of button, so it takes a secondary button's surface and edge, as the frame\n // takes a button's edge (the owner, 3 October 2026: the 3:1 strong edge it kept from Sprint 25.14 drew the\n // darker outline 25.19 took off the fields). It shows its state as the raised surface on the muted track, with\n // the default variant's shadow and its label's stronger colour. While its segment has keyboard focus the shadow\n // steps aside, as a button's does, so the segment's outline sits clear (Sprint 30, D7)\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n variant === \"default\" && \"shadow-[var(--ds-shadow-sm)]\",\n \"[[role=radiogroup]:has([aria-checked=true]:focus-visible)_&]:shadow-none\",\n \"transition-all motion-reduce:transition-none\",\n )}\n data-animate={animate ? \"true\" : \"false\"}\n style={{\n left: indicator.left,\n width: indicator.width,\n top: indicatorInset[size],\n bottom: indicatorInset[size],\n transitionDuration: animate ? \"var(--ds-transition-speed)\" : \"0ms\",\n transitionTimingFunction: \"var(--ds-transition-ease)\",\n }}\n />\n {options.map((opt, i) => (\n <button\n key={opt.value}\n type=\"button\"\n role=\"radio\"\n aria-checked={opt.value === value}\n data-active={opt.value === value}\n data-value={opt.value}\n tabIndex={i === tabStop ? 0 : -1}\n disabled={opt.disabled}\n onClick={() => !opt.disabled && select(opt.value)}\n onMouseEnter={onHoverChange && !opt.disabled ? () => onHoverChange(opt.value) : undefined}\n onMouseLeave={onHoverChange ? () => onHoverChange(null) : undefined}\n className={cn(\n \"relative z-10 inline-flex items-center justify-center self-stretch font-medium whitespace-nowrap\",\n fullWidth && \"flex-1\",\n \"rounded-[var(--ds-segment-radius)]\",\n \"transition-colors cursor-pointer\",\n FOCUS_RING,\n sizes[size],\n opt.value === value\n ? \"text-[var(--ds-text-primary)]\"\n : \"text-[var(--ds-text-muted)] hover:text-[var(--ds-text-secondary)]\",\n opt.disabled && \"opacity-disabled cursor-not-allowed\",\n )}\n >\n {opt.label}\n </button>\n ))}\n </div>\n );\n}\n","/**\n * A title's text classes: CardTitle's, which a titled Table's band and a ChartContainer's title share (Sprint 25.9, A1),\n * one definition so they cannot drift. A word or a name longer than its line breaks at the line's edge (round 27,\n * R27-16: run 70's \"primary-logo-navy.svg\" ran out of its card); break-word, not anywhere, so a title's words still\n * set its minimum and the actions beside it wrap under it before a word is broken (HEADER_LAYOUT.floor).\n */\nexport const TITLE_TEXT = \"font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] break-words\";\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { usePosition, useLatest, cn, GLASS_DROPDOWN } from './chunk-
|
|
3
|
-
import { version, createContext, isValidElement, cloneElement, useContext, useState, useRef, useId, useCallback, useEffect } from 'react';
|
|
2
|
+
import { usePosition, useLatest, cn, GLASS_DROPDOWN } from './chunk-EZ3ZGFGA.js';
|
|
3
|
+
import { version, createContext, isValidElement, cloneElement, useContext, useState, useRef, useId, useCallback, useEffect, useLayoutEffect } from 'react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
5
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
6
|
|
|
@@ -57,6 +57,7 @@ function AutoTooltip(props) {
|
|
|
57
57
|
function RevealTooltip(props) {
|
|
58
58
|
return /* @__PURE__ */ jsx(TooltipBase, { ...props, auto: true, reveal: true });
|
|
59
59
|
}
|
|
60
|
+
var shownTooltip = null;
|
|
60
61
|
function TooltipBase({ content, children, side = "top", delay = 600, className, container, disabled = false, auto = false, reveal = false, ref: forwardedRef, ...slotProps }) {
|
|
61
62
|
const around = useContext(TooltipCtx);
|
|
62
63
|
const [open, setOpen] = useState(false);
|
|
@@ -130,6 +131,18 @@ function TooltipBase({ content, children, side = "top", delay = 600, className,
|
|
|
130
131
|
};
|
|
131
132
|
}, [open, hide, reveal]);
|
|
132
133
|
useEffect(() => cancel, [cancel]);
|
|
134
|
+
useLayoutEffect(() => {
|
|
135
|
+
if (!open) return;
|
|
136
|
+
const mine = { close: () => {
|
|
137
|
+
cancel();
|
|
138
|
+
setOpen(false);
|
|
139
|
+
} };
|
|
140
|
+
if (shownTooltip && shownTooltip !== mine) shownTooltip.close();
|
|
141
|
+
shownTooltip = mine;
|
|
142
|
+
return () => {
|
|
143
|
+
if (shownTooltip === mine) shownTooltip = null;
|
|
144
|
+
};
|
|
145
|
+
}, [open, cancel]);
|
|
133
146
|
const nativeTitle = isValidElement(children) && (typeof children.type === "string" || NATIVE_TITLE in children.type);
|
|
134
147
|
const trigger = nativeTitle && children.props.title !== void 0 ? cloneElement(children, { title: void 0 }) : children;
|
|
135
148
|
return /* @__PURE__ */ jsxs(TooltipCtx, { value: covers, children: [
|
|
@@ -224,5 +237,5 @@ function useRevealFocus(target, enabled = true) {
|
|
|
224
237
|
}
|
|
225
238
|
|
|
226
239
|
export { AutoTooltip, NATIVE_TITLE, RevealTooltip, Slot, Tooltip, mergeRefs, useRevealFocus };
|
|
227
|
-
//# sourceMappingURL=chunk-
|
|
228
|
-
//# sourceMappingURL=chunk-
|
|
240
|
+
//# sourceMappingURL=chunk-DR6WEI3A.js.map
|
|
241
|
+
//# sourceMappingURL=chunk-DR6WEI3A.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../showcase/src/lib/slot.tsx","../../../showcase/src/components/Tooltip.tsx","../../../showcase/src/lib/reveal-focus.ts"],"names":["jsx","isValidElement","cloneElement","useEffect"],"mappings":"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","file":"chunk-DR6WEI3A.js","sourcesContent":["import {\n type ReactNode,\n type ReactElement,\n type HTMLAttributes,\n type Ref,\n isValidElement,\n cloneElement,\n createElement,\n Children,\n version,\n} from \"react\";\n\nconst REACT_18 = version.startsWith(\"18.\");\n\ntype SlotProps = HTMLAttributes<HTMLElement> & {\n children?: ReactNode;\n ref?: Ref<HTMLElement>;\n};\n\nexport function mergeRefs(...refs: (Ref<HTMLElement> | undefined)[]): Ref<HTMLElement> | undefined {\n const filtered = refs.filter(Boolean) as Ref<HTMLElement>[];\n if (filtered.length === 0) return undefined;\n if (filtered.length === 1) return filtered[0];\n return (node: HTMLElement | null) => {\n for (const ref of filtered) {\n if (typeof ref === \"function\") ref(node);\n else if (ref && typeof ref === \"object\") (ref as { current: HTMLElement | null }).current = node;\n }\n };\n}\n\n/**\n * Merges its own props onto its single React element child instead of\n * rendering a wrapper DOM node. Used by trigger-based components\n * (Tooltip, HoverCard, Popover, DropdownMenu) via the `asChild` prop\n * to let consumers control the rendered element.\n *\n * Follows the same contract as Radix UI's Slot / `asChild` pattern.\n */\nexport function Slot({ children, ref, ...slotProps }: SlotProps) {\n if (!isValidElement(children)) {\n console.warn(\"Slot: expected a single React element child.\");\n return null;\n }\n\n const childProps = children.props as Record<string, unknown>;\n const slotRecord = slotProps as Record<string, unknown>;\n\n const mergedProps: Record<string, unknown> = { ...slotProps };\n for (const key of Object.keys(childProps)) {\n if (key === \"style\") {\n mergedProps.style = { ...(slotProps.style ?? {}), ...(childProps.style as object) };\n } else if (key === \"className\") {\n mergedProps.className = [slotProps.className, childProps.className].filter(Boolean).join(\" \");\n } else if (key.startsWith(\"on\") && typeof slotRecord[key] === \"function\") {\n const slotHandler = slotRecord[key] as (...args: unknown[]) => void;\n const childHandler = childProps[key] as (...args: unknown[]) => void;\n mergedProps[key] = (...args: unknown[]) => {\n childHandler?.(...args);\n slotHandler?.(...args);\n };\n } else {\n mergedProps[key] = childProps[key] ?? slotRecord[key];\n }\n }\n\n if (ref) {\n // React 19 passes ref as a prop and warns on element.ref; React 18 keeps it on the element.\n const childRef = (childProps.ref as Ref<HTMLElement> | undefined)\n ?? (REACT_18 ? (children as ReactElement & { ref?: Ref<HTMLElement> }).ref : undefined);\n mergedProps.ref = mergeRefs(ref, childRef);\n }\n\n return cloneElement(children as ReactElement, mergedProps);\n}\n\n/**\n * Renders either a Slot (when `asChild` is true — merging props onto\n * the consumer's child element) or the given `fallback` tag (default\n * behaviour, rendering a wrapper element with the given props).\n */\nexport function Slottable({\n asChild,\n fallback: Tag = \"span\",\n children,\n ref,\n ...rest\n}: {\n asChild?: boolean;\n fallback?: keyof HTMLElementTagNameMap;\n children?: ReactNode;\n ref?: Ref<HTMLElement>;\n} & HTMLAttributes<HTMLElement>) {\n if (asChild) {\n const child = Children.only(children);\n return <Slot ref={ref} {...rest}>{child}</Slot>;\n }\n // `ref` is a forwarded prop being passed on, not a ref object being read.\n // eslint-disable-next-line react-hooks/refs -- forwarding, not dereferencing\n return createElement(Tag, { ref, ...rest } as Record<string, unknown>, children);\n}\n","import { useState, useRef, useCallback, useEffect, useLayoutEffect, useId, useContext, cloneElement, isValidElement, type HTMLAttributes, type ReactElement, type ReactNode, type Ref, createContext } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn, GLASS_DROPDOWN } from \"../lib/utils\";\nimport { Slot, mergeRefs } from \"../lib/slot\";\nimport { usePosition, type Side } from \"../lib/use-position\";\nimport { useLatest } from \"../lib/use-latest\";\n\ninterface TooltipProps extends Omit<HTMLAttributes<HTMLElement>, \"content\"> {\n content: ReactNode;\n children: ReactNode;\n side?: Side;\n delay?: number;\n className?: string;\n container?: Element | DocumentFragment;\n /**\n * Keep the Tooltip in the tree but never show it. Use this instead of rendering\n * the Tooltip only sometimes: adding or removing the wrapper remounts its child,\n * which drops focus from a field inside it (Sprint 25.7, the Combobox).\n */\n disabled?: boolean;\n /**\n * A Tooltip can itself be the child of a Slot — `<DropdownMenu trigger={<Tooltip …><Button/></Tooltip>}>` —\n * so the ref and handlers a parent merges onto it are passed through to its own trigger (14 Sep 2026).\n */\n ref?: Ref<HTMLElement>;\n}\n\n/**\n * Inside a tooltip's trigger: asks for the element that tooltip covers, or null when it can't show (disabled, in which\n * case the one around it answers). A tooltip a component draws on its own (an icon Button's from its aria-label, every\n * AutoTooltip) asks when it would show, and steps aside when the answer holds its own trigger: the outer one shows\n * whenever the pointer is in it, so both would show at once (round 15, 52.7, two tooltips on one button; every\n * component's own tooltip since the review of 21.6 session B). Asked then, not at render: a Tooltip around a component\n * that passes no props on never fires, and the component's own tooltip still shows.\n */\nexport const TooltipCtx = createContext<(() => HTMLElement | null) | null>(null);\n\n/** Marks a component whose `title` prop is the native attribute (the library's Button): a Tooltip drops it, as an element's. */\nexport const NATIVE_TITLE = Symbol(\"native title\");\n\n// 600ms: long enough that passing the pointer over a toolbar, or landing focus\n// on an icon button, does not flash a label; short enough to still feel live.\nexport function Tooltip({ content, children, side = \"top\", delay = 600, className, container, disabled = false, ref, ...slotProps }: TooltipProps) {\n return <TooltipBase content={content} side={side} delay={delay} className={className} container={container} disabled={disabled} ref={ref} {...slotProps}>{children}</TooltipBase>;\n}\n\n/**\n * The tooltip a library component draws on its own: an icon Button's label, cut-off text in full, a relative time's\n * date, an avatar's name, its own controls' labels. Where a tooltip that can show already covers its trigger (a Tooltip\n * the page put around it) it steps aside, so the control shows one (the review of 21.6 session B: a Tooltip around a\n * cut-off Badge showed two). Every component's own tooltip is one of these; `<Tooltip>` is the page's, and never steps aside.\n */\nexport function AutoTooltip(props: TooltipProps) {\n return <TooltipBase {...props} auto />;\n}\n\n/**\n * The AutoTooltip of text cut to fit (Sprint 25.8.7 item 5, lib's CutText): the tooltip is the way to the full text, so a\n * tap shows it at once and a second tap or a press elsewhere hides it, and a press or Enter never dismisses it, there\n * being nothing to activate.\n */\nexport function RevealTooltip(props: TooltipProps) {\n return <TooltipBase {...props} auto reveal />;\n}\n\n/**\n * The drawn tooltip on screen now (Sprint 25.32, round 29's double tooltips; the owner's rule, one tooltip per control,\n * and APG's one at a time): a tooltip that opens closes this one first. A focused control's label (a RelativeTime's full\n * date, shown by keyboard focus) gives way when the pointer rests on another control, so two never show at once.\n */\nlet shownTooltip: { close: () => void } | null = null;\n\nfunction TooltipBase({ content, children, side = \"top\", delay = 600, className, container, disabled = false, auto = false, reveal = false, ref: forwardedRef, ...slotProps }: TooltipProps & { auto?: boolean; reveal?: boolean }) {\n const around = useContext(TooltipCtx);\n const [open, setOpen] = useState(false);\n // Disabling while shown hides it (render-time state adjustment).\n if (disabled && open) setOpen(false);\n const triggerRef = useRef<HTMLElement>(null);\n const floatingRef = useRef<HTMLDivElement>(null);\n const timeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n // Set when the trigger is activated: the label must not reappear (from the\n // focus the click leaves behind, or a still-pending timer) until the pointer\n // leaves and comes back.\n const suppressedRef = useRef(false);\n const id = useId();\n\n const pos = usePosition(triggerRef, floatingRef, open, { side, offset: 6, flip: true });\n\n const cancel = useCallback(() => {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = undefined;\n }, []);\n\n // What this tooltip covers, for one inside its trigger to ask: its trigger while it can show, else the answer of the\n // one around it. An automatic tooltip is covered when that answer holds its own trigger.\n const disabledRef = useLatest(disabled);\n const covers = useCallback(() => (disabledRef.current ? around?.() ?? null : triggerRef.current), [around, disabledRef]);\n const covered = useCallback(() => {\n const mine = triggerRef.current;\n const theirs = auto ? around?.() : null;\n return !!(mine && theirs && theirs.contains(mine));\n }, [auto, around]);\n\n // One pending timer at a time: hover and focus both call show(), and the\n // earlier timer used to be orphaned, firing after hide() had run and leaving\n // the tooltip up with nothing left to close it.\n const show = useCallback(() => {\n if (disabled || suppressedRef.current || covered()) return;\n cancel();\n timeoutRef.current = setTimeout(() => setOpen(true), delay);\n }, [delay, cancel, disabled, covered]);\n\n const hide = useCallback(() => {\n cancel();\n setOpen(false);\n }, [cancel]);\n\n // Pointer focus (a click) does not warrant a label; keyboard focus does. The element focused is asked, not the\n // trigger: focus that reaches the trigger from a control inside it (a card title's button) is that control's, and\n // the trigger itself never matched, so the label never showed by keyboard (the review of 30 September).\n const showOnFocus = useCallback((e: React.FocusEvent<HTMLElement>) => {\n let keyboard = true;\n const focused = e.target instanceof Element ? e.target : e.currentTarget;\n try { keyboard = focused.matches(\":focus-visible\"); } catch { /* selector unsupported: assume keyboard */ }\n if (keyboard) show();\n }, [show]);\n\n // Activating the trigger dismisses the label and keeps it away until the\n // pointer has left — what opens next (a menu, a dialog) owns the screen.\n const dismissOnActivate = useCallback(() => {\n suppressedRef.current = true;\n hide();\n }, [hide]);\n\n // Suppression lifts when the pointer leaves or focus moves on — a keyboard\n // user never produces a mouseleave, so blur must reset it too.\n const leave = useCallback(() => {\n suppressedRef.current = false;\n hide();\n }, [hide]);\n\n // Cut text's tooltip (`reveal`): a finger's tap shows it at once, and taps it away; a mouse's press leaves it as the\n // hover has it\n const revealOnPress = (e: React.PointerEvent<HTMLElement>) => {\n if (e.pointerType !== \"touch\" || disabled || covered()) return;\n if (open) { hide(); return; }\n cancel();\n suppressedRef.current = false;\n setOpen(true);\n };\n\n // Escape dismisses an open tooltip (WCAG 1.4.13); it does not stop the key\n // reaching whatever overlay is also listening. Cut text's goes with a press anywhere else too, as a tap showed it.\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => { if (e.key === \"Escape\") hide(); };\n const onPress = (e: PointerEvent) => { if (!triggerRef.current?.contains(e.target as Node)) hide(); };\n document.addEventListener(\"keydown\", onKey);\n if (reveal) document.addEventListener(\"pointerdown\", onPress, true);\n return () => {\n document.removeEventListener(\"keydown\", onKey);\n document.removeEventListener(\"pointerdown\", onPress, true);\n };\n }, [open, hide, reveal]);\n\n useEffect(() => cancel, [cancel]);\n\n // one on screen at a time: this one, once it opens, closes the one showing, before either is painted (a passive effect\n // drew both for a frame, the review of 18 October)\n useLayoutEffect(() => {\n if (!open) return;\n const mine = { close: () => { cancel(); setOpen(false); } };\n if (shownTooltip && shownTooltip !== mine) shownTooltip.close();\n shownTooltip = mine;\n return () => { if (shownTooltip === mine) shownTooltip = null; };\n }, [open, cancel]);\n\n // A native title on the trigger would be the browser's second tooltip, a moment after this one (the review of 21.6\n // session B): an element's own title goes, and the library Button's. Another component's title prop is left alone: it\n // often means something else (a Card's heading).\n const nativeTitle = isValidElement(children) && (typeof children.type === \"string\" || NATIVE_TITLE in (children.type as object));\n const trigger = nativeTitle && (children.props as { title?: unknown }).title !== undefined\n ? cloneElement(children as ReactElement<{ title?: string }>, { title: undefined })\n : children;\n\n return (\n <TooltipCtx value={covers}>\n <Slot\n {...slotProps}\n ref={mergeRefs(triggerRef, forwardedRef)}\n onMouseEnter={(e) => { slotProps.onMouseEnter?.(e); show(); }}\n onMouseLeave={(e) => { slotProps.onMouseLeave?.(e); leave(); }}\n onFocus={(e) => { slotProps.onFocus?.(e); showOnFocus(e); }}\n onBlur={(e) => { slotProps.onBlur?.(e); leave(); }}\n onPointerDown={(e) => { slotProps.onPointerDown?.(e); if (reveal) revealOnPress(e); else dismissOnActivate(); }}\n onKeyDown={(e) => { slotProps.onKeyDown?.(e); if (!reveal && (e.key === \"Enter\" || e.key === \" \")) dismissOnActivate(); }}\n aria-describedby={open ? id : undefined}\n >\n {trigger}\n </Slot>\n {open && createPortal(\n <div\n ref={floatingRef}\n id={id}\n role=\"tooltip\"\n className={cn(\n \"fixed z-[var(--ds-z-tooltip)] pointer-events-none\",\n \"px-ds-2.5 py-ds-1.5 text-xs\",\n \"max-w-[280px] break-words\",\n \"bg-[var(--ds-bg-surface-elevated)] text-[var(--ds-text-primary)]\",\n GLASS_DROPDOWN,\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n \"rounded-[var(--ds-radius-sm)]\",\n className\n )}\n style={{ top: pos.top, left: pos.left }}\n >\n {content}\n </div>,\n container ?? document.body\n )}\n </TooltipCtx>\n );\n}\n\n","import { useEffect, type RefObject } from \"react\";\n\n/**\n * A scroller's focused item, shown whole with its keyboard outline (Sprint 30, P3). A browser scrolls an item into view\n * when focus reaches it only if none of it shows: one partly in view (a list's last option, a card at a board's edge, a\n * row under the fold) stays cut off, its outline with it. When a key moves focus to something inside the scroller (Tab,\n * or a component's arrows), this scrolls it into view, as little as it takes; the scroller's scroll padding\n * (`scroll-p-[4px]`, P3's room) leaves the outline its 4 px. Focus a pointer gave is left where it is, so nothing moves\n * under the mouse. Pass the element itself (a callback ref's), as useKeyboardScroll takes it, or a ref object, read when\n * focus arrives (for an element whose ref is already taken).\n */\nexport function useRevealFocus(target: HTMLElement | null | RefObject<HTMLElement | null>, enabled = true): void {\n useEffect(() => {\n if (!enabled || !target) return;\n const read = () => (\"current\" in target ? target.current : target);\n const first = read();\n const doc = first?.ownerDocument ?? (typeof document !== \"undefined\" ? document : null);\n if (!doc) return;\n // whether a key or a pointer came last: the fields' tracker counts typing as neither, and an arrow from a list's\n // search box moves focus by the keyboard all the same\n let keyed = false;\n const onKey = () => { keyed = true; };\n const onPointer = () => { keyed = false; };\n const onFocusIn = (e: FocusEvent) => {\n const el = read();\n const focused = e.target;\n // a typing surface keeps its own caret in view: scrolled \"into view\" whole, a long one would jump\n if (!keyed || !el || focused === el || !(focused instanceof Element) || !el.contains(focused) || (focused as HTMLElement).isContentEditable) return;\n focused.scrollIntoView?.({ block: \"nearest\", inline: \"nearest\" });\n };\n doc.addEventListener(\"keydown\", onKey, true);\n doc.addEventListener(\"pointerdown\", onPointer, true);\n doc.addEventListener(\"focusin\", onFocusIn);\n return () => {\n doc.removeEventListener(\"keydown\", onKey, true);\n doc.removeEventListener(\"pointerdown\", onPointer, true);\n doc.removeEventListener(\"focusin\", onFocusIn);\n };\n }, [target, enabled]);\n}\n"]}
|