gridsmith-ui 0.6.2 → 0.6.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/kanban.js CHANGED
@@ -1,5 +1,5 @@
1
- import { Badge } from './chunk-TYU2LQGK.js';
2
- import { useTextOverflow, Tooltip } from './chunk-R5QGJURZ.js';
1
+ import { Badge } from './chunk-KXNJJKIV.js';
2
+ import { useTextOverflow, Tooltip } from './chunk-DDKCQVKO.js';
3
3
  import { Icon } from './chunk-GX6O72IV.js';
4
4
  import { cn } from './chunk-TTZSZNJ6.js';
5
5
  import { useState } from 'react';
package/dist/resizable.js CHANGED
@@ -1,4 +1,4 @@
1
- import { useBreakpoint } from './chunk-DPF6776I.js';
1
+ import { useBreakpoint } from './chunk-EKQHHP72.js';
2
2
  import { Icon } from './chunk-GX6O72IV.js';
3
3
  import { cn } from './chunk-TTZSZNJ6.js';
4
4
  import { Group, Panel, Separator } from 'react-resizable-panels';
@@ -22,6 +22,9 @@ declare function BreakpointWidthProvider({ width, children }: {
22
22
  * Returns true when the viewport (or override container) is at or below the
23
23
  * named breakpoint.
24
24
  *
25
+ * Implemented with useSyncExternalStore so the first render already reflects
26
+ * the media query and a change between render and subscribe cannot be missed.
27
+ *
25
28
  * @example
26
29
  * const isMobile = useBreakpoint("md"); // true when ≤768px
27
30
  */
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../showcase/src/components/VirtualizedList.tsx"],"names":[],"mappings":";;;;;AAcO,SAAS,eAAA,CAAmB;AAAA,EACjC,KAAA;AAAA,EACA,YAAA;AAAA,EACA,UAAA;AAAA,EACA,QAAA,GAAW,CAAA;AAAA,EACX,MAAA,GAAS,GAAA;AAAA,EACT,SAAA;AAAA,EACA;AACF,CAAA,EAA4B;AAC1B,EAAA,MAAM,SAAA,GAAY,OAAuB,IAAI,CAAA;AAE7C,EAAA,MAAM,cAAc,cAAA,CAAe;AAAA,IACjC,OAAO,KAAA,CAAM,MAAA;AAAA,IACb,gBAAA,EAAkB,MAAM,SAAA,CAAU,OAAA;AAAA,IAClC,YAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA,EAAK,CAAC,IAAA,KAAS;AACb,QAAC,UAA4D,OAAA,GAAU,IAAA;AACvE,QAAA,IAAI,OAAO,GAAA,KAAQ,UAAA,EAAY,GAAA,CAAI,IAAI,CAAA;AAAA,aAAA,IAC9B,GAAA,EAAM,GAAA,CAAsD,OAAA,GAAU,IAAA;AAAA,MACjF,CAAA;AAAA,MACA,SAAA,EAAW,EAAA;AAAA,QACT,eAAA;AAAA,QACA,+BAAA;AAAA,QACA,yEAAA;AAAA,QACA,2BAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA,KAAA,EAAO,EAAE,MAAA,EAAO;AAAA,MAEhB,8BAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iBAAA,EAAkB,KAAA,EAAO,EAAE,MAAA,EAAQ,WAAA,CAAY,YAAA,EAAa,IACxE,QAAA,EAAA,WAAA,CAAY,eAAA,EAAgB,CAAE,GAAA,CAAI,CAAC,WAAA,qBAClC,GAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UAEC,cAAY,WAAA,CAAY,KAAA;AAAA,UACxB,KAAK,WAAA,CAAY,cAAA;AAAA,UACjB,SAAA,EAAU,8BAAA;AAAA,UACV,OAAO,EAAE,SAAA,EAAW,CAAA,WAAA,EAAc,WAAA,CAAY,KAAK,CAAA,GAAA,CAAA,EAAM;AAAA,UAExD,qBAAW,KAAA,CAAM,WAAA,CAAY,KAAK,CAAA,EAAG,WAAA,CAAY,OAAO,WAAW;AAAA,SAAA;AAAA,QAN/D,WAAA,CAAY;AAAA,OAQpB,CAAA,EACH;AAAA;AAAA,GACF;AAEJ","file":"virtualized.js","sourcesContent":["import { useRef, type ReactNode, type Ref } from \"react\";\nimport { useVirtualizer, type VirtualItem } from \"@tanstack/react-virtual\";\nimport { cn } from \"../lib/utils\";\n\nexport interface VirtualizedListProps<T> {\n items: T[];\n estimateSize: (index: number) => number;\n renderItem: (item: T, index: number, virtualItem: VirtualItem) => ReactNode;\n overscan?: number;\n height?: number | string;\n className?: string;\n ref?: Ref<HTMLDivElement>;\n}\n\nexport function VirtualizedList<T>({\n items,\n estimateSize,\n renderItem,\n overscan = 5,\n height = 400,\n className,\n ref,\n}: VirtualizedListProps<T>) {\n const parentRef = useRef<HTMLDivElement>(null);\n\n const virtualizer = useVirtualizer({\n count: items.length,\n getScrollElement: () => parentRef.current,\n estimateSize,\n overscan,\n });\n\n return (\n <div\n ref={(node) => {\n (parentRef as React.MutableRefObject<HTMLDivElement | null>).current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;\n }}\n className={cn(\n \"overflow-auto\",\n \"rounded-[var(--ds-radius-md)]\",\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]\",\n \"bg-[var(--ds-bg-surface)]\",\n className,\n )}\n style={{ height }}\n >\n <div className=\"relative w-full\" style={{ height: virtualizer.getTotalSize() }}>\n {virtualizer.getVirtualItems().map((virtualItem) => (\n <div\n key={virtualItem.key}\n data-index={virtualItem.index}\n ref={virtualizer.measureElement}\n className=\"absolute top-0 left-0 w-full\"\n style={{ transform: `translateY(${virtualItem.start}px)` }}\n >\n {renderItem(items[virtualItem.index], virtualItem.index, virtualItem)}\n </div>\n ))}\n </div>\n </div>\n );\n}\n"]}
1
+ {"version":3,"sources":["../../../showcase/src/components/VirtualizedList.tsx"],"names":[],"mappings":";;;;;AAcO,SAAS,eAAA,CAAmB;AAAA,EACjC,KAAA;AAAA,EACA,YAAA;AAAA,EACA,UAAA;AAAA,EACA,QAAA,GAAW,CAAA;AAAA,EACX,MAAA,GAAS,GAAA;AAAA,EACT,SAAA;AAAA,EACA;AACF,CAAA,EAA4B;AAC1B,EAAA,MAAM,SAAA,GAAY,OAAuB,IAAI,CAAA;AAG7C,EAAA,MAAM,cAAc,cAAA,CAAe;AAAA,IACjC,OAAO,KAAA,CAAM,MAAA;AAAA,IACb,gBAAA,EAAkB,MAAM,SAAA,CAAU,OAAA;AAAA,IAClC,YAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA,EAAK,CAAC,IAAA,KAAS;AACb,QAAC,UAA4D,OAAA,GAAU,IAAA;AACvE,QAAA,IAAI,OAAO,GAAA,KAAQ,UAAA,EAAY,GAAA,CAAI,IAAI,CAAA;AAAA,aAAA,IAC9B,GAAA,EAAM,GAAA,CAAsD,OAAA,GAAU,IAAA;AAAA,MACjF,CAAA;AAAA,MACA,SAAA,EAAW,EAAA;AAAA,QACT,eAAA;AAAA,QACA,+BAAA;AAAA,QACA,yEAAA;AAAA,QACA,2BAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA,KAAA,EAAO,EAAE,MAAA,EAAO;AAAA,MAEhB,8BAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iBAAA,EAAkB,KAAA,EAAO,EAAE,MAAA,EAAQ,WAAA,CAAY,YAAA,EAAa,IACxE,QAAA,EAAA,WAAA,CAAY,eAAA,EAAgB,CAAE,GAAA,CAAI,CAAC,WAAA,qBAClC,GAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UAEC,cAAY,WAAA,CAAY,KAAA;AAAA,UACxB,KAAK,WAAA,CAAY,cAAA;AAAA,UACjB,SAAA,EAAU,8BAAA;AAAA,UACV,OAAO,EAAE,SAAA,EAAW,CAAA,WAAA,EAAc,WAAA,CAAY,KAAK,CAAA,GAAA,CAAA,EAAM;AAAA,UAExD,qBAAW,KAAA,CAAM,WAAA,CAAY,KAAK,CAAA,EAAG,WAAA,CAAY,OAAO,WAAW;AAAA,SAAA;AAAA,QAN/D,WAAA,CAAY;AAAA,OAQpB,CAAA,EACH;AAAA;AAAA,GACF;AAEJ","file":"virtualized.js","sourcesContent":["import { useRef, type ReactNode, type Ref } from \"react\";\nimport { useVirtualizer, type VirtualItem } from \"@tanstack/react-virtual\";\nimport { cn } from \"../lib/utils\";\n\nexport interface VirtualizedListProps<T> {\n items: T[];\n estimateSize: (index: number) => number;\n renderItem: (item: T, index: number, virtualItem: VirtualItem) => ReactNode;\n overscan?: number;\n height?: number | string;\n className?: string;\n ref?: Ref<HTMLDivElement>;\n}\n\nexport function VirtualizedList<T>({\n items,\n estimateSize,\n renderItem,\n overscan = 5,\n height = 400,\n className,\n ref,\n}: VirtualizedListProps<T>) {\n const parentRef = useRef<HTMLDivElement>(null);\n\n // eslint-disable-next-line react-hooks/incompatible-library -- TanStack Virtual is not compiler-compatible (informational; the project does not use the React Compiler)\n const virtualizer = useVirtualizer({\n count: items.length,\n getScrollElement: () => parentRef.current,\n estimateSize,\n overscan,\n });\n\n return (\n <div\n ref={(node) => {\n (parentRef as React.MutableRefObject<HTMLDivElement | null>).current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;\n }}\n className={cn(\n \"overflow-auto\",\n \"rounded-[var(--ds-radius-md)]\",\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]\",\n \"bg-[var(--ds-bg-surface)]\",\n className,\n )}\n style={{ height }}\n >\n <div className=\"relative w-full\" style={{ height: virtualizer.getTotalSize() }}>\n {virtualizer.getVirtualItems().map((virtualItem) => (\n <div\n key={virtualItem.key}\n data-index={virtualItem.index}\n ref={virtualizer.measureElement}\n className=\"absolute top-0 left-0 w-full\"\n style={{ transform: `translateY(${virtualItem.start}px)` }}\n >\n {renderItem(items[virtualItem.index], virtualItem.index, virtualItem)}\n </div>\n ))}\n </div>\n </div>\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gridsmith-ui",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "Gridsmith component library \u2014 98 React components with design tokens and a CSS custom property theming engine",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../showcase/src/lib/use-breakpoint.ts"],"names":[],"mappings":";;;AAEA,IAAM,mBAAA,GAAsB,EAAE,EAAA,EAAI,GAAA,EAAK,IAAI,GAAA,EAAK,EAAA,EAAI,IAAA,EAAM,EAAA,EAAI,IAAA,EAAK;AAInE,IAAM,kBAAA,GAAqB,cAA6B,IAAI,CAAA;AAOrD,SAAS,uBAAA,CAAwB,EAAE,KAAA,EAAO,QAAA,EAAS,EAAkD;AAC1G,EAAA,OAAO,cAAc,kBAAA,EAAoB,EAAE,KAAA,EAAO,KAAA,IAAS,QAAQ,CAAA;AACrE;AAEA,SAAS,cAAc,GAAA,EAA4B;AACjD,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,oBAAoB,GAAG,CAAA;AACjE,EAAA,MAAM,GAAA,GAAM,gBAAA,CAAiB,QAAA,CAAS,eAAe,CAAA,CAClD,iBAAiB,CAAA,QAAA,EAAW,GAAG,CAAA,CAAE,CAAA,CACjC,IAAA,EAAK;AACR,EAAA,MAAM,EAAA,GAAK,QAAA,CAAS,GAAA,EAAK,EAAE,CAAA;AAC3B,EAAA,OAAO,KAAA,CAAM,EAAE,CAAA,GAAI,mBAAA,CAAoB,GAAG,CAAA,GAAI,EAAA;AAChD;AAUO,SAAS,cAAc,GAAA,EAA6B;AACzD,EAAA,MAAM,aAAA,GAAgB,WAAW,kBAAkB,CAAA;AACnD,EAAA,MAAM,EAAA,GAAK,QAAQ,MAAM,aAAA,CAAc,GAAG,CAAA,EAAG,CAAC,GAAG,CAAC,CAAA;AAElD,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,MAAM;AAC3C,IAAA,IAAI,aAAA,KAAkB,IAAA,EAAM,OAAO,aAAA,IAAiB,EAAA;AACpD,IAAA,OAAO,OAAO,WAAW,WAAA,GACrB,MAAA,CAAO,WAAW,CAAA,YAAA,EAAe,EAAE,CAAA,GAAA,CAAK,CAAA,CAAE,OAAA,GAC1C,KAAA;AAAA,EACN,CAAC,CAAA;AAED,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,kBAAkB,IAAA,EAAM;AAC5B,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,CAAA,YAAA,EAAe,EAAE,CAAA,GAAA,CAAK,CAAA;AACpD,IAAA,MAAM,OAAA,GAAU,CAAC,CAAA,KAA2B,UAAA,CAAW,EAAE,OAAO,CAAA;AAChE,IAAA,GAAA,CAAI,gBAAA,CAAiB,UAAU,OAAO,CAAA;AACtC,IAAA,UAAA,CAAW,IAAI,OAAO,CAAA;AACtB,IAAA,OAAO,MAAM,GAAA,CAAI,mBAAA,CAAoB,QAAA,EAAU,OAAO,CAAA;AAAA,EACxD,CAAA,EAAG,CAAC,EAAA,EAAI,aAAa,CAAC,CAAA;AAEtB,EAAA,IAAI,aAAA,KAAkB,IAAA,EAAM,OAAO,aAAA,IAAiB,EAAA;AACpD,EAAA,OAAO,OAAA;AACT","file":"chunk-DPF6776I.js","sourcesContent":["import { useState, useEffect, useMemo, createContext, useContext, createElement, type ReactNode } from \"react\";\n\nconst DEFAULT_BREAKPOINTS = { sm: 640, md: 768, lg: 1024, xl: 1280 } as const;\n\nexport type BreakpointKey = keyof typeof DEFAULT_BREAKPOINTS;\n\nconst BreakpointWidthCtx = createContext<number | null>(null);\n\n/**\n * Provides an override container width for all `useBreakpoint` calls within\n * this subtree. Used by the builder's viewport preview to make components\n * respond to the constrained canvas width instead of the browser viewport.\n */\nexport function BreakpointWidthProvider({ width, children }: { width: number | null; children: ReactNode }) {\n return createElement(BreakpointWidthCtx, { value: width }, children);\n}\n\nfunction readBpFromCSS(key: BreakpointKey): number {\n if (typeof window === \"undefined\") return DEFAULT_BREAKPOINTS[key];\n const raw = getComputedStyle(document.documentElement)\n .getPropertyValue(`--ds-bp-${key}`)\n .trim();\n const px = parseInt(raw, 10);\n return isNaN(px) ? DEFAULT_BREAKPOINTS[key] : px;\n}\n\n/**\n * Reactive breakpoint check that reads `--ds-bp-*` CSS variables.\n * Returns true when the viewport (or override container) is at or below the\n * named breakpoint.\n *\n * @example\n * const isMobile = useBreakpoint(\"md\"); // true when ≤768px\n */\nexport function useBreakpoint(key: BreakpointKey): boolean {\n const overrideWidth = useContext(BreakpointWidthCtx);\n const px = useMemo(() => readBpFromCSS(key), [key]);\n\n const [matches, setMatches] = useState(() => {\n if (overrideWidth !== null) return overrideWidth <= px;\n return typeof window !== \"undefined\"\n ? window.matchMedia(`(max-width: ${px}px)`).matches\n : false;\n });\n\n useEffect(() => {\n if (overrideWidth !== null) return;\n const mql = window.matchMedia(`(max-width: ${px}px)`);\n const handler = (e: MediaQueryListEvent) => setMatches(e.matches);\n mql.addEventListener(\"change\", handler);\n setMatches(mql.matches);\n return () => mql.removeEventListener(\"change\", handler);\n }, [px, overrideWidth]);\n\n if (overrideWidth !== null) return overrideWidth <= px;\n return matches;\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../showcase/src/lib/slot.tsx","../../../showcase/src/lib/use-position.ts","../../../showcase/src/components/Tooltip.tsx","../../../showcase/src/lib/use-text-overflow.ts"],"names":["rafId","useState","useRef","useCallback","jsx"],"mappings":"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","file":"chunk-R5QGJURZ.js","sourcesContent":["import {\n type ReactNode,\n type ReactElement,\n type HTMLAttributes,\n type Ref,\n isValidElement,\n cloneElement,\n createElement,\n Children,\n} from \"react\";\n\ntype SlotProps = HTMLAttributes<HTMLElement> & {\n children?: ReactNode;\n ref?: Ref<HTMLElement>;\n};\n\nfunction mergeRefs(...refs: (Ref<HTMLElement> | undefined)[]): Ref<HTMLElement> | undefined {\n const filtered = refs.filter(Boolean) as Ref<HTMLElement>[];\n if (filtered.length === 0) return undefined;\n if (filtered.length === 1) return filtered[0];\n return (node: HTMLElement | null) => {\n for (const ref of filtered) {\n if (typeof ref === \"function\") ref(node);\n else if (ref && typeof ref === \"object\") (ref as { current: HTMLElement | null }).current = node;\n }\n };\n}\n\n/**\n * Merges its own props onto its single React element child instead of\n * rendering a wrapper DOM node. Used by trigger-based components\n * (Tooltip, HoverCard, Popover, DropdownMenu) via the `asChild` prop\n * to let consumers control the rendered element.\n *\n * Follows the same contract as Radix UI's Slot / `asChild` pattern.\n */\nexport function Slot({ children, ref, ...slotProps }: SlotProps) {\n if (!isValidElement(children)) {\n console.warn(\"Slot: expected a single React element child.\");\n return null;\n }\n\n const childProps = children.props as Record<string, unknown>;\n const slotRecord = slotProps as Record<string, unknown>;\n\n const mergedProps: Record<string, unknown> = { ...slotProps };\n for (const key of Object.keys(childProps)) {\n if (key === \"style\") {\n mergedProps.style = { ...(slotProps.style ?? {}), ...(childProps.style as object) };\n } else if (key === \"className\") {\n mergedProps.className = [slotProps.className, childProps.className].filter(Boolean).join(\" \");\n } else if (key.startsWith(\"on\") && typeof slotRecord[key] === \"function\") {\n const slotHandler = slotRecord[key] as (...args: unknown[]) => void;\n const childHandler = childProps[key] as (...args: unknown[]) => void;\n mergedProps[key] = (...args: unknown[]) => {\n childHandler?.(...args);\n slotHandler?.(...args);\n };\n } else {\n mergedProps[key] = childProps[key] ?? slotRecord[key];\n }\n }\n\n if (ref) {\n const childRef = (children as ReactElement & { ref?: Ref<HTMLElement> }).ref ?? (childProps.ref as Ref<HTMLElement> | undefined);\n mergedProps.ref = mergeRefs(ref, childRef);\n }\n\n return cloneElement(children as ReactElement, mergedProps);\n}\n\n/**\n * Renders either a Slot (when `asChild` is true — merging props onto\n * the consumer's child element) or the given `fallback` tag (default\n * behaviour, rendering a wrapper element with the given props).\n */\nexport function Slottable({\n asChild,\n fallback: Tag = \"span\",\n children,\n ref,\n ...rest\n}: {\n asChild?: boolean;\n fallback?: keyof HTMLElementTagNameMap;\n children?: ReactNode;\n ref?: Ref<HTMLElement>;\n} & HTMLAttributes<HTMLElement>) {\n if (asChild) {\n const child = Children.only(children);\n return <Slot ref={ref} {...rest}>{child}</Slot>;\n }\n return createElement(Tag, { ref, ...rest } as Record<string, unknown>, children);\n}\n","import { useState, useCallback, useLayoutEffect, useRef, type RefObject } from \"react\";\n\nexport type Side = \"top\" | \"right\" | \"bottom\" | \"left\";\nexport type Align = \"start\" | \"center\" | \"end\";\n\ninterface PositionOptions {\n side?: Side;\n align?: Align;\n offset?: number;\n flip?: boolean;\n}\n\ninterface PositionResult {\n top: number;\n left: number;\n placement: Side;\n}\n\nconst VIEWPORT_PADDING = 8;\n\nexport function usePosition(\n triggerRef: RefObject<HTMLElement | null>,\n floatingRef: RefObject<HTMLElement | null>,\n open: boolean,\n options: PositionOptions = {}\n): PositionResult {\n const { side = \"bottom\", align = \"center\", offset = 8, flip = true } = options;\n const [pos, setPos] = useState<PositionResult>({ top: 0, left: 0, placement: side });\n const rafId = useRef(0);\n\n const compute = useCallback(() => {\n const trigger = triggerRef.current;\n const floating = floatingRef.current;\n if (!trigger || !floating) return;\n\n const tRect = trigger.getBoundingClientRect();\n const fRect = floating.getBoundingClientRect();\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n let placement = side;\n\n if (flip) {\n if (placement === \"bottom\" && tRect.bottom + offset + fRect.height > vh - VIEWPORT_PADDING && tRect.top - offset - fRect.height > VIEWPORT_PADDING) {\n placement = \"top\";\n } else if (placement === \"top\" && tRect.top - offset - fRect.height < VIEWPORT_PADDING && tRect.bottom + offset + fRect.height < vh - VIEWPORT_PADDING) {\n placement = \"bottom\";\n } else if (placement === \"right\" && tRect.right + offset + fRect.width > vw - VIEWPORT_PADDING && tRect.left - offset - fRect.width > VIEWPORT_PADDING) {\n placement = \"left\";\n } else if (placement === \"left\" && tRect.left - offset - fRect.width < VIEWPORT_PADDING && tRect.right + offset + fRect.width < vw - VIEWPORT_PADDING) {\n placement = \"right\";\n }\n }\n\n let top: number;\n let left: number;\n\n if (placement === \"bottom\" || placement === \"top\") {\n top = placement === \"bottom\" ? tRect.bottom + offset : tRect.top - fRect.height - offset;\n\n switch (align) {\n case \"start\":\n left = tRect.left;\n break;\n case \"end\":\n left = tRect.right - fRect.width;\n break;\n default:\n left = tRect.left + (tRect.width - fRect.width) / 2;\n }\n } else {\n left = placement === \"right\" ? tRect.right + offset : tRect.left - fRect.width - offset;\n\n switch (align) {\n case \"start\":\n top = tRect.top;\n break;\n case \"end\":\n top = tRect.bottom - fRect.height;\n break;\n default:\n top = tRect.top + (tRect.height - fRect.height) / 2;\n }\n }\n\n left = Math.max(VIEWPORT_PADDING, Math.min(left, vw - fRect.width - VIEWPORT_PADDING));\n top = Math.max(VIEWPORT_PADDING, Math.min(top, vh - fRect.height - VIEWPORT_PADDING));\n\n setPos({ top, left, placement });\n }, [triggerRef, floatingRef, side, align, offset, flip]);\n\n useLayoutEffect(() => {\n if (!open) return;\n\n compute();\n\n const onUpdate = () => {\n cancelAnimationFrame(rafId.current);\n rafId.current = requestAnimationFrame(compute);\n };\n\n window.addEventListener(\"scroll\", onUpdate, true);\n window.addEventListener(\"resize\", onUpdate);\n\n return () => {\n cancelAnimationFrame(rafId.current);\n window.removeEventListener(\"scroll\", onUpdate, true);\n window.removeEventListener(\"resize\", onUpdate);\n };\n }, [open, compute]);\n\n return pos;\n}\n","import { useState, useRef, useCallback, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn, GLASS_DROPDOWN } from \"../lib/utils\";\nimport { Slot } from \"../lib/slot\";\nimport { usePosition, type Side } from \"../lib/use-position\";\n\ninterface TooltipProps {\n content: ReactNode;\n children: ReactNode;\n side?: Side;\n delay?: number;\n className?: string;\n container?: Element | DocumentFragment;\n}\n\nexport function Tooltip({ content, children, side = \"top\", delay = 300, className, container }: TooltipProps) {\n const [open, setOpen] = useState(false);\n const triggerRef = useRef<HTMLElement>(null);\n const floatingRef = useRef<HTMLDivElement>(null);\n const timeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n const id = useRef(`tooltip-${Math.random().toString(36).slice(2, 8)}`).current;\n\n const pos = usePosition(triggerRef, floatingRef, open, { side, offset: 6, flip: true });\n\n const show = useCallback(() => {\n timeoutRef.current = setTimeout(() => setOpen(true), delay);\n }, [delay]);\n\n const hide = useCallback(() => {\n clearTimeout(timeoutRef.current);\n setOpen(false);\n }, []);\n\n return (\n <>\n <Slot\n ref={triggerRef}\n onMouseEnter={show}\n onMouseLeave={hide}\n onFocus={show}\n onBlur={hide}\n aria-describedby={open ? id : undefined}\n >\n {children}\n </Slot>\n {open && createPortal(\n <div\n ref={floatingRef}\n id={id}\n role=\"tooltip\"\n className={cn(\n \"fixed z-[var(--ds-z-tooltip)] pointer-events-none\",\n \"px-ds-2.5 py-ds-1.5 text-xs\",\n \"max-w-[280px] break-words\",\n \"bg-[var(--ds-bg-surface-elevated)] text-[var(--ds-text-primary)]\",\n GLASS_DROPDOWN,\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n \"rounded-[var(--ds-radius-sm)]\",\n className\n )}\n style={{ top: pos.top, left: pos.left }}\n >\n {content}\n </div>,\n container ?? document.body\n )}\n </>\n );\n}\n","import { useState, useCallback, useRef, useEffect } from \"react\";\n\n// ─── Shared ResizeObserver (singleton per document) ────────────────────────────\n// All useTextOverflow instances share a single ResizeObserver. This avoids the\n// performance cost of 80+ independent observers on pages with many truncated elements.\n\ntype OverflowCallback = () => void;\n\nconst registry = new WeakMap<Element, Set<OverflowCallback>>();\nlet sharedObserver: ResizeObserver | null = null;\nlet rafId: number | null = null;\nconst pendingElements = new Set<Element>();\n\nfunction getSharedObserver(): ResizeObserver {\n if (sharedObserver) return sharedObserver;\n sharedObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n pendingElements.add(entry.target);\n }\n if (rafId === null) {\n rafId = requestAnimationFrame(flush);\n }\n });\n return sharedObserver;\n}\n\nfunction flush() {\n rafId = null;\n for (const el of pendingElements) {\n const callbacks = registry.get(el);\n if (callbacks) {\n for (const cb of callbacks) cb();\n }\n }\n pendingElements.clear();\n}\n\nfunction observe(el: Element, callback: OverflowCallback) {\n let set = registry.get(el);\n if (!set) {\n set = new Set();\n registry.set(el, set);\n }\n set.add(callback);\n if (set.size === 1) {\n getSharedObserver().observe(el);\n }\n}\n\nfunction unobserve(el: Element, callback: OverflowCallback) {\n const set = registry.get(el);\n if (!set) return;\n set.delete(callback);\n if (set.size === 0) {\n registry.delete(el);\n getSharedObserver().unobserve(el);\n }\n}\n\n// ─── Overflow detection ────────────────────────────────────────────────────────\n\nconst TOLERANCE = 1; // px — accounts for integer rounding in scrollWidth/clientWidth\n\nfunction checkOverflow(el: HTMLElement, lines: number): boolean {\n if (lines <= 1) {\n return el.scrollWidth - el.clientWidth > TOLERANCE;\n }\n return el.scrollHeight - el.clientHeight > TOLERANCE;\n}\n\n// ─── Hook ──────────────────────────────────────────────────────────────────────\n\nexport interface UseTextOverflowOptions {\n /** Number of visible lines before truncation. 1 = single-line ellipsis, >1 = line-clamp. */\n lines?: number;\n /** Disable overflow detection (e.g. when component is hidden) */\n disabled?: boolean;\n /** Content key — triggers re-check when this value changes (use for dynamic text content) */\n content?: unknown;\n}\n\nexport interface UseTextOverflowResult {\n /** Callback ref — attach to the element that has truncation CSS applied */\n ref: (node: HTMLElement | null) => void;\n /** True when the element's text is currently overflowing/truncated */\n isTruncated: boolean;\n}\n\n/**\n * Detects whether an element's text content is overflowing (truncated).\n * Uses a single shared ResizeObserver for all instances on the page.\n *\n * @example\n * const { ref, isTruncated } = useTextOverflow({ lines: 1 });\n * return (\n * <Tooltip content={fullText} disabled={!isTruncated}>\n * <span ref={ref} className=\"truncate\">{fullText}</span>\n * </Tooltip>\n * );\n */\nexport function useTextOverflow(options: UseTextOverflowOptions = {}): UseTextOverflowResult {\n const { lines = 1, disabled = false, content } = options;\n const [isTruncated, setIsTruncated] = useState(false);\n const elementRef = useRef<HTMLElement | null>(null);\n // Separate refs: one for the latest check logic, one for the observer-registered function\n const checkRef = useRef<OverflowCallback | null>(null);\n const registeredRef = useRef<OverflowCallback | null>(null);\n\n const doCheck = useCallback(() => {\n if (!elementRef.current || disabled) {\n setIsTruncated(false);\n return;\n }\n const next = checkOverflow(elementRef.current, lines);\n setIsTruncated((prev) => (prev !== next ? next : prev));\n }, [lines, disabled]);\n\n // Keep the check logic ref current so the stable wrapper always uses the latest\n checkRef.current = doCheck;\n\n // Re-check when content changes (handles dynamic text like Dropdown selection)\n // or when disabled state changes\n useEffect(() => {\n if (disabled) {\n setIsTruncated(false);\n } else if (elementRef.current) {\n const next = checkOverflow(elementRef.current, lines);\n setIsTruncated((prev) => (prev !== next ? next : prev));\n }\n }, [content, lines, disabled]);\n\n const ref = useCallback(\n (node: HTMLElement | null) => {\n const prev = elementRef.current;\n\n // Unregister previous element using the SAME function we registered with\n if (prev && registeredRef.current) {\n unobserve(prev, registeredRef.current);\n registeredRef.current = null;\n }\n\n elementRef.current = node;\n\n if (node && !disabled) {\n // Stable wrapper that delegates to the latest check logic\n const wrapper: OverflowCallback = () => checkRef.current?.();\n registeredRef.current = wrapper;\n observe(node, wrapper);\n // Initial check on mount\n const initial = checkOverflow(node, lines);\n setIsTruncated(initial);\n }\n },\n [lines, disabled],\n );\n\n // Cleanup on unmount — uses the registered wrapper, not the check function\n useEffect(() => {\n return () => {\n if (elementRef.current && registeredRef.current) {\n unobserve(elementRef.current, registeredRef.current);\n }\n };\n }, []);\n\n return { ref, isTruncated };\n}\n"]}