{"version":3,"file":"resizable_utils-DhuzXRdP.cjs","names":[],"sources":["../components/resizable/resizable_constants.ts","../components/resizable/resizable_utils.ts"],"sourcesContent":["/**\n * Resizable panel system — types and injection keys.\n *\n * @see resizable.vue — provides all keys\n * @see resizable_panel.vue — consumes panel/layout keys\n * @see resizable_handle.vue — consumes handle/resize keys\n */\nimport type { InjectionKey, ComputedRef, ComponentInternalInstance } from 'vue';\nimport type { LayoutResult } from './composables/computeLayout';\n\n// ─── Sizing Types ──────────────────────────────────────────────────────────\n\n/**\n * Resizable panels accept size tokens (numeric strings mapped to pixel values)\n * or percentage values with 'p' suffix (e.g., '50p').\n * parseSizeToPixels() warns for unrecognized values.\n */\nexport type ResizableSizeValue = string;\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport type ResizableDirection = 'row' | 'column';\n\nexport interface ResizablePanelConfig {\n  /** Panel identifier. Auto-generated by ResizableGroup if not provided. */\n  id: string;\n  /** Initial size - size token (e.g., '925') or percentage with 'p' suffix (e.g., '50p') */\n  initialSize?: ResizableSizeValue;\n\n  // ─── User Drag Limits (Absolute Boundaries) ───\n  /** Hard floor for user dragging. User cannot drag panel smaller than this. */\n  userMinSize?: ResizableSizeValue;\n  /** Hard ceiling for user dragging. User cannot drag panel larger than this. */\n  userMaxSize?: ResizableSizeValue;\n\n  // ─── System Scaling Limits (Operating Range) ───\n  /**\n   * System scaling floor. System won't compress panel below this during viewport resize.\n   * Must be >= userMinSize. Falls back to userMinSize if not specified.\n   */\n  systemMinSize?: ResizableSizeValue;\n  /**\n   * System scaling ceiling. System won't expand panel above this during viewport resize.\n   * Must be <= userMaxSize. Falls back to userMaxSize if not specified.\n   */\n  systemMaxSize?: ResizableSizeValue;\n\n  // ─── Auto-Collapse ───\n  /**\n   * Container width threshold for auto-collapse.\n   * When container width drops below this value, panel auto-collapses.\n   * This is the container width, not the panel width.\n   */\n  collapseSize?: ResizableSizeValue;\n\n  // ─── Behavior Flags ───\n  /** Whether this panel can be resized by user dragging */\n  resizable?: boolean;\n  /** Whether this panel can be manually collapsed via UI */\n  collapsible?: boolean;\n  /** Initial collapsed state */\n  collapsed?: boolean;\n\n}\n\nexport interface ResizablePanelState extends ResizablePanelConfig {\n  // ─── Core State ───\n  /** Current size in pixels (primary storage) */\n  pixelSize: number;\n\n  // ─── User Drag Constraints (Computed Pixels) ───\n  /** Computed pixel value for userMinSize (absolute floor for user dragging) */\n  userMinSizePixels?: number;\n  /** Computed pixel value for userMaxSize (absolute ceiling for user dragging) */\n  userMaxSizePixels?: number;\n\n  // ─── System Scaling Constraints (Computed Pixels) ───\n  /** Computed pixel value for systemMinSize (system scaling floor) */\n  systemMinSizePixels?: number;\n  /** Computed pixel value for systemMaxSize (system scaling ceiling) */\n  systemMaxSizePixels?: number;\n\n  // ─── Auto-Collapse State ───\n  /** Computed pixel value for collapseSize (container width threshold) */\n  collapseSizePixels?: number;\n  /** True if panel was auto-collapsed (vs manually collapsed via UI) */\n  autoCollapsed?: boolean;\n\n  // ─── Manual Resize Tracking ───\n  /**\n   * Internal: Stores the user's preferred pixel size when they drag a panel.\n   * Used by the drag system to track intent before converting to ratio.\n   * Not exposed as a component prop.\n   */\n  manualTargetSize?: number;\n  /**\n   * Stores the user's preferred ratio (0–1) when they drag a panel.\n   * - undefined: no manual target, panel scales proportionally with viewport\n   * - number: panel targets (ratio × containerSize) pixels on each render,\n   *   still subject to min/max constraints.\n   *\n   * Enables correct proportional scaling when the viewport changes.\n   * Panel feels \"sticky\" at the chosen proportion.\n   */\n  manualTargetRatio?: number;\n\n  // ─── Storage Restoration ───\n  /**\n   * True if this panel's state was loaded from localStorage on the current\n   * initialization pass. Prevents double-restoration when loadFromStorage is\n   * called multiple times (e.g. the deferred 100ms retry in useResizableCore).\n   */\n  restoredFromStorage?: boolean;\n\n}\n\nexport interface ResizableHandleConfig {\n  id: string;\n  beforePanelId: string;\n  afterPanelId: string;\n  direction: ResizableDirection;\n}\n\nexport type ResizableSizeMode = 'percentage' | 'pixels';\n\n\nexport interface ResizableGroupConfig {\n  direction: ResizableDirection;\n  panels: ResizablePanelConfig[];\n  storageKey?: string; // Optional localStorage key for persistence\n  sizeMode?: ResizableSizeMode; // Whether to use percentage or pixel-based sizing\n  limitToParent?: boolean; // Limit resizing to parent viewport bounds\n}\n\nexport interface ResizableGroupState {\n  direction: ResizableDirection;\n  panels: ResizablePanelState[];\n  containerSize: number; // Total container size in pixels\n  isResizing: boolean;\n  activeHandleId?: string;\n}\n\nexport interface ResizableEvents {\n  'panel-resize': (panelId: string, size: number) => void;\n  'panel-collapse': (panelId: string, collapsed: boolean) => void;\n  'resize-start': (handleId: string) => void;\n  'resize-end': (handleId: string) => void;\n}\n\n/**\n * Defines collapse behavior for a panel during space constraints.\n * Used to determine which panels collapse first when viewport shrinks.\n */\nexport interface CollapseRule {\n  /** Panel ID this rule applies to */\n  panelId: string;\n  /** Collapse priority - lower numbers collapse first */\n  priority: number;\n  /** Optional threshold that triggers collapse (overrides panel's userMinSize) */\n  minSizeBeforeCollapse?: ResizableSizeValue;\n}\n\n/**\n// ─── Defaults ─────────────────────────────────────────────────────────────\n\n/** Default panel size when no initialSize is specified (50% of container). */\nexport const DEFAULT_PANEL_SIZE = '50p';\n\n/** Minimum panel size in pixels below which a resize is rejected. */\nexport const MIN_PANEL_SIZE_PX = 10;\n\n// ─── Storage Adapter ───────────────────────────────────────────────────────\n\n/**\n * Interface for pluggable storage backends.\n * Implement this to persist panel layouts to Pinia, Vuex, IndexedDB, etc.\n * The built-in `localStorageAdapter(key)` factory creates a localStorage-backed adapter.\n */\nexport interface ResizableStorageAdapter {\n  /** Persist the current panel layout. */\n  save(data: ResizableStoragePanelData[]): void;\n  /** Load a previously saved layout. Returns null if nothing is stored. */\n  load(): ResizableStoragePanelData[] | null;\n  /** Remove all persisted data for this layout. */\n  clear(): void;\n}\n\n/**\n * Shape of a single panel's persisted data.\n * Intentionally minimal — only what's needed to restore a layout.\n */\nexport interface ResizableStoragePanelData {\n  id: string;\n  pixelSize: number;\n  collapsed?: boolean;\n  autoCollapsed?: boolean;\n  manualTargetRatio?: number;\n}\n\n// ─── Injection Context ──────────────────────────────────────────────────────\n// Single context object for provide/inject between ResizableGroup, Panel, Handle.\n\nexport interface ResizableContext {\n  // Reactive state\n  layout: ComputedRef<LayoutResult>;\n  panels: ComputedRef<ResizablePanelState[]>;\n  panelMap: ComputedRef<Map<string, ResizablePanelState>>;\n  direction: ComputedRef<ResizableDirection>;\n  containerSize: ComputedRef<number>;\n  containerElement: ComputedRef<HTMLElement | null>;\n  isResizing: ComputedRef<boolean>;\n  activeHandleId: ComputedRef<string | undefined>;\n  isInitializing: ComputedRef<boolean>;\n  messages: Record<string, string>;\n\n  // Offset (from fixed header/toolbar)\n  offsetHandleStyles: ComputedRef<Record<string, string>>;\n  offsetContentStyles: ComputedRef<Record<string, string>>;\n\n  // Operations\n  startResize: (handleId: string) => void;\n  resetPanels: (\n    beforePanelId?: string,\n    afterPanelId?: string,\n    behavior?: 'both' | 'before' | 'after' | 'all',\n  ) => void;\n  registerHandle: (instance: ComponentInternalInstance | null) => void;\n  unregisterHandle: (instance: ComponentInternalInstance | null) => void;\n  registerPanel: (config: ResizablePanelConfig) => void;\n  unregisterPanel: (id: string) => void;\n  saveToStorage: () => void;\n  announce: (message: string) => void;\n  collapsePanel: (panelId: string, collapsed: boolean) => void;\n  emitPanelResize: (panelId: string, size: number) => void;\n  commitPanelSize: (panelId: string, pixels: number) => void;\n  updateSavedPanel: (panelId: string, updates: Partial<ResizableStoragePanelData>) => void;\n}\n\nexport const RESIZABLE_CONTEXT_KEY: InjectionKey<ResizableContext> = Symbol('resizable-context');\n\n/**\n * Build a composite handle ID from the before and after panel IDs.\n * Handles use the format \"{beforePanelId}:{afterPanelId}\" throughout the system.\n */\nexport function buildHandleId(beforeId: string, afterId: string): string {\n  return `${beforeId}:${afterId}`;\n}\n","import type { ResizableSizeValue } from './resizable_constants';\n\n// ─── Size Token Resolution ─────────────────────────────────────────────────\n// Resolves Dialtone size tokens (e.g., '925') to pixel values.\n// Reads --dt-size-{token} CSS custom properties at runtime to stay in sync\n// with the token pipeline. Falls back to a static map in environments where\n// CSS custom properties aren't available (tests, SSR).\n//\n// Will map to --dt-layout-* tokens when they land on the `next` branch.\n\n/** Cache for resolved token pixel values (populated on first use). */\nconst tokenCache = new Map<string, number>();\n\n/** Root font size cache — read once from getComputedStyle. */\nlet cachedRootFontSize: number | null = null;\n\nfunction getRootFontSize(): number {\n  if (cachedRootFontSize !== null) return cachedRootFontSize;\n  if (typeof document !== 'undefined') {\n    cachedRootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize) || 10;\n  } else {\n    cachedRootFontSize = 10; // Dialtone default\n  }\n  return cachedRootFontSize;\n}\n\n/**\n * Resolve a Dialtone size token to pixels via CSS custom properties.\n * Falls back to FALLBACK_SIZE_TOKENS when CSS isn't available.\n */\nfunction resolveTokenPixels(token: string): number | undefined {\n  if (tokenCache.has(token)) return tokenCache.get(token);\n\n  // Try runtime CSS resolution\n  if (typeof document !== 'undefined') {\n    const cssValue = getComputedStyle(document.documentElement)\n      .getPropertyValue(`--dt-size-${token}`)\n      .trim();\n\n    if (cssValue) {\n      const remMatch = cssValue.match(/^([\\d.]+)rem$/);\n      if (remMatch) {\n        const px = parseFloat(remMatch[1]) * getRootFontSize();\n        tokenCache.set(token, px);\n        return px;\n      }\n      const pxMatch = cssValue.match(/^([\\d.]+)px$/);\n      if (pxMatch) {\n        const px = parseFloat(pxMatch[1]);\n        tokenCache.set(token, px);\n        return px;\n      }\n    }\n  }\n\n  // Fallback: static map mirrors --dt-size-* tokens from dialtone-tokens\n  // Kept for jsdom tests and SSR where CSS custom properties aren't loaded.\n  if (token in FALLBACK_SIZE_TOKENS) {\n    const px = FALLBACK_SIZE_TOKENS[token];\n    tokenCache.set(token, px);\n    return px;\n  }\n\n  return undefined;\n}\n\n/**\n * Static fallback map — mirrors Dialtone size tokens (base/default.json).\n * Only used when CSS custom properties are unavailable (tests, SSR).\n */\nconst FALLBACK_SIZE_TOKENS: Record<string, number> = {\n  '0': 0, '50': 0.5, '100': 1, '200': 2, '300': 4, '350': 6,\n  '400': 8, '450': 12, '500': 16, '525': 20, '550': 24, '600': 32,\n  '625': 42, '650': 48, '700': 64, '720': 72, '730': 84, '750': 96,\n  '760': 102, '775': 114, '800': 128, '825': 164, '850': 192, '875': 216,\n  '900': 256, '905': 264, '925': 332, '950': 384, '975': 464, '1000': 512,\n  '1020': 628, '1040': 764, '1050': 768, '1060': 828, '1080': 912,\n  '1100': 1024, '1115': 1140, '1120': 1268, '1125': 1280, '1130': 1340,\n  '1150': 1536, '1200': 2048,\n};\n\n// ─── Percentage Resolution ─────────────────────────────────────────────────\n// Percentage tokens use a simple pattern: numeric value + 'p' suffix.\n\nfunction parsePercentage(value: string): number | undefined {\n  if (!value.endsWith('p')) return undefined;\n  const num = parseFloat(value.slice(0, -1));\n  return isFinite(num) && num >= 0 && num <= 100 ? num : undefined;\n}\n\n// ─── Token Helpers ──────────────────────────────────────────────────────────\n\nfunction isSizeToken(value: string): boolean {\n  return resolveTokenPixels(value) !== undefined;\n}\n\nfunction isPercentageToken(value: string): boolean {\n  return parsePercentage(value) !== undefined;\n}\n\nexport function isValidSizing(value: string): boolean {\n  return isSizeToken(value) || isPercentageToken(value);\n}\n\nfunction parseTokenToPixels(value: string, containerSize: number): number {\n  const sizePixels = resolveTokenPixels(value);\n  if (sizePixels !== undefined) return sizePixels;\n\n  const percentage = parsePercentage(value);\n  if (percentage !== undefined) return (percentage / 100) * containerSize;\n\n  console.warn(`[resizable] Invalid sizing value: ${value}`);\n  return 0;\n}\n\n// ─── Public API ─────────────────────────────────────────────────────────────\n\nexport interface ParseSizeOptions {\n  /**\n   * When true, clamps the result to container size.\n   * Panels cannot exceed their parent container.\n   * @default true\n   */\n  clampToContainer?: boolean;\n}\n\n/**\n * Parses a ResizableSizeValue and returns the pixel value.\n * Handles size tokens (e.g., '925') and percentage tokens (e.g., '50p').\n *\n * Size tokens resolve from --dt-size-{token} CSS custom properties at runtime,\n * falling back to a static map in test/SSR environments.\n *\n * @param value - Size token or percentage token\n * @param containerSize - Container size in pixels\n * @param options - Optional configuration\n * @returns Pixel value, clamped to container by default\n *\n * @example\n * parseSizeToPixels('925', 1000)  // Returns 332 (from --dt-size-925)\n * parseSizeToPixels('50p', 1000)  // Returns 500 (50% of 1000)\n * parseSizeToPixels('1100', 1000) // Returns 1000 (clamped from 1024px)\n */\nexport function parseSizeToPixels(\n  value: ResizableSizeValue,\n  containerSize: number,\n  options?: ParseSizeOptions\n): number {\n  const { clampToContainer = true } = options ?? {};\n  const validatedContainerSize = validateContainerSize(containerSize);\n\n  if (isCollapsedPanel(validatedContainerSize, value)) {\n    return 0;\n  }\n\n  const calculationContainerSize = validatedContainerSize === 0 ? 1000 : validatedContainerSize;\n\n  if (typeof value === 'string' && isValidSizing(value)) {\n    const result = parseTokenToPixels(value, calculationContainerSize);\n    return validatePixelResult(result, value, validatedContainerSize, clampToContainer);\n  }\n\n  console.warn(\n    `[resizable] Invalid ResizableSizeValue: ${value}. Expected a size token or percentage with 'p' suffix.`\n  );\n  return 0;\n}\n\nexport function validateContainerSize(containerSize: number): number {\n  if (!isFinite(containerSize) || containerSize < 0) {\n    console.warn(`[resizable] Invalid containerSize: ${containerSize}. Using fallback value of 1000px.`);\n    return 1000;\n  }\n\n  if (containerSize > 10000) {\n    console.warn(`[resizable] Unusually large containerSize: ${containerSize}px. Capping at 10000px.`);\n    return 10000;\n  }\n\n  return containerSize;\n}\n\nfunction isCollapsedPanel(containerSize: number, value: ResizableSizeValue): boolean {\n  return containerSize === 0 && value === '0';\n}\n\nfunction validatePixelResult(\n  result: number,\n  value: ResizableSizeValue,\n  containerSize: number,\n  clampToContainer: boolean\n): number {\n  if (!isFinite(result) || result < 0) {\n    console.warn(\n      `[resizable] Invalid pixel calculation result: ${result} for value: ${value}, containerSize: ${containerSize}`\n    );\n    return 0;\n  }\n\n  if (clampToContainer && containerSize > 0 && result > containerSize) {\n    console.warn(\n      `[resizable] Size value '${value}' (${result}px) exceeds container (${containerSize}px). Clamping to container.`\n    );\n    return containerSize;\n  }\n\n  return result;\n}\n\nexport function isPercentageValue(value: ResizableSizeValue): boolean {\n  return isPercentageToken(value);\n}\n\nexport function isCSSValue(value: ResizableSizeValue): boolean {\n  return isSizeToken(value);\n}\n\nexport function pixelsToPercentage(pixels: number, containerSize: number): number {\n  return (pixels / containerSize) * 100;\n}\n\n/**\n * Checks if a panel's userMinSize is percentage-based (e.g., '50p').\n */\nexport function hasPercentageMinSize(panel: { userMinSize?: ResizableSizeValue }): boolean {\n  if (!panel.userMinSize) return false;\n  return isPercentageToken(panel.userMinSize);\n}\n\n/**\n * Invalidate the token cache. Call when the theme changes or\n * when token values may have been updated at runtime.\n */\nexport function invalidateTokenCache(): void {\n  tokenCache.clear();\n  cachedRootFontSize = null;\n}\n"],"mappings":"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"}