@adea-ai/ui 0.99.3 → 0.99.4
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/dist/components/composites/workspace-mark/workspace-mark.d.ts.map +1 -1
- package/dist/components/composites/workspace-mark/workspace-mark.js +2 -2
- package/dist/components/composites/workspace-mark/workspace-mark.js.map +1 -1
- package/dist/components/conversation/message-row.d.ts.map +1 -1
- package/dist/components/conversation/message-row.js +2 -2
- package/dist/components/conversation/message-row.js.map +1 -1
- package/dist/components/conversation/thread-panel.d.ts.map +1 -1
- package/dist/components/conversation/thread-panel.js +1 -1
- package/dist/components/conversation/thread-panel.js.map +1 -1
- package/dist/components/ui/badge/badge.d.ts +1 -1
- package/dist/components/ui/badge/badge.d.ts.map +1 -1
- package/dist/components/ui/badge/badge.js +8 -1
- package/dist/components/ui/badge/badge.js.map +1 -1
- package/dist/components/ui/tabs/tabs.d.ts +6 -0
- package/dist/components/ui/tabs/tabs.d.ts.map +1 -1
- package/dist/components/ui/tabs/tabs.js +7 -1
- package/dist/components/ui/tabs/tabs.js.map +1 -1
- package/dist/components/ui/tooltip/tooltip.d.ts.map +1 -1
- package/dist/components/ui/tooltip/tooltip.js +1 -0
- package/dist/components/ui/tooltip/tooltip.js.map +1 -1
- package/dist/lib/version-notes.d.ts +6 -0
- package/dist/lib/version-notes.d.ts.map +1 -1
- package/dist/lib/version-notes.js +9 -1
- package/dist/lib/version-notes.js.map +1 -1
- package/dist/r/src/components/composites/workspace-mark/workspace-mark.tsx +13 -2
- package/dist/r/src/components/conversation/message-row.tsx +6 -1
- package/dist/r/src/components/conversation/thread-panel.tsx +4 -1
- package/dist/r/src/components/ui/badge/badge.tsx +7 -0
- package/dist/r/src/components/ui/tabs/tabs.tsx +7 -1
- package/dist/r/src/components/ui/tooltip/tooltip.tsx +3 -0
- package/dist/r/src/lib/version-notes.ts +8 -0
- package/dist/r/src/styles/base.css +13 -0
- package/package.json +1 -1
- package/src/components/composites/workspace-mark/workspace-mark.tsx +13 -2
- package/src/components/conversation/message-row.tsx +6 -1
- package/src/components/conversation/thread-panel.tsx +4 -1
- package/src/components/ui/badge/badge.tsx +7 -0
- package/src/components/ui/tabs/tabs.tsx +7 -1
- package/src/components/ui/tooltip/tooltip.tsx +3 -0
- package/src/lib/version-notes.ts +8 -0
- package/src/styles/base.css +13 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-mark.d.ts","sourceRoot":"","sources":["../../../../src/components/composites/workspace-mark/workspace-mark.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAKnD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,QAAQ,CAAC,GAAG;IAC1D,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAA;IACZ,4BAA4B;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACnB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;CACvB,CAAA;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"workspace-mark.d.ts","sourceRoot":"","sources":["../../../../src/components/composites/workspace-mark/workspace-mark.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAKnD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,QAAQ,CAAC,GAAG;IAC1D,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAA;IACZ,4BAA4B;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACnB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;CACvB,CAAA;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,eAuDtD"}
|
|
@@ -3,9 +3,9 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "../../ui/tooltip/toolti
|
|
|
3
3
|
import { createComponent, insert, mergeProps, spread, template } from "solid-js/web";
|
|
4
4
|
import { Show, splitProps } from "solid-js";
|
|
5
5
|
//#region src/components/composites/workspace-mark/workspace-mark.tsx
|
|
6
|
-
var _tmpl$ = /*#__PURE__*/ template(`<span class="absolute
|
|
6
|
+
var _tmpl$ = /*#__PURE__*/ template(`<span data-slot=workspace-mark-badge class="absolute end-0 top-0 flex translate-x-1/2 -translate-y-1/2 items-center justify-center rtl:-translate-x-1/2">`);
|
|
7
7
|
var _tmpl$2 = /*#__PURE__*/ template(`<button type=button>`);
|
|
8
|
-
var _tmpl$3 = /*#__PURE__*/ template(`<span aria-hidden=true>`);
|
|
8
|
+
var _tmpl$3 = /*#__PURE__*/ template(`<span data-slot=workspace-mark-initial aria-hidden=true>`);
|
|
9
9
|
function WorkspaceMark(props) {
|
|
10
10
|
const [local, rest] = splitProps(props, [
|
|
11
11
|
"class",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-mark.js","names":["ComponentProps","JSX","Show","splitProps","cn","Tooltip","TooltipContent","TooltipTrigger","WorkspaceMarkProps","name","active","badge","Element","children","WorkspaceMark","props","local","rest","mark","_el$","_tmpl$2","_$spread","_$mergeProps","aria-label","aria-current","undefined","data-active","class","_$insert","_$createComponent","when","fallback","_el$3","_tmpl$3","charAt","toUpperCase","_el$2","_tmpl$","placement","as","_$mergeProps","_$createComponent"],"sources":["../../../../src/components/composites/workspace-mark/workspace-mark.tsx"],"sourcesContent":["import type { ComponentProps, JSX } from 'solid-js'\nimport { Show, splitProps } from 'solid-js'\nimport { cn } from '#lib/utils'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '../../ui/tooltip/tooltip'\n\n/**\n * WorkspaceMark.\n *\n * The mark for one workspace in a rail: an initial or a glyph, a ring while it is\n * active, and a badge for something unread. It is deliberately not an `Avatar` —\n * a workspace is not a person, it does not need an image or initials derived from a\n * name, and a rail of them needs an *active* treatment an avatar does not have.\n *\n * The mark is square with a rounded corner rather than round, which is what\n * distinguishes a workspace from a person at a glance in a rail that holds both.\n */\nexport type WorkspaceMarkProps = ComponentProps<'button'> & {\n /** The workspace's name. Used for the label, the initial, and the tooltip. */\n name: string\n /** Draw the active ring. */\n active?: boolean\n /** A count or a dot, drawn at the corner. */\n badge?: JSX.Element\n /** Replace the initial with a glyph. */\n children?: JSX.Element\n}\n\nexport function WorkspaceMark(props: WorkspaceMarkProps) {\n const [local, rest] = splitProps(props, ['class', 'name', 'active', 'badge', 'children'])\n\n const mark = (\n <button\n type=\"button\"\n aria-label={local.name}\n aria-current={local.active ? 'true' : undefined}\n data-active={local.active ? '' : undefined}\n class={cn(\n 'relative flex size-9 shrink-0 items-center justify-center rounded-lg border text-sm font-semibold',\n 'transition-[color,background-color,border-color,transform] ease-out outline-none select-none',\n 'hover:scale-105 active:scale-95',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n 'disabled:pointer-events-none disabled:opacity-50',\n local.active\n ? 'border-primary bg-primary-subtle text-primary'\n : 'border-border bg-card text-muted-foreground hover:text-foreground',\n local.class\n )}\n {...rest}\n >\n <Show\n when={local.children}\n fallback={<span aria-hidden=\"true\"
|
|
1
|
+
{"version":3,"file":"workspace-mark.js","names":["ComponentProps","JSX","Show","splitProps","cn","Tooltip","TooltipContent","TooltipTrigger","WorkspaceMarkProps","name","active","badge","Element","children","WorkspaceMark","props","local","rest","mark","_el$","_tmpl$2","_$spread","_$mergeProps","aria-label","aria-current","undefined","data-active","class","_$insert","_$createComponent","when","fallback","_el$3","_tmpl$3","charAt","toUpperCase","_el$2","_tmpl$","placement","as","_$mergeProps","_$createComponent"],"sources":["../../../../src/components/composites/workspace-mark/workspace-mark.tsx"],"sourcesContent":["import type { ComponentProps, JSX } from 'solid-js'\nimport { Show, splitProps } from 'solid-js'\nimport { cn } from '#lib/utils'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '../../ui/tooltip/tooltip'\n\n/**\n * WorkspaceMark.\n *\n * The mark for one workspace in a rail: an initial or a glyph, a ring while it is\n * active, and a badge for something unread. It is deliberately not an `Avatar` —\n * a workspace is not a person, it does not need an image or initials derived from a\n * name, and a rail of them needs an *active* treatment an avatar does not have.\n *\n * The mark is square with a rounded corner rather than round, which is what\n * distinguishes a workspace from a person at a glance in a rail that holds both.\n */\nexport type WorkspaceMarkProps = ComponentProps<'button'> & {\n /** The workspace's name. Used for the label, the initial, and the tooltip. */\n name: string\n /** Draw the active ring. */\n active?: boolean\n /** A count or a dot, drawn at the corner. */\n badge?: JSX.Element\n /** Replace the initial with a glyph. */\n children?: JSX.Element\n}\n\nexport function WorkspaceMark(props: WorkspaceMarkProps) {\n const [local, rest] = splitProps(props, ['class', 'name', 'active', 'badge', 'children'])\n\n const mark = (\n <button\n type=\"button\"\n aria-label={local.name}\n aria-current={local.active ? 'true' : undefined}\n data-active={local.active ? '' : undefined}\n class={cn(\n 'relative flex size-9 shrink-0 items-center justify-center rounded-lg border text-sm font-semibold',\n 'transition-[color,background-color,border-color,transform] ease-out outline-none select-none',\n 'hover:scale-105 active:scale-95',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n 'disabled:pointer-events-none disabled:opacity-50',\n local.active\n ? 'border-primary bg-primary-subtle text-primary'\n : 'border-border bg-card text-muted-foreground hover:text-foreground',\n local.class\n )}\n {...rest}\n >\n <Show\n when={local.children}\n fallback={\n <span data-slot=\"workspace-mark-initial\" aria-hidden=\"true\">\n {local.name.charAt(0).toUpperCase()}\n </span>\n }\n >\n {local.children}\n </Show>\n {/* The badge is centred on the corner rather than tucked inside it. Inset\n by an edge offset, a count badge is wider than the space between the\n corner and the monogram, so it sat on the letter; centred on the corner,\n a dot, a one-digit and a two-digit count all clear the letter's box. */}\n <Show when={local.badge}>\n <span\n data-slot=\"workspace-mark-badge\"\n class=\"absolute end-0 top-0 flex translate-x-1/2 -translate-y-1/2 items-center justify-center rtl:-translate-x-1/2\"\n >\n {local.badge}\n </span>\n </Show>\n </button>\n )\n\n return (\n <Tooltip placement=\"right\">\n <TooltipTrigger as=\"span\" class=\"contents\">\n {mark}\n </TooltipTrigger>\n <TooltipContent>{local.name}</TooltipContent>\n </Tooltip>\n )\n}\n"],"mappings":";;;;;;;;AA2BA,SAAgBc,cAAcC,OAA2B;CACvD,MAAM,CAACC,OAAOC,QAAQd,WAAWY,OAAO;EAAC;EAAS;EAAQ;EAAU;EAAS;CAAU,CAAC;CAExF,MAAMG,cAAI;EAAA,IAAAC,OAAAC,QAAA;EAAAC,OAAAF,MAAAG,WAAA;GAAA,KAAA,gBAAA;IAAA,OAGMN,MAAMP;GAAI;GAAA,KAAA,kBAAA;IAAA,OACRO,MAAMN,SAAS,SAASe,KAAAA;GAAS;GAAA,KAAA,iBAAA;IAAA,OAClCT,MAAMN,SAAS,KAAKe,KAAAA;GAAS;GAAA,KAAA,WAAA;IAAA,OACnCrB,GACL,qGACA,gGACA,mCACA,oFACA,oDACAY,MAAMN,SACF,kDACA,qEACJM,MAAMW,KACR;GAAC;EAAA,GACGV,IAAI,GAAA,OAAA,IAAA;EAAAW,OAAAT,MAAAU,gBAEP3B,MAAI;GAAA,IACH4B,OAAI;IAAA,OAAEd,MAAMH;GAAQ;GAAA,IACpBkB,WAAQ;IAAA,cAAA;KAAA,IAAAC,QAAAC,QAAA;KAAAL,OAAAI,aAEHhB,MAAMP,KAAKyB,OAAO,CAAC,CAAC,CAACC,YAAY,CAAC;KAAA,OAAAH;IAAA,EAAA,CAAA;GAAA;GAAA,IAAAnB,WAAA;IAAA,OAItCG,MAAMH;GAAQ;EAAA,CAAA,GAAA,IAAA;EAAAe,OAAAT,MAAAU,gBAMhB3B,MAAI;GAAA,IAAC4B,OAAI;IAAA,OAAEd,MAAML;GAAK;GAAA,IAAAE,WAAA;IAAA,IAAAuB,QAAAC,OAAA;IAAAT,OAAAQ,aAKlBpB,MAAML,KAAK;IAAA,OAAAyB;GAAA;EAAA,CAAA,GAAA,IAAA;EAAA,OAAAjB;CAAA,EAAA,CAAA;CAMpB,OAAAU,gBACGxB,SAAO;EAACiC,WAAS;EAAA,IAAAzB,WAAA;GAAA,OAAA,CAAAgB,gBACftB,gBAAc;IAACgC,IAAE;IAAA,SAAA;IAAA1B,UACfK;GAAI,CAAA,GAAAW,gBAENvB,gBAAc,EAAA,IAAAO,WAAA;IAAA,OAAEG,MAAMP;GAAI,EAAA,CAAA,CAAA;EAAA;CAAA,CAAA;AAGjC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-row.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/message-row.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAKnD,OAAO,EAAsB,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAEvF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,UAAU,CAAC,GAAG;IAC1E,8DAA8D;IAC9D,UAAU,EAAE,sBAAsB,CAAA;IAClC,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAA;IAClB,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACzB,kFAAkF;IAClF,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IAClB,gEAAgE;IAChE,OAAO,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACrB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;IACzB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB,CAAA;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,eAiLhD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,eAOxD;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,eAgBjF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,qFAAqF;IACrF,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,
|
|
1
|
+
{"version":3,"file":"message-row.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/message-row.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAKnD,OAAO,EAAsB,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAEvF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,UAAU,CAAC,GAAG;IAC1E,8DAA8D;IAC9D,UAAU,EAAE,sBAAsB,CAAA;IAClC,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAA;IAClB,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACzB,kFAAkF;IAClF,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IAClB,gEAAgE;IAChE,OAAO,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACrB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;IACzB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB,CAAA;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,eAiLhD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,eAOxD;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,eAgBjF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,qFAAqF;IACrF,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,eA2DF"}
|
|
@@ -298,7 +298,7 @@ function MessageBody(props) {
|
|
|
298
298
|
const segments = () => local.text.split(/(```[\s\S]*?(?:```|$))/g).filter(Boolean).map((segment) => {
|
|
299
299
|
if (!segment.startsWith("```")) return {
|
|
300
300
|
kind: "prose",
|
|
301
|
-
value: segment
|
|
301
|
+
value: segment.replace(/^\n+|\n+$/g, "")
|
|
302
302
|
};
|
|
303
303
|
const body = segment.replace(/^```/, "").replace(/```$/, "");
|
|
304
304
|
const newline = body.indexOf("\n");
|
|
@@ -309,7 +309,7 @@ function MessageBody(props) {
|
|
|
309
309
|
value: language ? body.slice(newline + 1) : body,
|
|
310
310
|
language
|
|
311
311
|
};
|
|
312
|
-
});
|
|
312
|
+
}).filter((segment) => segment.kind === "code" || segment.value !== "");
|
|
313
313
|
const last = () => segments().at(-1);
|
|
314
314
|
return (() => {
|
|
315
315
|
var _el$19 = _tmpl$11();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-row.js","names":["CheckCheck","MessageSquareReply","Pencil","RotateCcw","Trash2","ComponentProps","JSX","For","Show","splitProps","cn","Button","CodeBlock","InlineCode","ConversationAvatar","ConversationAvatarKind","MessageRowProps","Omit","senderKind","senderName","time","dateTime","edited","pending","streaming","deleted","highlighted","avatar","Element","attachments","link","actions","onOpenThread","onThreadIntent","onEdit","onDelete","onRetry","MessageRow","props","local","rest","mirrored","_el$","_tmpl$8","_el$2","firstChild","_el$3","nextSibling","_el$4","_el$5","_el$6","_el$9","_el$13","_$spread","_$mergeProps","data-message-kind","aria-busy","undefined","tabindex","class","_$insert","_$createComponent","kind","size","children","when","fallback","_tmpl$9","_el$7","_tmpl$","_el$8","_tmpl$2","_el$0","_tmpl$3","_$effect","_$setAttribute","_tmpl$4","_tmpl$5","_tmpl$6","_$memo","_el$12","_tmpl$7","variant","onClick","onPointerEnter","onFocus","_p$","_v$","_v$2","_v$3","_v$4","e","_$className","t","a","o","MessageGroup","_el$15","_tmpl$0","MessageDayDivider","label","_el$16","_tmpl$1","_el$17","_el$18","MessageBody","text","segments","split","filter","Boolean","map","segment","startsWith","const","value","body","replace","newline","indexOf","firstLine","slice","language","test","trim","last","at","_el$19","_tmpl$11","each","Prose","code","complete","_tmpl$10","parts","_el$21","_tmpl$12","part","endsWith","length","_$mergeProps","_$createComponent","_$setAttribute","_$memo","_$className"],"sources":["../../../src/components/conversation/message-row.tsx"],"sourcesContent":["import { CheckCheck, MessageSquareReply, Pencil, RotateCcw, Trash2 } from 'lucide-solid'\nimport type { ComponentProps, JSX } from 'solid-js'\nimport { For, Show, splitProps } from 'solid-js'\nimport { cn } from '#lib/utils'\nimport { Button } from '../ui/button/button'\nimport { CodeBlock, InlineCode } from '../ui/code-block'\nimport { ConversationAvatar, type ConversationAvatarKind } from './conversation-avatar'\n\n/**\n * MessageRow.\n *\n * One turn in a transcript: a speaker, a bubble, and the actions that apply to it.\n *\n * ## What it does not know\n *\n * It takes no domain types. A caller supplies the speaker's name, the timestamp, and\n * the body as children; attachments, a linked task and the action row are slots. That\n * is deliberate and it is the difference between a component a second application can\n * use and one that only fits the application it was written in — the first version of\n * this row was written against adea's message model and could not have been used by\n * cortana at all.\n *\n * ## The two things it does know\n *\n * **The speaker's own messages mirror.** A user's turn puts the avatar on the trailing\n * edge and the bubble on the leading one, so a transcript can be read as a\n * conversation rather than as a log. `senderKind` decides that, and it is the only\n * place the layout branches.\n *\n * **`content-visibility: auto`.** A transcript is the one list in an application that\n * can hold thousands of rows, and this lets the engine skip layout and paint for the\n * ones off screen. `contain-intrinsic-size` is what keeps the scrollbar honest while\n * it does — without it the scroll height collapses as rows are skipped and the\n * thumb jumps. Both are load-bearing rather than an optimisation to add later.\n */\nexport type MessageRowProps = Omit<ComponentProps<'article'>, 'onSelect'> & {\n /** Who spoke. Decides the mirroring and the avatar's tone. */\n senderKind: ConversationAvatarKind\n /** The speaker's name, announced with the message rather than drawn. */\n senderName: string\n /** An ISO timestamp, rendered by the caller's own formatter. */\n time?: string\n /** The machine-readable time, for `<time datetime>`. */\n dateTime?: string\n /** Set when the message has been edited after sending. */\n edited?: boolean\n /** Set while the message is in flight. */\n pending?: boolean\n /**\n * Set while tokens are still arriving. Shows a caret after the body and drops\n * the delivered tick; distinct from `pending`, which is the *sending* state of\n * a message the user wrote. A turn can be neither, either, or (briefly) both.\n */\n streaming?: boolean\n /** Set when the message has been deleted, which replaces the body. */\n deleted?: boolean\n /** Draw attention to this row, e.g. after arriving from a search result. */\n highlighted?: boolean\n /** The caller's own avatar content. Falls back to a glyph for the kind. */\n avatar?: JSX.Element\n /** Attachments, rendered under the body. */\n attachments?: JSX.Element\n /** A linked entity — a task, an artifact, a session — rendered under the body. */\n link?: JSX.Element\n /** Extra rows in the action strip, before the built-in ones. */\n actions?: JSX.Element\n /** Show the reply affordance. Omit it to hide the action. */\n onOpenThread?: () => void\n /** Fires on hover and focus of the reply affordance, for prefetching. */\n onThreadIntent?: () => void\n onEdit?: () => void\n onDelete?: () => void\n onRetry?: () => void\n}\n\nexport function MessageRow(props: MessageRowProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'senderKind',\n 'senderName',\n 'time',\n 'dateTime',\n 'edited',\n 'pending',\n 'streaming',\n 'deleted',\n 'highlighted',\n 'avatar',\n 'attachments',\n 'link',\n 'actions',\n 'onOpenThread',\n 'onThreadIntent',\n 'onEdit',\n 'onDelete',\n 'onRetry',\n 'children',\n ])\n\n const mirrored = () => local.senderKind === 'user'\n\n return (\n <article\n data-slot=\"message-row\"\n data-message-kind={local.senderKind}\n aria-busy={local.pending || undefined}\n tabindex={local.highlighted ? -1 : undefined}\n class={cn(\n 'group/message grid max-w-3xl gap-2.5 px-1 py-1.5',\n /* The row is skipped by the engine while off screen; the intrinsic size keeps\n the scroll height stable while that happens. */\n '[content-visibility:auto] [contain-intrinsic-size:auto_7rem]',\n mirrored() ? 'grid-cols-[minmax(0,1fr)_2rem]' : 'grid-cols-[1.9rem_minmax(0,1fr)]',\n 'transition-colors ease-out hover:bg-muted/45',\n local.highlighted && 'bg-primary-subtle shadow-[inset_3px_0_var(--primary)]',\n local.class\n )}\n {...rest}\n >\n <span data-slot=\"message-avatar\" class={cn('mt-0.5', mirrored() ? 'order-2' : 'order-1')}>\n <ConversationAvatar kind={local.senderKind} size={mirrored() ? 'lg' : 'md'}>\n {local.avatar}\n </ConversationAvatar>\n </span>\n\n <div\n data-slot=\"message-content\"\n class={cn('flex min-w-0 flex-col gap-1', mirrored() ? 'order-1' : 'order-2')}\n >\n <div\n data-slot=\"message-bubble\"\n class={cn(\n 'relative w-fit min-w-16 max-w-[min(100%,38rem)] rounded-lg border border-border px-3.5 pt-2.5 pb-2',\n 'bg-card text-card-foreground',\n /* The tail: a squared corner on the side the speaker sits, which is what\n makes a bubble read as belonging to one voice. */\n mirrored() ? 'rounded-ee-sm ms-auto' : 'rounded-es-sm'\n )}\n >\n {/* The name is announced with the message rather than drawn: a transcript\n that repeats every speaker's name in every bubble is one nobody reads. */}\n <span class=\"visually-hidden\">{local.senderName}</span>\n\n <div data-slot=\"message-body\" class=\"text-sm\">\n <Show\n when={!local.deleted}\n fallback={<p class=\"text-muted-foreground italic\">Message deleted</p>}\n >\n {local.children}\n </Show>\n </div>\n\n <Show when={local.attachments}>\n <div class=\"mt-2 flex flex-wrap gap-2\">{local.attachments}</div>\n </Show>\n\n <Show when={local.link}>\n <div class=\"mt-2\">{local.link}</div>\n </Show>\n\n <div\n data-slot=\"message-meta\"\n class=\"mt-1 flex items-center gap-2 text-2xs text-muted-foreground\"\n >\n <Show when={local.time}>\n <time datetime={local.dateTime}>{local.time}</time>\n </Show>\n <Show when={local.edited}>\n <span>edited</span>\n </Show>\n <Show when={local.pending}>\n <span role=\"status\">sending…</span>\n </Show>\n <Show when={local.streaming}>\n <span role=\"status\">responding…</span>\n </Show>\n <Show when={mirrored() && !local.pending && !local.streaming && !local.deleted}>\n {/*\n `role=\"img\"` because a bare span cannot carry `aria-label` — axe's\n `aria-prohibited-attr`. The glyph is the whole message, so the role\n is also the accurate one: this element *is* an image whose meaning\n is its label.\n */}\n <span role=\"img\" aria-label=\"Delivered\" class=\"inline-flex\">\n <CheckCheck aria-hidden=\"true\" class=\"size-3\" />\n </span>\n </Show>\n </div>\n </div>\n\n {/* The action strip is hidden until the row is hovered or focused, so a\n transcript is not a wall of buttons — but it stays in the accessibility\n tree and the tab order, because a control that only exists under the\n pointer cannot be reached by keyboard. */}\n <footer\n data-slot=\"message-actions\"\n class={cn(\n 'flex items-center gap-1 opacity-0 transition-opacity ease-out group-hover/message:opacity-100 group-focus-within/message:opacity-100',\n mirrored() && 'justify-end'\n )}\n >\n <Show when={local.actions}>{local.actions}</Show>\n <Show when={local.onOpenThread && !local.deleted}>\n <Button\n variant=\"ghost\"\n size=\"2xs\"\n class=\"gap-1.5 text-muted-foreground\"\n onClick={() => local.onOpenThread?.()}\n onPointerEnter={() => local.onThreadIntent?.()}\n onFocus={() => local.onThreadIntent?.()}\n >\n <MessageSquareReply />\n Thread\n </Button>\n </Show>\n <Show when={local.onEdit && !local.deleted}>\n <Button\n variant=\"ghost\"\n size=\"2xs\"\n class=\"gap-1.5 text-muted-foreground\"\n onClick={() => local.onEdit?.()}\n >\n <Pencil />\n Edit\n </Button>\n </Show>\n <Show when={local.onDelete && !local.deleted}>\n <Button\n variant=\"ghost\"\n size=\"2xs\"\n class=\"gap-1.5 text-muted-foreground\"\n onClick={() => local.onDelete?.()}\n >\n <Trash2 />\n Delete\n </Button>\n </Show>\n <Show when={local.onRetry}>\n <Button\n variant=\"ghost\"\n size=\"2xs\"\n class=\"gap-1.5 text-muted-foreground\"\n onClick={() => local.onRetry?.()}\n >\n <RotateCcw />\n Retry\n </Button>\n </Show>\n </footer>\n </div>\n </article>\n )\n}\n\n/**\n * A run of messages from the same speaker.\n *\n * Consecutive turns from one voice are a *paragraph*, and a transcript that repeats\n * the avatar and the name for each line reads as noise. The group draws the first\n * row fully and the rest without their avatar.\n */\nexport function MessageGroup(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class', 'children'])\n return (\n <div data-slot=\"message-group\" role=\"group\" class={cn('flex flex-col', local.class)} {...rest}>\n {local.children}\n </div>\n )\n}\n\n/** A day divider, so a long transcript has somewhere to rest the eye. */\nexport function MessageDayDivider(props: ComponentProps<'div'> & { label: string }) {\n const [local, rest] = splitProps(props, ['class', 'label'])\n return (\n <div\n data-slot=\"message-day-divider\"\n role=\"separator\"\n class={cn('flex items-center gap-3 px-4 py-3', local.class)}\n {...rest}\n >\n <span class=\"h-px flex-1 bg-border\" />\n <span class=\"text-2xs font-medium tracking-wide text-muted-foreground uppercase\">\n {local.label}\n </span>\n <span class=\"h-px flex-1 bg-border\" />\n </div>\n )\n}\n\n/**\n * Renders a body's code fences as blocks, which a plain `<p>` cannot.\n *\n * The fence grammar is the part worth owning: a fence opens with ``` and an\n * optional language, and closes with ``` on its own. Everything outside a fence\n * is prose, and inside it the language tag belongs to the block's header rather\n * than to the code.\n *\n * Inline code — a single backtick pair inside a line — is rendered too, because\n * the alternative is an identifier in the prose that is indistinguishable from a\n * word. Only the *first* pair on a line is treated as code by the splitter here,\n * which is the common case; a caller that needs full inline grammar should pass\n * already-rendered children instead of `text`.\n */\nexport function MessageBody(\n props: ComponentProps<'div'> & {\n text: string\n /** True while the turn is still streaming, so a partial fence renders as a block. */\n streaming?: boolean\n }\n) {\n const [local, rest] = splitProps(props, ['class', 'text', 'streaming'])\n\n /**\n * Split on fences, then on inline code. `filter(Boolean)` because a text that\n * begins with a fence produces an empty leading segment.\n */\n const segments = () =>\n local.text\n .split(/(```[\\s\\S]*?(?:```|$))/g)\n .filter(Boolean)\n .map((segment) => {\n if (!segment.startsWith('```')) {\n return { kind: 'prose' as const, value: segment }\n }\n const body = segment.replace(/^```/, '').replace(/```$/, '')\n // The language is the first token on the opening line, and only when the\n // fence actually carried one — a fence with a bare newline has none.\n const newline = body.indexOf('\\n')\n const firstLine = newline === -1 ? '' : body.slice(0, newline)\n const language = /^[\\w+#.-]+$/.test(firstLine.trim()) ? firstLine.trim() : undefined\n return {\n kind: 'code' as const,\n value: language ? body.slice(newline + 1) : body,\n language,\n }\n })\n\n const last = () => segments().at(-1)\n\n return (\n <div data-slot=\"message-body\" class={cn('flex flex-col gap-2', local.class)} {...rest}>\n <For each={segments()}>\n {(segment) => (\n <Show when={segment.kind === 'code'} fallback={<Prose text={segment.value} />}>\n <CodeBlock\n code={segment.value}\n language={segment.kind === 'code' ? segment.language : undefined}\n complete={!local.streaming}\n />\n </Show>\n )}\n </For>\n {/*\n The caret trails the last segment, and only when that segment is prose: a\n streaming code block already carries its own generating indicator, and two\n of them on one turn read as two streams.\n */}\n <Show when={local.streaming && last()?.kind === 'prose'}>\n <span class=\"streaming-caret\" aria-hidden=\"true\" />\n </Show>\n </div>\n )\n}\n\n/**\n * A run of prose, with its inline code marked. The split is non-greedy and\n * requires a non-empty span, so an unmatched backtick stays a backtick rather\n * than swallowing the rest of the paragraph.\n */\nfunction Prose(props: { text: string }) {\n const parts = () => props.text.split(/(`[^`\\n]+`)/g).filter(Boolean)\n\n return (\n <p class=\"whitespace-pre-wrap\">\n <For each={parts()}>\n {(part) => (\n <Show\n when={part.startsWith('`') && part.endsWith('`') && part.length > 2}\n fallback={part}\n >\n <InlineCode>{part.slice(1, -1)}</InlineCode>\n </Show>\n )}\n </For>\n </p>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA2EA,SAAgBqC,WAAWC,OAAwB;CACjD,MAAM,CAACC,OAAOC,QAAQ/B,WAAW6B,OAAO;EACtC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CAAU,CACX;CAED,MAAMG,iBAAiBF,MAAMrB,eAAe;CAE5C,cAAA;EAAA,IAAAwB,OAAAC,QAAA,GAAAC,QAAAF,KAAAG,YAAAC,QAAAF,MAAAG,aAAAC,QAAAF,MAAAD,YAAAI,QAAAD,MAAAH,YAAAK,QAAAD,MAAAF,aAAAI,QAAAD,MAAAH,aAAAK,SAAAJ,MAAAD;EAAAM,OAAAX,MAAAY,WAAA;GAAA,KAAA,uBAAA;IAAA,OAGuBf,MAAMrB;GAAU;GAAA,KAAA,eAAA;IAAA,OACxBqB,MAAMhB,WAAWkC,KAAAA;GAAS;GAAA,IACrCC,WAAQ;IAAA,OAAEnB,MAAMb,cAAc,KAAK+B,KAAAA;GAAS;GAAA,KAAA,WAAA;IAAA,OACrC/C,GACL,oDAGA,gEACA+B,SAAS,IAAI,mCAAmC,oCAChD,gDACAF,MAAMb,eAAe,yDACrBa,MAAMoB,KACR;GAAC;EAAA,GACGnB,IAAI,GAAA,OAAA,IAAA;EAAAoB,OAAAhB,OAAAiB,gBAGL/C,oBAAkB;GAAA,IAACgD,OAAI;IAAA,OAAEvB,MAAMrB;GAAU;GAAA,IAAE6C,OAAI;IAAA,OAAEtB,SAAS,IAAI,OAAO;GAAI;GAAA,IAAAuB,WAAA;IAAA,OACvEzB,MAAMZ;GAAM;EAAA,CAAA,CAAA;EAAAiC,OAAAX,aAoBkBV,MAAMpB,UAAU;EAAAyC,OAAAV,OAAAW,gBAG5CrD,MAAI;GAAA,IACHyD,OAAI;IAAA,OAAE,CAAC1B,MAAMd;GAAO;GAAA,IACpByC,WAAQ;IAAA,OAAAC,QAAA;GAAA;GAAA,IAAAH,WAAA;IAAA,OAEPzB,MAAMyB;GAAQ;EAAA,CAAA,CAAA;EAAAJ,OAAAZ,OAAAa,gBAIlBrD,MAAI;GAAA,IAACyD,OAAI;IAAA,OAAE1B,MAAMV;GAAW;GAAA,IAAAmC,WAAA;IAAA,IAAAI,QAAAC,OAAA;IAAAT,OAAAQ,aACa7B,MAAMV,WAAW;IAAA,OAAAuC;GAAA;EAAA,CAAA,GAAAjB,KAAA;EAAAS,OAAAZ,OAAAa,gBAG1DrD,MAAI;GAAA,IAACyD,OAAI;IAAA,OAAE1B,MAAMT;GAAI;GAAA,IAAAkC,WAAA;IAAA,IAAAM,QAAAC,QAAA;IAAAX,OAAAU,aACD/B,MAAMT,IAAI;IAAA,OAAAwC;GAAA;EAAA,CAAA,GAAAnB,KAAA;EAAAS,OAAAT,OAAAU,gBAO5BrD,MAAI;GAAA,IAACyD,OAAI;IAAA,OAAE1B,MAAMnB;GAAI;GAAA,IAAA4C,WAAA;IAAA,IAAAQ,QAAAC,QAAA;IAAAb,OAAAY,aACajC,MAAMnB,IAAI;IAAAsD,aAAAC,aAAAH,OAAA,YAA3BjC,MAAMlB,QAAQ,CAAA;IAAA,OAAAmD;GAAA;EAAA,CAAA,GAAA,IAAA;EAAAZ,OAAAT,OAAAU,gBAE/BrD,MAAI;GAAA,IAACyD,OAAI;IAAA,OAAE1B,MAAMjB;GAAM;GAAA,IAAA0C,WAAA;IAAA,OAAAY,QAAA;GAAA;EAAA,CAAA,GAAA,IAAA;EAAAhB,OAAAT,OAAAU,gBAGvBrD,MAAI;GAAA,IAACyD,OAAI;IAAA,OAAE1B,MAAMhB;GAAO;GAAA,IAAAyC,WAAA;IAAA,OAAAa,QAAA;GAAA;EAAA,CAAA,GAAA,IAAA;EAAAjB,OAAAT,OAAAU,gBAGxBrD,MAAI;GAAA,IAACyD,OAAI;IAAA,OAAE1B,MAAMf;GAAS;GAAA,IAAAwC,WAAA;IAAA,OAAAc,QAAA;GAAA;EAAA,CAAA,GAAA,IAAA;EAAAlB,OAAAT,OAAAU,gBAG1BrD,MAAI;GAAA,IAACyD,OAAI;IAAA,OAAEc,WAAA,CAAA,EAAAtC,SAAS,KAAK,CAACF,MAAMhB,WAAW,CAACgB,MAAMf,UAAS,CAAA,CAAA,KAAI,CAACe,MAAMd;GAAO;GAAA,IAAAuC,WAAA;IAAA,IAAAgB,SAAAC,QAAA;IAAArB,OAAAoB,QAAAnB,gBAQzE7D,YAAU;KAAA,eAAA;KAAA,SAAA;IAAA,CAAA,CAAA;IAAA,OAAAgF;GAAA;EAAA,CAAA,GAAA,IAAA;EAAApB,OAAAR,QAAAS,gBAiBhBrD,MAAI;GAAA,IAACyD,OAAI;IAAA,OAAE1B,MAAMR;GAAO;GAAA,IAAAiC,WAAA;IAAA,OAAGzB,MAAMR;GAAO;EAAA,CAAA,GAAA,IAAA;EAAA6B,OAAAR,QAAAS,gBACxCrD,MAAI;GAAA,IAACyD,OAAI;IAAA,OAAEc,WAAA,CAAA,CAAAxC,MAAMP,YAAY,CAAA,CAAA,KAAI,CAACO,MAAMd;GAAO;GAAA,IAAAuC,WAAA;IAAA,OAAAH,gBAC7ClD,QAAM;KACLuE,SAAO;KACPnB,MAAI;KAAA,SAAA;KAEJoB,eAAe5C,MAAMP,eAAe;KACpCoD,sBAAsB7C,MAAMN,iBAAiB;KAC7CoD,eAAe9C,MAAMN,iBAAiB;KAAC,IAAA+B,WAAA;MAAA,OAAA,CAAAH,gBAEtC5D,oBAAkB,CAAA,CAAA,GAAA,QAAA;KAAA;IAAA,CAAA;GAAA;EAAA,CAAA,GAAA,IAAA;EAAA2D,OAAAR,QAAAS,gBAItBrD,MAAI;GAAA,IAACyD,OAAI;IAAA,OAAEc,WAAA,CAAA,CAAAxC,MAAML,MAAM,CAAA,CAAA,KAAI,CAACK,MAAMd;GAAO;GAAA,IAAAuC,WAAA;IAAA,OAAAH,gBACvClD,QAAM;KACLuE,SAAO;KACPnB,MAAI;KAAA,SAAA;KAEJoB,eAAe5C,MAAML,SAAS;KAAC,IAAA8B,WAAA;MAAA,OAAA,CAAAH,gBAE9B3D,QAAM,CAAA,CAAA,GAAA,MAAA;KAAA;IAAA,CAAA;GAAA;EAAA,CAAA,GAAA,IAAA;EAAA0D,OAAAR,QAAAS,gBAIVrD,MAAI;GAAA,IAACyD,OAAI;IAAA,OAAEc,WAAA,CAAA,CAAAxC,MAAMJ,QAAQ,CAAA,CAAA,KAAI,CAACI,MAAMd;GAAO;GAAA,IAAAuC,WAAA;IAAA,OAAAH,gBACzClD,QAAM;KACLuE,SAAO;KACPnB,MAAI;KAAA,SAAA;KAEJoB,eAAe5C,MAAMJ,WAAW;KAAC,IAAA6B,WAAA;MAAA,OAAA,CAAAH,gBAEhCzD,QAAM,CAAA,CAAA,GAAA,QAAA;KAAA;IAAA,CAAA;GAAA;EAAA,CAAA,GAAA,IAAA;EAAAwD,OAAAR,QAAAS,gBAIVrD,MAAI;GAAA,IAACyD,OAAI;IAAA,OAAE1B,MAAMH;GAAO;GAAA,IAAA4B,WAAA;IAAA,OAAAH,gBACtBlD,QAAM;KACLuE,SAAO;KACPnB,MAAI;KAAA,SAAA;KAEJoB,eAAe5C,MAAMH,UAAU;KAAC,IAAA4B,WAAA;MAAA,OAAA,CAAAH,gBAE/B1D,WAAS,CAAA,CAAA,GAAA,OAAA;KAAA;IAAA,CAAA;GAAA;EAAA,CAAA,GAAA,IAAA;EAAAuE,QAAAY,QAAA;GAAA,IAAAC,MA7HsB7E,GAAG,UAAU+B,SAAS,IAAI,YAAY,SAAS,GAAC+C,OAQ/E9E,GAAG,+BAA+B+B,SAAS,IAAI,YAAY,SAAS,GAACgD,OAInE/E,GACL,sGACA,gCAGA+B,SAAS,IAAI,0BAA0B,eACzC,GAACiD,OA2DMhF,GACL,wIACA+B,SAAS,KAAK,aAChB;GAAC8C,QAAAD,IAAAK,KAAAC,UAAAhD,OAAA0C,IAAAK,IAAAJ,GAAA;GAAAC,SAAAF,IAAAO,KAAAD,UAAA9C,OAAAwC,IAAAO,IAAAL,IAAA;GAAAC,SAAAH,IAAAQ,KAAAF,UAAA5C,OAAAsC,IAAAQ,IAAAL,IAAA;GAAAC,SAAAJ,IAAAS,KAAAH,UAAAxC,QAAAkC,IAAAS,IAAAL,IAAA;GAAA,OAAAJ;EAAA,GAAA;GAAAK,GAAAlC,KAAAA;GAAAoC,GAAApC,KAAAA;GAAAqC,GAAArC,KAAAA;GAAAsC,GAAAtC,KAAAA;EAAA,CAAA;EAAA,OAAAf;CAAA,EAAA,CAAA;AAqDX;;;;;;;;AASA,SAAgBsD,aAAa1D,OAA8B;CACzD,MAAM,CAACC,OAAOC,QAAQ/B,WAAW6B,OAAO,CAAC,SAAS,UAAU,CAAC;CAC7D,cAAA;EAAA,IAAA2D,SAAAC,QAAA;EAAA7C,OAAA4C,QAAA3C,WAAA,EAAA,KAAA,WAAA;GAAA,OACqD5C,GAAG,iBAAiB6B,MAAMoB,KAAK;EAAC,EAAA,GAAMnB,IAAI,GAAA,OAAA,IAAA;EAAAoB,OAAAqC,cAC1F1D,MAAMyB,QAAQ;EAAA,OAAAiC;CAAA,EAAA,CAAA;AAGrB;;AAGA,SAAgBE,kBAAkB7D,OAAkD;CAClF,MAAM,CAACC,OAAOC,QAAQ/B,WAAW6B,OAAO,CAAC,SAAS,OAAO,CAAC;CAC1D,cAAA;EAAA,IAAA+D,SAAAC,QAAA,GAAAE,SAAAH,OAAAxD,WAAAE;EAAAM,OAAAgD,QAAA/C,WAAA,EAAA,KAAA,WAAA;GAAA,OAIW5C,GAAG,qCAAqC6B,MAAMoB,KAAK;EAAC,EAAA,GACvDnB,IAAI,GAAA,OAAA,IAAA;EAAAoB,OAAA4C,cAILjE,MAAM6D,KAAK;EAAA,OAAAC;CAAA,EAAA,CAAA;AAKpB;;;;;;;;;;;;;;;AAgBA,SAAgBI,YACdnE,OAKA;CACA,MAAM,CAACC,OAAOC,QAAQ/B,WAAW6B,OAAO;EAAC;EAAS;EAAQ;CAAW,CAAC;;;;;CAMtE,MAAMqE,iBACJpE,MAAMmE,KACHE,MAAM,yBAAyB,CAAC,CAChCC,OAAOC,OAAO,CAAC,CACfC,KAAKC,YAAY;EAChB,IAAI,CAACA,QAAQC,WAAW,KAAK,GAC3B,OAAO;GAAEnD,MAAM;GAAkBqD,OAAOH;EAAQ;EAElD,MAAMI,OAAOJ,QAAQK,QAAQ,QAAQ,EAAE,CAAC,CAACA,QAAQ,QAAQ,EAAE;EAG3D,MAAMC,UAAUF,KAAKG,QAAQ,IAAI;EACjC,MAAMC,YAAYF,YAAY,KAAK,KAAKF,KAAKK,MAAM,GAAGH,OAAO;EAC7D,MAAMI,WAAW,cAAcC,KAAKH,UAAUI,KAAK,CAAC,IAAIJ,UAAUI,KAAK,IAAInE,KAAAA;EAC3E,OAAO;GACLK,MAAM;GACNqD,OAAOO,WAAWN,KAAKK,MAAMH,UAAU,CAAC,IAAIF;GAC5CM;EACF;CACF,CAAC;CAEL,MAAMG,aAAalB,SAAS,CAAC,CAACmB,GAAG,EAAE;CAEnC,cAAA;EAAA,IAAAC,SAAAC,SAAA;EAAA3E,OAAA0E,QAAAzE,WAAA,EAAA,KAAA,WAAA;GAAA,OACuC5C,GAAG,uBAAuB6B,MAAMoB,KAAK;EAAC,EAAA,GAAMnB,IAAI,GAAA,OAAA,IAAA;EAAAoB,OAAAmE,QAAAlE,gBAClFtD,KAAG;GAAA,IAAC0H,OAAI;IAAA,OAAEtB,SAAS;GAAC;GAAA3C,WACjBgD,YAAOnD,gBACNrD,MAAI;IAAA,IAACyD,OAAI;KAAA,OAAE+C,QAAQlD,SAAS;IAAM;IAAA,IAAEI,WAAQ;KAAA,OAAAL,gBAAGqE,OAAK,EAAA,IAACxB,OAAI;MAAA,OAAEM,QAAQG;KAAK,EAAA,CAAA;IAAA;IAAA,IAAAnD,WAAA;KAAA,OAAAH,gBACtEjD,WAAS;MAAA,IACRuH,OAAI;OAAA,OAAEnB,QAAQG;MAAK;MAAA,IACnBO,WAAQ;OAAA,OAAE3C,WAAAiC,QAAQlD,SAAS,MAAM,CAAA,CAAA,IAAGkD,QAAQU,WAAWjE,KAAAA;MAAS;MAAA,IAChE2E,WAAQ;OAAA,OAAE,CAAC7F,MAAMf;MAAS;KAAA,CAAA;IAAA;GAAA,CAAA;EAG/B,CAAA,GAAA,IAAA;EAAAoC,OAAAmE,QAAAlE,gBAOFrD,MAAI;GAAA,IAACyD,OAAI;IAAA,OAAEc,WAAA,CAAA,CAAAxC,MAAMf,SAAS,CAAA,CAAA,KAAIqG,KAAK,CAAC,EAAE/D,SAAS;GAAO;GAAA,IAAAE,WAAA;IAAA,OAAAqE,SAAA;GAAA;EAAA,CAAA,GAAA,IAAA;EAAA,OAAAN;CAAA,EAAA,CAAA;AAK7D;;;;;;AAOA,SAASG,MAAM5F,OAAyB;CACtC,MAAMgG,cAAchG,MAAMoE,KAAKE,MAAM,cAAc,CAAC,CAACC,OAAOC,OAAO;CAEnE,cAAA;EAAA,IAAAyB,SAAAC,SAAA;EAAA5E,OAAA2E,QAAA1E,gBAEKtD,KAAG;GAAA,IAAC0H,OAAI;IAAA,OAAEK,MAAM;GAAC;GAAAtE,WACdyE,SAAI5E,gBACHrD,MAAI;IAAA,IACHyD,OAAI;KAAA,OAAEc,WAAA,CAAA,EAAA0D,KAAKxB,WAAW,GAAG,KAAKwB,KAAKC,SAAS,GAAG,EAAC,CAAA,CAAA,KAAID,KAAKE,SAAS;IAAC;IACnEzE,UAAUuE;IAAI,IAAAzE,WAAA;KAAA,OAAAH,gBAEbhD,YAAU,EAAA,IAAAmD,WAAA;MAAA,OAAEyE,KAAKhB,MAAM,GAAG,EAAE;KAAC,EAAA,CAAA;IAAA;GAAA,CAAA;EAEjC,CAAA,CAAA;EAAA,OAAAc;CAAA,EAAA,CAAA;AAIT"}
|
|
1
|
+
{"version":3,"file":"message-row.js","names":["CheckCheck","MessageSquareReply","Pencil","RotateCcw","Trash2","ComponentProps","JSX","For","Show","splitProps","cn","Button","CodeBlock","InlineCode","ConversationAvatar","ConversationAvatarKind","MessageRowProps","Omit","senderKind","senderName","time","dateTime","edited","pending","streaming","deleted","highlighted","avatar","Element","attachments","link","actions","onOpenThread","onThreadIntent","onEdit","onDelete","onRetry","MessageRow","props","local","rest","mirrored","_el$","_tmpl$8","_el$2","firstChild","_el$3","nextSibling","_el$4","_el$5","_el$6","_el$9","_el$13","_$spread","_$mergeProps","data-message-kind","aria-busy","undefined","tabindex","class","_$insert","_$createComponent","kind","size","children","when","fallback","_tmpl$9","_el$7","_tmpl$","_el$8","_tmpl$2","_el$0","_tmpl$3","_$effect","_$setAttribute","_tmpl$4","_tmpl$5","_tmpl$6","_$memo","_el$12","_tmpl$7","variant","onClick","onPointerEnter","onFocus","_p$","_v$","_v$2","_v$3","_v$4","e","_$className","t","a","o","MessageGroup","_el$15","_tmpl$0","MessageDayDivider","label","_el$16","_tmpl$1","_el$17","_el$18","MessageBody","text","segments","split","filter","Boolean","map","segment","startsWith","const","value","replace","body","newline","indexOf","firstLine","slice","language","test","trim","last","at","_el$19","_tmpl$11","each","Prose","code","complete","_tmpl$10","parts","_el$21","_tmpl$12","part","endsWith","length","_$mergeProps","_$createComponent","_$setAttribute","_$memo","_$className"],"sources":["../../../src/components/conversation/message-row.tsx"],"sourcesContent":["import { CheckCheck, MessageSquareReply, Pencil, RotateCcw, Trash2 } from 'lucide-solid'\nimport type { ComponentProps, JSX } from 'solid-js'\nimport { For, Show, splitProps } from 'solid-js'\nimport { cn } from '#lib/utils'\nimport { Button } from '../ui/button/button'\nimport { CodeBlock, InlineCode } from '../ui/code-block'\nimport { ConversationAvatar, type ConversationAvatarKind } from './conversation-avatar'\n\n/**\n * MessageRow.\n *\n * One turn in a transcript: a speaker, a bubble, and the actions that apply to it.\n *\n * ## What it does not know\n *\n * It takes no domain types. A caller supplies the speaker's name, the timestamp, and\n * the body as children; attachments, a linked task and the action row are slots. That\n * is deliberate and it is the difference between a component a second application can\n * use and one that only fits the application it was written in — the first version of\n * this row was written against adea's message model and could not have been used by\n * cortana at all.\n *\n * ## The two things it does know\n *\n * **The speaker's own messages mirror.** A user's turn puts the avatar on the trailing\n * edge and the bubble on the leading one, so a transcript can be read as a\n * conversation rather than as a log. `senderKind` decides that, and it is the only\n * place the layout branches.\n *\n * **`content-visibility: auto`.** A transcript is the one list in an application that\n * can hold thousands of rows, and this lets the engine skip layout and paint for the\n * ones off screen. `contain-intrinsic-size` is what keeps the scrollbar honest while\n * it does — without it the scroll height collapses as rows are skipped and the\n * thumb jumps. Both are load-bearing rather than an optimisation to add later.\n */\nexport type MessageRowProps = Omit<ComponentProps<'article'>, 'onSelect'> & {\n /** Who spoke. Decides the mirroring and the avatar's tone. */\n senderKind: ConversationAvatarKind\n /** The speaker's name, announced with the message rather than drawn. */\n senderName: string\n /** An ISO timestamp, rendered by the caller's own formatter. */\n time?: string\n /** The machine-readable time, for `<time datetime>`. */\n dateTime?: string\n /** Set when the message has been edited after sending. */\n edited?: boolean\n /** Set while the message is in flight. */\n pending?: boolean\n /**\n * Set while tokens are still arriving. Shows a caret after the body and drops\n * the delivered tick; distinct from `pending`, which is the *sending* state of\n * a message the user wrote. A turn can be neither, either, or (briefly) both.\n */\n streaming?: boolean\n /** Set when the message has been deleted, which replaces the body. */\n deleted?: boolean\n /** Draw attention to this row, e.g. after arriving from a search result. */\n highlighted?: boolean\n /** The caller's own avatar content. Falls back to a glyph for the kind. */\n avatar?: JSX.Element\n /** Attachments, rendered under the body. */\n attachments?: JSX.Element\n /** A linked entity — a task, an artifact, a session — rendered under the body. */\n link?: JSX.Element\n /** Extra rows in the action strip, before the built-in ones. */\n actions?: JSX.Element\n /** Show the reply affordance. Omit it to hide the action. */\n onOpenThread?: () => void\n /** Fires on hover and focus of the reply affordance, for prefetching. */\n onThreadIntent?: () => void\n onEdit?: () => void\n onDelete?: () => void\n onRetry?: () => void\n}\n\nexport function MessageRow(props: MessageRowProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'senderKind',\n 'senderName',\n 'time',\n 'dateTime',\n 'edited',\n 'pending',\n 'streaming',\n 'deleted',\n 'highlighted',\n 'avatar',\n 'attachments',\n 'link',\n 'actions',\n 'onOpenThread',\n 'onThreadIntent',\n 'onEdit',\n 'onDelete',\n 'onRetry',\n 'children',\n ])\n\n const mirrored = () => local.senderKind === 'user'\n\n return (\n <article\n data-slot=\"message-row\"\n data-message-kind={local.senderKind}\n aria-busy={local.pending || undefined}\n tabindex={local.highlighted ? -1 : undefined}\n class={cn(\n 'group/message grid max-w-3xl gap-2.5 px-1 py-1.5',\n /* The row is skipped by the engine while off screen; the intrinsic size keeps\n the scroll height stable while that happens. */\n '[content-visibility:auto] [contain-intrinsic-size:auto_7rem]',\n mirrored() ? 'grid-cols-[minmax(0,1fr)_2rem]' : 'grid-cols-[1.9rem_minmax(0,1fr)]',\n 'transition-colors ease-out hover:bg-muted/45',\n local.highlighted && 'bg-primary-subtle shadow-[inset_3px_0_var(--primary)]',\n local.class\n )}\n {...rest}\n >\n <span data-slot=\"message-avatar\" class={cn('mt-0.5', mirrored() ? 'order-2' : 'order-1')}>\n <ConversationAvatar kind={local.senderKind} size={mirrored() ? 'lg' : 'md'}>\n {local.avatar}\n </ConversationAvatar>\n </span>\n\n <div\n data-slot=\"message-content\"\n class={cn('flex min-w-0 flex-col gap-1', mirrored() ? 'order-1' : 'order-2')}\n >\n <div\n data-slot=\"message-bubble\"\n class={cn(\n 'relative w-fit min-w-16 max-w-[min(100%,38rem)] rounded-lg border border-border px-3.5 pt-2.5 pb-2',\n 'bg-card text-card-foreground',\n /* The tail: a squared corner on the side the speaker sits, which is what\n makes a bubble read as belonging to one voice. */\n mirrored() ? 'rounded-ee-sm ms-auto' : 'rounded-es-sm'\n )}\n >\n {/* The name is announced with the message rather than drawn: a transcript\n that repeats every speaker's name in every bubble is one nobody reads. */}\n <span class=\"visually-hidden\">{local.senderName}</span>\n\n <div data-slot=\"message-body\" class=\"text-sm\">\n <Show\n when={!local.deleted}\n fallback={<p class=\"text-muted-foreground italic\">Message deleted</p>}\n >\n {local.children}\n </Show>\n </div>\n\n <Show when={local.attachments}>\n <div class=\"mt-2 flex flex-wrap gap-2\">{local.attachments}</div>\n </Show>\n\n <Show when={local.link}>\n <div class=\"mt-2\">{local.link}</div>\n </Show>\n\n <div\n data-slot=\"message-meta\"\n class=\"mt-1 flex items-center gap-2 text-2xs text-muted-foreground\"\n >\n <Show when={local.time}>\n <time datetime={local.dateTime}>{local.time}</time>\n </Show>\n <Show when={local.edited}>\n <span>edited</span>\n </Show>\n <Show when={local.pending}>\n <span role=\"status\">sending…</span>\n </Show>\n <Show when={local.streaming}>\n <span role=\"status\">responding…</span>\n </Show>\n <Show when={mirrored() && !local.pending && !local.streaming && !local.deleted}>\n {/*\n `role=\"img\"` because a bare span cannot carry `aria-label` — axe's\n `aria-prohibited-attr`. The glyph is the whole message, so the role\n is also the accurate one: this element *is* an image whose meaning\n is its label.\n */}\n <span role=\"img\" aria-label=\"Delivered\" class=\"inline-flex\">\n <CheckCheck aria-hidden=\"true\" class=\"size-3\" />\n </span>\n </Show>\n </div>\n </div>\n\n {/* The action strip is hidden until the row is hovered or focused, so a\n transcript is not a wall of buttons — but it stays in the accessibility\n tree and the tab order, because a control that only exists under the\n pointer cannot be reached by keyboard. */}\n <footer\n data-slot=\"message-actions\"\n class={cn(\n 'flex items-center gap-1 opacity-0 transition-opacity ease-out group-hover/message:opacity-100 group-focus-within/message:opacity-100',\n mirrored() && 'justify-end'\n )}\n >\n <Show when={local.actions}>{local.actions}</Show>\n <Show when={local.onOpenThread && !local.deleted}>\n <Button\n variant=\"ghost\"\n size=\"2xs\"\n class=\"gap-1.5 text-muted-foreground\"\n onClick={() => local.onOpenThread?.()}\n onPointerEnter={() => local.onThreadIntent?.()}\n onFocus={() => local.onThreadIntent?.()}\n >\n <MessageSquareReply />\n Thread\n </Button>\n </Show>\n <Show when={local.onEdit && !local.deleted}>\n <Button\n variant=\"ghost\"\n size=\"2xs\"\n class=\"gap-1.5 text-muted-foreground\"\n onClick={() => local.onEdit?.()}\n >\n <Pencil />\n Edit\n </Button>\n </Show>\n <Show when={local.onDelete && !local.deleted}>\n <Button\n variant=\"ghost\"\n size=\"2xs\"\n class=\"gap-1.5 text-muted-foreground\"\n onClick={() => local.onDelete?.()}\n >\n <Trash2 />\n Delete\n </Button>\n </Show>\n <Show when={local.onRetry}>\n <Button\n variant=\"ghost\"\n size=\"2xs\"\n class=\"gap-1.5 text-muted-foreground\"\n onClick={() => local.onRetry?.()}\n >\n <RotateCcw />\n Retry\n </Button>\n </Show>\n </footer>\n </div>\n </article>\n )\n}\n\n/**\n * A run of messages from the same speaker.\n *\n * Consecutive turns from one voice are a *paragraph*, and a transcript that repeats\n * the avatar and the name for each line reads as noise. The group draws the first\n * row fully and the rest without their avatar.\n */\nexport function MessageGroup(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class', 'children'])\n return (\n <div data-slot=\"message-group\" role=\"group\" class={cn('flex flex-col', local.class)} {...rest}>\n {local.children}\n </div>\n )\n}\n\n/** A day divider, so a long transcript has somewhere to rest the eye. */\nexport function MessageDayDivider(props: ComponentProps<'div'> & { label: string }) {\n const [local, rest] = splitProps(props, ['class', 'label'])\n return (\n <div\n data-slot=\"message-day-divider\"\n role=\"separator\"\n class={cn('flex items-center gap-3 px-4 py-3', local.class)}\n {...rest}\n >\n <span class=\"h-px flex-1 bg-border\" />\n <span class=\"text-2xs font-medium tracking-wide text-muted-foreground uppercase\">\n {local.label}\n </span>\n <span class=\"h-px flex-1 bg-border\" />\n </div>\n )\n}\n\n/**\n * Renders a body's code fences as blocks, which a plain `<p>` cannot.\n *\n * The fence grammar is the part worth owning: a fence opens with ``` and an\n * optional language, and closes with ``` on its own. Everything outside a fence\n * is prose, and inside it the language tag belongs to the block's header rather\n * than to the code.\n *\n * Inline code — a single backtick pair inside a line — is rendered too, because\n * the alternative is an identifier in the prose that is indistinguishable from a\n * word. Only the *first* pair on a line is treated as code by the splitter here,\n * which is the common case; a caller that needs full inline grammar should pass\n * already-rendered children instead of `text`.\n */\nexport function MessageBody(\n props: ComponentProps<'div'> & {\n text: string\n /** True while the turn is still streaming, so a partial fence renders as a block. */\n streaming?: boolean\n }\n) {\n const [local, rest] = splitProps(props, ['class', 'text', 'streaming'])\n\n /**\n * Split on fences, then on inline code. `filter(Boolean)` because a text that\n * begins with a fence produces an empty leading segment.\n */\n const segments = () =>\n local.text\n .split(/(```[\\s\\S]*?(?:```|$))/g)\n .filter(Boolean)\n .map((segment) => {\n if (!segment.startsWith('```')) {\n // The newlines that separate prose from a fence belong to the fence\n // syntax. Kept, `whitespace-pre-wrap` draws them as blank lines on top\n // of the column gap, doubling the space beside every code block.\n return { kind: 'prose' as const, value: segment.replace(/^\\n+|\\n+$/g, '') }\n }\n const body = segment.replace(/^```/, '').replace(/```$/, '')\n // The language is the first token on the opening line, and only when the\n // fence actually carried one — a fence with a bare newline has none.\n const newline = body.indexOf('\\n')\n const firstLine = newline === -1 ? '' : body.slice(0, newline)\n const language = /^[\\w+#.-]+$/.test(firstLine.trim()) ? firstLine.trim() : undefined\n return {\n kind: 'code' as const,\n value: language ? body.slice(newline + 1) : body,\n language,\n }\n })\n // Newlines alone between two fences leave an empty paragraph once trimmed.\n .filter((segment) => segment.kind === 'code' || segment.value !== '')\n\n const last = () => segments().at(-1)\n\n return (\n <div data-slot=\"message-body\" class={cn('flex flex-col gap-2', local.class)} {...rest}>\n <For each={segments()}>\n {(segment) => (\n <Show when={segment.kind === 'code'} fallback={<Prose text={segment.value} />}>\n <CodeBlock\n code={segment.value}\n language={segment.kind === 'code' ? segment.language : undefined}\n complete={!local.streaming}\n />\n </Show>\n )}\n </For>\n {/*\n The caret trails the last segment, and only when that segment is prose: a\n streaming code block already carries its own generating indicator, and two\n of them on one turn read as two streams.\n */}\n <Show when={local.streaming && last()?.kind === 'prose'}>\n <span class=\"streaming-caret\" aria-hidden=\"true\" />\n </Show>\n </div>\n )\n}\n\n/**\n * A run of prose, with its inline code marked. The split is non-greedy and\n * requires a non-empty span, so an unmatched backtick stays a backtick rather\n * than swallowing the rest of the paragraph.\n */\nfunction Prose(props: { text: string }) {\n const parts = () => props.text.split(/(`[^`\\n]+`)/g).filter(Boolean)\n\n return (\n <p class=\"whitespace-pre-wrap\">\n <For each={parts()}>\n {(part) => (\n <Show\n when={part.startsWith('`') && part.endsWith('`') && part.length > 2}\n fallback={part}\n >\n <InlineCode>{part.slice(1, -1)}</InlineCode>\n </Show>\n )}\n </For>\n </p>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thread-panel.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/thread-panel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAMnD;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG;IACvD,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IAClB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACtB,iFAAiF;IACjF,aAAa,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IAC3B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB,CAAA;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"thread-panel.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/thread-panel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAMnD;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG;IACvD,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IAClB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACtB,iFAAiF;IACjF,aAAa,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IAC3B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB,CAAA;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,eAuElD;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,QAAQ,CAAC,GAAG;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,mEAAmE;IACnE,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;CACnB,CAAA;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,eA4BxD"}
|
|
@@ -8,7 +8,7 @@ import { Show, splitProps } from "solid-js";
|
|
|
8
8
|
var _tmpl$ = /*#__PURE__*/ template(`<span class="text-muted-foreground text-2xs"> `);
|
|
9
9
|
var _tmpl$2 = /*#__PURE__*/ template(`<div class="bg-muted/40 shrink-0 border-b border-border px-3 py-2">`);
|
|
10
10
|
var _tmpl$3 = /*#__PURE__*/ template(`<div class="shrink-0 border-t border-border p-2">`);
|
|
11
|
-
var _tmpl$4 = /*#__PURE__*/ template(`<aside data-slot=thread-panel><header class="flex h-topbar shrink-0 items-center gap-2 border-b border-border px-3"><div class="flex min-w-0 flex-col"><span class="truncate text-sm font-semibold tracking-tight">Thread</span></div><div class="ms-auto flex shrink-0 items-center gap-1"></div></header><div class="min-h-0 flex-1 overflow-y-auto overscroll-contain"role=region aria-label="Thread replies"tabindex=0>`);
|
|
11
|
+
var _tmpl$4 = /*#__PURE__*/ template(`<aside data-slot=thread-panel><header class="flex h-topbar shrink-0 items-center gap-2 border-b border-border px-3"><div class="flex min-w-0 flex-col"><span class="truncate text-sm font-semibold tracking-tight">Thread</span></div><div class="ms-auto flex shrink-0 items-center gap-1"></div></header><div class="min-h-0 flex-1 overflow-y-auto overscroll-contain px-2 py-2"role=region aria-label="Thread replies"tabindex=0>`);
|
|
12
12
|
var _tmpl$5 = /*#__PURE__*/ template(`<span class="text-muted-foreground truncate text-2xs">`);
|
|
13
13
|
var _tmpl$6 = /*#__PURE__*/ template(`<button type=button><span class="flex min-w-0 flex-col"><span class="truncate text-xs font-medium">`);
|
|
14
14
|
function ThreadPanel(props) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thread-panel.js","names":["X","ComponentProps","JSX","Show","splitProps","cn","Button","Tooltip","TooltipContent","TooltipTrigger","ThreadPanelProps","label","count","root","Element","composer","headerActions","onClose","ThreadPanel","props","local","rest","_el$","_tmpl$4","_el$2","firstChild","_el$3","_el$4","_el$7","nextSibling","_el$9","_$spread","_$mergeProps","aria-label","class","_$insert","_$createComponent","when","undefined","children","_el$5","_tmpl$","_el$6","as","variant","size","onClick","_el$8","_tmpl$2","_el$0","_tmpl$3","AttachmentCardProps","name","detail","unavailable","icon","AttachmentCard","_el$1","_tmpl$6","_el$10","_el$11","disabled","_el$12","_tmpl$5","_$mergeProps","_$createComponent"],"sources":["../../../src/components/conversation/thread-panel.tsx"],"sourcesContent":["import { X } from 'lucide-solid'\nimport type { ComponentProps, JSX } from 'solid-js'\nimport { Show, splitProps } from 'solid-js'\nimport { cn } from '#lib/utils'\nimport { Button } from '../ui/button/button'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip/tooltip'\n\n/**\n * ThreadPanel.\n *\n * The replies to one message, shown beside the transcript rather than in it. Keeping\n * a thread out of the main flow is what stops a busy side conversation from burying\n * the conversation everyone is having.\n *\n * The root message is repeated at the top — with `pinned` — because a thread read out\n * of context is a set of answers to a question the reader can no longer see.\n *\n * The panel is a `complementary` landmark with a label, so it is reachable by\n * landmark navigation rather than by tabbing through the transcript to find it.\n */\nexport type ThreadPanelProps = ComponentProps<'aside'> & {\n /** The thread's title, usually the root message's author. */\n label: string\n /** A count of replies, shown beside the label. */\n count?: number\n /** The root message, drawn above the replies. */\n root?: JSX.Element\n /** The composer for a reply. */\n composer?: JSX.Element\n /** Host actions, such as marking the thread unread, before the close control. */\n headerActions?: JSX.Element\n onClose?: () => void\n}\n\nexport function ThreadPanel(props: ThreadPanelProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'label',\n 'count',\n 'root',\n 'composer',\n 'headerActions',\n 'onClose',\n 'children',\n ])\n\n return (\n <aside\n data-slot=\"thread-panel\"\n aria-label={`Thread: ${local.label}`}\n class={cn(\n 'bg-surface flex h-full w-96 max-w-full shrink-0 flex-col border-s border-border',\n local.class\n )}\n {...rest}\n >\n <header class=\"flex h-topbar shrink-0 items-center gap-2 border-b border-border px-3\">\n <div class=\"flex min-w-0 flex-col\">\n <span class=\"truncate text-sm font-semibold tracking-tight\">Thread</span>\n <Show when={local.count !== undefined}>\n <span class=\"text-muted-foreground text-2xs\">\n {local.count} {local.count === 1 ? 'reply' : 'replies'}\n </span>\n </Show>\n </div>\n <div class=\"ms-auto flex shrink-0 items-center gap-1\">\n {local.headerActions}\n <Show when={local.onClose}>\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label=\"Close thread\"\n onClick={() => local.onClose?.()}\n >\n <X />\n </TooltipTrigger>\n <TooltipContent>Close thread</TooltipContent>\n </Tooltip>\n </Show>\n </div>\n </header>\n\n <Show when={local.root}>\n <div class=\"bg-muted/40 shrink-0 border-b border-border px-3 py-2\">{local.root}</div>\n </Show>\n\n <div\n class=\"min-h-0 flex-1 overflow-y-auto overscroll-contain\"\n role=\"region\"\n aria-label=\"Thread replies\"\n tabindex=\"0\"\n >\n {local.children}\n </div>\n\n <Show when={local.composer}>\n <div class=\"shrink-0 border-t border-border p-2\">{local.composer}</div>\n </Show>\n </aside>\n )\n}\n\n/**\n * An attachment card.\n *\n * A file is not a link: the reader wants to know what it is and how big it is before\n * deciding to open it, so the media type and size are on the card rather than behind\n * a click. `unavailable` is a real state — an artifact can be deleted, or live on a\n * device this one is not authorized for — and it is drawn as text rather than as a\n * dead link.\n */\nexport type AttachmentCardProps = ComponentProps<'button'> & {\n name: string\n /** A short description of the type and size, e.g. \"image/png · 24,120 bytes\". */\n detail?: string\n /** Draw the card as unavailable, with the reason as the detail. */\n unavailable?: boolean\n icon?: JSX.Element\n}\n\nexport function AttachmentCard(props: AttachmentCardProps) {\n const [local, rest] = splitProps(props, ['class', 'name', 'detail', 'unavailable', 'icon'])\n\n return (\n <button\n type=\"button\"\n disabled={local.unavailable}\n class={cn(\n 'flex max-w-64 items-center gap-2.5 rounded-md border border-border bg-surface-sunken px-2.5 py-2 text-start',\n 'transition-colors ease-out outline-none',\n local.unavailable\n ? 'cursor-not-allowed opacity-60'\n : 'hover:border-input hover:bg-surface-hover',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n '[&_svg]:size-4 [&_svg]:shrink-0',\n local.class\n )}\n {...rest}\n >\n {local.icon}\n <span class=\"flex min-w-0 flex-col\">\n <span class=\"truncate text-xs font-medium\">{local.name}</span>\n <Show when={local.detail}>\n <span class=\"text-muted-foreground truncate text-2xs\">{local.detail}</span>\n </Show>\n </span>\n </button>\n )\n}\n"],"mappings":";;;;;;;;;;;;;AAkCA,SAAgBkB,YAAYC,OAAyB;CACnD,MAAM,CAACC,OAAOC,QAAQjB,WAAWe,OAAO;EACtC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CAAU,CACX;CAED,cAAA;EAAA,IAAAG,OAAAC,QAAA,GAAAC,QAAAF,KAAAG,YAAAC,QAAAF,MAAAC;EAAAC,MAAAD;EAAA,IAAAG,QAAAF,MAAAG,aAAAC,QAAAN,MAAAK;EAAAE,OAAAT,MAAAU,WAAA;GAAA,KAAA,gBAAA;IAAA,OAGgB,WAAWZ,MAAMT;GAAO;GAAA,KAAA,WAAA;IAAA,OAC7BN,GACL,mFACAe,MAAMc,KACR;GAAC;EAAA,GACGb,IAAI,GAAA,OAAA,IAAA;EAAAc,OAAAT,OAAAU,gBAKHjC,MAAI;GAAA,IAACkC,OAAI;IAAA,OAAEjB,MAAMR,UAAU0B,KAAAA;GAAS;GAAA,IAAAC,WAAA;IAAA,IAAAC,QAAAC,OAAA,GAAAC,QAAAF,MAAAf;IAAAU,OAAAK,aAEhCpB,MAAMR,OAAK8B,KAAA;IAAAP,OAAAK,aAAGpB,MAAMR,UAAU,IAAI,UAAU,WAAS,IAAA;IAAA,OAAA4B;GAAA;EAAA,CAAA,GAAA,IAAA;EAAAL,OAAAP,aAKzDR,MAAMJ,eAAa,IAAA;EAAAmB,OAAAP,OAAAQ,gBACnBjC,MAAI;GAAA,IAACkC,OAAI;IAAA,OAAEjB,MAAMH;GAAO;GAAA,IAAAsB,WAAA;IAAA,OAAAH,gBACtB7B,SAAO,EAAA,IAAAgC,WAAA;KAAA,OAAA,CAAAH,gBACL3B,gBAAc;MACbkC,IAAIrC;MACJsC,SAAO;MACPC,MAAI;MAAA,cAAA;MAEJC,eAAe1B,MAAMH,UAAU;MAAC,IAAAsB,WAAA;OAAA,OAAAH,gBAE/BpC,GAAC,CAAA,CAAA;MAAA;KAAA,CAAA,GAAAoC,gBAEH5B,gBAAc,EAAA+B,UAAA,eAAA,CAAA,CAAA;IAAA,EAAA,CAAA;GAAA;EAAA,CAAA,GAAA,IAAA;EAAAJ,OAAAb,MAAAc,gBAMtBjC,MAAI;GAAA,IAACkC,OAAI;IAAA,OAAEjB,MAAMP;GAAI;GAAA,IAAA0B,WAAA;IAAA,IAAAQ,QAAAC,QAAA;IAAAb,OAAAY,aACgD3B,MAAMP,IAAI;IAAA,OAAAkC;GAAA;EAAA,CAAA,GAAAjB,KAAA;EAAAK,OAAAL,
|
|
1
|
+
{"version":3,"file":"thread-panel.js","names":["X","ComponentProps","JSX","Show","splitProps","cn","Button","Tooltip","TooltipContent","TooltipTrigger","ThreadPanelProps","label","count","root","Element","composer","headerActions","onClose","ThreadPanel","props","local","rest","_el$","_tmpl$4","_el$2","firstChild","_el$3","_el$4","_el$7","nextSibling","_el$9","_$spread","_$mergeProps","aria-label","class","_$insert","_$createComponent","when","undefined","children","_el$5","_tmpl$","_el$6","as","variant","size","onClick","_el$8","_tmpl$2","_el$0","_tmpl$3","AttachmentCardProps","name","detail","unavailable","icon","AttachmentCard","_el$1","_tmpl$6","_el$10","_el$11","disabled","_el$12","_tmpl$5","_$mergeProps","_$createComponent"],"sources":["../../../src/components/conversation/thread-panel.tsx"],"sourcesContent":["import { X } from 'lucide-solid'\nimport type { ComponentProps, JSX } from 'solid-js'\nimport { Show, splitProps } from 'solid-js'\nimport { cn } from '#lib/utils'\nimport { Button } from '../ui/button/button'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip/tooltip'\n\n/**\n * ThreadPanel.\n *\n * The replies to one message, shown beside the transcript rather than in it. Keeping\n * a thread out of the main flow is what stops a busy side conversation from burying\n * the conversation everyone is having.\n *\n * The root message is repeated at the top — with `pinned` — because a thread read out\n * of context is a set of answers to a question the reader can no longer see.\n *\n * The panel is a `complementary` landmark with a label, so it is reachable by\n * landmark navigation rather than by tabbing through the transcript to find it.\n */\nexport type ThreadPanelProps = ComponentProps<'aside'> & {\n /** The thread's title, usually the root message's author. */\n label: string\n /** A count of replies, shown beside the label. */\n count?: number\n /** The root message, drawn above the replies. */\n root?: JSX.Element\n /** The composer for a reply. */\n composer?: JSX.Element\n /** Host actions, such as marking the thread unread, before the close control. */\n headerActions?: JSX.Element\n onClose?: () => void\n}\n\nexport function ThreadPanel(props: ThreadPanelProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'label',\n 'count',\n 'root',\n 'composer',\n 'headerActions',\n 'onClose',\n 'children',\n ])\n\n return (\n <aside\n data-slot=\"thread-panel\"\n aria-label={`Thread: ${local.label}`}\n class={cn(\n 'bg-surface flex h-full w-96 max-w-full shrink-0 flex-col border-s border-border',\n local.class\n )}\n {...rest}\n >\n <header class=\"flex h-topbar shrink-0 items-center gap-2 border-b border-border px-3\">\n <div class=\"flex min-w-0 flex-col\">\n <span class=\"truncate text-sm font-semibold tracking-tight\">Thread</span>\n <Show when={local.count !== undefined}>\n <span class=\"text-muted-foreground text-2xs\">\n {local.count} {local.count === 1 ? 'reply' : 'replies'}\n </span>\n </Show>\n </div>\n <div class=\"ms-auto flex shrink-0 items-center gap-1\">\n {local.headerActions}\n <Show when={local.onClose}>\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label=\"Close thread\"\n onClick={() => local.onClose?.()}\n >\n <X />\n </TooltipTrigger>\n <TooltipContent>Close thread</TooltipContent>\n </Tooltip>\n </Show>\n </div>\n </header>\n\n <Show when={local.root}>\n <div class=\"bg-muted/40 shrink-0 border-b border-border px-3 py-2\">{local.root}</div>\n </Show>\n\n {/* The replies carry their own inline padding: a row brings only `px-1`, so\n a bubble at its widest otherwise meets the panel's edge. `px-2` plus the\n row's `px-1` lands on the `px-3` line the header and root use. */}\n <div\n class=\"min-h-0 flex-1 overflow-y-auto overscroll-contain px-2 py-2\"\n role=\"region\"\n aria-label=\"Thread replies\"\n tabindex=\"0\"\n >\n {local.children}\n </div>\n\n <Show when={local.composer}>\n <div class=\"shrink-0 border-t border-border p-2\">{local.composer}</div>\n </Show>\n </aside>\n )\n}\n\n/**\n * An attachment card.\n *\n * A file is not a link: the reader wants to know what it is and how big it is before\n * deciding to open it, so the media type and size are on the card rather than behind\n * a click. `unavailable` is a real state — an artifact can be deleted, or live on a\n * device this one is not authorized for — and it is drawn as text rather than as a\n * dead link.\n */\nexport type AttachmentCardProps = ComponentProps<'button'> & {\n name: string\n /** A short description of the type and size, e.g. \"image/png · 24,120 bytes\". */\n detail?: string\n /** Draw the card as unavailable, with the reason as the detail. */\n unavailable?: boolean\n icon?: JSX.Element\n}\n\nexport function AttachmentCard(props: AttachmentCardProps) {\n const [local, rest] = splitProps(props, ['class', 'name', 'detail', 'unavailable', 'icon'])\n\n return (\n <button\n type=\"button\"\n disabled={local.unavailable}\n class={cn(\n 'flex max-w-64 items-center gap-2.5 rounded-md border border-border bg-surface-sunken px-2.5 py-2 text-start',\n 'transition-colors ease-out outline-none',\n local.unavailable\n ? 'cursor-not-allowed opacity-60'\n : 'hover:border-input hover:bg-surface-hover',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n '[&_svg]:size-4 [&_svg]:shrink-0',\n local.class\n )}\n {...rest}\n >\n {local.icon}\n <span class=\"flex min-w-0 flex-col\">\n <span class=\"truncate text-xs font-medium\">{local.name}</span>\n <Show when={local.detail}>\n <span class=\"text-muted-foreground truncate text-2xs\">{local.detail}</span>\n </Show>\n </span>\n </button>\n )\n}\n"],"mappings":"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"}
|
|
@@ -10,7 +10,7 @@ import { type VariantProps } from '#lib/variants';
|
|
|
10
10
|
* action.
|
|
11
11
|
*/
|
|
12
12
|
export declare const badgeVariants: (props?: ({
|
|
13
|
-
variant?: "default" | "destructive" | "success" | "warning" | "info" | "outline" | "secondary" | "subtle" | null | undefined;
|
|
13
|
+
variant?: "default" | "destructive" | "success" | "warning" | "info" | "outline" | "secondary" | "subtle" | "notification" | null | undefined;
|
|
14
14
|
size?: "sm" | "md" | "lg" | null | undefined;
|
|
15
15
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
16
16
|
type BadgeVariantProps = VariantProps<typeof badgeVariants>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"badge.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/badge/badge.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAC9E,OAAO,EAAmC,KAAK,cAAc,EAAE,MAAM,UAAU,CAAA;AAC/E,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,eAAe,CAAA;AAGtD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;;;
|
|
1
|
+
{"version":3,"file":"badge.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/badge/badge.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAC9E,OAAO,EAAmC,KAAK,cAAc,EAAE,MAAM,UAAU,CAAA;AAC/E,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,eAAe,CAAA;AAGtD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;;;8EAiCzB,CAAA;AAED,KAAK,iBAAiB,GAAG,YAAY,CAAC,OAAO,aAAa,CAAC,CAAA;AAE3D,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,cAAc,GAAG,MAAM,IAAI,gBAAgB,CAC1E,CAAC,EACD,iBAAiB,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CACvC,CAAA;AAED,wBAAgB,KAAK,CAAC,CAAC,SAAS,cAAc,GAAG,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,kCAU5E"}
|
|
@@ -23,7 +23,14 @@ var badgeVariants = cva("inline-flex w-fit shrink-0 items-center justify-center
|
|
|
23
23
|
warning: "bg-warning-subtle text-foreground",
|
|
24
24
|
destructive: "bg-destructive-subtle text-foreground",
|
|
25
25
|
info: "bg-info-subtle text-foreground",
|
|
26
|
-
subtle: "bg-primary-subtle text-primary"
|
|
26
|
+
subtle: "bg-primary-subtle text-primary",
|
|
27
|
+
/**
|
|
28
|
+
* A count that asks for attention, pinned to a corner of another control
|
|
29
|
+
* (a workspace mark, a rail icon). At `sm` it is a 2xs label on whatever
|
|
30
|
+
* surface sits behind the corner, where the destructive tint all but
|
|
31
|
+
* disappears — so it takes the solid, contrast-measured action fill.
|
|
32
|
+
*/
|
|
33
|
+
notification: "bg-destructive-action text-destructive-action-foreground"
|
|
27
34
|
},
|
|
28
35
|
size: {
|
|
29
36
|
sm: "h-4 px-1.5 text-2xs",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"badge.js","names":["Polymorphic","PolymorphicProps","ComponentProps","splitProps","ValidComponent","cva","VariantProps","cn","badgeVariants","variants","variant","default","secondary","outline","success","warning","destructive","info","subtle","size","sm","md","lg","defaultVariants","BadgeVariantProps","BadgeProps","T","class","Badge","props","local","rest","_$createComponent","_$mergeProps","as","_$createComponent","_$mergeProps"],"sources":["../../../../src/components/ui/badge/badge.tsx"],"sourcesContent":["import { Polymorphic, type PolymorphicProps } from '@kobalte/core/polymorphic'\nimport { type ComponentProps, splitProps, type ValidComponent } from 'solid-js'\nimport { cva, type VariantProps } from '#lib/variants'\nimport { cn } from '#lib/utils'\n\n/**\n * Badge.\n *\n * A short, non-interactive status label. If it can be clicked it is a Button\n * with `size=\"2xs\"`, not a Badge — the difference matters because a Badge\n * carries no focus or pressed state and must never be the only way to reach an\n * action.\n */\nexport const badgeVariants = cva(\n 'inline-flex w-fit shrink-0 items-center justify-center gap-1 whitespace-nowrap rounded-md border border-transparent font-medium transition-colors [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:size-3',\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground',\n secondary: 'bg-secondary text-secondary-foreground',\n outline: 'border-border text-foreground',\n /** Tinted status fills, for counts and states rather than actions. */\n success: 'bg-success-subtle text-foreground',\n warning: 'bg-warning-subtle text-foreground',\n destructive: 'bg-destructive-subtle text-foreground',\n info: 'bg-info-subtle text-foreground',\n subtle: 'bg-primary-subtle text-primary',\n },\n size: {\n sm: 'h-4 px-1.5 text-2xs',\n md: 'h-5 px-2 text-xs',\n lg: 'h-6 px-2.5 text-xs',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'md',\n },\n }\n)\n\ntype BadgeVariantProps = VariantProps<typeof badgeVariants>\n\nexport type BadgeProps<T extends ValidComponent = 'span'> = PolymorphicProps<\n T,\n BadgeVariantProps & { class?: string }\n>\n\nexport function Badge<T extends ValidComponent = 'span'>(props: BadgeProps<T>) {\n const [local, rest] = splitProps(props as BadgeProps, ['variant', 'size', 'class'])\n\n return (\n <Polymorphic\n as=\"span\"\n class={cn(badgeVariants({ variant: local.variant, size: local.size }), local.class)}\n {...(rest as ComponentProps<'span'>)}\n />\n )\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,IAAaQ,gBAAgBH,IAC3B,iNACA;CACEI,UAAU;EACRC,SAAS;GACPC,SAAS;GACTC,WAAW;GACXC,SAAS;;GAETC,SAAS;GACTC,SAAS;GACTC,aAAa;GACbC,MAAM;GACNC,QAAQ;
|
|
1
|
+
{"version":3,"file":"badge.js","names":["Polymorphic","PolymorphicProps","ComponentProps","splitProps","ValidComponent","cva","VariantProps","cn","badgeVariants","variants","variant","default","secondary","outline","success","warning","destructive","info","subtle","notification","size","sm","md","lg","defaultVariants","BadgeVariantProps","BadgeProps","T","class","Badge","props","local","rest","_$createComponent","_$mergeProps","as","_$createComponent","_$mergeProps"],"sources":["../../../../src/components/ui/badge/badge.tsx"],"sourcesContent":["import { Polymorphic, type PolymorphicProps } from '@kobalte/core/polymorphic'\nimport { type ComponentProps, splitProps, type ValidComponent } from 'solid-js'\nimport { cva, type VariantProps } from '#lib/variants'\nimport { cn } from '#lib/utils'\n\n/**\n * Badge.\n *\n * A short, non-interactive status label. If it can be clicked it is a Button\n * with `size=\"2xs\"`, not a Badge — the difference matters because a Badge\n * carries no focus or pressed state and must never be the only way to reach an\n * action.\n */\nexport const badgeVariants = cva(\n 'inline-flex w-fit shrink-0 items-center justify-center gap-1 whitespace-nowrap rounded-md border border-transparent font-medium transition-colors [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:size-3',\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground',\n secondary: 'bg-secondary text-secondary-foreground',\n outline: 'border-border text-foreground',\n /** Tinted status fills, for counts and states rather than actions. */\n success: 'bg-success-subtle text-foreground',\n warning: 'bg-warning-subtle text-foreground',\n destructive: 'bg-destructive-subtle text-foreground',\n info: 'bg-info-subtle text-foreground',\n subtle: 'bg-primary-subtle text-primary',\n /**\n * A count that asks for attention, pinned to a corner of another control\n * (a workspace mark, a rail icon). At `sm` it is a 2xs label on whatever\n * surface sits behind the corner, where the destructive tint all but\n * disappears — so it takes the solid, contrast-measured action fill.\n */\n notification: 'bg-destructive-action text-destructive-action-foreground',\n },\n size: {\n sm: 'h-4 px-1.5 text-2xs',\n md: 'h-5 px-2 text-xs',\n lg: 'h-6 px-2.5 text-xs',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'md',\n },\n }\n)\n\ntype BadgeVariantProps = VariantProps<typeof badgeVariants>\n\nexport type BadgeProps<T extends ValidComponent = 'span'> = PolymorphicProps<\n T,\n BadgeVariantProps & { class?: string }\n>\n\nexport function Badge<T extends ValidComponent = 'span'>(props: BadgeProps<T>) {\n const [local, rest] = splitProps(props as BadgeProps, ['variant', 'size', 'class'])\n\n return (\n <Polymorphic\n as=\"span\"\n class={cn(badgeVariants({ variant: local.variant, size: local.size }), local.class)}\n {...(rest as ComponentProps<'span'>)}\n />\n )\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,IAAaQ,gBAAgBH,IAC3B,iNACA;CACEI,UAAU;EACRC,SAAS;GACPC,SAAS;GACTC,WAAW;GACXC,SAAS;;GAETC,SAAS;GACTC,SAAS;GACTC,aAAa;GACbC,MAAM;GACNC,QAAQ;;;;;;;GAORC,cAAc;EAChB;EACAC,MAAM;GACJC,IAAI;GACJC,IAAI;GACJC,IAAI;EACN;CACF;CACAC,iBAAiB;EACfd,SAAS;EACTU,MAAM;CACR;AACF,CACF;AASA,SAAgBS,MAAyCC,OAAsB;CAC7E,MAAM,CAACC,OAAOC,QAAQ7B,WAAW2B,OAAqB;EAAC;EAAW;EAAQ;CAAO,CAAC;CAElF,OAAAG,gBACGjC,aAAWkC,WAAA;EACVC,IAAE;EAAA,KAAA,WAAA;GAAA,OACK5B,GAAGC,cAAc;IAAEE,SAASqB,MAAMrB;IAASU,MAAMW,MAAMX;GAAK,CAAC,GAAGW,MAAMH,KAAK;EAAC;CAAA,GAC9EI,IAA8B,CAAA;AAGzC"}
|
|
@@ -14,6 +14,12 @@ export declare function Tabs(props: ComponentProps<typeof KobalteTabs>): import(
|
|
|
14
14
|
export type TabsListProps = ComponentProps<typeof KobalteTabs.List> & {
|
|
15
15
|
appearance?: 'underline' | 'segmented';
|
|
16
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* The underline appearance's rule is the baseline a horizontal row of tabs sits
|
|
19
|
+
* on. A vertical list has no baseline — its triggers stack — so the rule is
|
|
20
|
+
* dropped there; otherwise it draws as a stray line under the last trigger,
|
|
21
|
+
* spanning only the list's width (the SettingsLayout rail showed exactly that).
|
|
22
|
+
*/
|
|
17
23
|
export declare function TabsList(props: TabsListProps): import("solid-js").JSX.Element;
|
|
18
24
|
export type TabsTriggerProps = ComponentProps<typeof KobalteTabs.Trigger> & {
|
|
19
25
|
appearance?: 'underline' | 'segmented';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/tabs/tabs.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EAAY,cAAc,EAAE,MAAM,UAAU,CAAA;AA2BxD;;;;;;;;;GASG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,cAAc,CAAC,OAAO,WAAW,CAAC,kCAiC7D;AAED,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,IAAI,CAAC,GAAG;IACpE,UAAU,CAAC,EAAE,WAAW,GAAG,WAAW,CAAA;CACvC,CAAA;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,kCAgB5C;AAED,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,OAAO,CAAC,GAAG;IAC1E,UAAU,CAAC,EAAE,WAAW,GAAG,WAAW,CAAA;CACvC,CAAA;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,kCA+BlD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,CAAC,OAAO,WAAW,CAAC,OAAO,CAAC,kCAc5E"}
|
|
1
|
+
{"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/tabs/tabs.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EAAY,cAAc,EAAE,MAAM,UAAU,CAAA;AA2BxD;;;;;;;;;GASG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,cAAc,CAAC,OAAO,WAAW,CAAC,kCAiC7D;AAED,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,IAAI,CAAC,GAAG;IACpE,UAAU,CAAC,EAAE,WAAW,GAAG,WAAW,CAAA;CACvC,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,kCAgB5C;AAED,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,OAAO,CAAC,GAAG;IAC1E,UAAU,CAAC,EAAE,WAAW,GAAG,WAAW,CAAA;CACvC,CAAA;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,kCA+BlD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,CAAC,OAAO,WAAW,CAAC,OAAO,CAAC,kCAc5E"}
|
|
@@ -53,10 +53,16 @@ function Tabs$1(props) {
|
|
|
53
53
|
}
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* The underline appearance's rule is the baseline a horizontal row of tabs sits
|
|
58
|
+
* on. A vertical list has no baseline — its triggers stack — so the rule is
|
|
59
|
+
* dropped there; otherwise it draws as a stray line under the last trigger,
|
|
60
|
+
* spanning only the list's width (the SettingsLayout rail showed exactly that).
|
|
61
|
+
*/
|
|
56
62
|
function TabsList(props) {
|
|
57
63
|
const [local, rest] = splitProps(props, ["class", "appearance"]);
|
|
58
64
|
return createComponent(Tabs.List, mergeProps({ get ["class"]() {
|
|
59
|
-
return cn("inline-flex w-fit shrink-0 items-center justify-center", local.appearance === "segmented" ? "gap-0.5 rounded-lg bg-surface-hover p-0.5" : "gap-1 rounded-none border-b border-border", "data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch", local.class);
|
|
65
|
+
return cn("inline-flex w-fit shrink-0 items-center justify-center", local.appearance === "segmented" ? "gap-0.5 rounded-lg bg-surface-hover p-0.5" : "gap-1 rounded-none border-b border-border data-[orientation=vertical]:border-b-0", "data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch", local.class);
|
|
60
66
|
} }, rest));
|
|
61
67
|
}
|
|
62
68
|
function TabsTrigger(props) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabs.js","names":["Tabs","KobalteTabs","Accessor","ComponentProps","createContext","createRenderEffect","createSignal","createUniqueId","onCleanup","splitProps","useContext","cn","TabsIdContextValue","id","triggerIds","ReadonlyMap","registerTriggerId","value","unregisterTriggerId","TabsIdContext","useTabsId","tabs","Error","props","local","rest","generatedId","setTriggerIds","Map","triggerId","current","set","get","next","delete","_$createComponent","Provider","children","_$mergeProps","class","TabsListProps","List","appearance","TabsList","TabsTriggerProps","Trigger","TabsTrigger","TabsContent","Content","aria-labelledby","Tabs","_$createComponent","KobalteTabs","_$mergeProps"],"sources":["../../../../src/components/ui/tabs/tabs.tsx"],"sourcesContent":["import { Tabs as KobalteTabs } from '@kobalte/core/tabs'\nimport type { Accessor, ComponentProps } from 'solid-js'\nimport {\n createContext,\n createRenderEffect,\n createSignal,\n createUniqueId,\n onCleanup,\n splitProps,\n useContext,\n} from 'solid-js'\nimport { cn } from '#lib/utils'\n\ntype TabsIdContextValue = {\n id: Accessor<string>\n triggerIds: Accessor<ReadonlyMap<string, string>>\n registerTriggerId: (value: string, id: string) => void\n unregisterTriggerId: (value: string, id: string) => void\n}\n\nconst TabsIdContext = createContext<TabsIdContextValue>()\n\nfunction useTabsId() {\n const tabs = useContext(TabsIdContext)\n if (!tabs) throw new Error('[adea-ui]: Tabs parts must be used within Tabs.')\n return tabs\n}\n\n/**\n * Tabs.\n *\n * For switching between sibling views of the *same* subject — a file's code,\n * diff and history. Not for moving between pages: a tab is expected to keep\n * its panel and its scroll position, which a route does not.\n *\n * `appearance=\"segmented\"` gives the joined bar KiroCrew uses inside a pane\n * where a full-width underline would collide with the panel's own header rule.\n */\nexport function Tabs(props: ComponentProps<typeof KobalteTabs>) {\n const [local, rest] = splitProps(props, ['class', 'id'])\n const generatedId = `tabs-${createUniqueId()}`\n const id = () => local.id ?? generatedId\n const [triggerIds, setTriggerIds] = createSignal<ReadonlyMap<string, string>>(new Map())\n const tabs: TabsIdContextValue = {\n id,\n triggerIds,\n registerTriggerId: (value, triggerId) => {\n setTriggerIds((current) => new Map(current).set(value, triggerId))\n },\n unregisterTriggerId: (value, triggerId) => {\n setTriggerIds((current) => {\n if (current.get(value) !== triggerId) return current\n const next = new Map(current)\n next.delete(value)\n return next\n })\n },\n }\n\n return (\n <TabsIdContext.Provider value={tabs}>\n <KobalteTabs\n id={id()}\n class={cn(\n 'flex min-h-0 min-w-0 flex-col gap-2 data-[orientation=vertical]:flex-row',\n local.class\n )}\n {...rest}\n />\n </TabsIdContext.Provider>\n )\n}\n\nexport type TabsListProps = ComponentProps<typeof KobalteTabs.List> & {\n appearance?: 'underline' | 'segmented'\n}\n\nexport function TabsList(props: TabsListProps) {\n const [local, rest] = splitProps(props, ['class', 'appearance'])\n\n return (\n <KobalteTabs.List\n class={cn(\n 'inline-flex w-fit shrink-0 items-center justify-center',\n local.appearance === 'segmented'\n ? 'gap-0.5 rounded-lg bg-surface-hover p-0.5'\n : 'gap-1 rounded-none border-b border-border',\n 'data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch',\n local.class\n )}\n {...rest}\n />\n )\n}\n\nexport type TabsTriggerProps = ComponentProps<typeof KobalteTabs.Trigger> & {\n appearance?: 'underline' | 'segmented'\n}\n\nexport function TabsTrigger(props: TabsTriggerProps) {\n const [local, rest] = splitProps(props, ['class', 'appearance', 'id'])\n const tabs = useTabsId()\n const triggerId = () => local.id ?? `${tabs.id()}-trigger-${rest.value}`\n\n // Register before panels render, including SSR where createEffect is skipped.\n createRenderEffect(() => {\n const value = rest.value\n const id = triggerId()\n tabs.registerTriggerId(value, id)\n onCleanup(() => tabs.unregisterTriggerId(value, id))\n })\n\n return (\n <KobalteTabs.Trigger\n id={triggerId()}\n class={cn(\n 'inline-flex shrink-0 items-center justify-center gap-1.5 whitespace-nowrap text-sm font-medium',\n 'transition-colors ease-out outline-none select-none',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n 'disabled:pointer-events-none disabled:opacity-50',\n 'text-foreground',\n '[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:size-4',\n local.appearance === 'segmented'\n ? 'h-control-sm rounded-md px-control-sm data-[selected]:bg-card data-[selected]:shadow-xs'\n : 'h-control-md -mb-px rounded-none border-b-2 border-transparent px-2 pb-2 data-[selected]:border-primary',\n local.class\n )}\n {...rest}\n />\n )\n}\n\nexport function TabsContent(props: ComponentProps<typeof KobalteTabs.Content>) {\n const [local, rest] = splitProps(props, ['class', 'aria-labelledby'])\n const tabs = useTabsId()\n return (\n <KobalteTabs.Content\n aria-labelledby={\n local['aria-labelledby'] ??\n tabs.triggerIds().get(rest.value) ??\n `${tabs.id()}-trigger-${rest.value}`\n }\n class={cn('min-h-0 min-w-0 flex-1 outline-none', local.class)}\n {...rest}\n />\n )\n}\n"],"mappings":";;;;;AAoBA,IAAMmB,gBAAgBf,cAAkC;AAExD,SAASgB,YAAY;CACnB,MAAMC,OAAOX,WAAWS,aAAa;CACrC,IAAI,CAACE,MAAM,MAAM,IAAIC,MAAM,iDAAiD;CAC5E,OAAOD;AACT;;;;;;;;;;;AAYA,SAAgBrB,OAAKuB,OAA2C;CAC9D,MAAM,CAACC,OAAOC,QAAQhB,WAAWc,OAAO,CAAC,SAAS,IAAI,CAAC;CACvD,MAAMG,cAAc,QAAQnB,eAAe;CAC3C,MAAMM,WAAWW,MAAMX,MAAMa;CAC7B,MAAM,CAACZ,YAAYa,iBAAiBrB,6BAA0C,IAAIsB,IAAI,CAAC;CACvF,MAAMP,OAA2B;EAC/BR;EACAC;EACAE,oBAAoBC,OAAOY,cAAc;GACvCF,eAAeG,YAAY,IAAIF,IAAIE,OAAO,CAAC,CAACC,IAAId,OAAOY,SAAS,CAAC;EACnE;EACAX,sBAAsBD,OAAOY,cAAc;GACzCF,eAAeG,YAAY;IACzB,IAAIA,QAAQE,IAAIf,KAAK,MAAMY,WAAW,OAAOC;IAC7C,MAAMG,OAAO,IAAIL,IAAIE,OAAO;IAC5BG,KAAKC,OAAOjB,KAAK;IACjB,OAAOgB;GACT,CAAC;EACH;CACF;CAEA,OAAAE,gBACGhB,cAAciB,UAAQ;EAACnB,OAAOI;EAAI,IAAAgB,WAAA;GAAA,OAAAF,gBAChClC,MAAWqC,WAAA;IAAA,IACVzB,KAAE;KAAA,OAAEA,GAAG;IAAC;IAAA,KAAA,WAAA;KAAA,OACDF,GACL,4EACAa,MAAMe,KACR;IAAC;GAAA,GACGd,IAAI,CAAA;EAAA;CAAA,CAAA;AAIhB
|
|
1
|
+
{"version":3,"file":"tabs.js","names":["Tabs","KobalteTabs","Accessor","ComponentProps","createContext","createRenderEffect","createSignal","createUniqueId","onCleanup","splitProps","useContext","cn","TabsIdContextValue","id","triggerIds","ReadonlyMap","registerTriggerId","value","unregisterTriggerId","TabsIdContext","useTabsId","tabs","Error","props","local","rest","generatedId","setTriggerIds","Map","triggerId","current","set","get","next","delete","_$createComponent","Provider","children","_$mergeProps","class","TabsListProps","List","appearance","TabsList","TabsTriggerProps","Trigger","TabsTrigger","TabsContent","Content","aria-labelledby","Tabs","_$createComponent","KobalteTabs","_$mergeProps"],"sources":["../../../../src/components/ui/tabs/tabs.tsx"],"sourcesContent":["import { Tabs as KobalteTabs } from '@kobalte/core/tabs'\nimport type { Accessor, ComponentProps } from 'solid-js'\nimport {\n createContext,\n createRenderEffect,\n createSignal,\n createUniqueId,\n onCleanup,\n splitProps,\n useContext,\n} from 'solid-js'\nimport { cn } from '#lib/utils'\n\ntype TabsIdContextValue = {\n id: Accessor<string>\n triggerIds: Accessor<ReadonlyMap<string, string>>\n registerTriggerId: (value: string, id: string) => void\n unregisterTriggerId: (value: string, id: string) => void\n}\n\nconst TabsIdContext = createContext<TabsIdContextValue>()\n\nfunction useTabsId() {\n const tabs = useContext(TabsIdContext)\n if (!tabs) throw new Error('[adea-ui]: Tabs parts must be used within Tabs.')\n return tabs\n}\n\n/**\n * Tabs.\n *\n * For switching between sibling views of the *same* subject — a file's code,\n * diff and history. Not for moving between pages: a tab is expected to keep\n * its panel and its scroll position, which a route does not.\n *\n * `appearance=\"segmented\"` gives the joined bar KiroCrew uses inside a pane\n * where a full-width underline would collide with the panel's own header rule.\n */\nexport function Tabs(props: ComponentProps<typeof KobalteTabs>) {\n const [local, rest] = splitProps(props, ['class', 'id'])\n const generatedId = `tabs-${createUniqueId()}`\n const id = () => local.id ?? generatedId\n const [triggerIds, setTriggerIds] = createSignal<ReadonlyMap<string, string>>(new Map())\n const tabs: TabsIdContextValue = {\n id,\n triggerIds,\n registerTriggerId: (value, triggerId) => {\n setTriggerIds((current) => new Map(current).set(value, triggerId))\n },\n unregisterTriggerId: (value, triggerId) => {\n setTriggerIds((current) => {\n if (current.get(value) !== triggerId) return current\n const next = new Map(current)\n next.delete(value)\n return next\n })\n },\n }\n\n return (\n <TabsIdContext.Provider value={tabs}>\n <KobalteTabs\n id={id()}\n class={cn(\n 'flex min-h-0 min-w-0 flex-col gap-2 data-[orientation=vertical]:flex-row',\n local.class\n )}\n {...rest}\n />\n </TabsIdContext.Provider>\n )\n}\n\nexport type TabsListProps = ComponentProps<typeof KobalteTabs.List> & {\n appearance?: 'underline' | 'segmented'\n}\n\n/**\n * The underline appearance's rule is the baseline a horizontal row of tabs sits\n * on. A vertical list has no baseline — its triggers stack — so the rule is\n * dropped there; otherwise it draws as a stray line under the last trigger,\n * spanning only the list's width (the SettingsLayout rail showed exactly that).\n */\nexport function TabsList(props: TabsListProps) {\n const [local, rest] = splitProps(props, ['class', 'appearance'])\n\n return (\n <KobalteTabs.List\n class={cn(\n 'inline-flex w-fit shrink-0 items-center justify-center',\n local.appearance === 'segmented'\n ? 'gap-0.5 rounded-lg bg-surface-hover p-0.5'\n : 'gap-1 rounded-none border-b border-border data-[orientation=vertical]:border-b-0',\n 'data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch',\n local.class\n )}\n {...rest}\n />\n )\n}\n\nexport type TabsTriggerProps = ComponentProps<typeof KobalteTabs.Trigger> & {\n appearance?: 'underline' | 'segmented'\n}\n\nexport function TabsTrigger(props: TabsTriggerProps) {\n const [local, rest] = splitProps(props, ['class', 'appearance', 'id'])\n const tabs = useTabsId()\n const triggerId = () => local.id ?? `${tabs.id()}-trigger-${rest.value}`\n\n // Register before panels render, including SSR where createEffect is skipped.\n createRenderEffect(() => {\n const value = rest.value\n const id = triggerId()\n tabs.registerTriggerId(value, id)\n onCleanup(() => tabs.unregisterTriggerId(value, id))\n })\n\n return (\n <KobalteTabs.Trigger\n id={triggerId()}\n class={cn(\n 'inline-flex shrink-0 items-center justify-center gap-1.5 whitespace-nowrap text-sm font-medium',\n 'transition-colors ease-out outline-none select-none',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n 'disabled:pointer-events-none disabled:opacity-50',\n 'text-foreground',\n '[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:size-4',\n local.appearance === 'segmented'\n ? 'h-control-sm rounded-md px-control-sm data-[selected]:bg-card data-[selected]:shadow-xs'\n : 'h-control-md -mb-px rounded-none border-b-2 border-transparent px-2 pb-2 data-[selected]:border-primary',\n local.class\n )}\n {...rest}\n />\n )\n}\n\nexport function TabsContent(props: ComponentProps<typeof KobalteTabs.Content>) {\n const [local, rest] = splitProps(props, ['class', 'aria-labelledby'])\n const tabs = useTabsId()\n return (\n <KobalteTabs.Content\n aria-labelledby={\n local['aria-labelledby'] ??\n tabs.triggerIds().get(rest.value) ??\n `${tabs.id()}-trigger-${rest.value}`\n }\n class={cn('min-h-0 min-w-0 flex-1 outline-none', local.class)}\n {...rest}\n />\n )\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/tooltip/tooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,cAAc,EAAqB,MAAM,uBAAuB,CAAA;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAyI9C,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAA;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B,CAAA;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,kCAY1D;AAED,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,cAAc,CAAC,CAAA;AAEpE,wBAAgB,OAAO,CAAC,KAAK,EAAE,gBAAgB,kCAgJ9C;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,CAAC,OAAO,cAAc,CAAC,OAAO,CAAC,kCA0ClF;AAED,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,cAAc,CAAC,OAAO,CAAC,CAAA;AAE/E,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,
|
|
1
|
+
{"version":3,"file":"tooltip.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/tooltip/tooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,cAAc,EAAqB,MAAM,uBAAuB,CAAA;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAyI9C,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAA;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B,CAAA;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,kCAY1D;AAED,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,cAAc,CAAC,CAAA;AAEpE,wBAAgB,OAAO,CAAC,KAAK,EAAE,gBAAgB,kCAgJ9C;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,CAAC,OAAO,cAAc,CAAC,OAAO,CAAC,kCA0ClF;AAED,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,cAAc,CAAC,OAAO,CAAC,CAAA;AAE/E,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,kCA8CxD"}
|
|
@@ -264,6 +264,7 @@ function TooltipContent(props) {
|
|
|
264
264
|
const context = useTooltipContext();
|
|
265
265
|
return createComponent(Tooltip.Portal, { get children() {
|
|
266
266
|
return createComponent(Tooltip.Content, mergeProps({
|
|
267
|
+
"data-slot": "tooltip-content",
|
|
267
268
|
get ["class"]() {
|
|
268
269
|
return cn(tooltipTip, "z-(--z-tooltip) w-fit max-w-(--tooltip-max-width) px-2 py-1", "origin-(--kb-tooltip-content-transform-origin) text-balance", "data-expanded:animate-in data-expanded:fade-in-0 data-expanded:zoom-in-95", "data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", "data-expanded:duration-150 data-closed:duration-100", local.class);
|
|
269
270
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip.js","names":["Tooltip","KobalteTooltip","useTooltipContext","ComponentProps","createContext","createEffect","createSignal","createUniqueId","onCleanup","splitProps","useContext","isServer","tooltipTip","cn","TooltipTiming","openDelay","closeDelay","skipDelayDuration","tooltipDefaults","TooltipTimingContext","TooltipInteraction","id","trigger","HTMLElement","setTrigger","element","isOpen","isDisabled","isTriggerFocused","markOpenIntent","clearOpenIntent","requestCloseFromAnotherTooltip","markPointerCloseIntent","clearPointerCloseIntent","markCloseIntent","TooltipInteractionContext","openTooltipInteractions","Set","pendingTooltipOpenId","tooltipScrollListenerAttached","clearPendingTooltipOpen","undefined","tooltipScrollContainsTrigger","event","Event","target","window","Node","contains","handleTooltipScroll","interaction","handleTooltipPointerMove","PointerEvent","setTooltipInteractionOpen","open","has","add","addEventListener","document","delete","size","removeEventListener","registerTooltipInteraction","invokeEventHandler","handler","Array","isArray","callback","data","TooltipProviderProps","children","TooltipProvider","props","_$createComponent","Provider","value","TooltipRootProps","timing","local","rest","controlled","uncontrolledOpen","setUncontrolledOpen","defaultOpen","closeIntent","ignoreDuplicateClose","onOpenChange","next","source","pointerCloseIntentUntil","pointerCloseIntentTimeout","ReturnType","setTimeout","clearTimeout","Date","now","queueMicrotask","disabled","Boolean","activeElement","openInteraction","focusedTrigger","anotherTooltipOpening","pointerClosePending","handleKeyDown","KeyboardEvent","key","_$mergeProps","TooltipTrigger","Trigger","ref","onFocus","defaultPrevented","onBlur","onPointerEnter","onPointerLeave","onClick","TooltipContentProps","Content","TooltipContent","context","Portal","class","aria-hidden","_$memo","onEscapeKeyDown","onPointerDownOutside","_$createComponent","Tooltip","KobalteTooltip","_$mergeProps","_$memo"],"sources":["../../../../src/components/ui/tooltip/tooltip.tsx"],"sourcesContent":["import { Tooltip as KobalteTooltip, useTooltipContext } from '@kobalte/core/tooltip'\nimport type { ComponentProps } from 'solid-js'\nimport {\n createContext,\n createEffect,\n createSignal,\n createUniqueId,\n onCleanup,\n splitProps,\n useContext,\n} from 'solid-js'\nimport { isServer } from 'solid-js/web'\nimport { tooltipTip } from '#lib/overlay'\nimport { cn } from '#lib/utils'\n\n/**\n * The provider sets the *timing* defaults for every tooltip below it.\n *\n * Timing is the part of a tooltip that has to agree across an app: one that\n * opens in 200ms in a toolbar and 700ms in a sidebar reads as a bug. The\n * provider is where that agreement lives. It is optional — a tooltip outside a\n * provider falls back to the same defaults — so a consumer can drop it in\n * without restructuring anything.\n */\ntype TooltipTiming = {\n openDelay: number\n closeDelay: number\n skipDelayDuration: number\n}\n\nconst tooltipDefaults: TooltipTiming = {\n /** Long enough that a pointer crossing a toolbar does not strobe tooltips. */\n openDelay: 400,\n /** Short, because the pointer has already left the control. */\n closeDelay: 80,\n /**\n * After a tooltip has been seen, the next one opens immediately for this\n * long. This is what makes moving along a row of icons feel like one\n * gesture instead of four separate waits.\n */\n skipDelayDuration: 500,\n}\n\nconst TooltipTimingContext = createContext<TooltipTiming>(tooltipDefaults)\n\ntype TooltipInteraction = {\n id: string\n trigger: () => HTMLElement | undefined\n setTrigger: (element: HTMLElement) => void\n isOpen: () => boolean\n isDisabled: () => boolean\n isTriggerFocused: () => boolean\n markOpenIntent: () => void\n clearOpenIntent: () => void\n requestCloseFromAnotherTooltip: () => void\n markPointerCloseIntent: () => void\n clearPointerCloseIntent: () => void\n markCloseIntent: () => void\n}\n\nconst TooltipInteractionContext = createContext<TooltipInteraction>()\nconst openTooltipInteractions = new Set<TooltipInteraction>()\nlet pendingTooltipOpenId: string | undefined\nlet tooltipScrollListenerAttached = false\n\nfunction clearPendingTooltipOpen(id: string) {\n if (pendingTooltipOpenId === id) pendingTooltipOpenId = undefined\n}\n\nfunction tooltipScrollContainsTrigger(event: Event, trigger: HTMLElement) {\n const target = event.target\n return target === window || (target instanceof Node && target.contains(trigger))\n}\n\nfunction handleTooltipScroll(event: Event) {\n for (const interaction of openTooltipInteractions) {\n const trigger = interaction.trigger()\n if (interaction.isOpen() && trigger && tooltipScrollContainsTrigger(event, trigger)) {\n interaction.markCloseIntent()\n }\n }\n}\n\nfunction handleTooltipPointerMove(event: PointerEvent) {\n for (const interaction of openTooltipInteractions) {\n const trigger = interaction.trigger()\n const target = event.target\n if (\n interaction.isOpen() &&\n interaction.isTriggerFocused() &&\n trigger &&\n !(target instanceof Node && trigger.contains(target))\n ) {\n interaction.markPointerCloseIntent()\n }\n }\n}\n\nfunction setTooltipInteractionOpen(interaction: TooltipInteraction, open: boolean) {\n if (isServer) return\n\n if (open) {\n if (openTooltipInteractions.has(interaction)) return\n openTooltipInteractions.add(interaction)\n if (!tooltipScrollListenerAttached) {\n tooltipScrollListenerAttached = true\n // Register before Kobalte's global listeners so intentional scroll\n // dismissal can be distinguished from its delayed pointer dismissal.\n window.addEventListener('scroll', handleTooltipScroll, true)\n document.addEventListener('pointermove', handleTooltipPointerMove, true)\n }\n } else {\n openTooltipInteractions.delete(interaction)\n if (openTooltipInteractions.size === 0 && tooltipScrollListenerAttached) {\n tooltipScrollListenerAttached = false\n window.removeEventListener('scroll', handleTooltipScroll, true)\n document.removeEventListener('pointermove', handleTooltipPointerMove, true)\n }\n }\n}\n\nfunction registerTooltipInteraction(interaction: TooltipInteraction) {\n onCleanup(() => {\n setTooltipInteractionOpen(interaction, false)\n clearPendingTooltipOpen(interaction.id)\n })\n}\n\nfunction invokeEventHandler(handler: unknown, event: Event) {\n if (Array.isArray(handler)) {\n const [callback, data] = handler as [(data: unknown, event: Event) => void, unknown]\n callback(data, event)\n } else if (typeof handler === 'function') {\n const callback = handler as (event: Event) => void\n callback(event)\n }\n}\n\nexport type TooltipProviderProps = {\n children?: ComponentProps<'div'>['children']\n openDelay?: number\n closeDelay?: number\n skipDelayDuration?: number\n}\n\nexport function TooltipProvider(props: TooltipProviderProps) {\n return (\n <TooltipTimingContext.Provider\n value={{\n openDelay: props.openDelay ?? tooltipDefaults.openDelay,\n closeDelay: props.closeDelay ?? tooltipDefaults.closeDelay,\n skipDelayDuration: props.skipDelayDuration ?? tooltipDefaults.skipDelayDuration,\n }}\n >\n {props.children}\n </TooltipTimingContext.Provider>\n )\n}\n\nexport type TooltipRootProps = ComponentProps<typeof KobalteTooltip>\n\nexport function Tooltip(props: TooltipRootProps) {\n const timing = useContext(TooltipTimingContext)\n const [local, rest] = splitProps(props, ['open', 'defaultOpen', 'onOpenChange', 'disabled'])\n const id = createUniqueId()\n const controlled = () => local.open !== undefined\n const [uncontrolledOpen, setUncontrolledOpen] = createSignal(local.defaultOpen ?? false)\n const isOpen = () => (controlled() ? (local.open ?? false) : uncontrolledOpen())\n const [trigger, setTrigger] = createSignal<HTMLElement>()\n let closeIntent = false\n let ignoreDuplicateClose = false\n let onOpenChange: (next: boolean, source?: 'handoff') => void\n let pointerCloseIntentUntil = 0\n let pointerCloseIntentTimeout: ReturnType<typeof setTimeout> | undefined\n const clearPointerCloseIntent = () => {\n pointerCloseIntentUntil = 0\n if (pointerCloseIntentTimeout) clearTimeout(pointerCloseIntentTimeout)\n pointerCloseIntentTimeout = undefined\n }\n const markPointerCloseIntent = () => {\n if (!isOpen()) return\n clearPointerCloseIntent()\n const closeDelay = props.closeDelay ?? timing.closeDelay\n pointerCloseIntentUntil = Date.now() + closeDelay + 50\n pointerCloseIntentTimeout = setTimeout(clearPointerCloseIntent, closeDelay + 50)\n }\n const markCloseIntent = () => {\n closeIntent = true\n clearPointerCloseIntent()\n queueMicrotask(() => {\n closeIntent = false\n })\n }\n const requestCloseFromAnotherTooltip = () => {\n if (!isOpen()) return\n\n // Another trigger is an explicit handoff, even when a focused tooltip has\n // a pending pointer-close intent that would otherwise suppress Kobalte's\n // close callback. Route the request through the same controlled/uncontrolled\n // state contract as Kobalte and ignore its duplicate callback for this turn.\n ignoreDuplicateClose = true\n queueMicrotask(() => {\n ignoreDuplicateClose = false\n })\n onOpenChange(false, 'handoff')\n }\n const interaction: TooltipInteraction = {\n id,\n trigger,\n setTrigger,\n isOpen,\n isDisabled: () => local.disabled ?? false,\n isTriggerFocused: () =>\n Boolean(trigger() && typeof document !== 'undefined' && trigger() === document.activeElement),\n markOpenIntent: () => {\n clearPointerCloseIntent()\n pendingTooltipOpenId = id\n // A focused tooltip ignores ordinary pointer movement, but entering or\n // focusing another tooltip is an explicit transfer.\n for (const openInteraction of openTooltipInteractions) {\n if (openInteraction.id !== id) openInteraction.requestCloseFromAnotherTooltip()\n }\n },\n clearOpenIntent: () => clearPendingTooltipOpen(id),\n requestCloseFromAnotherTooltip,\n markPointerCloseIntent,\n clearPointerCloseIntent,\n markCloseIntent,\n }\n registerTooltipInteraction(interaction)\n onCleanup(() => {\n clearPointerCloseIntent()\n ignoreDuplicateClose = false\n })\n if (isOpen()) setTooltipInteractionOpen(interaction, true)\n createEffect(() => setTooltipInteractionOpen(interaction, isOpen()))\n onOpenChange = (next: boolean, source) => {\n if (!next && source !== 'handoff' && ignoreDuplicateClose) return\n\n if (next) {\n clearPendingTooltipOpen(id)\n clearPointerCloseIntent()\n // Install this capture listener before Kobalte's open-state effect adds\n // its scroll listener later in the same update.\n setTooltipInteractionOpen(interaction, true)\n } else {\n const focusedTrigger = interaction.isTriggerFocused()\n const anotherTooltipOpening =\n pendingTooltipOpenId !== undefined && pendingTooltipOpenId !== id\n const pointerClosePending = Date.now() <= pointerCloseIntentUntil\n if (\n source !== 'handoff' &&\n focusedTrigger &&\n pointerClosePending &&\n !closeIntent &&\n !anotherTooltipOpening\n ) {\n clearPointerCloseIntent()\n return\n }\n closeIntent = false\n clearPointerCloseIntent()\n clearPendingTooltipOpen(id)\n }\n\n if (!controlled()) setUncontrolledOpen(next)\n local.onOpenChange?.(next)\n if (next) {\n // A controlled parent can reject the open request without changing its\n // signal, so the effect above would not rerun to release this listener.\n queueMicrotask(() => {\n if (!isOpen()) setTooltipInteractionOpen(interaction, false)\n })\n }\n }\n\n // Kobalte dismisses on Escape only when its content owns the top-most\n // layer, and a handoff keeps the previous tooltip's content mounted while\n // it exits — swallowing the key for the tooltip that just took over.\n // Listening on document (capture) closes this tooltip first; the layer's\n // own handler still runs for anything stacked above it.\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return\n markCloseIntent()\n onOpenChange(false)\n }\n createEffect(() => {\n if (isServer || !isOpen()) return\n document.addEventListener('keydown', handleKeyDown, true)\n onCleanup(() => document.removeEventListener('keydown', handleKeyDown, true))\n })\n\n return (\n <TooltipInteractionContext.Provider value={interaction}>\n <KobalteTooltip\n open={isOpen()}\n onOpenChange={onOpenChange}\n disabled={local.disabled}\n openDelay={props.openDelay ?? timing.openDelay}\n closeDelay={props.closeDelay ?? timing.closeDelay}\n skipDelayDuration={props.skipDelayDuration ?? timing.skipDelayDuration}\n {...rest}\n />\n </TooltipInteractionContext.Provider>\n )\n}\n\nexport function TooltipTrigger(props: ComponentProps<typeof KobalteTooltip.Trigger>) {\n const interaction = useContext(TooltipInteractionContext)\n const [local, rest] = splitProps(props, [\n 'ref',\n 'onFocus',\n 'onBlur',\n 'onPointerEnter',\n 'onPointerLeave',\n 'onClick',\n ])\n\n return (\n <KobalteTooltip.Trigger\n {...rest}\n ref={(element) => {\n interaction?.setTrigger(element)\n if (typeof local.ref === 'function') local.ref(element)\n }}\n onFocus={(event) => {\n invokeEventHandler(local.onFocus, event)\n if (!event.defaultPrevented && !interaction?.isDisabled()) interaction?.markOpenIntent()\n }}\n onBlur={(event) => {\n invokeEventHandler(local.onBlur, event)\n interaction?.clearOpenIntent()\n interaction?.markCloseIntent()\n }}\n onPointerEnter={(event) => {\n invokeEventHandler(local.onPointerEnter, event)\n if (!event.defaultPrevented && !interaction?.isDisabled()) interaction?.markOpenIntent()\n }}\n onPointerLeave={(event) => {\n invokeEventHandler(local.onPointerLeave, event)\n interaction?.clearOpenIntent()\n interaction?.markPointerCloseIntent()\n }}\n onClick={(event) => {\n invokeEventHandler(local.onClick, event)\n interaction?.markCloseIntent()\n }}\n />\n )\n}\n\nexport type TooltipContentProps = ComponentProps<typeof KobalteTooltip.Content>\n\nexport function TooltipContent(props: TooltipContentProps) {\n const interaction = useContext(TooltipInteractionContext)\n const [local, rest] = splitProps(props, [\n 'class',\n 'children',\n 'aria-hidden',\n 'onEscapeKeyDown',\n 'onPointerDownOutside',\n ])\n const context = useTooltipContext()\n\n // Kobalte keeps force-mounted and exiting content present after close. Hide\n // that retained tooltip from assistive technology until it opens again.\n return (\n <KobalteTooltip.Portal>\n <KobalteTooltip.Content\n class={cn(\n tooltipTip,\n // The max width is a token, not a fixed measure: it has to fall\n // behind the viewport at large root font sizes or the tip — which\n // Kobalte can shift but never shrink — forces a horizontal scroll.\n 'z-(--z-tooltip) w-fit max-w-(--tooltip-max-width) px-2 py-1',\n 'origin-(--kb-tooltip-content-transform-origin) text-balance',\n 'data-expanded:animate-in data-expanded:fade-in-0 data-expanded:zoom-in-95',\n 'data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',\n 'data-expanded:duration-150 data-closed:duration-100',\n local.class\n )}\n aria-hidden={context.isOpen() ? local['aria-hidden'] : true}\n onEscapeKeyDown={(event) => {\n invokeEventHandler(local.onEscapeKeyDown, event)\n if (!event.defaultPrevented) interaction?.markCloseIntent()\n }}\n onPointerDownOutside={(event) => {\n invokeEventHandler(local.onPointerDownOutside, event)\n if (!event.defaultPrevented) interaction?.markCloseIntent()\n }}\n {...rest}\n >\n {local.children}\n </KobalteTooltip.Content>\n </KobalteTooltip.Portal>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tooltip.js","names":["Tooltip","KobalteTooltip","useTooltipContext","ComponentProps","createContext","createEffect","createSignal","createUniqueId","onCleanup","splitProps","useContext","isServer","tooltipTip","cn","TooltipTiming","openDelay","closeDelay","skipDelayDuration","tooltipDefaults","TooltipTimingContext","TooltipInteraction","id","trigger","HTMLElement","setTrigger","element","isOpen","isDisabled","isTriggerFocused","markOpenIntent","clearOpenIntent","requestCloseFromAnotherTooltip","markPointerCloseIntent","clearPointerCloseIntent","markCloseIntent","TooltipInteractionContext","openTooltipInteractions","Set","pendingTooltipOpenId","tooltipScrollListenerAttached","clearPendingTooltipOpen","undefined","tooltipScrollContainsTrigger","event","Event","target","window","Node","contains","handleTooltipScroll","interaction","handleTooltipPointerMove","PointerEvent","setTooltipInteractionOpen","open","has","add","addEventListener","document","delete","size","removeEventListener","registerTooltipInteraction","invokeEventHandler","handler","Array","isArray","callback","data","TooltipProviderProps","children","TooltipProvider","props","_$createComponent","Provider","value","TooltipRootProps","timing","local","rest","controlled","uncontrolledOpen","setUncontrolledOpen","defaultOpen","closeIntent","ignoreDuplicateClose","onOpenChange","next","source","pointerCloseIntentUntil","pointerCloseIntentTimeout","ReturnType","setTimeout","clearTimeout","Date","now","queueMicrotask","disabled","Boolean","activeElement","openInteraction","focusedTrigger","anotherTooltipOpening","pointerClosePending","handleKeyDown","KeyboardEvent","key","_$mergeProps","TooltipTrigger","Trigger","ref","onFocus","defaultPrevented","onBlur","onPointerEnter","onPointerLeave","onClick","TooltipContentProps","Content","TooltipContent","context","Portal","class","aria-hidden","_$memo","onEscapeKeyDown","onPointerDownOutside","_$createComponent","Tooltip","KobalteTooltip","_$mergeProps","_$memo"],"sources":["../../../../src/components/ui/tooltip/tooltip.tsx"],"sourcesContent":["import { Tooltip as KobalteTooltip, useTooltipContext } from '@kobalte/core/tooltip'\nimport type { ComponentProps } from 'solid-js'\nimport {\n createContext,\n createEffect,\n createSignal,\n createUniqueId,\n onCleanup,\n splitProps,\n useContext,\n} from 'solid-js'\nimport { isServer } from 'solid-js/web'\nimport { tooltipTip } from '#lib/overlay'\nimport { cn } from '#lib/utils'\n\n/**\n * The provider sets the *timing* defaults for every tooltip below it.\n *\n * Timing is the part of a tooltip that has to agree across an app: one that\n * opens in 200ms in a toolbar and 700ms in a sidebar reads as a bug. The\n * provider is where that agreement lives. It is optional — a tooltip outside a\n * provider falls back to the same defaults — so a consumer can drop it in\n * without restructuring anything.\n */\ntype TooltipTiming = {\n openDelay: number\n closeDelay: number\n skipDelayDuration: number\n}\n\nconst tooltipDefaults: TooltipTiming = {\n /** Long enough that a pointer crossing a toolbar does not strobe tooltips. */\n openDelay: 400,\n /** Short, because the pointer has already left the control. */\n closeDelay: 80,\n /**\n * After a tooltip has been seen, the next one opens immediately for this\n * long. This is what makes moving along a row of icons feel like one\n * gesture instead of four separate waits.\n */\n skipDelayDuration: 500,\n}\n\nconst TooltipTimingContext = createContext<TooltipTiming>(tooltipDefaults)\n\ntype TooltipInteraction = {\n id: string\n trigger: () => HTMLElement | undefined\n setTrigger: (element: HTMLElement) => void\n isOpen: () => boolean\n isDisabled: () => boolean\n isTriggerFocused: () => boolean\n markOpenIntent: () => void\n clearOpenIntent: () => void\n requestCloseFromAnotherTooltip: () => void\n markPointerCloseIntent: () => void\n clearPointerCloseIntent: () => void\n markCloseIntent: () => void\n}\n\nconst TooltipInteractionContext = createContext<TooltipInteraction>()\nconst openTooltipInteractions = new Set<TooltipInteraction>()\nlet pendingTooltipOpenId: string | undefined\nlet tooltipScrollListenerAttached = false\n\nfunction clearPendingTooltipOpen(id: string) {\n if (pendingTooltipOpenId === id) pendingTooltipOpenId = undefined\n}\n\nfunction tooltipScrollContainsTrigger(event: Event, trigger: HTMLElement) {\n const target = event.target\n return target === window || (target instanceof Node && target.contains(trigger))\n}\n\nfunction handleTooltipScroll(event: Event) {\n for (const interaction of openTooltipInteractions) {\n const trigger = interaction.trigger()\n if (interaction.isOpen() && trigger && tooltipScrollContainsTrigger(event, trigger)) {\n interaction.markCloseIntent()\n }\n }\n}\n\nfunction handleTooltipPointerMove(event: PointerEvent) {\n for (const interaction of openTooltipInteractions) {\n const trigger = interaction.trigger()\n const target = event.target\n if (\n interaction.isOpen() &&\n interaction.isTriggerFocused() &&\n trigger &&\n !(target instanceof Node && trigger.contains(target))\n ) {\n interaction.markPointerCloseIntent()\n }\n }\n}\n\nfunction setTooltipInteractionOpen(interaction: TooltipInteraction, open: boolean) {\n if (isServer) return\n\n if (open) {\n if (openTooltipInteractions.has(interaction)) return\n openTooltipInteractions.add(interaction)\n if (!tooltipScrollListenerAttached) {\n tooltipScrollListenerAttached = true\n // Register before Kobalte's global listeners so intentional scroll\n // dismissal can be distinguished from its delayed pointer dismissal.\n window.addEventListener('scroll', handleTooltipScroll, true)\n document.addEventListener('pointermove', handleTooltipPointerMove, true)\n }\n } else {\n openTooltipInteractions.delete(interaction)\n if (openTooltipInteractions.size === 0 && tooltipScrollListenerAttached) {\n tooltipScrollListenerAttached = false\n window.removeEventListener('scroll', handleTooltipScroll, true)\n document.removeEventListener('pointermove', handleTooltipPointerMove, true)\n }\n }\n}\n\nfunction registerTooltipInteraction(interaction: TooltipInteraction) {\n onCleanup(() => {\n setTooltipInteractionOpen(interaction, false)\n clearPendingTooltipOpen(interaction.id)\n })\n}\n\nfunction invokeEventHandler(handler: unknown, event: Event) {\n if (Array.isArray(handler)) {\n const [callback, data] = handler as [(data: unknown, event: Event) => void, unknown]\n callback(data, event)\n } else if (typeof handler === 'function') {\n const callback = handler as (event: Event) => void\n callback(event)\n }\n}\n\nexport type TooltipProviderProps = {\n children?: ComponentProps<'div'>['children']\n openDelay?: number\n closeDelay?: number\n skipDelayDuration?: number\n}\n\nexport function TooltipProvider(props: TooltipProviderProps) {\n return (\n <TooltipTimingContext.Provider\n value={{\n openDelay: props.openDelay ?? tooltipDefaults.openDelay,\n closeDelay: props.closeDelay ?? tooltipDefaults.closeDelay,\n skipDelayDuration: props.skipDelayDuration ?? tooltipDefaults.skipDelayDuration,\n }}\n >\n {props.children}\n </TooltipTimingContext.Provider>\n )\n}\n\nexport type TooltipRootProps = ComponentProps<typeof KobalteTooltip>\n\nexport function Tooltip(props: TooltipRootProps) {\n const timing = useContext(TooltipTimingContext)\n const [local, rest] = splitProps(props, ['open', 'defaultOpen', 'onOpenChange', 'disabled'])\n const id = createUniqueId()\n const controlled = () => local.open !== undefined\n const [uncontrolledOpen, setUncontrolledOpen] = createSignal(local.defaultOpen ?? false)\n const isOpen = () => (controlled() ? (local.open ?? false) : uncontrolledOpen())\n const [trigger, setTrigger] = createSignal<HTMLElement>()\n let closeIntent = false\n let ignoreDuplicateClose = false\n let onOpenChange: (next: boolean, source?: 'handoff') => void\n let pointerCloseIntentUntil = 0\n let pointerCloseIntentTimeout: ReturnType<typeof setTimeout> | undefined\n const clearPointerCloseIntent = () => {\n pointerCloseIntentUntil = 0\n if (pointerCloseIntentTimeout) clearTimeout(pointerCloseIntentTimeout)\n pointerCloseIntentTimeout = undefined\n }\n const markPointerCloseIntent = () => {\n if (!isOpen()) return\n clearPointerCloseIntent()\n const closeDelay = props.closeDelay ?? timing.closeDelay\n pointerCloseIntentUntil = Date.now() + closeDelay + 50\n pointerCloseIntentTimeout = setTimeout(clearPointerCloseIntent, closeDelay + 50)\n }\n const markCloseIntent = () => {\n closeIntent = true\n clearPointerCloseIntent()\n queueMicrotask(() => {\n closeIntent = false\n })\n }\n const requestCloseFromAnotherTooltip = () => {\n if (!isOpen()) return\n\n // Another trigger is an explicit handoff, even when a focused tooltip has\n // a pending pointer-close intent that would otherwise suppress Kobalte's\n // close callback. Route the request through the same controlled/uncontrolled\n // state contract as Kobalte and ignore its duplicate callback for this turn.\n ignoreDuplicateClose = true\n queueMicrotask(() => {\n ignoreDuplicateClose = false\n })\n onOpenChange(false, 'handoff')\n }\n const interaction: TooltipInteraction = {\n id,\n trigger,\n setTrigger,\n isOpen,\n isDisabled: () => local.disabled ?? false,\n isTriggerFocused: () =>\n Boolean(trigger() && typeof document !== 'undefined' && trigger() === document.activeElement),\n markOpenIntent: () => {\n clearPointerCloseIntent()\n pendingTooltipOpenId = id\n // A focused tooltip ignores ordinary pointer movement, but entering or\n // focusing another tooltip is an explicit transfer.\n for (const openInteraction of openTooltipInteractions) {\n if (openInteraction.id !== id) openInteraction.requestCloseFromAnotherTooltip()\n }\n },\n clearOpenIntent: () => clearPendingTooltipOpen(id),\n requestCloseFromAnotherTooltip,\n markPointerCloseIntent,\n clearPointerCloseIntent,\n markCloseIntent,\n }\n registerTooltipInteraction(interaction)\n onCleanup(() => {\n clearPointerCloseIntent()\n ignoreDuplicateClose = false\n })\n if (isOpen()) setTooltipInteractionOpen(interaction, true)\n createEffect(() => setTooltipInteractionOpen(interaction, isOpen()))\n onOpenChange = (next: boolean, source) => {\n if (!next && source !== 'handoff' && ignoreDuplicateClose) return\n\n if (next) {\n clearPendingTooltipOpen(id)\n clearPointerCloseIntent()\n // Install this capture listener before Kobalte's open-state effect adds\n // its scroll listener later in the same update.\n setTooltipInteractionOpen(interaction, true)\n } else {\n const focusedTrigger = interaction.isTriggerFocused()\n const anotherTooltipOpening =\n pendingTooltipOpenId !== undefined && pendingTooltipOpenId !== id\n const pointerClosePending = Date.now() <= pointerCloseIntentUntil\n if (\n source !== 'handoff' &&\n focusedTrigger &&\n pointerClosePending &&\n !closeIntent &&\n !anotherTooltipOpening\n ) {\n clearPointerCloseIntent()\n return\n }\n closeIntent = false\n clearPointerCloseIntent()\n clearPendingTooltipOpen(id)\n }\n\n if (!controlled()) setUncontrolledOpen(next)\n local.onOpenChange?.(next)\n if (next) {\n // A controlled parent can reject the open request without changing its\n // signal, so the effect above would not rerun to release this listener.\n queueMicrotask(() => {\n if (!isOpen()) setTooltipInteractionOpen(interaction, false)\n })\n }\n }\n\n // Kobalte dismisses on Escape only when its content owns the top-most\n // layer, and a handoff keeps the previous tooltip's content mounted while\n // it exits — swallowing the key for the tooltip that just took over.\n // Listening on document (capture) closes this tooltip first; the layer's\n // own handler still runs for anything stacked above it.\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return\n markCloseIntent()\n onOpenChange(false)\n }\n createEffect(() => {\n if (isServer || !isOpen()) return\n document.addEventListener('keydown', handleKeyDown, true)\n onCleanup(() => document.removeEventListener('keydown', handleKeyDown, true))\n })\n\n return (\n <TooltipInteractionContext.Provider value={interaction}>\n <KobalteTooltip\n open={isOpen()}\n onOpenChange={onOpenChange}\n disabled={local.disabled}\n openDelay={props.openDelay ?? timing.openDelay}\n closeDelay={props.closeDelay ?? timing.closeDelay}\n skipDelayDuration={props.skipDelayDuration ?? timing.skipDelayDuration}\n {...rest}\n />\n </TooltipInteractionContext.Provider>\n )\n}\n\nexport function TooltipTrigger(props: ComponentProps<typeof KobalteTooltip.Trigger>) {\n const interaction = useContext(TooltipInteractionContext)\n const [local, rest] = splitProps(props, [\n 'ref',\n 'onFocus',\n 'onBlur',\n 'onPointerEnter',\n 'onPointerLeave',\n 'onClick',\n ])\n\n return (\n <KobalteTooltip.Trigger\n {...rest}\n ref={(element) => {\n interaction?.setTrigger(element)\n if (typeof local.ref === 'function') local.ref(element)\n }}\n onFocus={(event) => {\n invokeEventHandler(local.onFocus, event)\n if (!event.defaultPrevented && !interaction?.isDisabled()) interaction?.markOpenIntent()\n }}\n onBlur={(event) => {\n invokeEventHandler(local.onBlur, event)\n interaction?.clearOpenIntent()\n interaction?.markCloseIntent()\n }}\n onPointerEnter={(event) => {\n invokeEventHandler(local.onPointerEnter, event)\n if (!event.defaultPrevented && !interaction?.isDisabled()) interaction?.markOpenIntent()\n }}\n onPointerLeave={(event) => {\n invokeEventHandler(local.onPointerLeave, event)\n interaction?.clearOpenIntent()\n interaction?.markPointerCloseIntent()\n }}\n onClick={(event) => {\n invokeEventHandler(local.onClick, event)\n interaction?.markCloseIntent()\n }}\n />\n )\n}\n\nexport type TooltipContentProps = ComponentProps<typeof KobalteTooltip.Content>\n\nexport function TooltipContent(props: TooltipContentProps) {\n const interaction = useContext(TooltipInteractionContext)\n const [local, rest] = splitProps(props, [\n 'class',\n 'children',\n 'aria-hidden',\n 'onEscapeKeyDown',\n 'onPointerDownOutside',\n ])\n const context = useTooltipContext()\n\n // Kobalte keeps force-mounted and exiting content present after close. Hide\n // that retained tooltip from assistive technology until it opens again.\n return (\n <KobalteTooltip.Portal>\n <KobalteTooltip.Content\n /* The slot is what base.css's pointer-transparency rule keys on; every\n tip carries it, so no consumer has to remember to pass it. */\n data-slot=\"tooltip-content\"\n class={cn(\n tooltipTip,\n // The max width is a token, not a fixed measure: it has to fall\n // behind the viewport at large root font sizes or the tip — which\n // Kobalte can shift but never shrink — forces a horizontal scroll.\n 'z-(--z-tooltip) w-fit max-w-(--tooltip-max-width) px-2 py-1',\n 'origin-(--kb-tooltip-content-transform-origin) text-balance',\n 'data-expanded:animate-in data-expanded:fade-in-0 data-expanded:zoom-in-95',\n 'data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',\n 'data-expanded:duration-150 data-closed:duration-100',\n local.class\n )}\n aria-hidden={context.isOpen() ? local['aria-hidden'] : true}\n onEscapeKeyDown={(event) => {\n invokeEventHandler(local.onEscapeKeyDown, event)\n if (!event.defaultPrevented) interaction?.markCloseIntent()\n }}\n onPointerDownOutside={(event) => {\n invokeEventHandler(local.onPointerDownOutside, event)\n if (!event.defaultPrevented) interaction?.markCloseIntent()\n }}\n {...rest}\n >\n {local.children}\n </KobalteTooltip.Content>\n </KobalteTooltip.Portal>\n )\n}\n"],"mappings":"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"}
|
|
@@ -15,6 +15,12 @@ export declare function plainTextFromMarkdown(markdown: string): string;
|
|
|
15
15
|
* A release date, formatted for a reader. Returns the input when it cannot be
|
|
16
16
|
* parsed — a date a build tool wrote in an unusual format is still better shown
|
|
17
17
|
* verbatim than dropped.
|
|
18
|
+
*
|
|
19
|
+
* A date-only value (`2026-09-24`) is a calendar day, not an instant. `new Date`
|
|
20
|
+
* reads it as UTC midnight, and formatting that instant in the reader's zone
|
|
21
|
+
* moves it to the previous day anywhere west of UTC — so a date-only value is
|
|
22
|
+
* formatted in UTC, where its midnight still falls on the day it names. A full
|
|
23
|
+
* timestamp is a real instant and keeps the reader's zone.
|
|
18
24
|
*/
|
|
19
25
|
export declare function formatReleaseDate(value: string | null): string | null;
|
|
20
26
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version-notes.d.ts","sourceRoot":"","sources":["../../src/lib/version-notes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAkB9D;AAED
|
|
1
|
+
{"version":3,"file":"version-notes.d.ts","sourceRoot":"","sources":["../../src/lib/version-notes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAkB9D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAWrE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWjD"}
|
|
@@ -18,15 +18,23 @@ function plainTextFromMarkdown(markdown) {
|
|
|
18
18
|
* A release date, formatted for a reader. Returns the input when it cannot be
|
|
19
19
|
* parsed — a date a build tool wrote in an unusual format is still better shown
|
|
20
20
|
* verbatim than dropped.
|
|
21
|
+
*
|
|
22
|
+
* A date-only value (`2026-09-24`) is a calendar day, not an instant. `new Date`
|
|
23
|
+
* reads it as UTC midnight, and formatting that instant in the reader's zone
|
|
24
|
+
* moves it to the previous day anywhere west of UTC — so a date-only value is
|
|
25
|
+
* formatted in UTC, where its midnight still falls on the day it names. A full
|
|
26
|
+
* timestamp is a real instant and keeps the reader's zone.
|
|
21
27
|
*/
|
|
22
28
|
function formatReleaseDate(value) {
|
|
23
29
|
if (!value) return null;
|
|
24
30
|
const date = new Date(value);
|
|
25
31
|
if (Number.isNaN(date.getTime())) return value;
|
|
32
|
+
const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(value.trim());
|
|
26
33
|
return new Intl.DateTimeFormat(void 0, {
|
|
27
34
|
day: "numeric",
|
|
28
35
|
month: "short",
|
|
29
|
-
year: "numeric"
|
|
36
|
+
year: "numeric",
|
|
37
|
+
...dateOnly ? { timeZone: "UTC" } : {}
|
|
30
38
|
}).format(date);
|
|
31
39
|
}
|
|
32
40
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version-notes.js","names":[],"sources":["../../src/lib/version-notes.ts"],"sourcesContent":["/**\n * Version notes, as text.\n *\n * A release changelog arrives as markdown and is shown to a reader who wants to\n * know what changed, not to a renderer. Rendering it would mean a markdown\n * dependency, a sanitiser, and a typographic result that competes with the\n * dialog's own type scale — so the markup is stripped to its words instead.\n *\n * The order matters: images and links are unwrapped to their alt text and label\n * *before* the emphasis rules run, because `[a](b)` contains brackets and\n * parentheses that the later patterns would otherwise trip on.\n */\nexport function plainTextFromMarkdown(markdown: string): string {\n return markdown\n .replace(/\\r\\n/g, '\\n')\n .replace(/!\\[([^\\]]*)\\]\\([^)]*\\)/g, '$1')\n .replace(/\\[([^\\]]+)\\]\\([^)]*\\)/g, '$1')\n .replace(/^[ \\t]*#{1,6}[ \\t]*/gm, '')\n .replace(/^[ \\t]*>[ \\t]?/gm, '')\n .replace(/^[ \\t]*---+[ \\t]*$/gm, '')\n .replace(/^[ \\t]*[-*+][ \\t]+/gm, '• ')\n .replace(/^[ \\t]*\\d+\\.[ \\t]+/gm, '')\n .replace(/```[^\\n]*\\n?/g, '')\n .replace(/`([^`]+)`/g, '$1')\n .replace(/\\*\\*([^*]+)\\*\\*/g, '$1')\n .replace(/__([^_]+)__/g, '$1')\n .replace(/\\*([^*]+)\\*/g, '$1')\n .replace(/_([^_]+)_/g, '$1')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n}\n\n/**\n * A release date, formatted for a reader. Returns the input when it cannot be\n * parsed — a date a build tool wrote in an unusual format is still better shown\n * verbatim than dropped.\n */\nexport function formatReleaseDate(value: string | null): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return value\n return new Intl.DateTimeFormat(undefined, {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n }).format(date)\n}\n\n/**\n * Bytes as a reader-facing size. Used by the update dialog's progress readout,\n * where the number is reassurance rather than data — \"12.4 MB of 48.1 MB\" says\n * the download is moving, which a percentage alone does not.\n */\nexport function formatBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return '—'\n if (bytes < 1024) return `${bytes} B`\n const units = ['kB', 'MB', 'GB']\n let value = bytes / 1024\n let unit = 0\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024\n unit += 1\n }\n return `${value.toFixed(value >= 10 ? 0 : 1)} ${units[unit]}`\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,SAAgB,sBAAsB,UAA0B;CAC9D,OAAO,SACJ,QAAQ,SAAS,IAAI,CAAC,CACtB,QAAQ,2BAA2B,IAAI,CAAC,CACxC,QAAQ,0BAA0B,IAAI,CAAC,CACvC,QAAQ,yBAAyB,EAAE,CAAC,CACpC,QAAQ,oBAAoB,EAAE,CAAC,CAC/B,QAAQ,wBAAwB,EAAE,CAAC,CACnC,QAAQ,wBAAwB,IAAI,CAAC,CACrC,QAAQ,wBAAwB,EAAE,CAAC,CACnC,QAAQ,iBAAiB,EAAE,CAAC,CAC5B,QAAQ,cAAc,IAAI,CAAC,CAC3B,QAAQ,oBAAoB,IAAI,CAAC,CACjC,QAAQ,gBAAgB,IAAI,CAAC,CAC7B,QAAQ,gBAAgB,IAAI,CAAC,CAC7B,QAAQ,cAAc,IAAI,CAAC,CAC3B,QAAQ,WAAW,MAAM,CAAC,CAC1B,KAAK;AACV
|
|
1
|
+
{"version":3,"file":"version-notes.js","names":[],"sources":["../../src/lib/version-notes.ts"],"sourcesContent":["/**\n * Version notes, as text.\n *\n * A release changelog arrives as markdown and is shown to a reader who wants to\n * know what changed, not to a renderer. Rendering it would mean a markdown\n * dependency, a sanitiser, and a typographic result that competes with the\n * dialog's own type scale — so the markup is stripped to its words instead.\n *\n * The order matters: images and links are unwrapped to their alt text and label\n * *before* the emphasis rules run, because `[a](b)` contains brackets and\n * parentheses that the later patterns would otherwise trip on.\n */\nexport function plainTextFromMarkdown(markdown: string): string {\n return markdown\n .replace(/\\r\\n/g, '\\n')\n .replace(/!\\[([^\\]]*)\\]\\([^)]*\\)/g, '$1')\n .replace(/\\[([^\\]]+)\\]\\([^)]*\\)/g, '$1')\n .replace(/^[ \\t]*#{1,6}[ \\t]*/gm, '')\n .replace(/^[ \\t]*>[ \\t]?/gm, '')\n .replace(/^[ \\t]*---+[ \\t]*$/gm, '')\n .replace(/^[ \\t]*[-*+][ \\t]+/gm, '• ')\n .replace(/^[ \\t]*\\d+\\.[ \\t]+/gm, '')\n .replace(/```[^\\n]*\\n?/g, '')\n .replace(/`([^`]+)`/g, '$1')\n .replace(/\\*\\*([^*]+)\\*\\*/g, '$1')\n .replace(/__([^_]+)__/g, '$1')\n .replace(/\\*([^*]+)\\*/g, '$1')\n .replace(/_([^_]+)_/g, '$1')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n}\n\n/**\n * A release date, formatted for a reader. Returns the input when it cannot be\n * parsed — a date a build tool wrote in an unusual format is still better shown\n * verbatim than dropped.\n *\n * A date-only value (`2026-09-24`) is a calendar day, not an instant. `new Date`\n * reads it as UTC midnight, and formatting that instant in the reader's zone\n * moves it to the previous day anywhere west of UTC — so a date-only value is\n * formatted in UTC, where its midnight still falls on the day it names. A full\n * timestamp is a real instant and keeps the reader's zone.\n */\nexport function formatReleaseDate(value: string | null): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return value\n const dateOnly = /^\\d{4}-\\d{2}-\\d{2}$/.test(value.trim())\n return new Intl.DateTimeFormat(undefined, {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n ...(dateOnly ? { timeZone: 'UTC' } : {}),\n }).format(date)\n}\n\n/**\n * Bytes as a reader-facing size. Used by the update dialog's progress readout,\n * where the number is reassurance rather than data — \"12.4 MB of 48.1 MB\" says\n * the download is moving, which a percentage alone does not.\n */\nexport function formatBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return '—'\n if (bytes < 1024) return `${bytes} B`\n const units = ['kB', 'MB', 'GB']\n let value = bytes / 1024\n let unit = 0\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024\n unit += 1\n }\n return `${value.toFixed(value >= 10 ? 0 : 1)} ${units[unit]}`\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,SAAgB,sBAAsB,UAA0B;CAC9D,OAAO,SACJ,QAAQ,SAAS,IAAI,CAAC,CACtB,QAAQ,2BAA2B,IAAI,CAAC,CACxC,QAAQ,0BAA0B,IAAI,CAAC,CACvC,QAAQ,yBAAyB,EAAE,CAAC,CACpC,QAAQ,oBAAoB,EAAE,CAAC,CAC/B,QAAQ,wBAAwB,EAAE,CAAC,CACnC,QAAQ,wBAAwB,IAAI,CAAC,CACrC,QAAQ,wBAAwB,EAAE,CAAC,CACnC,QAAQ,iBAAiB,EAAE,CAAC,CAC5B,QAAQ,cAAc,IAAI,CAAC,CAC3B,QAAQ,oBAAoB,IAAI,CAAC,CACjC,QAAQ,gBAAgB,IAAI,CAAC,CAC7B,QAAQ,gBAAgB,IAAI,CAAC,CAC7B,QAAQ,cAAc,IAAI,CAAC,CAC3B,QAAQ,WAAW,MAAM,CAAC,CAC1B,KAAK;AACV;;;;;;;;;;;;AAaA,SAAgB,kBAAkB,OAAqC;CACrE,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,OAAO,IAAI,KAAK,KAAK;CAC3B,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,GAAG,OAAO;CACzC,MAAM,WAAW,sBAAsB,KAAK,MAAM,KAAK,CAAC;CACxD,OAAO,IAAI,KAAK,eAAe,KAAA,GAAW;EACxC,KAAK;EACL,OAAO;EACP,MAAM;EACN,GAAI,WAAW,EAAE,UAAU,MAAM,IAAI,CAAC;CACxC,CAAC,CAAC,CAAC,OAAO,IAAI;AAChB;;;;;;AAOA,SAAgB,YAAY,OAAuB;CACjD,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GAAG,OAAO;CACjD,IAAI,QAAQ,MAAM,OAAO,GAAG,MAAM;CAClC,MAAM,QAAQ;EAAC;EAAM;EAAM;CAAI;CAC/B,IAAI,QAAQ,QAAQ;CACpB,IAAI,OAAO;CACX,OAAO,SAAS,QAAQ,OAAO,MAAM,SAAS,GAAG;EAC/C,SAAS;EACT,QAAQ;CACV;CACA,OAAO,GAAG,MAAM,QAAQ,SAAS,KAAK,IAAI,CAAC,EAAE,GAAG,MAAM;AACxD"}
|
|
@@ -49,12 +49,23 @@ export function WorkspaceMark(props: WorkspaceMarkProps) {
|
|
|
49
49
|
>
|
|
50
50
|
<Show
|
|
51
51
|
when={local.children}
|
|
52
|
-
fallback={
|
|
52
|
+
fallback={
|
|
53
|
+
<span data-slot="workspace-mark-initial" aria-hidden="true">
|
|
54
|
+
{local.name.charAt(0).toUpperCase()}
|
|
55
|
+
</span>
|
|
56
|
+
}
|
|
53
57
|
>
|
|
54
58
|
{local.children}
|
|
55
59
|
</Show>
|
|
60
|
+
{/* The badge is centred on the corner rather than tucked inside it. Inset
|
|
61
|
+
by an edge offset, a count badge is wider than the space between the
|
|
62
|
+
corner and the monogram, so it sat on the letter; centred on the corner,
|
|
63
|
+
a dot, a one-digit and a two-digit count all clear the letter's box. */}
|
|
56
64
|
<Show when={local.badge}>
|
|
57
|
-
<span
|
|
65
|
+
<span
|
|
66
|
+
data-slot="workspace-mark-badge"
|
|
67
|
+
class="absolute end-0 top-0 flex translate-x-1/2 -translate-y-1/2 items-center justify-center rtl:-translate-x-1/2"
|
|
68
|
+
>
|
|
58
69
|
{local.badge}
|
|
59
70
|
</span>
|
|
60
71
|
</Show>
|
|
@@ -320,7 +320,10 @@ export function MessageBody(
|
|
|
320
320
|
.filter(Boolean)
|
|
321
321
|
.map((segment) => {
|
|
322
322
|
if (!segment.startsWith('```')) {
|
|
323
|
-
|
|
323
|
+
// The newlines that separate prose from a fence belong to the fence
|
|
324
|
+
// syntax. Kept, `whitespace-pre-wrap` draws them as blank lines on top
|
|
325
|
+
// of the column gap, doubling the space beside every code block.
|
|
326
|
+
return { kind: 'prose' as const, value: segment.replace(/^\n+|\n+$/g, '') }
|
|
324
327
|
}
|
|
325
328
|
const body = segment.replace(/^```/, '').replace(/```$/, '')
|
|
326
329
|
// The language is the first token on the opening line, and only when the
|
|
@@ -334,6 +337,8 @@ export function MessageBody(
|
|
|
334
337
|
language,
|
|
335
338
|
}
|
|
336
339
|
})
|
|
340
|
+
// Newlines alone between two fences leave an empty paragraph once trimmed.
|
|
341
|
+
.filter((segment) => segment.kind === 'code' || segment.value !== '')
|
|
337
342
|
|
|
338
343
|
const last = () => segments().at(-1)
|
|
339
344
|
|
|
@@ -86,8 +86,11 @@ export function ThreadPanel(props: ThreadPanelProps) {
|
|
|
86
86
|
<div class="bg-muted/40 shrink-0 border-b border-border px-3 py-2">{local.root}</div>
|
|
87
87
|
</Show>
|
|
88
88
|
|
|
89
|
+
{/* The replies carry their own inline padding: a row brings only `px-1`, so
|
|
90
|
+
a bubble at its widest otherwise meets the panel's edge. `px-2` plus the
|
|
91
|
+
row's `px-1` lands on the `px-3` line the header and root use. */}
|
|
89
92
|
<div
|
|
90
|
-
class="min-h-0 flex-1 overflow-y-auto overscroll-contain"
|
|
93
|
+
class="min-h-0 flex-1 overflow-y-auto overscroll-contain px-2 py-2"
|
|
91
94
|
role="region"
|
|
92
95
|
aria-label="Thread replies"
|
|
93
96
|
tabindex="0"
|
|
@@ -25,6 +25,13 @@ export const badgeVariants = cva(
|
|
|
25
25
|
destructive: 'bg-destructive-subtle text-foreground',
|
|
26
26
|
info: 'bg-info-subtle text-foreground',
|
|
27
27
|
subtle: 'bg-primary-subtle text-primary',
|
|
28
|
+
/**
|
|
29
|
+
* A count that asks for attention, pinned to a corner of another control
|
|
30
|
+
* (a workspace mark, a rail icon). At `sm` it is a 2xs label on whatever
|
|
31
|
+
* surface sits behind the corner, where the destructive tint all but
|
|
32
|
+
* disappears — so it takes the solid, contrast-measured action fill.
|
|
33
|
+
*/
|
|
34
|
+
notification: 'bg-destructive-action text-destructive-action-foreground',
|
|
28
35
|
},
|
|
29
36
|
size: {
|
|
30
37
|
sm: 'h-4 px-1.5 text-2xs',
|
|
@@ -75,6 +75,12 @@ export type TabsListProps = ComponentProps<typeof KobalteTabs.List> & {
|
|
|
75
75
|
appearance?: 'underline' | 'segmented'
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* The underline appearance's rule is the baseline a horizontal row of tabs sits
|
|
80
|
+
* on. A vertical list has no baseline — its triggers stack — so the rule is
|
|
81
|
+
* dropped there; otherwise it draws as a stray line under the last trigger,
|
|
82
|
+
* spanning only the list's width (the SettingsLayout rail showed exactly that).
|
|
83
|
+
*/
|
|
78
84
|
export function TabsList(props: TabsListProps) {
|
|
79
85
|
const [local, rest] = splitProps(props, ['class', 'appearance'])
|
|
80
86
|
|
|
@@ -84,7 +90,7 @@ export function TabsList(props: TabsListProps) {
|
|
|
84
90
|
'inline-flex w-fit shrink-0 items-center justify-center',
|
|
85
91
|
local.appearance === 'segmented'
|
|
86
92
|
? 'gap-0.5 rounded-lg bg-surface-hover p-0.5'
|
|
87
|
-
: 'gap-1 rounded-none border-b border-border',
|
|
93
|
+
: 'gap-1 rounded-none border-b border-border data-[orientation=vertical]:border-b-0',
|
|
88
94
|
'data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch',
|
|
89
95
|
local.class
|
|
90
96
|
)}
|
|
@@ -367,6 +367,9 @@ export function TooltipContent(props: TooltipContentProps) {
|
|
|
367
367
|
return (
|
|
368
368
|
<KobalteTooltip.Portal>
|
|
369
369
|
<KobalteTooltip.Content
|
|
370
|
+
/* The slot is what base.css's pointer-transparency rule keys on; every
|
|
371
|
+
tip carries it, so no consumer has to remember to pass it. */
|
|
372
|
+
data-slot="tooltip-content"
|
|
370
373
|
class={cn(
|
|
371
374
|
tooltipTip,
|
|
372
375
|
// The max width is a token, not a fixed measure: it has to fall
|
|
@@ -34,15 +34,23 @@ export function plainTextFromMarkdown(markdown: string): string {
|
|
|
34
34
|
* A release date, formatted for a reader. Returns the input when it cannot be
|
|
35
35
|
* parsed — a date a build tool wrote in an unusual format is still better shown
|
|
36
36
|
* verbatim than dropped.
|
|
37
|
+
*
|
|
38
|
+
* A date-only value (`2026-09-24`) is a calendar day, not an instant. `new Date`
|
|
39
|
+
* reads it as UTC midnight, and formatting that instant in the reader's zone
|
|
40
|
+
* moves it to the previous day anywhere west of UTC — so a date-only value is
|
|
41
|
+
* formatted in UTC, where its midnight still falls on the day it names. A full
|
|
42
|
+
* timestamp is a real instant and keeps the reader's zone.
|
|
37
43
|
*/
|
|
38
44
|
export function formatReleaseDate(value: string | null): string | null {
|
|
39
45
|
if (!value) return null
|
|
40
46
|
const date = new Date(value)
|
|
41
47
|
if (Number.isNaN(date.getTime())) return value
|
|
48
|
+
const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(value.trim())
|
|
42
49
|
return new Intl.DateTimeFormat(undefined, {
|
|
43
50
|
day: 'numeric',
|
|
44
51
|
month: 'short',
|
|
45
52
|
year: 'numeric',
|
|
53
|
+
...(dateOnly ? { timeZone: 'UTC' } : {}),
|
|
46
54
|
}).format(date)
|
|
47
55
|
}
|
|
48
56
|
|
|
@@ -592,6 +592,19 @@
|
|
|
592
592
|
color var(--duration-normal) var(--ease-out);
|
|
593
593
|
}
|
|
594
594
|
|
|
595
|
+
/* A tooltip is informational: neither its popper positioner nor its content
|
|
596
|
+
may intercept the pointer and block the control the tooltip describes —
|
|
597
|
+
or a control beside it. The layer stack writes `pointer-events: auto`
|
|
598
|
+
inline on the active layer, so the content needs `!important` to stay out
|
|
599
|
+
of the pointer path. */
|
|
600
|
+
[data-popper-positioner]:has(> [data-slot='tooltip-content']) {
|
|
601
|
+
pointer-events: none;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
[data-slot='tooltip-content'] {
|
|
605
|
+
pointer-events: none !important;
|
|
606
|
+
}
|
|
607
|
+
|
|
595
608
|
/* Keyboard focus only. `:focus-visible` rather than `:focus` so a pointer
|
|
596
609
|
click never paints a ring. */
|
|
597
610
|
:focus-visible {
|
package/package.json
CHANGED
|
@@ -49,12 +49,23 @@ export function WorkspaceMark(props: WorkspaceMarkProps) {
|
|
|
49
49
|
>
|
|
50
50
|
<Show
|
|
51
51
|
when={local.children}
|
|
52
|
-
fallback={
|
|
52
|
+
fallback={
|
|
53
|
+
<span data-slot="workspace-mark-initial" aria-hidden="true">
|
|
54
|
+
{local.name.charAt(0).toUpperCase()}
|
|
55
|
+
</span>
|
|
56
|
+
}
|
|
53
57
|
>
|
|
54
58
|
{local.children}
|
|
55
59
|
</Show>
|
|
60
|
+
{/* The badge is centred on the corner rather than tucked inside it. Inset
|
|
61
|
+
by an edge offset, a count badge is wider than the space between the
|
|
62
|
+
corner and the monogram, so it sat on the letter; centred on the corner,
|
|
63
|
+
a dot, a one-digit and a two-digit count all clear the letter's box. */}
|
|
56
64
|
<Show when={local.badge}>
|
|
57
|
-
<span
|
|
65
|
+
<span
|
|
66
|
+
data-slot="workspace-mark-badge"
|
|
67
|
+
class="absolute end-0 top-0 flex translate-x-1/2 -translate-y-1/2 items-center justify-center rtl:-translate-x-1/2"
|
|
68
|
+
>
|
|
58
69
|
{local.badge}
|
|
59
70
|
</span>
|
|
60
71
|
</Show>
|
|
@@ -320,7 +320,10 @@ export function MessageBody(
|
|
|
320
320
|
.filter(Boolean)
|
|
321
321
|
.map((segment) => {
|
|
322
322
|
if (!segment.startsWith('```')) {
|
|
323
|
-
|
|
323
|
+
// The newlines that separate prose from a fence belong to the fence
|
|
324
|
+
// syntax. Kept, `whitespace-pre-wrap` draws them as blank lines on top
|
|
325
|
+
// of the column gap, doubling the space beside every code block.
|
|
326
|
+
return { kind: 'prose' as const, value: segment.replace(/^\n+|\n+$/g, '') }
|
|
324
327
|
}
|
|
325
328
|
const body = segment.replace(/^```/, '').replace(/```$/, '')
|
|
326
329
|
// The language is the first token on the opening line, and only when the
|
|
@@ -334,6 +337,8 @@ export function MessageBody(
|
|
|
334
337
|
language,
|
|
335
338
|
}
|
|
336
339
|
})
|
|
340
|
+
// Newlines alone between two fences leave an empty paragraph once trimmed.
|
|
341
|
+
.filter((segment) => segment.kind === 'code' || segment.value !== '')
|
|
337
342
|
|
|
338
343
|
const last = () => segments().at(-1)
|
|
339
344
|
|
|
@@ -86,8 +86,11 @@ export function ThreadPanel(props: ThreadPanelProps) {
|
|
|
86
86
|
<div class="bg-muted/40 shrink-0 border-b border-border px-3 py-2">{local.root}</div>
|
|
87
87
|
</Show>
|
|
88
88
|
|
|
89
|
+
{/* The replies carry their own inline padding: a row brings only `px-1`, so
|
|
90
|
+
a bubble at its widest otherwise meets the panel's edge. `px-2` plus the
|
|
91
|
+
row's `px-1` lands on the `px-3` line the header and root use. */}
|
|
89
92
|
<div
|
|
90
|
-
class="min-h-0 flex-1 overflow-y-auto overscroll-contain"
|
|
93
|
+
class="min-h-0 flex-1 overflow-y-auto overscroll-contain px-2 py-2"
|
|
91
94
|
role="region"
|
|
92
95
|
aria-label="Thread replies"
|
|
93
96
|
tabindex="0"
|
|
@@ -25,6 +25,13 @@ export const badgeVariants = cva(
|
|
|
25
25
|
destructive: 'bg-destructive-subtle text-foreground',
|
|
26
26
|
info: 'bg-info-subtle text-foreground',
|
|
27
27
|
subtle: 'bg-primary-subtle text-primary',
|
|
28
|
+
/**
|
|
29
|
+
* A count that asks for attention, pinned to a corner of another control
|
|
30
|
+
* (a workspace mark, a rail icon). At `sm` it is a 2xs label on whatever
|
|
31
|
+
* surface sits behind the corner, where the destructive tint all but
|
|
32
|
+
* disappears — so it takes the solid, contrast-measured action fill.
|
|
33
|
+
*/
|
|
34
|
+
notification: 'bg-destructive-action text-destructive-action-foreground',
|
|
28
35
|
},
|
|
29
36
|
size: {
|
|
30
37
|
sm: 'h-4 px-1.5 text-2xs',
|
|
@@ -75,6 +75,12 @@ export type TabsListProps = ComponentProps<typeof KobalteTabs.List> & {
|
|
|
75
75
|
appearance?: 'underline' | 'segmented'
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* The underline appearance's rule is the baseline a horizontal row of tabs sits
|
|
80
|
+
* on. A vertical list has no baseline — its triggers stack — so the rule is
|
|
81
|
+
* dropped there; otherwise it draws as a stray line under the last trigger,
|
|
82
|
+
* spanning only the list's width (the SettingsLayout rail showed exactly that).
|
|
83
|
+
*/
|
|
78
84
|
export function TabsList(props: TabsListProps) {
|
|
79
85
|
const [local, rest] = splitProps(props, ['class', 'appearance'])
|
|
80
86
|
|
|
@@ -84,7 +90,7 @@ export function TabsList(props: TabsListProps) {
|
|
|
84
90
|
'inline-flex w-fit shrink-0 items-center justify-center',
|
|
85
91
|
local.appearance === 'segmented'
|
|
86
92
|
? 'gap-0.5 rounded-lg bg-surface-hover p-0.5'
|
|
87
|
-
: 'gap-1 rounded-none border-b border-border',
|
|
93
|
+
: 'gap-1 rounded-none border-b border-border data-[orientation=vertical]:border-b-0',
|
|
88
94
|
'data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch',
|
|
89
95
|
local.class
|
|
90
96
|
)}
|
|
@@ -367,6 +367,9 @@ export function TooltipContent(props: TooltipContentProps) {
|
|
|
367
367
|
return (
|
|
368
368
|
<KobalteTooltip.Portal>
|
|
369
369
|
<KobalteTooltip.Content
|
|
370
|
+
/* The slot is what base.css's pointer-transparency rule keys on; every
|
|
371
|
+
tip carries it, so no consumer has to remember to pass it. */
|
|
372
|
+
data-slot="tooltip-content"
|
|
370
373
|
class={cn(
|
|
371
374
|
tooltipTip,
|
|
372
375
|
// The max width is a token, not a fixed measure: it has to fall
|
package/src/lib/version-notes.ts
CHANGED
|
@@ -34,15 +34,23 @@ export function plainTextFromMarkdown(markdown: string): string {
|
|
|
34
34
|
* A release date, formatted for a reader. Returns the input when it cannot be
|
|
35
35
|
* parsed — a date a build tool wrote in an unusual format is still better shown
|
|
36
36
|
* verbatim than dropped.
|
|
37
|
+
*
|
|
38
|
+
* A date-only value (`2026-09-24`) is a calendar day, not an instant. `new Date`
|
|
39
|
+
* reads it as UTC midnight, and formatting that instant in the reader's zone
|
|
40
|
+
* moves it to the previous day anywhere west of UTC — so a date-only value is
|
|
41
|
+
* formatted in UTC, where its midnight still falls on the day it names. A full
|
|
42
|
+
* timestamp is a real instant and keeps the reader's zone.
|
|
37
43
|
*/
|
|
38
44
|
export function formatReleaseDate(value: string | null): string | null {
|
|
39
45
|
if (!value) return null
|
|
40
46
|
const date = new Date(value)
|
|
41
47
|
if (Number.isNaN(date.getTime())) return value
|
|
48
|
+
const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(value.trim())
|
|
42
49
|
return new Intl.DateTimeFormat(undefined, {
|
|
43
50
|
day: 'numeric',
|
|
44
51
|
month: 'short',
|
|
45
52
|
year: 'numeric',
|
|
53
|
+
...(dateOnly ? { timeZone: 'UTC' } : {}),
|
|
46
54
|
}).format(date)
|
|
47
55
|
}
|
|
48
56
|
|
package/src/styles/base.css
CHANGED
|
@@ -592,6 +592,19 @@
|
|
|
592
592
|
color var(--duration-normal) var(--ease-out);
|
|
593
593
|
}
|
|
594
594
|
|
|
595
|
+
/* A tooltip is informational: neither its popper positioner nor its content
|
|
596
|
+
may intercept the pointer and block the control the tooltip describes —
|
|
597
|
+
or a control beside it. The layer stack writes `pointer-events: auto`
|
|
598
|
+
inline on the active layer, so the content needs `!important` to stay out
|
|
599
|
+
of the pointer path. */
|
|
600
|
+
[data-popper-positioner]:has(> [data-slot='tooltip-content']) {
|
|
601
|
+
pointer-events: none;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
[data-slot='tooltip-content'] {
|
|
605
|
+
pointer-events: none !important;
|
|
606
|
+
}
|
|
607
|
+
|
|
595
608
|
/* Keyboard focus only. `:focus-visible` rather than `:focus` so a pointer
|
|
596
609
|
click never paints a ring. */
|
|
597
610
|
:focus-visible {
|