@aster-ui/prefixed 0.12.89 → 0.12.90

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.
@@ -1 +1 @@
1
- {"version":3,"file":"Terminal.js","sources":["../../src/components/Terminal.tsx"],"sourcesContent":["import React, { useEffect, useRef, useImperativeHandle, forwardRef } from 'react'\nimport * as XTermPkg from '@xterm/xterm'\nimport * as FitAddonPkg from '@xterm/addon-fit'\nimport type { Terminal as XTermType, ITerminalOptions, ITerminalInitOnlyOptions } from '@xterm/xterm'\nimport type { FitAddon as FitAddonType } from '@xterm/addon-fit'\nimport { useTheme } from '../hooks/useTheme'\n\n// Handle both ESM and CJS module formats\nconst XTerm = (XTermPkg as { Terminal?: typeof XTermType }).Terminal\n ?? (XTermPkg as { default?: { Terminal: typeof XTermType } }).default?.Terminal\n ?? (XTermPkg as unknown as typeof XTermType)\nconst FitAddon = (FitAddonPkg as { FitAddon?: typeof FitAddonType }).FitAddon\n ?? (FitAddonPkg as { default?: { FitAddon: typeof FitAddonType } }).default?.FitAddon\n ?? (FitAddonPkg as unknown as typeof FitAddonType)\n\n// Inject xterm.css once (inlined for reliability across bundlers)\nlet cssInjected = false\nfunction injectXtermCSS() {\n if (cssInjected || typeof document === 'undefined') return\n cssInjected = true\n\n const style = document.createElement('style')\n style.setAttribute('data-xterm', '')\n style.textContent = `.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:.5}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}`\n document.head.appendChild(style)\n}\n\nexport interface TerminalRef {\n /** The underlying xterm.js Terminal instance */\n terminal: XTermType | null\n /** Write data to the terminal */\n write: (data: string) => void\n /** Write a line to the terminal (with newline) */\n writeln: (data: string) => void\n /** Clear the terminal */\n clear: () => void\n /** Focus the terminal */\n focus: () => void\n /** Fit the terminal to its container */\n fit: () => void\n /** Command history (only available with readline mode) */\n history: string[]\n /** Clear command history (only available with readline mode) */\n clearHistory: () => void\n}\n\nexport interface TerminalProps {\n /** Callback when user types in the terminal (raw input) */\n onData?: (data: string) => void\n /** Callback when terminal is ready */\n onReady?: (terminal: XTermType) => void\n /** Enable readline mode with line editing and history */\n readline?: boolean\n /** Prompt string for readline mode (supports ANSI colors) */\n prompt?: string\n /** Callback when user submits a line in readline mode. Return a Promise to defer the next prompt. */\n onLine?: (line: string) => void | Promise<void>\n /** Convert LF to CRLF for proper newline handling (default: true) */\n convertEol?: boolean\n /** xterm.js options (theme is auto-applied unless you override it) */\n options?: ITerminalOptions & ITerminalInitOnlyOptions\n /** Additional CSS classes for the container */\n className?: string\n /** Container style */\n style?: React.CSSProperties\n 'data-testid'?: string\n}\n\nexport const Terminal = forwardRef<TerminalRef, TerminalProps>(({\n onData,\n onReady,\n readline = false,\n prompt = '$ ',\n onLine,\n convertEol = true,\n options = {},\n className = '',\n style,\n 'data-testid': testId,\n}, ref) => {\n const containerRef = useRef<HTMLDivElement>(null)\n const terminalRef = useRef<XTermType | null>(null)\n const fitAddonRef = useRef<FitAddonType | null>(null)\n const { isDark, colors } = useTheme()\n\n // Readline state\n const readlineState = useRef({\n buffer: '',\n cursor: 0,\n history: [] as string[],\n historyIndex: -1,\n savedBuffer: '',\n })\n\n // Build theme from DaisyUI colors\n const getTheme = () => ({\n background: colors.background,\n foreground: colors.foreground,\n cursor: colors.foreground,\n cursorAccent: colors.background,\n selectionBackground: colors.primary + '40',\n selectionForeground: colors.foreground,\n black: isDark ? '#000000' : '#2e3436',\n red: colors.error,\n green: colors.success,\n yellow: colors.warning,\n blue: colors.info,\n magenta: colors.secondary,\n cyan: colors.accent,\n white: isDark ? '#d3d7cf' : '#eeeeec',\n brightBlack: '#555753',\n brightRed: colors.error,\n brightGreen: colors.success,\n brightYellow: colors.warning,\n brightBlue: colors.info,\n brightMagenta: colors.secondary,\n brightCyan: colors.accent,\n brightWhite: isDark ? '#eeeeec' : '#ffffff',\n })\n\n useImperativeHandle(ref, () => ({\n terminal: terminalRef.current,\n write: (data: string) => terminalRef.current?.write(data),\n writeln: (data: string) => terminalRef.current?.writeln(data),\n clear: () => terminalRef.current?.clear(),\n focus: () => terminalRef.current?.focus(),\n fit: () => fitAddonRef.current?.fit(),\n history: readlineState.current.history,\n clearHistory: () => { readlineState.current.history = [] },\n }), [])\n\n // Redraw the current line in readline mode\n const redrawLine = () => {\n const term = terminalRef.current\n if (!term) return\n const s = readlineState.current\n // Move to start of line, clear line, write prompt + buffer, position cursor\n term.write('\\r\\x1b[K' + prompt + s.buffer)\n // Move cursor to correct position\n const moveBack = s.buffer.length - s.cursor\n if (moveBack > 0) {\n term.write(`\\x1b[${moveBack}D`)\n }\n }\n\n // Handle readline input\n const handleReadlineData = (data: string) => {\n const term = terminalRef.current\n if (!term) return\n const s = readlineState.current\n\n // Handle escape sequences (arrow keys)\n if (data === '\\x1b[A') {\n // Up arrow - history previous\n if (s.history.length > 0 && s.historyIndex < s.history.length - 1) {\n if (s.historyIndex === -1) s.savedBuffer = s.buffer\n s.historyIndex++\n s.buffer = s.history[s.history.length - 1 - s.historyIndex]\n s.cursor = s.buffer.length\n redrawLine()\n }\n return\n }\n if (data === '\\x1b[B') {\n // Down arrow - history next\n if (s.historyIndex > -1) {\n s.historyIndex--\n s.buffer = s.historyIndex === -1 ? s.savedBuffer : s.history[s.history.length - 1 - s.historyIndex]\n s.cursor = s.buffer.length\n redrawLine()\n }\n return\n }\n if (data === '\\x1b[C') {\n // Right arrow\n if (s.cursor < s.buffer.length) {\n s.cursor++\n term.write('\\x1b[C')\n }\n return\n }\n if (data === '\\x1b[D') {\n // Left arrow\n if (s.cursor > 0) {\n s.cursor--\n term.write('\\x1b[D')\n }\n return\n }\n if (data === '\\x1b[H' || data === '\\x1bOH' || data === '\\x1b[1~') {\n // Home - move to start of line\n if (s.cursor > 0) {\n term.write(`\\x1b[${s.cursor}D`)\n s.cursor = 0\n }\n return\n }\n if (data === '\\x1b[F' || data === '\\x1bOF' || data === '\\x1b[4~') {\n // End - move to end of line\n if (s.cursor < s.buffer.length) {\n term.write(`\\x1b[${s.buffer.length - s.cursor}C`)\n s.cursor = s.buffer.length\n }\n return\n }\n if (data === '\\x1b[3~') {\n // Delete key - delete character at cursor\n if (s.cursor < s.buffer.length) {\n s.buffer = s.buffer.slice(0, s.cursor) + s.buffer.slice(s.cursor + 1)\n redrawLine()\n }\n return\n }\n\n // Ignore other escape sequences\n if (data.startsWith('\\x1b')) return\n\n if (data === '\\r') {\n // Enter\n term.writeln('')\n const line = s.buffer\n if (line.trim()) {\n s.history.push(line)\n }\n s.buffer = ''\n s.cursor = 0\n s.historyIndex = -1\n s.savedBuffer = ''\n const result = onLine?.(line)\n if (result && typeof (result as any).then === 'function') {\n (result as any).then(() => term.write(prompt))\n } else {\n term.write(prompt)\n }\n } else if (data === '\\x7f' || data === '\\b') {\n // Backspace\n if (s.cursor > 0) {\n s.buffer = s.buffer.slice(0, s.cursor - 1) + s.buffer.slice(s.cursor)\n s.cursor--\n redrawLine()\n }\n } else if (data === '\\x03') {\n // Ctrl+C\n term.writeln('^C')\n s.buffer = ''\n s.cursor = 0\n s.historyIndex = -1\n term.write(prompt)\n } else if (data >= ' ' || data === '\\t') {\n // Printable characters\n s.buffer = s.buffer.slice(0, s.cursor) + data + s.buffer.slice(s.cursor)\n s.cursor += data.length\n redrawLine()\n }\n }\n\n // Initialize terminal\n useEffect(() => {\n if (!containerRef.current) return\n\n injectXtermCSS()\n\n const container = containerRef.current\n let terminal: XTermType | null = null\n let fitAddon: FitAddonType | null = null\n let resizeObserver: ResizeObserver | null = null\n let initialized = false\n let disposed = false\n\n const initTerminal = () => {\n if (initialized || disposed || !container) return\n\n // Check container has dimensions before opening\n const rect = container.getBoundingClientRect()\n if (rect.width === 0 || rect.height === 0) return\n\n initialized = true\n\n const { theme: userTheme, ...restOptions } = options\n terminal = new XTerm({\n theme: { ...getTheme(), ...userTheme },\n cursorBlink: true,\n convertEol,\n fontFamily: 'ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace',\n fontSize: 14,\n ...restOptions,\n })\n fitAddon = new FitAddon()\n\n terminal.loadAddon(fitAddon)\n terminal.open(container)\n fitAddon.fit()\n\n terminalRef.current = terminal\n fitAddonRef.current = fitAddon\n\n if (readline) {\n terminal.onData(handleReadlineData)\n } else if (onData) {\n terminal.onData(onData)\n }\n\n onReady?.(terminal)\n\n // Write initial prompt after onReady so welcome messages appear first\n if (readline) {\n terminal.write(prompt)\n }\n }\n\n // Use ResizeObserver to wait for container to have dimensions\n resizeObserver = new ResizeObserver((entries) => {\n const entry = entries[0]\n if (!entry || disposed) return\n\n if (!initialized) {\n initTerminal()\n } else if (fitAddon) {\n fitAddon.fit()\n }\n })\n resizeObserver.observe(container)\n\n // Also try immediately in case container already has dimensions\n requestAnimationFrame(initTerminal)\n\n return () => {\n disposed = true\n resizeObserver?.disconnect()\n terminal?.dispose()\n terminalRef.current = null\n fitAddonRef.current = null\n }\n }, []) // eslint-disable-line react-hooks/exhaustive-deps\n\n // Update theme when colors change\n useEffect(() => {\n if (!terminalRef.current) return\n terminalRef.current.options.theme = { ...getTheme(), ...options.theme }\n }, [isDark, colors]) // eslint-disable-line react-hooks/exhaustive-deps\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{ width: '100%', height: '100%', ...style }}\n data-testid={testId}\n />\n )\n})\n\nTerminal.displayName = 'Terminal'\n"],"names":["XTerm","XTermPkg","FitAddon","FitAddonPkg","cssInjected","injectXtermCSS","style","Terminal","forwardRef","onData","onReady","readline","prompt","onLine","convertEol","options","className","testId","ref","containerRef","useRef","terminalRef","fitAddonRef","isDark","colors","useTheme","readlineState","getTheme","useImperativeHandle","data","redrawLine","term","s","moveBack","handleReadlineData","line","result","useEffect","container","terminal","fitAddon","resizeObserver","initialized","disposed","initTerminal","rect","userTheme","restOptions","entries","jsx"],"mappings":"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"}
1
+ {"version":3,"file":"Terminal.js","sources":["../../src/components/Terminal.tsx"],"sourcesContent":["import React, { useEffect, useRef, useImperativeHandle, forwardRef } from 'react'\nimport * as XTermPkg from '@xterm/xterm'\nimport * as FitAddonPkg from '@xterm/addon-fit'\nimport type { Terminal as XTermType, ITerminalOptions, ITerminalInitOnlyOptions } from '@xterm/xterm'\nimport type { FitAddon as FitAddonType } from '@xterm/addon-fit'\nimport { useTheme, getThemeColors } from '../hooks/useTheme'\n\n// Handle both ESM and CJS module formats\nconst XTerm = (XTermPkg as { Terminal?: typeof XTermType }).Terminal\n ?? (XTermPkg as { default?: { Terminal: typeof XTermType } }).default?.Terminal\n ?? (XTermPkg as unknown as typeof XTermType)\nconst FitAddon = (FitAddonPkg as { FitAddon?: typeof FitAddonType }).FitAddon\n ?? (FitAddonPkg as { default?: { FitAddon: typeof FitAddonType } }).default?.FitAddon\n ?? (FitAddonPkg as unknown as typeof FitAddonType)\n\n// Inject xterm.css once (inlined for reliability across bundlers)\nlet cssInjected = false\nfunction injectXtermCSS() {\n if (cssInjected || typeof document === 'undefined') return\n cssInjected = true\n\n const style = document.createElement('style')\n style.setAttribute('data-xterm', '')\n style.textContent = `.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:.5}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}`\n document.head.appendChild(style)\n}\n\nexport interface TerminalRef {\n /** The underlying xterm.js Terminal instance */\n terminal: XTermType | null\n /** Write data to the terminal */\n write: (data: string) => void\n /** Write a line to the terminal (with newline) */\n writeln: (data: string) => void\n /** Clear the terminal */\n clear: () => void\n /** Focus the terminal */\n focus: () => void\n /** Fit the terminal to its container */\n fit: () => void\n /** Command history (only available with readline mode) */\n history: string[]\n /** Clear command history (only available with readline mode) */\n clearHistory: () => void\n}\n\nexport interface TerminalProps {\n /** Callback when user types in the terminal (raw input) */\n onData?: (data: string) => void\n /** Callback when terminal is ready */\n onReady?: (terminal: XTermType) => void\n /** Enable readline mode with line editing and history */\n readline?: boolean\n /** Prompt string for readline mode (supports ANSI colors) */\n prompt?: string\n /** Callback when user submits a line in readline mode. Return a Promise to defer the next prompt. */\n onLine?: (line: string) => void | Promise<void>\n /** Convert LF to CRLF for proper newline handling (default: true) */\n convertEol?: boolean\n /** xterm.js options (theme is auto-applied unless you override it) */\n options?: ITerminalOptions & ITerminalInitOnlyOptions\n /** Additional CSS classes for the container */\n className?: string\n /** Container style */\n style?: React.CSSProperties\n 'data-testid'?: string\n}\n\nexport const Terminal = forwardRef<TerminalRef, TerminalProps>(({\n onData,\n onReady,\n readline = false,\n prompt = '$ ',\n onLine,\n convertEol = true,\n options = {},\n className = '',\n style,\n 'data-testid': testId,\n}, ref) => {\n const containerRef = useRef<HTMLDivElement>(null)\n const terminalRef = useRef<XTermType | null>(null)\n const fitAddonRef = useRef<FitAddonType | null>(null)\n const { isDark } = useTheme()\n\n // Readline state\n const readlineState = useRef({\n buffer: '',\n cursor: 0,\n history: [] as string[],\n historyIndex: -1,\n savedBuffer: '',\n })\n\n // Build theme from DaisyUI colors (computed on demand)\n const getTheme = () => {\n const colors = getThemeColors()\n return {\n background: colors.background,\n foreground: colors.foreground,\n cursor: colors.foreground,\n cursorAccent: colors.background,\n selectionBackground: colors.primary + '40',\n selectionForeground: colors.foreground,\n black: isDark ? '#000000' : '#2e3436',\n red: colors.error,\n green: colors.success,\n yellow: colors.warning,\n blue: colors.info,\n magenta: colors.secondary,\n cyan: colors.accent,\n white: isDark ? '#d3d7cf' : '#eeeeec',\n brightBlack: '#555753',\n brightRed: colors.error,\n brightGreen: colors.success,\n brightYellow: colors.warning,\n brightBlue: colors.info,\n brightMagenta: colors.secondary,\n brightCyan: colors.accent,\n brightWhite: isDark ? '#eeeeec' : '#ffffff',\n }\n }\n\n useImperativeHandle(ref, () => ({\n terminal: terminalRef.current,\n write: (data: string) => terminalRef.current?.write(data),\n writeln: (data: string) => terminalRef.current?.writeln(data),\n clear: () => terminalRef.current?.clear(),\n focus: () => terminalRef.current?.focus(),\n fit: () => fitAddonRef.current?.fit(),\n history: readlineState.current.history,\n clearHistory: () => { readlineState.current.history = [] },\n }), [])\n\n // Redraw the current line in readline mode\n const redrawLine = () => {\n const term = terminalRef.current\n if (!term) return\n const s = readlineState.current\n // Move to start of line, clear line, write prompt + buffer, position cursor\n term.write('\\r\\x1b[K' + prompt + s.buffer)\n // Move cursor to correct position\n const moveBack = s.buffer.length - s.cursor\n if (moveBack > 0) {\n term.write(`\\x1b[${moveBack}D`)\n }\n }\n\n // Handle readline input\n const handleReadlineData = (data: string) => {\n const term = terminalRef.current\n if (!term) return\n const s = readlineState.current\n\n // Handle escape sequences (arrow keys)\n if (data === '\\x1b[A') {\n // Up arrow - history previous\n if (s.history.length > 0 && s.historyIndex < s.history.length - 1) {\n if (s.historyIndex === -1) s.savedBuffer = s.buffer\n s.historyIndex++\n s.buffer = s.history[s.history.length - 1 - s.historyIndex]\n s.cursor = s.buffer.length\n redrawLine()\n }\n return\n }\n if (data === '\\x1b[B') {\n // Down arrow - history next\n if (s.historyIndex > -1) {\n s.historyIndex--\n s.buffer = s.historyIndex === -1 ? s.savedBuffer : s.history[s.history.length - 1 - s.historyIndex]\n s.cursor = s.buffer.length\n redrawLine()\n }\n return\n }\n if (data === '\\x1b[C') {\n // Right arrow\n if (s.cursor < s.buffer.length) {\n s.cursor++\n term.write('\\x1b[C')\n }\n return\n }\n if (data === '\\x1b[D') {\n // Left arrow\n if (s.cursor > 0) {\n s.cursor--\n term.write('\\x1b[D')\n }\n return\n }\n if (data === '\\x1b[H' || data === '\\x1bOH' || data === '\\x1b[1~') {\n // Home - move to start of line\n if (s.cursor > 0) {\n term.write(`\\x1b[${s.cursor}D`)\n s.cursor = 0\n }\n return\n }\n if (data === '\\x1b[F' || data === '\\x1bOF' || data === '\\x1b[4~') {\n // End - move to end of line\n if (s.cursor < s.buffer.length) {\n term.write(`\\x1b[${s.buffer.length - s.cursor}C`)\n s.cursor = s.buffer.length\n }\n return\n }\n if (data === '\\x1b[3~') {\n // Delete key - delete character at cursor\n if (s.cursor < s.buffer.length) {\n s.buffer = s.buffer.slice(0, s.cursor) + s.buffer.slice(s.cursor + 1)\n redrawLine()\n }\n return\n }\n\n // Ignore other escape sequences\n if (data.startsWith('\\x1b')) return\n\n if (data === '\\r') {\n // Enter\n term.writeln('')\n const line = s.buffer\n if (line.trim()) {\n s.history.push(line)\n }\n s.buffer = ''\n s.cursor = 0\n s.historyIndex = -1\n s.savedBuffer = ''\n const result = onLine?.(line)\n if (result && typeof (result as any).then === 'function') {\n (result as any).then(() => term.write(prompt))\n } else {\n term.write(prompt)\n }\n } else if (data === '\\x7f' || data === '\\b') {\n // Backspace\n if (s.cursor > 0) {\n s.buffer = s.buffer.slice(0, s.cursor - 1) + s.buffer.slice(s.cursor)\n s.cursor--\n redrawLine()\n }\n } else if (data === '\\x03') {\n // Ctrl+C\n term.writeln('^C')\n s.buffer = ''\n s.cursor = 0\n s.historyIndex = -1\n term.write(prompt)\n } else if (data >= ' ' || data === '\\t') {\n // Printable characters\n s.buffer = s.buffer.slice(0, s.cursor) + data + s.buffer.slice(s.cursor)\n s.cursor += data.length\n redrawLine()\n }\n }\n\n // Initialize terminal\n useEffect(() => {\n if (!containerRef.current) return\n\n injectXtermCSS()\n\n const container = containerRef.current\n let terminal: XTermType | null = null\n let fitAddon: FitAddonType | null = null\n let resizeObserver: ResizeObserver | null = null\n let initialized = false\n let disposed = false\n\n const initTerminal = () => {\n if (initialized || disposed || !container) return\n\n // Check container has dimensions before opening\n const rect = container.getBoundingClientRect()\n if (rect.width === 0 || rect.height === 0) return\n\n initialized = true\n\n const { theme: userTheme, ...restOptions } = options\n terminal = new XTerm({\n theme: { ...getTheme(), ...userTheme },\n cursorBlink: true,\n convertEol,\n fontFamily: 'ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace',\n fontSize: 14,\n ...restOptions,\n })\n fitAddon = new FitAddon()\n\n terminal.loadAddon(fitAddon)\n terminal.open(container)\n fitAddon.fit()\n\n terminalRef.current = terminal\n fitAddonRef.current = fitAddon\n\n if (readline) {\n terminal.onData(handleReadlineData)\n } else if (onData) {\n terminal.onData(onData)\n }\n\n onReady?.(terminal)\n\n // Write initial prompt after onReady so welcome messages appear first\n if (readline) {\n terminal.write(prompt)\n }\n }\n\n // Use ResizeObserver to wait for container to have dimensions\n resizeObserver = new ResizeObserver((entries) => {\n const entry = entries[0]\n if (!entry || disposed) return\n\n if (!initialized) {\n initTerminal()\n } else if (fitAddon) {\n fitAddon.fit()\n }\n })\n resizeObserver.observe(container)\n\n // Also try immediately in case container already has dimensions\n requestAnimationFrame(initTerminal)\n\n return () => {\n disposed = true\n resizeObserver?.disconnect()\n terminal?.dispose()\n terminalRef.current = null\n fitAddonRef.current = null\n }\n }, []) // eslint-disable-line react-hooks/exhaustive-deps\n\n // Update theme when colors change\n useEffect(() => {\n if (!terminalRef.current) return\n terminalRef.current.options.theme = { ...getTheme(), ...options.theme }\n }, [isDark]) // eslint-disable-line react-hooks/exhaustive-deps\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{ width: '100%', height: '100%', ...style }}\n data-testid={testId}\n />\n )\n})\n\nTerminal.displayName = 'Terminal'\n"],"names":["XTerm","XTermPkg","FitAddon","FitAddonPkg","cssInjected","injectXtermCSS","style","Terminal","forwardRef","onData","onReady","readline","prompt","onLine","convertEol","options","className","testId","ref","containerRef","useRef","terminalRef","fitAddonRef","isDark","useTheme","readlineState","getTheme","colors","getThemeColors","useImperativeHandle","data","redrawLine","term","s","moveBack","handleReadlineData","line","result","useEffect","container","terminal","fitAddon","resizeObserver","initialized","disposed","initTerminal","rect","userTheme","restOptions","entries","jsx"],"mappings":"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"}
@@ -71,7 +71,7 @@ function R({
71
71
  className: h = "",
72
72
  "data-testid": d
73
73
  }) {
74
- const { setTheme: p, resolvedTheme: e } = x(), w = p ?? T, a = S(), [u, i] = v(() => {
74
+ const { setTheme: p, theme: e } = x(), w = p ?? T, a = S(), [u, i] = v(() => {
75
75
  if (e && o.includes(e)) return e;
76
76
  const t = g();
77
77
  return t && o.includes(t) ? t : c || o[0] || "light";
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeController.js","sources":["../../src/components/ThemeController.tsx"],"sourcesContent":["import React, { useState, useEffect, useId } from 'react'\nimport { useConfig } from '../providers/ConfigProvider'\nimport { useTheme } from '../hooks/useTheme'\n\n// DaisyUI classes\nconst dSwap = 'd-swap'\nconst dSwapRotate = 'd-swap-rotate'\nconst dSwapOff = 'd-swap-off'\nconst dSwapOn = 'd-swap-on'\nconst dDropdown = 'd-dropdown'\nconst dDropdownEnd = 'd-dropdown-end'\nconst dDropdownContent = 'd-dropdown-content'\nconst dBtn = 'd-btn'\nconst dBtnSm = 'd-btn-sm'\nconst dBtnBlock = 'd-btn-block'\nconst dBtnGhost = 'd-btn-ghost'\nconst dToggle = 'd-toggle'\nconst dToggleXs = 'd-toggle-xs'\nconst dToggleSm = 'd-toggle-sm'\nconst dToggleMd = 'd-toggle-md'\nconst dToggleLg = 'd-toggle-lg'\nconst dToggleXl = 'd-toggle-xl'\n\nexport interface ThemeControllerSwapProps {\n lightTheme?: string\n darkTheme?: string\n onChange?: (theme: string) => void\n className?: string\n 'data-testid'?: string\n}\n\nexport interface ThemeControllerDropdownProps {\n themes: string[]\n defaultTheme?: string\n onChange?: (theme: string) => void\n className?: string\n 'data-testid'?: string\n}\n\nexport interface ThemeControllerToggleProps {\n lightTheme?: string\n darkTheme?: string\n onChange?: (theme: string) => void\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n className?: string\n 'data-testid'?: string\n}\n\n// Get current theme from document\nfunction getCurrentTheme(): string | null {\n return document.documentElement.getAttribute('data-theme')\n}\n\n// Set theme directly on document (fallback when no ThemeProvider)\nfunction setThemeDirectly(theme: string) {\n document.documentElement.setAttribute('data-theme', theme)\n}\n\nfunction ThemeControllerSwap({\n lightTheme = 'light',\n darkTheme = 'dark',\n onChange,\n className = '',\n 'data-testid': testId,\n}: ThemeControllerSwapProps) {\n const { setTheme: contextSetTheme, isDark: contextIsDark } = useTheme()\n const setTheme = contextSetTheme ?? setThemeDirectly\n\n const [isDark, setIsDark] = useState(() => {\n if (contextIsDark !== undefined) return contextIsDark\n const current = getCurrentTheme()\n return current === darkTheme\n })\n\n // Sync with context or external theme changes\n useEffect(() => {\n if (contextIsDark !== undefined) {\n setIsDark(contextIsDark)\n return\n }\n const observer = new MutationObserver(() => {\n const current = getCurrentTheme()\n setIsDark(current === darkTheme)\n })\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] })\n return () => observer.disconnect()\n }, [darkTheme, contextIsDark])\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const checked = e.target.checked\n const theme = checked ? darkTheme : lightTheme\n setTheme(theme)\n onChange?.(theme)\n }\n\n const getTestId = (suffix: string) => (testId ? `${testId}-${suffix}` : undefined)\n\n return (\n <label className={`${dSwap} ${dSwapRotate} ${className}`} data-testid={testId}>\n <input\n type=\"checkbox\"\n checked={isDark}\n onChange={handleChange}\n data-testid={getTestId('input')}\n />\n {/* sun icon */}\n <svg\n className={`${dSwapOff} h-8 w-8 fill-current`}\n data-testid={getTestId('icon-light')}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z\" />\n </svg>\n {/* moon icon */}\n <svg\n className={`${dSwapOn} h-8 w-8 fill-current`}\n data-testid={getTestId('icon-dark')}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z\" />\n </svg>\n </label>\n )\n}\n\nfunction ThemeControllerDropdown({\n themes,\n defaultTheme,\n onChange,\n className = '',\n 'data-testid': testId,\n}: ThemeControllerDropdownProps) {\n const { setTheme: contextSetTheme, resolvedTheme } = useTheme()\n const setTheme = contextSetTheme ?? setThemeDirectly\n const radioName = useId()\n\n const [selectedTheme, setSelectedTheme] = useState(() => {\n if (resolvedTheme && themes.includes(resolvedTheme)) return resolvedTheme\n const current = getCurrentTheme()\n if (current && themes.includes(current)) return current\n return defaultTheme || themes[0] || 'light'\n })\n\n // Sync with context or external theme changes\n useEffect(() => {\n if (resolvedTheme && themes.includes(resolvedTheme)) {\n setSelectedTheme(resolvedTheme)\n return\n }\n const observer = new MutationObserver(() => {\n const current = getCurrentTheme()\n if (current && themes.includes(current)) {\n setSelectedTheme(current)\n }\n })\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] })\n return () => observer.disconnect()\n }, [themes, resolvedTheme])\n\n const handleChange = (theme: string) => {\n setSelectedTheme(theme)\n setTheme(theme)\n onChange?.(theme)\n }\n\n const getTestId = (suffix: string) => (testId ? `${testId}-${suffix}` : undefined)\n\n return (\n <div className={`${dDropdown} ${dDropdownEnd} ${className}`} data-testid={testId}>\n <div tabIndex={0} role=\"button\" className={dBtn} data-testid={getTestId('trigger')}>\n Theme\n <svg\n width=\"12px\"\n height=\"12px\"\n className=\"inline-block h-2 w-2 fill-current opacity-60\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 2048 2048\"\n >\n <path d=\"M1799 349l242 241-1017 1017L7 590l242-241 775 775 775-775z\"></path>\n </svg>\n </div>\n <ul\n tabIndex={0}\n className={`${dDropdownContent} bg-base-300 rounded-box z-[1] w-52 p-2 shadow-2xl max-h-96 overflow-y-auto`}\n data-testid={getTestId('menu')}\n >\n {themes.map((theme) => (\n <li key={theme}>\n <input\n type=\"radio\"\n name={radioName}\n className={`${dBtn} ${dBtnSm} ${dBtnBlock} ${dBtnGhost} justify-start`}\n aria-label={theme}\n value={theme}\n checked={selectedTheme === theme}\n onChange={() => handleChange(theme)}\n data-testid={getTestId(`option-${theme}`)}\n />\n </li>\n ))}\n </ul>\n </div>\n )\n}\n\nconst sizeClasses: Record<string, string> = {\n xs: dToggleXs,\n sm: dToggleSm,\n md: dToggleMd,\n lg: dToggleLg,\n xl: dToggleXl,\n}\n\nfunction ThemeControllerToggle({\n lightTheme = 'light',\n darkTheme = 'dark',\n onChange,\n size,\n className = '',\n 'data-testid': testId,\n}: ThemeControllerToggleProps) {\n const { componentSize } = useConfig()\n const { setTheme: contextSetTheme, isDark: contextIsDark } = useTheme()\n const setTheme = contextSetTheme ?? setThemeDirectly\n const effectiveSize = size ?? componentSize ?? 'md'\n\n const [isDark, setIsDark] = useState(() => {\n if (contextIsDark !== undefined) return contextIsDark\n const current = getCurrentTheme()\n return current === darkTheme\n })\n\n // Sync with context or external theme changes\n useEffect(() => {\n if (contextIsDark !== undefined) {\n setIsDark(contextIsDark)\n return\n }\n const observer = new MutationObserver(() => {\n const current = getCurrentTheme()\n setIsDark(current === darkTheme)\n })\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] })\n return () => observer.disconnect()\n }, [darkTheme, contextIsDark])\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const checked = e.target.checked\n const theme = checked ? darkTheme : lightTheme\n setTheme(theme)\n onChange?.(theme)\n }\n\n return (\n <input\n type=\"checkbox\"\n className={`${dToggle} ${sizeClasses[effectiveSize]} ${className}`}\n checked={isDark}\n onChange={handleChange}\n aria-label=\"Toggle theme\"\n data-testid={testId}\n />\n )\n}\n\nexport const ThemeController = Object.assign(\n {},\n {\n Swap: ThemeControllerSwap,\n Dropdown: ThemeControllerDropdown,\n Toggle: ThemeControllerToggle,\n }\n)\n"],"names":["dSwap","dSwapRotate","dSwapOff","dSwapOn","dDropdown","dDropdownEnd","dDropdownContent","dBtn","dBtnSm","dBtnBlock","dBtnGhost","dToggle","dToggleXs","dToggleSm","dToggleMd","dToggleLg","dToggleXl","getCurrentTheme","setThemeDirectly","theme","ThemeControllerSwap","lightTheme","darkTheme","onChange","className","testId","contextSetTheme","contextIsDark","useTheme","setTheme","isDark","setIsDark","useState","useEffect","observer","current","handleChange","e","getTestId","suffix","jsxs","jsx","ThemeControllerDropdown","themes","defaultTheme","resolvedTheme","radioName","useId","selectedTheme","setSelectedTheme","sizeClasses","ThemeControllerToggle","size","componentSize","useConfig","effectiveSize","ThemeController"],"mappings":"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"}
1
+ {"version":3,"file":"ThemeController.js","sources":["../../src/components/ThemeController.tsx"],"sourcesContent":["import React, { useState, useEffect, useId } from 'react'\nimport { useConfig } from '../providers/ConfigProvider'\nimport { useTheme } from '../hooks/useTheme'\n\n// DaisyUI classes\nconst dSwap = 'd-swap'\nconst dSwapRotate = 'd-swap-rotate'\nconst dSwapOff = 'd-swap-off'\nconst dSwapOn = 'd-swap-on'\nconst dDropdown = 'd-dropdown'\nconst dDropdownEnd = 'd-dropdown-end'\nconst dDropdownContent = 'd-dropdown-content'\nconst dBtn = 'd-btn'\nconst dBtnSm = 'd-btn-sm'\nconst dBtnBlock = 'd-btn-block'\nconst dBtnGhost = 'd-btn-ghost'\nconst dToggle = 'd-toggle'\nconst dToggleXs = 'd-toggle-xs'\nconst dToggleSm = 'd-toggle-sm'\nconst dToggleMd = 'd-toggle-md'\nconst dToggleLg = 'd-toggle-lg'\nconst dToggleXl = 'd-toggle-xl'\n\nexport interface ThemeControllerSwapProps {\n lightTheme?: string\n darkTheme?: string\n onChange?: (theme: string) => void\n className?: string\n 'data-testid'?: string\n}\n\nexport interface ThemeControllerDropdownProps {\n themes: string[]\n defaultTheme?: string\n onChange?: (theme: string) => void\n className?: string\n 'data-testid'?: string\n}\n\nexport interface ThemeControllerToggleProps {\n lightTheme?: string\n darkTheme?: string\n onChange?: (theme: string) => void\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n className?: string\n 'data-testid'?: string\n}\n\n// Get current theme from document\nfunction getCurrentTheme(): string | null {\n return document.documentElement.getAttribute('data-theme')\n}\n\n// Set theme directly on document (fallback when no ThemeProvider)\nfunction setThemeDirectly(theme: string) {\n document.documentElement.setAttribute('data-theme', theme)\n}\n\nfunction ThemeControllerSwap({\n lightTheme = 'light',\n darkTheme = 'dark',\n onChange,\n className = '',\n 'data-testid': testId,\n}: ThemeControllerSwapProps) {\n const { setTheme: contextSetTheme, isDark: contextIsDark } = useTheme()\n const setTheme = contextSetTheme ?? setThemeDirectly\n\n const [isDark, setIsDark] = useState(() => {\n if (contextIsDark !== undefined) return contextIsDark\n const current = getCurrentTheme()\n return current === darkTheme\n })\n\n // Sync with context or external theme changes\n useEffect(() => {\n if (contextIsDark !== undefined) {\n setIsDark(contextIsDark)\n return\n }\n const observer = new MutationObserver(() => {\n const current = getCurrentTheme()\n setIsDark(current === darkTheme)\n })\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] })\n return () => observer.disconnect()\n }, [darkTheme, contextIsDark])\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const checked = e.target.checked\n const theme = checked ? darkTheme : lightTheme\n setTheme(theme)\n onChange?.(theme)\n }\n\n const getTestId = (suffix: string) => (testId ? `${testId}-${suffix}` : undefined)\n\n return (\n <label className={`${dSwap} ${dSwapRotate} ${className}`} data-testid={testId}>\n <input\n type=\"checkbox\"\n checked={isDark}\n onChange={handleChange}\n data-testid={getTestId('input')}\n />\n {/* sun icon */}\n <svg\n className={`${dSwapOff} h-8 w-8 fill-current`}\n data-testid={getTestId('icon-light')}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z\" />\n </svg>\n {/* moon icon */}\n <svg\n className={`${dSwapOn} h-8 w-8 fill-current`}\n data-testid={getTestId('icon-dark')}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z\" />\n </svg>\n </label>\n )\n}\n\nfunction ThemeControllerDropdown({\n themes,\n defaultTheme,\n onChange,\n className = '',\n 'data-testid': testId,\n}: ThemeControllerDropdownProps) {\n const { setTheme: contextSetTheme, theme: resolvedTheme } = useTheme()\n const setTheme = contextSetTheme ?? setThemeDirectly\n const radioName = useId()\n\n const [selectedTheme, setSelectedTheme] = useState(() => {\n if (resolvedTheme && themes.includes(resolvedTheme)) return resolvedTheme\n const current = getCurrentTheme()\n if (current && themes.includes(current)) return current\n return defaultTheme || themes[0] || 'light'\n })\n\n // Sync with context or external theme changes\n useEffect(() => {\n if (resolvedTheme && themes.includes(resolvedTheme)) {\n setSelectedTheme(resolvedTheme)\n return\n }\n const observer = new MutationObserver(() => {\n const current = getCurrentTheme()\n if (current && themes.includes(current)) {\n setSelectedTheme(current)\n }\n })\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] })\n return () => observer.disconnect()\n }, [themes, resolvedTheme])\n\n const handleChange = (theme: string) => {\n setSelectedTheme(theme)\n setTheme(theme)\n onChange?.(theme)\n }\n\n const getTestId = (suffix: string) => (testId ? `${testId}-${suffix}` : undefined)\n\n return (\n <div className={`${dDropdown} ${dDropdownEnd} ${className}`} data-testid={testId}>\n <div tabIndex={0} role=\"button\" className={dBtn} data-testid={getTestId('trigger')}>\n Theme\n <svg\n width=\"12px\"\n height=\"12px\"\n className=\"inline-block h-2 w-2 fill-current opacity-60\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 2048 2048\"\n >\n <path d=\"M1799 349l242 241-1017 1017L7 590l242-241 775 775 775-775z\"></path>\n </svg>\n </div>\n <ul\n tabIndex={0}\n className={`${dDropdownContent} bg-base-300 rounded-box z-[1] w-52 p-2 shadow-2xl max-h-96 overflow-y-auto`}\n data-testid={getTestId('menu')}\n >\n {themes.map((theme) => (\n <li key={theme}>\n <input\n type=\"radio\"\n name={radioName}\n className={`${dBtn} ${dBtnSm} ${dBtnBlock} ${dBtnGhost} justify-start`}\n aria-label={theme}\n value={theme}\n checked={selectedTheme === theme}\n onChange={() => handleChange(theme)}\n data-testid={getTestId(`option-${theme}`)}\n />\n </li>\n ))}\n </ul>\n </div>\n )\n}\n\nconst sizeClasses: Record<string, string> = {\n xs: dToggleXs,\n sm: dToggleSm,\n md: dToggleMd,\n lg: dToggleLg,\n xl: dToggleXl,\n}\n\nfunction ThemeControllerToggle({\n lightTheme = 'light',\n darkTheme = 'dark',\n onChange,\n size,\n className = '',\n 'data-testid': testId,\n}: ThemeControllerToggleProps) {\n const { componentSize } = useConfig()\n const { setTheme: contextSetTheme, isDark: contextIsDark } = useTheme()\n const setTheme = contextSetTheme ?? setThemeDirectly\n const effectiveSize = size ?? componentSize ?? 'md'\n\n const [isDark, setIsDark] = useState(() => {\n if (contextIsDark !== undefined) return contextIsDark\n const current = getCurrentTheme()\n return current === darkTheme\n })\n\n // Sync with context or external theme changes\n useEffect(() => {\n if (contextIsDark !== undefined) {\n setIsDark(contextIsDark)\n return\n }\n const observer = new MutationObserver(() => {\n const current = getCurrentTheme()\n setIsDark(current === darkTheme)\n })\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] })\n return () => observer.disconnect()\n }, [darkTheme, contextIsDark])\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const checked = e.target.checked\n const theme = checked ? darkTheme : lightTheme\n setTheme(theme)\n onChange?.(theme)\n }\n\n return (\n <input\n type=\"checkbox\"\n className={`${dToggle} ${sizeClasses[effectiveSize]} ${className}`}\n checked={isDark}\n onChange={handleChange}\n aria-label=\"Toggle theme\"\n data-testid={testId}\n />\n )\n}\n\nexport const ThemeController = Object.assign(\n {},\n {\n Swap: ThemeControllerSwap,\n Dropdown: ThemeControllerDropdown,\n Toggle: ThemeControllerToggle,\n }\n)\n"],"names":["dSwap","dSwapRotate","dSwapOff","dSwapOn","dDropdown","dDropdownEnd","dDropdownContent","dBtn","dBtnSm","dBtnBlock","dBtnGhost","dToggle","dToggleXs","dToggleSm","dToggleMd","dToggleLg","dToggleXl","getCurrentTheme","setThemeDirectly","theme","ThemeControllerSwap","lightTheme","darkTheme","onChange","className","testId","contextSetTheme","contextIsDark","useTheme","setTheme","isDark","setIsDark","useState","useEffect","observer","current","handleChange","e","getTestId","suffix","jsxs","jsx","ThemeControllerDropdown","themes","defaultTheme","resolvedTheme","radioName","useId","selectedTheme","setSelectedTheme","sizeClasses","ThemeControllerToggle","size","componentSize","useConfig","effectiveSize","ThemeController"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { jsxs as D, jsx as M } from "react/jsx-runtime";
2
2
  import { useState as U, useMemo as F, useEffect as B } from "react";
3
- import { useTheme as X } from "../hooks/useTheme.js";
3
+ import { getThemeColors as X } from "../hooks/useTheme.js";
4
4
  const _ = "asterui", K = 0.22;
5
5
  function q(r, c) {
6
6
  const n = Math.round(c * 255).toString(16).padStart(2, "0");
@@ -27,18 +27,18 @@ const G = (r, c) => {
27
27
  gap: $,
28
28
  offset: k,
29
29
  rotate: b = -22,
30
- zIndex: H = 1e3,
30
+ zIndex: C = 1e3,
31
31
  font: y,
32
32
  "data-testid": h,
33
- ...I
33
+ ...H
34
34
  }) => {
35
- const [u, L] = U(null), f = $?.[0] ?? 120, m = $?.[1] ?? 120, N = k?.[0] ?? f / 2, P = k?.[1] ?? m / 2, { colors: j } = X(), S = F(
35
+ const [u, L] = U(null), f = $?.[0] ?? 120, m = $?.[1] ?? 120, I = k?.[0] ?? f / 2, N = k?.[1] ?? m / 2, P = X(), S = F(
36
36
  () => typeof d == "string" ? [d] : Array.isArray(d) ? d : [_],
37
37
  [d]
38
- ), T = y?.color ?? q(j.foreground, K), a = F(
38
+ ), T = y?.color ?? q(P.foreground, K), a = F(
39
39
  () => G(y, T),
40
40
  [y, T]
41
- ), E = Math.PI / 180 * b, z = S.join("|");
41
+ ), E = Math.PI / 180 * b, j = S.join("|");
42
42
  B(() => {
43
43
  if (typeof window > "u") return;
44
44
  let l = !1;
@@ -85,17 +85,17 @@ const G = (r, c) => {
85
85
  i,
86
86
  x,
87
87
  E,
88
- z,
88
+ j,
89
89
  s
90
90
  ]);
91
- const C = ["relative", c].filter(Boolean).join(" "), O = (l) => h ? `${h}-${l}` : void 0;
91
+ const z = ["relative", c].filter(Boolean).join(" "), O = (l) => h ? `${h}-${l}` : void 0;
92
92
  return /* @__PURE__ */ D(
93
93
  "div",
94
94
  {
95
- className: C,
95
+ className: z,
96
96
  style: { position: n?.position ?? "relative", ...n },
97
97
  "data-testid": h,
98
- ...I,
98
+ ...H,
99
99
  children: [
100
100
  r,
101
101
  u && /* @__PURE__ */ M(
@@ -104,11 +104,11 @@ const G = (r, c) => {
104
104
  "aria-hidden": !0,
105
105
  className: "pointer-events-none absolute inset-0",
106
106
  style: {
107
- zIndex: H,
107
+ zIndex: C,
108
108
  backgroundImage: `url(${u.url})`,
109
109
  backgroundRepeat: "repeat",
110
110
  backgroundSize: `${u.width}px ${u.height}px`,
111
- backgroundPosition: `${N}px ${P}px`
111
+ backgroundPosition: `${I}px ${N}px`
112
112
  },
113
113
  "data-testid": O("overlay")
114
114
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Watermark.js","sources":["../../src/components/Watermark.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useState } from 'react'\nimport { useTheme } from '../hooks/useTheme'\n\nexport type WatermarkGap = [number, number]\nexport type WatermarkOffset = [number, number]\n\nexport interface WatermarkFontOptions {\n /** Text color for watermark content */\n color?: string\n /** Font size in pixels */\n fontSize?: number\n /** Font weight for watermark text */\n fontWeight?: number | 'normal' | 'bold' | 'bolder' | 'lighter'\n /** Font style for watermark text */\n fontStyle?: 'normal' | 'italic' | 'oblique'\n /** Font family for watermark text */\n fontFamily?: string\n /** Line height in pixels for multi-line content */\n lineHeight?: number\n}\n\nexport interface WatermarkProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'content'> {\n /** Text to render inside the watermark; falls back to \"asterui\" */\n content?: string | string[]\n /** Image source (URL or base64) to render instead of text */\n image?: string\n /** Width of a single watermark tile in pixels */\n width?: number\n /** Height of a single watermark tile in pixels */\n height?: number\n /** Horizontal/vertical gap between watermarks in pixels */\n gap?: WatermarkGap\n /** Offset for the first watermark tile from the top-left corner */\n offset?: WatermarkOffset\n /** Rotation angle in degrees */\n rotate?: number\n /** z-index for the overlay layer */\n zIndex?: number\n /** Font settings for text watermarks */\n font?: WatermarkFontOptions\n /** Content to protect with the watermark */\n children?: React.ReactNode\n 'data-testid'?: string\n}\n\ntype WatermarkImage = {\n url: string\n width: number\n height: number\n}\n\nconst DEFAULT_CONTENT = 'asterui'\nconst DEFAULT_OPACITY = 0.22\n\n// Add opacity to a hex color (returns #rrggbbaa format)\nfunction hexWithOpacity(hex: string, opacity: number): string {\n const alpha = Math.round(opacity * 255).toString(16).padStart(2, '0')\n return hex + alpha\n}\n\nconst getFontSettings = (font: WatermarkFontOptions | undefined, resolvedColor: string) => {\n const fontSize = font?.fontSize ?? 16\n\n return {\n color: resolvedColor,\n fontSize,\n fontWeight: font?.fontWeight ?? 600,\n fontStyle: font?.fontStyle ?? 'normal',\n fontFamily: font?.fontFamily ?? 'sans-serif',\n lineHeight: font?.lineHeight ?? fontSize * 1.2,\n }\n}\n\nexport const Watermark: React.FC<WatermarkProps> = ({\n children,\n className = '',\n style,\n content,\n image,\n width = 120,\n height = 64,\n gap,\n offset,\n rotate = -22,\n zIndex = 1000,\n font,\n 'data-testid': testId,\n ...rest\n}) => {\n const [watermark, setWatermark] = useState<WatermarkImage | null>(null)\n\n const gapX = gap?.[0] ?? 120\n const gapY = gap?.[1] ?? 120\n const offsetX = offset?.[0] ?? gapX / 2\n const offsetY = offset?.[1] ?? gapY / 2\n const { colors } = useTheme()\n const textLines = useMemo(\n () =>\n typeof content === 'string'\n ? [content]\n : Array.isArray(content)\n ? content\n : [DEFAULT_CONTENT],\n [content]\n )\n // Use provided color or theme foreground with opacity\n const resolvedColor = font?.color ?? hexWithOpacity(colors.foreground, DEFAULT_OPACITY)\n const fontSettings = useMemo(\n () => getFontSettings(font, resolvedColor),\n [font, resolvedColor]\n )\n const rotationInRadians = (Math.PI / 180) * rotate\n const textKey = textLines.join('|')\n\n useEffect(() => {\n if (typeof window === 'undefined') return\n\n let cancelled = false\n const ratio = window.devicePixelRatio || 1\n const tileWidth = width + gapX\n const tileHeight = height + gapY\n const canvas = document.createElement('canvas')\n canvas.width = tileWidth * ratio\n canvas.height = tileHeight * ratio\n const ctx = canvas.getContext('2d')\n\n if (!ctx) return\n\n const commitWatermark = () => {\n const url = canvas.toDataURL()\n if (!cancelled) {\n setWatermark({ url, width: tileWidth, height: tileHeight })\n }\n }\n\n const drawText = () => {\n ctx.save()\n ctx.translate((gapX / 2 + width / 2) * ratio, (gapY / 2 + height / 2) * ratio)\n ctx.rotate(rotationInRadians)\n ctx.fillStyle = fontSettings.color\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n ctx.font = `${fontSettings.fontStyle} normal ${fontSettings.fontWeight} ${fontSettings.fontSize * ratio}px ${fontSettings.fontFamily}`\n\n const lineHeight = fontSettings.lineHeight * ratio\n const startY = -((textLines.length - 1) * lineHeight) / 2\n\n textLines.forEach((line, index) => {\n ctx.fillText(line, 0, startY + index * lineHeight)\n })\n\n ctx.restore()\n }\n\n if (image) {\n const img = new Image()\n img.crossOrigin = 'anonymous'\n img.referrerPolicy = 'no-referrer'\n\n const handleLoad = () => {\n ctx.save()\n ctx.translate((gapX / 2 + width / 2) * ratio, (gapY / 2 + height / 2) * ratio)\n ctx.rotate(rotationInRadians)\n ctx.drawImage(\n img,\n (-width / 2) * ratio,\n (-height / 2) * ratio,\n width * ratio,\n height * ratio\n )\n ctx.restore()\n commitWatermark()\n }\n\n const handleError = () => {\n if (!cancelled) setWatermark(null)\n }\n\n img.addEventListener('load', handleLoad)\n img.addEventListener('error', handleError)\n img.src = image\n\n return () => {\n cancelled = true\n img.removeEventListener('load', handleLoad)\n img.removeEventListener('error', handleError)\n }\n } else {\n drawText()\n commitWatermark()\n }\n\n return () => {\n cancelled = true\n }\n }, [\n fontSettings,\n gapX,\n gapY,\n height,\n image,\n rotationInRadians,\n textKey,\n width,\n ])\n\n const classes = ['relative', className].filter(Boolean).join(' ')\n\n const getTestId = (suffix: string) => (testId ? `${testId}-${suffix}` : undefined)\n\n return (\n <div\n className={classes}\n style={{ position: style?.position ?? 'relative', ...style }}\n data-testid={testId}\n {...rest}\n >\n {children}\n {watermark && (\n <div\n aria-hidden\n className=\"pointer-events-none absolute inset-0\"\n style={{\n zIndex,\n backgroundImage: `url(${watermark.url})`,\n backgroundRepeat: 'repeat',\n backgroundSize: `${watermark.width}px ${watermark.height}px`,\n backgroundPosition: `${offsetX}px ${offsetY}px`,\n }}\n data-testid={getTestId('overlay')}\n />\n )}\n </div>\n )\n}\n\nWatermark.displayName = 'Watermark'\n"],"names":["DEFAULT_CONTENT","DEFAULT_OPACITY","hexWithOpacity","hex","opacity","alpha","getFontSettings","font","resolvedColor","fontSize","Watermark","children","className","style","content","image","width","height","gap","offset","rotate","zIndex","testId","rest","watermark","setWatermark","useState","gapX","gapY","offsetX","offsetY","colors","useTheme","textLines","useMemo","fontSettings","rotationInRadians","textKey","useEffect","cancelled","ratio","tileWidth","tileHeight","canvas","ctx","commitWatermark","url","drawText","lineHeight","startY","line","index","img","handleLoad","handleError","classes","getTestId","suffix","jsxs","jsx"],"mappings":"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"}
1
+ {"version":3,"file":"Watermark.js","sources":["../../src/components/Watermark.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useState } from 'react'\nimport { getThemeColors } from '../hooks/useTheme'\n\nexport type WatermarkGap = [number, number]\nexport type WatermarkOffset = [number, number]\n\nexport interface WatermarkFontOptions {\n /** Text color for watermark content */\n color?: string\n /** Font size in pixels */\n fontSize?: number\n /** Font weight for watermark text */\n fontWeight?: number | 'normal' | 'bold' | 'bolder' | 'lighter'\n /** Font style for watermark text */\n fontStyle?: 'normal' | 'italic' | 'oblique'\n /** Font family for watermark text */\n fontFamily?: string\n /** Line height in pixels for multi-line content */\n lineHeight?: number\n}\n\nexport interface WatermarkProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'content'> {\n /** Text to render inside the watermark; falls back to \"asterui\" */\n content?: string | string[]\n /** Image source (URL or base64) to render instead of text */\n image?: string\n /** Width of a single watermark tile in pixels */\n width?: number\n /** Height of a single watermark tile in pixels */\n height?: number\n /** Horizontal/vertical gap between watermarks in pixels */\n gap?: WatermarkGap\n /** Offset for the first watermark tile from the top-left corner */\n offset?: WatermarkOffset\n /** Rotation angle in degrees */\n rotate?: number\n /** z-index for the overlay layer */\n zIndex?: number\n /** Font settings for text watermarks */\n font?: WatermarkFontOptions\n /** Content to protect with the watermark */\n children?: React.ReactNode\n 'data-testid'?: string\n}\n\ntype WatermarkImage = {\n url: string\n width: number\n height: number\n}\n\nconst DEFAULT_CONTENT = 'asterui'\nconst DEFAULT_OPACITY = 0.22\n\n// Add opacity to a hex color (returns #rrggbbaa format)\nfunction hexWithOpacity(hex: string, opacity: number): string {\n const alpha = Math.round(opacity * 255).toString(16).padStart(2, '0')\n return hex + alpha\n}\n\nconst getFontSettings = (font: WatermarkFontOptions | undefined, resolvedColor: string) => {\n const fontSize = font?.fontSize ?? 16\n\n return {\n color: resolvedColor,\n fontSize,\n fontWeight: font?.fontWeight ?? 600,\n fontStyle: font?.fontStyle ?? 'normal',\n fontFamily: font?.fontFamily ?? 'sans-serif',\n lineHeight: font?.lineHeight ?? fontSize * 1.2,\n }\n}\n\nexport const Watermark: React.FC<WatermarkProps> = ({\n children,\n className = '',\n style,\n content,\n image,\n width = 120,\n height = 64,\n gap,\n offset,\n rotate = -22,\n zIndex = 1000,\n font,\n 'data-testid': testId,\n ...rest\n}) => {\n const [watermark, setWatermark] = useState<WatermarkImage | null>(null)\n\n const gapX = gap?.[0] ?? 120\n const gapY = gap?.[1] ?? 120\n const offsetX = offset?.[0] ?? gapX / 2\n const offsetY = offset?.[1] ?? gapY / 2\n const colors = getThemeColors()\n const textLines = useMemo(\n () =>\n typeof content === 'string'\n ? [content]\n : Array.isArray(content)\n ? content\n : [DEFAULT_CONTENT],\n [content]\n )\n // Use provided color or theme foreground with opacity\n const resolvedColor = font?.color ?? hexWithOpacity(colors.foreground, DEFAULT_OPACITY)\n const fontSettings = useMemo(\n () => getFontSettings(font, resolvedColor),\n [font, resolvedColor]\n )\n const rotationInRadians = (Math.PI / 180) * rotate\n const textKey = textLines.join('|')\n\n useEffect(() => {\n if (typeof window === 'undefined') return\n\n let cancelled = false\n const ratio = window.devicePixelRatio || 1\n const tileWidth = width + gapX\n const tileHeight = height + gapY\n const canvas = document.createElement('canvas')\n canvas.width = tileWidth * ratio\n canvas.height = tileHeight * ratio\n const ctx = canvas.getContext('2d')\n\n if (!ctx) return\n\n const commitWatermark = () => {\n const url = canvas.toDataURL()\n if (!cancelled) {\n setWatermark({ url, width: tileWidth, height: tileHeight })\n }\n }\n\n const drawText = () => {\n ctx.save()\n ctx.translate((gapX / 2 + width / 2) * ratio, (gapY / 2 + height / 2) * ratio)\n ctx.rotate(rotationInRadians)\n ctx.fillStyle = fontSettings.color\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n ctx.font = `${fontSettings.fontStyle} normal ${fontSettings.fontWeight} ${fontSettings.fontSize * ratio}px ${fontSettings.fontFamily}`\n\n const lineHeight = fontSettings.lineHeight * ratio\n const startY = -((textLines.length - 1) * lineHeight) / 2\n\n textLines.forEach((line, index) => {\n ctx.fillText(line, 0, startY + index * lineHeight)\n })\n\n ctx.restore()\n }\n\n if (image) {\n const img = new Image()\n img.crossOrigin = 'anonymous'\n img.referrerPolicy = 'no-referrer'\n\n const handleLoad = () => {\n ctx.save()\n ctx.translate((gapX / 2 + width / 2) * ratio, (gapY / 2 + height / 2) * ratio)\n ctx.rotate(rotationInRadians)\n ctx.drawImage(\n img,\n (-width / 2) * ratio,\n (-height / 2) * ratio,\n width * ratio,\n height * ratio\n )\n ctx.restore()\n commitWatermark()\n }\n\n const handleError = () => {\n if (!cancelled) setWatermark(null)\n }\n\n img.addEventListener('load', handleLoad)\n img.addEventListener('error', handleError)\n img.src = image\n\n return () => {\n cancelled = true\n img.removeEventListener('load', handleLoad)\n img.removeEventListener('error', handleError)\n }\n } else {\n drawText()\n commitWatermark()\n }\n\n return () => {\n cancelled = true\n }\n }, [\n fontSettings,\n gapX,\n gapY,\n height,\n image,\n rotationInRadians,\n textKey,\n width,\n ])\n\n const classes = ['relative', className].filter(Boolean).join(' ')\n\n const getTestId = (suffix: string) => (testId ? `${testId}-${suffix}` : undefined)\n\n return (\n <div\n className={classes}\n style={{ position: style?.position ?? 'relative', ...style }}\n data-testid={testId}\n {...rest}\n >\n {children}\n {watermark && (\n <div\n aria-hidden\n className=\"pointer-events-none absolute inset-0\"\n style={{\n zIndex,\n backgroundImage: `url(${watermark.url})`,\n backgroundRepeat: 'repeat',\n backgroundSize: `${watermark.width}px ${watermark.height}px`,\n backgroundPosition: `${offsetX}px ${offsetY}px`,\n }}\n data-testid={getTestId('overlay')}\n />\n )}\n </div>\n )\n}\n\nWatermark.displayName = 'Watermark'\n"],"names":["DEFAULT_CONTENT","DEFAULT_OPACITY","hexWithOpacity","hex","opacity","alpha","getFontSettings","font","resolvedColor","fontSize","Watermark","children","className","style","content","image","width","height","gap","offset","rotate","zIndex","testId","rest","watermark","setWatermark","useState","gapX","gapY","offsetX","offsetY","colors","getThemeColors","textLines","useMemo","fontSettings","rotationInRadians","textKey","useEffect","cancelled","ratio","tileWidth","tileHeight","canvas","ctx","commitWatermark","url","drawText","lineHeight","startY","line","index","img","handleLoad","handleError","classes","getTestId","suffix","jsxs","jsx"],"mappings":"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"}
@@ -3,41 +3,31 @@ export type { ThemeColors };
3
3
  export interface UseThemeReturn {
4
4
  /** The theme setting (what user selected). Only available with ThemeProvider. */
5
5
  theme: string | undefined;
6
- /** The actual applied theme. Only available with ThemeProvider. */
7
- resolvedTheme: string | undefined;
8
6
  /** Whether dark mode is active */
9
7
  isDark: boolean;
10
8
  /** Set the theme. Only available with ThemeProvider. */
11
9
  setTheme: ((theme: string) => void) | undefined;
12
10
  /** Toggle between light and dark. Only available with ThemeProvider. */
13
11
  toggleTheme: (() => void) | undefined;
14
- /** Computed theme colors as hex values. Computed asynchronously after theme changes. */
15
- colors: ThemeColors;
16
12
  /** The system preference. Only available with ThemeProvider. */
17
13
  systemTheme: 'light' | 'dark' | undefined;
18
14
  }
19
15
  /**
20
- * Hook to detect current theme and get computed colors.
16
+ * Hook to detect and control the current theme.
21
17
  *
22
- * When used within a ThemeProvider, returns full theme control including
23
- * setTheme, theme selection, and resolved theme.
18
+ * When used within a ThemeProvider, returns full theme control.
19
+ * When used standalone, provides read-only isDark based on data-theme or system preference.
24
20
  *
25
- * When used standalone (without ThemeProvider), provides read-only access
26
- * to isDark and colors based on the current data-theme attribute and
27
- * system preference.
28
- *
29
- * Colors are computed asynchronously after mount and theme changes,
30
- * using DOM-based color conversion (no canvas).
31
- *
32
- * @example
33
- * // With ThemeProvider (full control)
34
- * const { theme, setTheme, resolvedTheme, isDark, colors } = useTheme()
35
- * setTheme('dark')
21
+ * Does NOT compute colors — use getThemeColors() where you need hex values.
36
22
  *
37
23
  * @example
38
- * // Without ThemeProvider (read-only)
39
- * const { isDark, colors } = useTheme()
40
- * // colors.primary, colors.foreground, etc.
24
+ * const { isDark, setTheme, toggleTheme } = useTheme()
41
25
  */
42
26
  export declare function useTheme(): UseThemeReturn;
27
+ /**
28
+ * Compute current DaisyUI theme colors as hex values.
29
+ * Call this on demand when you need hex colors (e.g., for canvas, xterm).
30
+ * Not reactive — call it inside useEffect after theme changes.
31
+ */
32
+ export declare function getThemeColors(): ThemeColors;
43
33
  export default useTheme;
@@ -1,6 +1,5 @@
1
- import { useState as u, useRef as g, useEffect as f, useMemo as l } from "react";
2
- import { useHasThemeProvider as b, useThemeContext as T } from "../providers/ThemeProvider.js";
3
- const p = /* @__PURE__ */ new Set([
1
+ import { useHasThemeProvider as a, useThemeContext as f } from "../providers/ThemeProvider.js";
2
+ const d = /* @__PURE__ */ new Set([
4
3
  "dark",
5
4
  "synthwave",
6
5
  "halloween",
@@ -13,106 +12,76 @@ const p = /* @__PURE__ */ new Set([
13
12
  "coffee",
14
13
  "dim",
15
14
  "sunset"
16
- ]), a = {
17
- background: "#ffffff",
18
- foreground: "#000000",
19
- primary: "#6366f1",
20
- primaryContent: "#ffffff",
21
- secondary: "#f000b8",
22
- accent: "#37cdbe",
23
- info: "#3abff8",
24
- success: "#36d399",
25
- warning: "#fbbd23",
26
- error: "#f87272"
27
- };
15
+ ]);
16
+ function h() {
17
+ if (a()) {
18
+ const t = f();
19
+ return {
20
+ theme: t.theme,
21
+ isDark: t.isDark,
22
+ setTheme: t.setTheme,
23
+ toggleTheme: t.toggleTheme,
24
+ systemTheme: t.systemTheme
25
+ };
26
+ }
27
+ const e = typeof document < "u" ? document.documentElement.getAttribute("data-theme") ?? void 0 : void 0, r = typeof window < "u" && window.matchMedia("(prefers-color-scheme: dark)").matches, n = e ? d.has(e) : r;
28
+ return {
29
+ theme: e,
30
+ isDark: n,
31
+ setTheme: (t) => document.documentElement.setAttribute("data-theme", t),
32
+ toggleTheme: () => {
33
+ const t = document.documentElement.getAttribute("data-theme"), s = t ? d.has(t) : r;
34
+ document.documentElement.setAttribute("data-theme", s ? "light" : "dark");
35
+ },
36
+ systemTheme: r ? "dark" : "light"
37
+ };
38
+ }
28
39
  let o = null;
29
- function v() {
40
+ function u() {
30
41
  return o || (o = document.createElement("span"), o.style.position = "absolute", o.style.visibility = "hidden", o.style.pointerEvents = "none", o.style.width = "0", o.style.height = "0", o.style.overflow = "hidden", document.body.appendChild(o)), o;
31
42
  }
32
- function w(e) {
33
- const t = v();
34
- t.style.color = e;
35
- const n = getComputedStyle(t).color.match(/\d+/g);
43
+ function i(c) {
44
+ const e = u();
45
+ e.style.color = c;
46
+ const n = getComputedStyle(e).color.match(/\d+/g);
36
47
  if (!n) return "#000000";
37
- const [r, c, m] = n.map(Number);
38
- return `#${((1 << 24) + (r << 16) + (c << 8) + m).toString(16).slice(1)}`;
48
+ const [t, s, m] = n.map(Number);
49
+ return `#${((1 << 24) + (t << 16) + (s << 8) + m).toString(16).slice(1)}`;
39
50
  }
40
- function h() {
41
- if (typeof document > "u") return a;
42
- const e = getComputedStyle(document.documentElement), t = (s, n) => {
43
- const r = e.getPropertyValue(s).trim();
44
- return r ? w(r) : n;
51
+ function y() {
52
+ if (typeof document > "u")
53
+ return {
54
+ background: "#ffffff",
55
+ foreground: "#000000",
56
+ primary: "#6366f1",
57
+ primaryContent: "#ffffff",
58
+ secondary: "#f000b8",
59
+ accent: "#37cdbe",
60
+ info: "#3abff8",
61
+ success: "#36d399",
62
+ warning: "#fbbd23",
63
+ error: "#f87272"
64
+ };
65
+ const c = getComputedStyle(document.documentElement), e = (r, n) => {
66
+ const t = c.getPropertyValue(r).trim();
67
+ return t ? i(t) : n;
45
68
  };
46
69
  return {
47
- background: t("--color-base-100", "#ffffff"),
48
- foreground: t("--color-base-content", "#000000"),
49
- primary: t("--color-primary", "#6366f1"),
50
- primaryContent: t("--color-primary-content", "#ffffff"),
51
- secondary: t("--color-secondary", "#f000b8"),
52
- accent: t("--color-accent", "#37cdbe"),
53
- info: t("--color-info", "#3abff8"),
54
- success: t("--color-success", "#36d399"),
55
- warning: t("--color-warning", "#fbbd23"),
56
- error: t("--color-error", "#f87272")
70
+ background: e("--color-base-100", "#ffffff"),
71
+ foreground: e("--color-base-content", "#000000"),
72
+ primary: e("--color-primary", "#6366f1"),
73
+ primaryContent: e("--color-primary-content", "#ffffff"),
74
+ secondary: e("--color-secondary", "#f000b8"),
75
+ accent: e("--color-accent", "#37cdbe"),
76
+ info: e("--color-info", "#3abff8"),
77
+ success: e("--color-success", "#36d399"),
78
+ warning: e("--color-warning", "#fbbd23"),
79
+ error: e("--color-error", "#f87272")
57
80
  };
58
81
  }
59
- function k() {
60
- return typeof window > "u" ? "light" : window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
61
- }
62
- function C() {
63
- return typeof document > "u" ? null : document.documentElement.getAttribute("data-theme");
64
- }
65
- function x() {
66
- return b() ? S() : E();
67
- }
68
- function S() {
69
- const e = T(), [t, s] = u(a), n = g(!1);
70
- return f(() => {
71
- requestAnimationFrame(() => {
72
- s(h());
73
- }), n.current = !0;
74
- }, [e.resolvedTheme]), l(() => ({
75
- theme: e.theme,
76
- resolvedTheme: e.resolvedTheme,
77
- isDark: e.isDark,
78
- setTheme: e.setTheme,
79
- toggleTheme: e.toggleTheme,
80
- colors: t,
81
- systemTheme: e.systemTheme
82
- }), [e.theme, e.resolvedTheme, e.isDark, e.setTheme, e.toggleTheme, t, e.systemTheme]);
83
- }
84
- function E() {
85
- const [e, t] = u(!1), [s, n] = u(a);
86
- return f(() => {
87
- const r = () => {
88
- const d = C(), y = k();
89
- let i = !1;
90
- d ? i = p.has(d) : i = y === "dark", t(i), requestAnimationFrame(() => {
91
- n(h());
92
- });
93
- };
94
- r();
95
- const c = new MutationObserver(r);
96
- c.observe(document.documentElement, {
97
- attributes: !0,
98
- attributeFilter: ["data-theme", "class"]
99
- });
100
- const m = window.matchMedia("(prefers-color-scheme: dark)");
101
- return m.addEventListener("change", r), () => {
102
- c.disconnect(), m.removeEventListener("change", r);
103
- };
104
- }, []), l(() => ({
105
- theme: void 0,
106
- resolvedTheme: void 0,
107
- isDark: e,
108
- setTheme: void 0,
109
- toggleTheme: void 0,
110
- colors: s,
111
- systemTheme: void 0
112
- }), [e, s]);
113
- }
114
82
  export {
115
- x as default,
116
- x as useTheme
83
+ h as default,
84
+ y as getThemeColors,
85
+ h as useTheme
117
86
  };
118
87
  //# sourceMappingURL=useTheme.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useTheme.js","sources":["../../src/hooks/useTheme.ts"],"sourcesContent":["import { useEffect, useState, useMemo, useRef } from 'react'\nimport { useHasThemeProvider, useThemeContext, type ThemeColors } from '../providers/ThemeProvider'\n\nexport type { ThemeColors }\n\n// Common dark themes in DaisyUI\nconst DARK_THEMES = new Set([\n 'dark', 'synthwave', 'halloween', 'forest', 'black', 'luxury', 'dracula',\n 'business', 'night', 'coffee', 'dim', 'sunset'\n])\n\nexport interface UseThemeReturn {\n /** The theme setting (what user selected). Only available with ThemeProvider. */\n theme: string | undefined\n /** The actual applied theme. Only available with ThemeProvider. */\n resolvedTheme: string | undefined\n /** Whether dark mode is active */\n isDark: boolean\n /** Set the theme. Only available with ThemeProvider. */\n setTheme: ((theme: string) => void) | undefined\n /** Toggle between light and dark. Only available with ThemeProvider. */\n toggleTheme: (() => void) | undefined\n /** Computed theme colors as hex values. Computed asynchronously after theme changes. */\n colors: ThemeColors\n /** The system preference. Only available with ThemeProvider. */\n systemTheme: 'light' | 'dark' | undefined\n}\n\nconst SSR_COLORS: ThemeColors = {\n background: '#ffffff',\n foreground: '#000000',\n primary: '#6366f1',\n primaryContent: '#ffffff',\n secondary: '#f000b8',\n accent: '#37cdbe',\n info: '#3abff8',\n success: '#36d399',\n warning: '#fbbd23',\n error: '#f87272',\n}\n\n// Persistent hidden element for DOM-based color conversion\nlet colorProbe: HTMLSpanElement | null = null\n\nfunction getColorProbe(): HTMLSpanElement {\n if (!colorProbe) {\n colorProbe = document.createElement('span')\n colorProbe.style.position = 'absolute'\n colorProbe.style.visibility = 'hidden'\n colorProbe.style.pointerEvents = 'none'\n colorProbe.style.width = '0'\n colorProbe.style.height = '0'\n colorProbe.style.overflow = 'hidden'\n document.body.appendChild(colorProbe)\n }\n return colorProbe\n}\n\n/** Convert any CSS color (including oklch) to hex via getComputedStyle */\nfunction colorToHex(color: string): string {\n const probe = getColorProbe()\n probe.style.color = color\n const computed = getComputedStyle(probe).color\n // getComputedStyle returns rgb(r, g, b) or rgba(r, g, b, a)\n const match = computed.match(/\\d+/g)\n if (!match) return '#000000'\n const [r, g, b] = match.map(Number)\n return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`\n}\n\nfunction computeThemeColors(): ThemeColors {\n if (typeof document === 'undefined') return SSR_COLORS\n\n const style = getComputedStyle(document.documentElement)\n const getColor = (varName: string, fallback: string): string => {\n const value = style.getPropertyValue(varName).trim()\n return value ? colorToHex(value) : fallback\n }\n\n return {\n background: getColor('--color-base-100', '#ffffff'),\n foreground: getColor('--color-base-content', '#000000'),\n primary: getColor('--color-primary', '#6366f1'),\n primaryContent: getColor('--color-primary-content', '#ffffff'),\n secondary: getColor('--color-secondary', '#f000b8'),\n accent: getColor('--color-accent', '#37cdbe'),\n info: getColor('--color-info', '#3abff8'),\n success: getColor('--color-success', '#36d399'),\n warning: getColor('--color-warning', '#fbbd23'),\n error: getColor('--color-error', '#f87272'),\n }\n}\n\nfunction getSystemTheme(): 'light' | 'dark' {\n if (typeof window === 'undefined') return 'light'\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'\n}\n\nfunction getCurrentTheme(): string | null {\n if (typeof document === 'undefined') return null\n return document.documentElement.getAttribute('data-theme')\n}\n\n/**\n * Hook to detect current theme and get computed colors.\n *\n * When used within a ThemeProvider, returns full theme control including\n * setTheme, theme selection, and resolved theme.\n *\n * When used standalone (without ThemeProvider), provides read-only access\n * to isDark and colors based on the current data-theme attribute and\n * system preference.\n *\n * Colors are computed asynchronously after mount and theme changes,\n * using DOM-based color conversion (no canvas).\n *\n * @example\n * // With ThemeProvider (full control)\n * const { theme, setTheme, resolvedTheme, isDark, colors } = useTheme()\n * setTheme('dark')\n *\n * @example\n * // Without ThemeProvider (read-only)\n * const { isDark, colors } = useTheme()\n * // colors.primary, colors.foreground, etc.\n */\nexport function useTheme(): UseThemeReturn {\n const hasProvider = useHasThemeProvider()\n\n if (hasProvider) {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return useThemeWithProvider()\n }\n\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return useThemeStandalone()\n}\n\n/**\n * Theme hook when ThemeProvider is present.\n */\nfunction useThemeWithProvider(): UseThemeReturn {\n const context = useThemeContext()\n const [colors, setColors] = useState<ThemeColors>(SSR_COLORS)\n const mountedRef = useRef(false)\n\n useEffect(() => {\n // On mount and when theme changes, compute colors after CSS has applied\n requestAnimationFrame(() => {\n setColors(computeThemeColors())\n })\n mountedRef.current = true\n }, [context.resolvedTheme])\n\n return useMemo(() => ({\n theme: context.theme,\n resolvedTheme: context.resolvedTheme,\n isDark: context.isDark,\n setTheme: context.setTheme,\n toggleTheme: context.toggleTheme,\n colors,\n systemTheme: context.systemTheme,\n }), [context.theme, context.resolvedTheme, context.isDark, context.setTheme, context.toggleTheme, colors, context.systemTheme])\n}\n\n/**\n * Standalone theme detection (no ThemeProvider)\n */\nfunction useThemeStandalone(): UseThemeReturn {\n const [isDark, setIsDark] = useState(false)\n const [colors, setColors] = useState<ThemeColors>(SSR_COLORS)\n\n useEffect(() => {\n const updateTheme = () => {\n const currentTheme = getCurrentTheme()\n const systemTheme = getSystemTheme()\n\n let dark = false\n if (currentTheme) {\n dark = DARK_THEMES.has(currentTheme)\n } else {\n dark = systemTheme === 'dark'\n }\n\n setIsDark(dark)\n\n // Compute colors after CSS has applied\n requestAnimationFrame(() => {\n setColors(computeThemeColors())\n })\n }\n\n updateTheme()\n\n const observer = new MutationObserver(updateTheme)\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['data-theme', 'class']\n })\n\n const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')\n mediaQuery.addEventListener('change', updateTheme)\n\n return () => {\n observer.disconnect()\n mediaQuery.removeEventListener('change', updateTheme)\n }\n }, [])\n\n return useMemo(() => ({\n theme: undefined,\n resolvedTheme: undefined,\n isDark,\n setTheme: undefined,\n toggleTheme: undefined,\n colors,\n systemTheme: undefined,\n }), [isDark, colors])\n}\n\nexport default useTheme\n"],"names":["DARK_THEMES","SSR_COLORS","colorProbe","getColorProbe","colorToHex","color","probe","match","g","b","computeThemeColors","style","getColor","varName","fallback","value","getSystemTheme","getCurrentTheme","useTheme","useHasThemeProvider","useThemeWithProvider","useThemeStandalone","context","useThemeContext","colors","setColors","useState","mountedRef","useRef","useEffect","useMemo","isDark","setIsDark","updateTheme","currentTheme","systemTheme","dark","observer","mediaQuery"],"mappings":"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"}
1
+ {"version":3,"file":"useTheme.js","sources":["../../src/hooks/useTheme.ts"],"sourcesContent":["import { useHasThemeProvider, useThemeContext, type ThemeColors } from '../providers/ThemeProvider'\n\nexport type { ThemeColors }\n\n// Common dark themes in DaisyUI\nconst DARK_THEMES = new Set([\n 'dark', 'synthwave', 'halloween', 'forest', 'black', 'luxury', 'dracula',\n 'business', 'night', 'coffee', 'dim', 'sunset'\n])\n\nexport interface UseThemeReturn {\n /** The theme setting (what user selected). Only available with ThemeProvider. */\n theme: string | undefined\n /** Whether dark mode is active */\n isDark: boolean\n /** Set the theme. Only available with ThemeProvider. */\n setTheme: ((theme: string) => void) | undefined\n /** Toggle between light and dark. Only available with ThemeProvider. */\n toggleTheme: (() => void) | undefined\n /** The system preference. Only available with ThemeProvider. */\n systemTheme: 'light' | 'dark' | undefined\n}\n\n/**\n * Hook to detect and control the current theme.\n *\n * When used within a ThemeProvider, returns full theme control.\n * When used standalone, provides read-only isDark based on data-theme or system preference.\n *\n * Does NOT compute colors — use getThemeColors() where you need hex values.\n *\n * @example\n * const { isDark, setTheme, toggleTheme } = useTheme()\n */\nexport function useTheme(): UseThemeReturn {\n const hasProvider = useHasThemeProvider()\n\n if (hasProvider) {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const context = useThemeContext()\n return {\n theme: context.theme,\n isDark: context.isDark,\n setTheme: context.setTheme,\n toggleTheme: context.toggleTheme,\n systemTheme: context.systemTheme,\n }\n }\n\n // Fallback when used outside ThemeProvider — read from DOM\n const theme = typeof document !== 'undefined'\n ? document.documentElement.getAttribute('data-theme') ?? undefined\n : undefined\n const systemDark = typeof window !== 'undefined'\n && window.matchMedia('(prefers-color-scheme: dark)').matches\n const isDark = theme ? DARK_THEMES.has(theme) : systemDark\n\n return {\n theme,\n isDark,\n setTheme: (t: string) => document.documentElement.setAttribute('data-theme', t),\n toggleTheme: () => {\n const current = document.documentElement.getAttribute('data-theme')\n const currentIsDark = current ? DARK_THEMES.has(current) : systemDark\n document.documentElement.setAttribute('data-theme', currentIsDark ? 'light' : 'dark')\n },\n systemTheme: (systemDark ? 'dark' : 'light') as 'light' | 'dark',\n }\n}\n\n// --- Color utilities (for components that need hex values) ---\n\n/** Persistent hidden element for DOM-based color conversion */\nlet colorProbe: HTMLSpanElement | null = null\n\nfunction getColorProbe(): HTMLSpanElement {\n if (!colorProbe) {\n colorProbe = document.createElement('span')\n colorProbe.style.position = 'absolute'\n colorProbe.style.visibility = 'hidden'\n colorProbe.style.pointerEvents = 'none'\n colorProbe.style.width = '0'\n colorProbe.style.height = '0'\n colorProbe.style.overflow = 'hidden'\n document.body.appendChild(colorProbe)\n }\n return colorProbe\n}\n\n/** Convert any CSS color (including oklch) to hex via getComputedStyle */\nfunction colorToHex(color: string): string {\n const probe = getColorProbe()\n probe.style.color = color\n const computed = getComputedStyle(probe).color\n const match = computed.match(/\\d+/g)\n if (!match) return '#000000'\n const [r, g, b] = match.map(Number)\n return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`\n}\n\n/**\n * Compute current DaisyUI theme colors as hex values.\n * Call this on demand when you need hex colors (e.g., for canvas, xterm).\n * Not reactive — call it inside useEffect after theme changes.\n */\nexport function getThemeColors(): ThemeColors {\n if (typeof document === 'undefined') {\n return {\n background: '#ffffff',\n foreground: '#000000',\n primary: '#6366f1',\n primaryContent: '#ffffff',\n secondary: '#f000b8',\n accent: '#37cdbe',\n info: '#3abff8',\n success: '#36d399',\n warning: '#fbbd23',\n error: '#f87272',\n }\n }\n\n const style = getComputedStyle(document.documentElement)\n const getColor = (varName: string, fallback: string): string => {\n const value = style.getPropertyValue(varName).trim()\n return value ? colorToHex(value) : fallback\n }\n\n return {\n background: getColor('--color-base-100', '#ffffff'),\n foreground: getColor('--color-base-content', '#000000'),\n primary: getColor('--color-primary', '#6366f1'),\n primaryContent: getColor('--color-primary-content', '#ffffff'),\n secondary: getColor('--color-secondary', '#f000b8'),\n accent: getColor('--color-accent', '#37cdbe'),\n info: getColor('--color-info', '#3abff8'),\n success: getColor('--color-success', '#36d399'),\n warning: getColor('--color-warning', '#fbbd23'),\n error: getColor('--color-error', '#f87272'),\n }\n}\n\nexport default useTheme\n"],"names":["DARK_THEMES","useTheme","useHasThemeProvider","context","useThemeContext","theme","systemDark","isDark","current","currentIsDark","colorProbe","getColorProbe","colorToHex","color","probe","match","r","g","b","getThemeColors","style","getColor","varName","fallback","value"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -220,5 +220,5 @@ export { useKeyPress, useKeyPressCallback } from './hooks/useKeyPress';
220
220
  export type { UseKeyPressOptions } from './hooks/useKeyPress';
221
221
  export { useWindowSize } from './hooks/useWindowSize';
222
222
  export type { WindowSize } from './hooks/useWindowSize';
223
- export { useTheme } from './hooks/useTheme';
223
+ export { useTheme, getThemeColors } from './hooks/useTheme';
224
224
  export type { UseThemeReturn, ThemeColors } from './hooks/useTheme';