@adea-ai/ui 0.99.2 → 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/context-menu/context-menu.d.ts +8 -0
- package/dist/components/ui/context-menu/context-menu.d.ts.map +1 -1
- package/dist/components/ui/context-menu/context-menu.js +8 -0
- package/dist/components/ui/context-menu/context-menu.js.map +1 -1
- package/dist/components/ui/drawer/drawer.d.ts +8 -1
- package/dist/components/ui/drawer/drawer.d.ts.map +1 -1
- package/dist/components/ui/drawer/drawer.js.map +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts +8 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu.js +8 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.js.map +1 -1
- package/dist/components/ui/menubar/menubar.d.ts +8 -0
- package/dist/components/ui/menubar/menubar.d.ts.map +1 -1
- package/dist/components/ui/menubar/menubar.js +8 -0
- package/dist/components/ui/menubar/menubar.js.map +1 -1
- package/dist/components/ui/navigation-menu/navigation-menu.d.ts +14 -2
- package/dist/components/ui/navigation-menu/navigation-menu.d.ts.map +1 -1
- package/dist/components/ui/navigation-menu/navigation-menu.js +28 -23
- package/dist/components/ui/navigation-menu/navigation-menu.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/toast/toast.d.ts.map +1 -1
- package/dist/components/ui/toast/toast.js +19 -6
- package/dist/components/ui/toast/toast.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/context-menu/context-menu.tsx +8 -0
- package/dist/r/src/components/ui/drawer/drawer.tsx +8 -1
- package/dist/r/src/components/ui/dropdown-menu/dropdown-menu.tsx +8 -0
- package/dist/r/src/components/ui/menubar/menubar.tsx +8 -0
- package/dist/r/src/components/ui/navigation-menu/navigation-menu.tsx +42 -28
- package/dist/r/src/components/ui/tabs/tabs.tsx +7 -1
- package/dist/r/src/components/ui/toast/toast.tsx +21 -6
- 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/context-menu/context-menu.tsx +8 -0
- package/src/components/ui/drawer/drawer.tsx +8 -1
- package/src/components/ui/dropdown-menu/dropdown-menu.tsx +8 -0
- package/src/components/ui/menubar/menubar.tsx +8 -0
- package/src/components/ui/navigation-menu/navigation-menu.tsx +42 -28
- package/src/components/ui/tabs/tabs.tsx +7 -1
- package/src/components/ui/toast/toast.tsx +21 -6
- package/src/components/ui/tooltip/tooltip.tsx +3 -0
- package/src/lib/version-notes.ts +8 -0
- package/src/styles/base.css +13 -0
|
@@ -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":";;;;;;AA8BA,IAAMkB,kBAAiC;;CAErCH,WAAW;;CAEXC,YAAY;;;;;;CAMZC,mBAAmB;AACrB;AAEA,IAAME,uBAAuBf,cAA6Bc,eAAe;AAiBzE,IAAMiB,4BAA4B/B,cAAkC;AACpE,IAAMgC,0CAA0B,IAAIC,IAAwB;AAC5D,IAAIC;AACJ,IAAIC,gCAAgC;AAEpC,SAASC,wBAAwBnB,IAAY;CAC3C,IAAIiB,yBAAyBjB,IAAIiB,uBAAuBG,KAAAA;AAC1D;AAEA,SAASC,6BAA6BC,OAAcrB,SAAsB;CACxE,MAAMuB,SAASF,MAAME;CACrB,OAAOA,WAAWC,UAAWD,kBAAkBE,QAAQF,OAAOG,SAAS1B,OAAO;AAChF;AAEA,SAAS2B,oBAAoBN,OAAc;CACzC,KAAK,MAAMO,eAAed,yBAAyB;EACjD,MAAMd,UAAU4B,YAAY5B,QAAQ;EACpC,IAAI4B,YAAYxB,OAAO,KAAKJ,WAAWoB,6BAA6BC,OAAOrB,OAAO,GAChF4B,YAAYhB,gBAAgB;CAEhC;AACF;AAEA,SAASiB,yBAAyBR,OAAqB;CACrD,KAAK,MAAMO,eAAed,yBAAyB;EACjD,MAAMd,UAAU4B,YAAY5B,QAAQ;EACpC,MAAMuB,SAASF,MAAME;EACrB,IACEK,YAAYxB,OAAO,KACnBwB,YAAYtB,iBAAiB,KAC7BN,WACA,EAAEuB,kBAAkBE,QAAQzB,QAAQ0B,SAASH,MAAM,IAEnDK,YAAYlB,uBAAuB;CAEvC;AACF;AAEA,SAASqB,0BAA0BH,aAAiCI,MAAe;CACjF,IAAI3C,UAAU;CAEd,IAAI2C,MAAM;EACR,IAAIlB,wBAAwBmB,IAAIL,WAAW,GAAG;EAC9Cd,wBAAwBoB,IAAIN,WAAW;EACvC,IAAI,CAACX,+BAA+B;GAClCA,gCAAgC;GAGhCO,OAAOW,iBAAiB,UAAUR,qBAAqB,IAAI;GAC3DS,SAASD,iBAAiB,eAAeN,0BAA0B,IAAI;EACzE;CACF,OAAO;EACLf,wBAAwBuB,OAAOT,WAAW;EAC1C,IAAId,wBAAwBwB,SAAS,KAAKrB,+BAA+B;GACvEA,gCAAgC;GAChCO,OAAOe,oBAAoB,UAAUZ,qBAAqB,IAAI;GAC9DS,SAASG,oBAAoB,eAAeV,0BAA0B,IAAI;EAC5E;CACF;AACF;AAEA,SAASW,2BAA2BZ,aAAiC;CACnE1C,gBAAgB;EACd6C,0BAA0BH,aAAa,KAAK;EAC5CV,wBAAwBU,YAAY7B,EAAE;CACxC,CAAC;AACH;AAEA,SAAS0C,mBAAmBC,SAAkBrB,OAAc;CAC1D,IAAIsB,MAAMC,QAAQF,OAAO,GAAG;EAC1B,MAAM,CAACG,UAAUC,QAAQJ;EACzBG,SAASC,MAAMzB,KAAK;CACtB,OAAO,IAAI,OAAOqB,YAAY,YAE5BG,QAASxB,KAAK;AAElB;AASA,SAAgB4B,gBAAgBC,OAA6B;CAC3D,OAAAC,gBACGtD,qBAAqBuD,UAAQ;EAAA,IAC5BC,QAAK;GAAA,OAAE;IACL5D,WAAWyD,MAAMzD,aAAaG,gBAAgBH;IAC9CC,YAAYwD,MAAMxD,cAAcE,gBAAgBF;IAChDC,mBAAmBuD,MAAMvD,qBAAqBC,gBAAgBD;GAChE;EAAC;EAAA,IAAAqD,WAAA;GAAA,OAEAE,MAAMF;EAAQ;CAAA,CAAA;AAGrB;AAIA,SAAgBtE,UAAQwE,OAAyB;CAC/C,MAAMK,SAASnE,WAAWS,oBAAoB;CAC9C,MAAM,CAAC2D,OAAOC,QAAQtE,WAAW+D,OAAO;EAAC;EAAQ;EAAe;EAAgB;CAAU,CAAC;CAC3F,MAAMnD,KAAKd,eAAe;CAC1B,MAAMyE,mBAAmBF,MAAMxB,SAASb,KAAAA;CACxC,MAAM,CAACwC,kBAAkBC,uBAAuB5E,aAAawE,MAAMK,eAAe,KAAK;CACvF,MAAMzD,eAAgBsD,WAAW,IAAKF,MAAMxB,QAAQ,QAAS2B,iBAAiB;CAC9E,MAAM,CAAC3D,SAASE,cAAclB,aAA0B;CACxD,IAAI8E,cAAc;CAClB,IAAIC,uBAAuB;CAC3B,IAAIC;CACJ,IAAIG,0BAA0B;CAC9B,IAAIC;CACJ,MAAMzD,gCAAgC;EACpCwD,0BAA0B;EAC1B,IAAIC,2BAA2BG,aAAaH,yBAAyB;EACrEA,4BAA4BjD,KAAAA;CAC9B;CACA,MAAMT,+BAA+B;EACnC,IAAI,CAACN,OAAO,GAAG;EACfO,wBAAwB;EACxB,MAAMjB,aAAawD,MAAMxD,cAAc6D,OAAO7D;EAC9CyE,0BAA0BK,KAAKC,IAAI,IAAI/E,aAAa;EACpD0E,4BAA4BE,WAAW3D,yBAAyBjB,aAAa,EAAE;CACjF;CACA,MAAMkB,wBAAwB;EAC5BkD,cAAc;EACdnD,wBAAwB;EACxB+D,qBAAqB;GACnBZ,cAAc;EAChB,CAAC;CACH;CACA,MAAMrD,uCAAuC;EAC3C,IAAI,CAACL,OAAO,GAAG;EAMf2D,uBAAuB;EACvBW,qBAAqB;GACnBX,uBAAuB;EACzB,CAAC;EACDC,aAAa,OAAO,SAAS;CAC/B;CACA,MAAMpC,cAAkC;EACtC7B;EACAC;EACAE;EACAE;EACAC,kBAAkBmD,MAAMmB,YAAY;EACpCrE,wBACEsE,QAAQ5E,QAAQ,KAAK,OAAOoC,aAAa,eAAepC,QAAQ,MAAMoC,SAASyC,aAAa;EAC9FtE,sBAAsB;GACpBI,wBAAwB;GACxBK,uBAAuBjB;GAGvB,KAAK,MAAM+E,mBAAmBhE,yBAC5B,IAAIgE,gBAAgB/E,OAAOA,IAAI+E,gBAAgBrE,+BAA+B;EAElF;EACAD,uBAAuBU,wBAAwBnB,EAAE;EACjDU;EACAC;EACAC;EACAC;CACF;CACA4B,2BAA2BZ,WAAW;CACtC1C,gBAAgB;EACdyB,wBAAwB;EACxBoD,uBAAuB;CACzB,CAAC;CACD,IAAI3D,OAAO,GAAG2B,0BAA0BH,aAAa,IAAI;CACzD7C,mBAAmBgD,0BAA0BH,aAAaxB,OAAO,CAAC,CAAC;CACnE4D,gBAAgBC,MAAeC,WAAW;EACxC,IAAI,CAACD,QAAQC,WAAW,aAAaH,sBAAsB;EAE3D,IAAIE,MAAM;GACR/C,wBAAwBnB,EAAE;GAC1BY,wBAAwB;GAGxBoB,0BAA0BH,aAAa,IAAI;EAC7C,OAAO;GACL,MAAMmD,iBAAiBnD,YAAYtB,iBAAiB;GACpD,MAAM0E,wBACJhE,yBAAyBG,KAAAA,KAAaH,yBAAyBjB;GAEjE,IACEmE,WAAW,aACXa,kBAH0BP,KAAKC,IAAI,KAAKN,2BAKxC,CAACL,eACD,CAACkB,uBACD;IACArE,wBAAwB;IACxB;GACF;GACAmD,cAAc;GACdnD,wBAAwB;GACxBO,wBAAwBnB,EAAE;EAC5B;EAEA,IAAI,CAAC2D,WAAW,GAAGE,oBAAoBK,IAAI;EAC3CT,MAAMQ,eAAeC,IAAI;EACzB,IAAIA,MAGFS,qBAAqB;GACnB,IAAI,CAACtE,OAAO,GAAG2B,0BAA0BH,aAAa,KAAK;EAC7D,CAAC;CAEL;CAOA,MAAMsD,iBAAiB7D,UAAyB;EAC9C,IAAIA,MAAM+D,QAAQ,UAAU;EAC5BxE,gBAAgB;EAChBoD,aAAa,KAAK;CACpB;CACAjF,mBAAmB;EACjB,IAAIM,YAAY,CAACe,OAAO,GAAG;EAC3BgC,SAASD,iBAAiB,WAAW+C,eAAe,IAAI;EACxDhG,gBAAgBkD,SAASG,oBAAoB,WAAW2C,eAAe,IAAI,CAAC;CAC9E,CAAC;CAED,OAAA/B,gBACGtC,0BAA0BuC,UAAQ;EAACC,OAAOzB;EAAW,IAAAoB,WAAA;GAAA,OAAAG,gBACnDxE,SAAc0G,WAAA;IAAA,IACbrD,OAAI;KAAA,OAAE5B,OAAO;IAAC;IACA4D;IAAY,IAC1BW,WAAQ;KAAA,OAAEnB,MAAMmB;IAAQ;IAAA,IACxBlF,YAAS;KAAA,OAAEyD,MAAMzD,aAAa8D,OAAO9D;IAAS;IAAA,IAC9CC,aAAU;KAAA,OAAEwD,MAAMxD,cAAc6D,OAAO7D;IAAU;IAAA,IACjDC,oBAAiB;KAAA,OAAEuD,MAAMvD,qBAAqB4D,OAAO5D;IAAiB;GAAA,GAClE8D,IAAI,CAAA;EAAA;CAAA,CAAA;AAIhB;AAEA,SAAgB6B,eAAepC,OAAsD;CACnF,MAAMtB,cAAcxC,WAAWyB,yBAAyB;CACxD,MAAM,CAAC2C,OAAOC,QAAQtE,WAAW+D,OAAO;EACtC;EACA;EACA;EACA;EACA;EACA;CAAS,CACV;CAED,OAAAC,gBACGxE,QAAe4G,SAAOF,WACjB5B,MAAI;EAAA+B,MACFrF,YAAY;GAChByB,aAAa1B,WAAWC,OAAO;GAC/B,IAAI,OAAOqD,MAAMgC,QAAQ,YAAYhC,MAAMgC,IAAIrF,OAAO;EACxD;EACAsF,UAAUpE,UAAU;GAClBoB,mBAAmBe,MAAMiC,SAASpE,KAAK;GACvC,IAAI,CAACA,MAAMqE,oBAAoB,CAAC9D,aAAavB,WAAW,GAAGuB,aAAarB,eAAe;EACzF;EACAoF,SAAStE,UAAU;GACjBoB,mBAAmBe,MAAMmC,QAAQtE,KAAK;GACtCO,aAAapB,gBAAgB;GAC7BoB,aAAahB,gBAAgB;EAC/B;EACAgF,iBAAiBvE,UAAU;GACzBoB,mBAAmBe,MAAMoC,gBAAgBvE,KAAK;GAC9C,IAAI,CAACA,MAAMqE,oBAAoB,CAAC9D,aAAavB,WAAW,GAAGuB,aAAarB,eAAe;EACzF;EACAsF,iBAAiBxE,UAAU;GACzBoB,mBAAmBe,MAAMqC,gBAAgBxE,KAAK;GAC9CO,aAAapB,gBAAgB;GAC7BoB,aAAalB,uBAAuB;EACtC;EACAoF,UAAUzE,UAAU;GAClBoB,mBAAmBe,MAAMsC,SAASzE,KAAK;GACvCO,aAAahB,gBAAgB;EAC/B;CAAC,CAAA,CAAA;AAGP;AAIA,SAAgBqF,eAAe/C,OAA4B;CACzD,MAAMtB,cAAcxC,WAAWyB,yBAAyB;CACxD,MAAM,CAAC2C,OAAOC,QAAQtE,WAAW+D,OAAO;EACtC;EACA;EACA;EACA;EACA;CAAsB,CACvB;CACD,MAAMgD,UAAUtH,kBAAkB;CAIlC,OAAAuE,gBACGxE,QAAewH,QAAM,EAAA,IAAAnD,WAAA;EAAA,OAAAG,gBACnBxE,QAAeqH,SAAOX,WAAA;GAAA,aAAA;GAAA,KAAA,WAAA;IAAA,OAId9F,GACLD,YAIA,+DACA,+DACA,6EACA,0EACA,uDACAkE,MAAM4C,KACR;GAAC;GAAA,KAAA,iBAAA;IAAA,OACYE,WAAA,CAAA,CAAAJ,QAAQ9F,OAAO,CAAC,CAAA,CAAA,IAAGoD,MAAM,iBAAiB;GAAI;GAC3D+C,kBAAkBlF,UAAU;IAC1BoB,mBAAmBe,MAAM+C,iBAAiBlF,KAAK;IAC/C,IAAI,CAACA,MAAMqE,kBAAkB9D,aAAahB,gBAAgB;GAC5D;GACA4F,uBAAuBnF,UAAU;IAC/BoB,mBAAmBe,MAAMgD,sBAAsBnF,KAAK;IACpD,IAAI,CAACA,MAAMqE,kBAAkB9D,aAAahB,gBAAgB;GAC5D;EAAC,GACG6C,MAAI,EAAA,IAAAT,WAAA;GAAA,OAEPQ,MAAMR;EAAQ,EAAA,CAAA,CAAA;CAAA,EAAA,CAAA;AAIvB"}
|
|
@@ -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',
|
|
@@ -131,6 +131,14 @@ export function ContextMenuRadioItem(props: ComponentProps<typeof KobalteContext
|
|
|
131
131
|
)
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* The caption of a `ContextMenuGroup`, and that group's accessible name. It must be a
|
|
136
|
+
* child of the group it names: Kobalte reads the group from context, so a label
|
|
137
|
+
* placed directly in the content throws `useMenuGroupContext must be used within
|
|
138
|
+
* a Menu.Group` the moment the menu opens. For an ungrouped caption, wrap the
|
|
139
|
+
* label and its items in a group anyway — the grouping is what a screen reader
|
|
140
|
+
* announces.
|
|
141
|
+
*/
|
|
134
142
|
export function ContextMenuLabel(props: ComponentProps<typeof KobalteContextMenu.GroupLabel>) {
|
|
135
143
|
const [local, rest] = splitProps(props, ['class'])
|
|
136
144
|
return <KobalteContextMenu.GroupLabel class={cn(menuLabel, local.class)} {...rest} />
|
|
@@ -41,7 +41,14 @@ export function DrawerOverlay(props: ComponentProps<typeof DrawerPrimitive.Overl
|
|
|
41
41
|
)
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export type DrawerContentProps = ComponentProps<typeof DrawerPrimitive.Content> & {
|
|
44
|
+
export type DrawerContentProps = Omit<ComponentProps<typeof DrawerPrimitive.Content>, 'side'> & {
|
|
45
|
+
/**
|
|
46
|
+
* Not accepted here. corvu reads the side from the root `Drawer`, which uses it
|
|
47
|
+
* for the drag axis and publishes it as `data-side`; a `side` on the content
|
|
48
|
+
* would type-check through the polymorphic props and be silently ignored,
|
|
49
|
+
* leaving the drawer on the bottom edge. Set `<Drawer side="right">` instead.
|
|
50
|
+
*/
|
|
51
|
+
side?: never
|
|
45
52
|
/**
|
|
46
53
|
* Draw the grabber at the leading edge of the sheet.
|
|
47
54
|
*
|
|
@@ -187,6 +187,14 @@ export function DropdownMenuRadioItem(props: ComponentProps<typeof KobalteDropdo
|
|
|
187
187
|
)
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
/**
|
|
191
|
+
* The caption of a `DropdownMenuGroup`, and that group's accessible name. It must be a
|
|
192
|
+
* child of the group it names: Kobalte reads the group from context, so a label
|
|
193
|
+
* placed directly in the content throws `useMenuGroupContext must be used within
|
|
194
|
+
* a Menu.Group` the moment the menu opens. For an ungrouped caption, wrap the
|
|
195
|
+
* label and its items in a group anyway — the grouping is what a screen reader
|
|
196
|
+
* announces.
|
|
197
|
+
*/
|
|
190
198
|
export function DropdownMenuLabel(props: ComponentProps<typeof KobalteDropdownMenu.GroupLabel>) {
|
|
191
199
|
const [local, rest] = splitProps(props, ['class'])
|
|
192
200
|
return <KobalteDropdownMenu.GroupLabel class={cn(menuLabel, local.class)} {...rest} />
|
|
@@ -160,6 +160,14 @@ export function MenubarRadioItem(props: ComponentProps<typeof KobalteMenubar.Rad
|
|
|
160
160
|
)
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
/**
|
|
164
|
+
* The caption of a `MenubarGroup`, and that group's accessible name. It must be a
|
|
165
|
+
* child of the group it names: Kobalte reads the group from context, so a label
|
|
166
|
+
* placed directly in the content throws `useMenuGroupContext must be used within
|
|
167
|
+
* a Menu.Group` the moment the menu opens. For an ungrouped caption, wrap the
|
|
168
|
+
* label and its items in a group anyway — the grouping is what a screen reader
|
|
169
|
+
* announces.
|
|
170
|
+
*/
|
|
163
171
|
export function MenubarLabel(props: ComponentProps<typeof KobalteMenubar.GroupLabel>) {
|
|
164
172
|
const [local, rest] = splitProps(props, ['class'])
|
|
165
173
|
return <KobalteMenubar.GroupLabel class={cn(menuLabel, local.class)} {...rest} />
|
|
@@ -41,9 +41,15 @@ import { menuContentPadding, menuItem, overlayMotion, overlaySurface } from '../
|
|
|
41
41
|
* - `Content` must be a direct child of `Menu`, and the `Viewport` is a **sibling
|
|
42
42
|
* of the menus** — not inside them. All the panels share one viewport, which is
|
|
43
43
|
* what lets the bar animate between them instead of each panel popping in place.
|
|
44
|
+
* - The viewport is an empty surface. Each `Content` reaches it through Kobalte's
|
|
45
|
+
* `Portal`, which mounts the panel inside the viewport while keeping it in its
|
|
46
|
+
* own `Menu`'s context. A `Content` rendered anywhere else — the viewport's own
|
|
47
|
+
* children included — has no `Menu` above it, and Kobalte throws
|
|
48
|
+
* `useMenuRootContext must be used within a MenuRoot` the moment a panel opens.
|
|
44
49
|
*
|
|
45
|
-
* The wrapper enforces the last
|
|
46
|
-
*
|
|
50
|
+
* The wrapper enforces the last two by rendering the `Viewport` itself and by
|
|
51
|
+
* portalling every `NavigationMenuContent`, so a caller cannot forget either and
|
|
52
|
+
* get a menu whose panels never appear.
|
|
47
53
|
*/
|
|
48
54
|
|
|
49
55
|
export type NavigationMenuProps = ComponentProps<typeof KobalteNavigationMenu.Root> & {
|
|
@@ -112,25 +118,41 @@ export function NavigationMenuContent(props: ComponentProps<typeof KobalteNaviga
|
|
|
112
118
|
const [local, rest] = splitProps(props, ['class', 'children'])
|
|
113
119
|
|
|
114
120
|
return (
|
|
115
|
-
<KobalteNavigationMenu.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
<KobalteNavigationMenu.Portal>
|
|
122
|
+
<KobalteNavigationMenu.Content
|
|
123
|
+
class={cn(
|
|
124
|
+
// Absolute so the viewport sizes to the active panel's measured box and
|
|
125
|
+
// animates between panels, rather than stacking them.
|
|
126
|
+
'absolute top-0 left-0 grid w-max gap-1 p-2 outline-none',
|
|
127
|
+
// The motion is the panel's, not the viewport's. Kobalte keeps an
|
|
128
|
+
// outgoing panel mounted until the viewport's own animation ends; a
|
|
129
|
+
// viewport that carries an entrance animation never fires that again
|
|
130
|
+
// after it opens, so the previous panel would stay painted under the
|
|
131
|
+
// next one.
|
|
132
|
+
overlayMotion,
|
|
133
|
+
// A two-column panel is the shape most menu bars want, and it is opt-in
|
|
134
|
+
// through this hook rather than a prop because the column count is a
|
|
135
|
+
// layout decision the caller's content makes.
|
|
136
|
+
'data-[wide]:grid-cols-2',
|
|
137
|
+
local.class
|
|
138
|
+
)}
|
|
139
|
+
{...rest}
|
|
140
|
+
>
|
|
141
|
+
{local.children}
|
|
142
|
+
</KobalteNavigationMenu.Content>
|
|
143
|
+
</KobalteNavigationMenu.Portal>
|
|
128
144
|
)
|
|
129
145
|
}
|
|
130
146
|
|
|
131
147
|
/**
|
|
132
148
|
* The shared panel surface. One per menu bar, and the wrapper supplies it — but
|
|
133
149
|
* exported so a caller with an unusual layout can place it themselves.
|
|
150
|
+
*
|
|
151
|
+
* It renders no children: the panels arrive through each `NavigationMenuContent`'s
|
|
152
|
+
* portal. Kobalte positions it under the bar and publishes the active panel's size
|
|
153
|
+
* as `--kb-navigation-menu-viewport-*`, which the surface animates between.
|
|
154
|
+
* `box-content` keeps the border outside that measured size so a panel is never
|
|
155
|
+
* clipped by its own frame.
|
|
134
156
|
*/
|
|
135
157
|
export function NavigationMenuViewport(
|
|
136
158
|
props: ComponentProps<typeof KobalteNavigationMenu.Viewport>
|
|
@@ -139,24 +161,16 @@ export function NavigationMenuViewport(
|
|
|
139
161
|
|
|
140
162
|
return (
|
|
141
163
|
<KobalteNavigationMenu.Viewport
|
|
164
|
+
data-slot="navigation-menu-viewport"
|
|
142
165
|
class={cn(
|
|
143
|
-
|
|
166
|
+
overlaySurface,
|
|
167
|
+
'isolate z-(--z-menu) mt-1.5 box-content list-none overflow-hidden',
|
|
144
168
|
'h-(--kb-navigation-menu-viewport-height) w-(--kb-navigation-menu-viewport-width)',
|
|
145
|
-
'origin-
|
|
169
|
+
'origin-(--kb-menu-content-transform-origin) transition-[width,height] duration-200 ease-out',
|
|
146
170
|
local.class
|
|
147
171
|
)}
|
|
148
172
|
{...rest}
|
|
149
|
-
|
|
150
|
-
<div
|
|
151
|
-
class={cn(
|
|
152
|
-
overlaySurface,
|
|
153
|
-
overlayMotion,
|
|
154
|
-
'w-full rounded-lg border border-border bg-popover p-1 shadow-lg'
|
|
155
|
-
)}
|
|
156
|
-
>
|
|
157
|
-
<KobalteNavigationMenu.Content />
|
|
158
|
-
</div>
|
|
159
|
-
</KobalteNavigationMenu.Viewport>
|
|
173
|
+
/>
|
|
160
174
|
)
|
|
161
175
|
}
|
|
162
176
|
|
|
@@ -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
|
)}
|
|
@@ -179,11 +179,23 @@ export type ToasterProps = ComponentProps<typeof KobalteToast.Region> & {
|
|
|
179
179
|
region?: string
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
/**
|
|
183
|
+
* The corner is set on the region, not the list. The region is the `fixed`
|
|
184
|
+
* element: an inset on the list inside it positions nothing, and the region falls
|
|
185
|
+
* back to wherever it happens to sit in the document flow.
|
|
186
|
+
*/
|
|
182
187
|
const toasterPosition = {
|
|
183
|
-
'top-left': 'top-0 left-0
|
|
184
|
-
'top-right': 'top-0 right-0
|
|
185
|
-
'bottom-left': 'bottom-0 left-0
|
|
186
|
-
'bottom-right': 'bottom-0 right-0
|
|
188
|
+
'top-left': 'top-0 left-0',
|
|
189
|
+
'top-right': 'top-0 right-0',
|
|
190
|
+
'bottom-left': 'bottom-0 left-0',
|
|
191
|
+
'bottom-right': 'bottom-0 right-0',
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const toasterAlignment = {
|
|
195
|
+
'top-left': 'items-start',
|
|
196
|
+
'top-right': 'items-end',
|
|
197
|
+
'bottom-left': 'items-start',
|
|
198
|
+
'bottom-right': 'items-end',
|
|
187
199
|
}
|
|
188
200
|
|
|
189
201
|
/** Mount once, near the root. Renders the stack and owns its timing defaults. */
|
|
@@ -203,13 +215,16 @@ export function Toaster(props: ToasterProps) {
|
|
|
203
215
|
* in-flight notification, and clicking a toast must not dismiss the
|
|
204
216
|
* dialog behind it.
|
|
205
217
|
*/
|
|
206
|
-
class=
|
|
218
|
+
class={cn(
|
|
219
|
+
'pointer-events-none fixed z-(--z-toast) flex max-h-screen w-full flex-col gap-2 p-4 sm:max-w-sm',
|
|
220
|
+
toasterPosition[local.position ?? 'bottom-right']
|
|
221
|
+
)}
|
|
207
222
|
{...rest}
|
|
208
223
|
>
|
|
209
224
|
<KobalteToast.List
|
|
210
225
|
class={cn(
|
|
211
226
|
'flex flex-col gap-2',
|
|
212
|
-
|
|
227
|
+
toasterAlignment[local.position ?? 'bottom-right'],
|
|
213
228
|
local.class
|
|
214
229
|
)}
|
|
215
230
|
/>
|
|
@@ -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
|
|