@pythia-software/query-table-ui 0.5.0 → 0.6.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/FormulaEditor.tsx","../src/formulaEditorHelpers.ts","../src/FormulaFunctionBrowser.tsx","../src/formulaFunctionCatalogue.ts"],"sourcesContent":["import {\n useEffect,\n useId,\n useRef,\n useState,\n type KeyboardEvent,\n type MutableRefObject,\n} from \"react\";\nimport { FormulaError, type FieldDef } from \"@pythia-software/query-table-core\";\nimport {\n formulaCompletions,\n formulaContext,\n signatureAt,\n type FormulaCompletions,\n type FormulaSuggestion,\n} from \"./formulaEditorHelpers\";\n\nimport { FormulaFunctionBrowser } from \"./FormulaFunctionBrowser\";\n\nexport interface FormulaEditorProps {\n value: string;\n onChange: (value: string) => void;\n fields: FieldDef[];\n compile: (value: string) => unknown;\n /** Metric authoring uses a separate reference pane and no suggestions. */\n suggestions?: boolean;\n showLibrary?: boolean;\n rows?: number;\n ariaLabel?: string;\n inputRef?: MutableRefObject<HTMLTextAreaElement | null>;\n onFocus?: () => void;\n onReady?: (input: HTMLTextAreaElement) => void;\n}\n\nexport default function FormulaEditor({\n value,\n onChange,\n fields,\n compile,\n suggestions = true,\n showLibrary = true,\n ariaLabel = \"Computed column formula\",\n inputRef,\n onFocus,\n onReady,\n rows = 6,\n}: FormulaEditorProps) {\n const id = useId();\n const editor = useRef<HTMLTextAreaElement | null>(null);\n const applyingEdit = useRef(false);\n const composing = useRef(false);\n // Track generated pairs through edits; existing closing characters stay editable.\n const paired = useRef<{\n value: string;\n ranges: { open: number; close: number }[];\n }>({ value, ranges: [] });\n const trackEdit = (next: string) => {\n const previous = paired.current.value;\n if (previous === next) return;\n let from = 0;\n while (\n from < previous.length &&\n from < next.length &&\n previous[from] === next[from]\n )\n from++;\n let oldEnd = previous.length,\n newEnd = next.length;\n while (\n oldEnd > from &&\n newEnd > from &&\n previous[oldEnd - 1] === next[newEnd - 1]\n ) {\n oldEnd--;\n newEnd--;\n }\n const shift = newEnd - oldEnd;\n paired.current.ranges = paired.current.ranges\n .filter(\n ({ open, close }) =>\n !(open >= from && open < oldEnd) &&\n !(close >= from && close < oldEnd),\n )\n .map(({ open, close }) => ({\n open: open >= oldEnd ? open + shift : open,\n close: close >= oldEnd ? close + shift : close,\n }));\n paired.current.value = next;\n };\n const selection = useRef({ start: 0, end: 0 });\n const latestCompile = useRef(compile);\n latestCompile.current = compile;\n const [caret, setCaret] = useState(0);\n const [completions, setCompletions] = useState<FormulaCompletions | null>(\n null,\n );\n const [active, setActive] = useState(0);\n const [diagnostic, setDiagnostic] = useState<{\n message: string;\n from: number;\n to: number;\n } | null>(null);\n const options = suggestions ? (completions?.options.slice(0, 50) ?? []) : [];\n const hint = suggestions ? signatureAt(value.slice(0, caret)) : \"\";\n\n useEffect(() => {\n // Native edits already updated the DOM; avoid resetting selection/undo on each keystroke.\n const input = editor.current;\n if (input && input.value !== value) {\n input.value = value;\n paired.current = { value, ranges: [] };\n setCaret(input.selectionStart);\n setCompletions(null);\n }\n setDiagnostic(null);\n const timer = setTimeout(() => {\n try {\n latestCompile.current(value);\n setDiagnostic(null);\n } catch (error) {\n const from = error instanceof FormulaError ? error.from : 0;\n const to = error instanceof FormulaError ? error.to : value.length;\n setDiagnostic({\n message: error instanceof Error ? error.message : String(error),\n from: Math.max(0, Math.min(from, value.length)),\n to: Math.max(0, Math.min(to, value.length)),\n });\n }\n }, 250);\n return () => clearTimeout(timer);\n }, [value]);\n\n useEffect(() => {\n if (editor.current) onReady?.(editor.current);\n }, [onReady]);\n\n useEffect(() => {\n document\n .getElementById(`${id}-option-${active}`)\n ?.scrollIntoView?.({ block: \"nearest\" });\n }, [active, id]);\n\n const suggest = (explicit = false) => {\n const input = editor.current;\n if (!suggestions || !input || composing.current) return;\n selection.current = {\n start: input.selectionStart,\n end: input.selectionEnd,\n };\n setCaret(input.selectionStart);\n setActive(0);\n setCompletions(\n formulaCompletions(\n input.value,\n input.selectionStart,\n input.selectionEnd,\n fields,\n explicit,\n ),\n );\n };\n\n const insert = (\n from: number,\n to: number,\n text: string,\n caretOffset = text.length,\n pair?: { open: number; close: number },\n ) => {\n const input = editor.current;\n if (!input) return;\n input.focus();\n input.setSelectionRange(from, to);\n applyingEdit.current = true;\n try {\n // insertText preserves native textarea undo in Chromium/WebKit. Browsers\n // without this editing command still have a plain-text insertion fallback.\n const before = input.value;\n try {\n document.execCommand?.(\"insertText\", false, text);\n } catch {\n /* use fallback */\n }\n if (input.value === before) input.setRangeText(text, from, to, \"end\");\n } finally {\n applyingEdit.current = false;\n }\n trackEdit(input.value);\n if (pair)\n paired.current.ranges.push({\n open: from + pair.open,\n close: from + pair.close,\n });\n input.setSelectionRange(from + caretOffset, from + caretOffset);\n setCaret(input.selectionStart);\n setCompletions(null);\n onChange(input.value);\n };\n\n const accept = (option: FormulaSuggestion) => {\n if (completions)\n insert(\n completions.from,\n completions.to,\n option.insert,\n option.caretOffset,\n option.insert.endsWith(\"()\")\n ? { open: option.insert.length - 2, close: option.insert.length - 1 }\n : undefined,\n );\n };\n\n const onKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {\n const input = event.currentTarget;\n if (composing.current || event.nativeEvent.isComposing) return;\n if (suggestions && event.ctrlKey && event.code === \"Space\") {\n event.preventDefault();\n suggest(true);\n return;\n }\n if (options.length) {\n if (event.key === \"Escape\") {\n event.preventDefault();\n event.stopPropagation();\n setCompletions(null);\n return;\n }\n if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n setActive(\n (index) =>\n (index + (event.key === \"ArrowDown\" ? 1 : options.length - 1)) %\n options.length,\n );\n return;\n }\n if (event.key === \"Enter\") {\n event.preventDefault();\n const option = options[active];\n if (option) accept(option);\n return;\n }\n }\n if (event.ctrlKey || event.metaKey || event.altKey) return;\n const start = input.selectionStart,\n end = input.selectionEnd;\n const context = formulaContext(input.value, start);\n const close = event.key === \"(\" ? \")\" : event.key === '\"' ? '\"' : undefined;\n if (\n close &&\n !context.quoted &&\n context.fieldStart < 0 &&\n (start !== end ||\n end === input.value.length ||\n /[\\s)\\]}]/.test(input.value[end]!))\n ) {\n event.preventDefault();\n const selection = input.value.slice(start, end);\n insert(start, end, event.key + selection + close, selection.length + 1, {\n open: 0,\n close: selection.length + 1,\n });\n } else if (\n start === end &&\n input.value[start] === event.key &&\n paired.current.ranges.some((pair) => pair.close === start) &&\n ((event.key === \")\" && !context.quoted && context.fieldStart < 0) ||\n (event.key === '\"' && context.quoted && !context.escaped))\n ) {\n event.preventDefault();\n paired.current.ranges = paired.current.ranges.filter(\n (pair) => pair.close !== start,\n );\n input.setSelectionRange(start + 1, start + 1);\n setCaret(start + 1);\n setCompletions(null);\n } else if (\n event.key === \"Backspace\" &&\n start === end &&\n start > 0 &&\n [\"()\", \"[]\", '\"\"'].includes(input.value.slice(start - 1, start + 1)) &&\n (!context.quoted ||\n (input.value.slice(start - 1, start + 1) === '\"\"' && !context.escaped))\n ) {\n event.preventDefault();\n insert(start - 1, start + 1, \"\");\n }\n };\n\n return (\n <div className=\"qt-formula-layout\">\n <div className=\"qt-formula-editor\">\n <textarea\n ref={(input) => {\n editor.current = input;\n if (inputRef) inputRef.current = input;\n }}\n className=\"qt-formula-input\"\n aria-label={ariaLabel}\n aria-describedby={`${id}-hint${diagnostic ? ` ${id}-error` : \"\"}`}\n aria-invalid={diagnostic ? true : undefined}\n aria-autocomplete={suggestions ? \"list\" : \"none\"}\n aria-controls={options.length ? `${id}-suggestions` : undefined}\n aria-activedescendant={\n options.length ? `${id}-option-${active}` : undefined\n }\n defaultValue={value}\n rows={rows}\n spellCheck={false}\n autoCapitalize=\"off\"\n autoCorrect=\"off\"\n onChange={(event) => {\n if (applyingEdit.current) return;\n trackEdit(event.currentTarget.value);\n onChange(event.currentTarget.value);\n if (!composing.current) suggest();\n }}\n onSelect={(event) => {\n setCaret(event.currentTarget.selectionStart);\n const input = event.currentTarget;\n if (\n !applyingEdit.current &&\n (input.selectionStart !== selection.current.start ||\n input.selectionEnd !== selection.current.end)\n )\n setCompletions(null);\n }}\n onKeyDown={onKeyDown}\n onCompositionStart={() => {\n composing.current = true;\n setCompletions(null);\n }}\n onCompositionEnd={() => {\n composing.current = false;\n suggest();\n }}\n onBlur={() => setCompletions(null)}\n onFocus={onFocus}\n />\n <div className=\"qt-formula-tools\">\n {suggestions && (\n <button\n type=\"button\"\n className=\"qt-link-btn\"\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => {\n editor.current?.focus();\n suggest(true);\n }}\n >\n Suggestions\n </button>\n )}\n <span>\n Line {value.slice(0, caret).split(\"\\n\").length}, column{\" \"}\n {caret - value.slice(0, caret).lastIndexOf(\"\\n\")}\n </span>\n </div>\n {options.length > 0 && (\n <div\n className=\"qt-formula-completions\"\n data-qt-formula-suggestions=\"\"\n >\n <ul\n id={`${id}-suggestions`}\n role=\"listbox\"\n aria-label=\"Formula suggestions\"\n onMouseDown={(event) => event.preventDefault()}\n >\n {options.map((option, index) => (\n <li\n key={option.label}\n id={`${id}-option-${index}`}\n role=\"option\"\n aria-selected={index === active}\n title={option.description}\n onClick={() => accept(option)}\n >\n <strong>{option.label}</strong>\n <small>{option.detail}</small>\n </li>\n ))}\n </ul>\n <p>\n {options[active]?.description} · ↑↓ choose, Enter inserts, Esc\n closes\n </p>\n {completions && completions.options.length > options.length && (\n <p>Type to narrow the first {options.length} suggestions.</p>\n )}\n </div>\n )}\n <p id={`${id}-hint`} className=\"qt-formula-hint\" aria-live=\"polite\">\n {hint}\n </p>\n {diagnostic && (\n <div id={`${id}-error`} className=\"qt-formula-error\" role=\"status\">\n {diagnostic.message}{\" \"}\n <button\n type=\"button\"\n className=\"qt-link-btn\"\n onClick={() => {\n editor.current?.focus();\n editor.current?.setSelectionRange(\n diagnostic.from,\n diagnostic.to,\n );\n setCaret(diagnostic.from);\n }}\n >\n Select error location\n </button>\n </div>\n )}\n </div>\n {showLibrary && (\n <FormulaFunctionBrowser\n onInsert={(name) => {\n const input = editor.current;\n if (!input) return;\n const from = input.selectionStart;\n const to = input.selectionEnd;\n const selected = input.value.slice(from, to);\n insert(\n from,\n to,\n `${name}(${selected})`,\n name.length + selected.length + 1,\n { open: name.length, close: name.length + selected.length + 1 },\n );\n }}\n />\n )}\n </div>\n );\n}\n","import {\n FORMULA_FUNCTIONS,\n type FieldDef,\n} from \"@pythia-software/query-table-core\";\n\nexport const FORMULA_EDITOR_HELP =\n \"Use [field] references. Ctrl+Space opens suggestions.\";\n\nexport interface FormulaSuggestion {\n label: string;\n detail: string;\n description: string;\n insert: string;\n // Place the caret inside the function call after inserting paired parentheses.\n caretOffset?: number;\n}\n\nexport interface FormulaCompletions {\n from: number;\n to: number;\n options: FormulaSuggestion[];\n}\n\n/** Scan strings and escaped field names before interpreting punctuation. */\nexport function formulaContext(text: string, end = text.length) {\n let quoted = false;\n let fieldStart = -1;\n let escaped = false;\n for (let i = 0; i < end; i++) {\n const c = text[i];\n if (quoted) {\n if (c === \"\\\\\") {\n escaped = i + 1 >= end;\n i++;\n } else if (c === '\"') quoted = false;\n } else if (fieldStart >= 0) {\n if (c === \"]\") {\n if (text[i + 1] === \"]\") i++;\n else fieldStart = -1;\n }\n } else if (c === '\"') quoted = true;\n else if (c === \"[\") fieldStart = i;\n }\n return { quoted, fieldStart, escaped };\n}\n\nexport function formulaCompletions(\n text: string,\n start: number,\n end: number,\n fields: FieldDef[],\n explicit = false,\n): FormulaCompletions | null {\n const before = text.slice(0, start);\n const { quoted, fieldStart } = formulaContext(text, start);\n if (quoted || (!explicit && start !== end)) return null;\n const word = before.match(/\\b[A-Za-z_][A-Za-z_0-9]*$/)?.[0] ?? \"\";\n if (!explicit && fieldStart < 0 && !word) return null;\n const from =\n start !== end ? start : fieldStart >= 0 ? fieldStart : start - word.length;\n let to = end;\n if (start === end && fieldStart >= 0) {\n // Replace the rest of a field reference too, including escaped ]] characters.\n for (let i = fieldStart + 1; i < text.length; i++) {\n if (text[i] === \"]\") {\n if (text[i + 1] === \"]\") {\n i++;\n continue;\n }\n to = i + 1;\n break;\n }\n if (text[i] === \"\\n\" || text[i] === \"[\") break;\n }\n } else if (start === end) {\n to += text.slice(end).match(/^[A-Za-z_0-9]*/)?.[0].length ?? 0;\n }\n const prefix = start !== end ? \"\" : before.slice(from).toLowerCase();\n const options: FormulaSuggestion[] = fields.map((f) => ({\n label: `[${f.name.replace(/\\]/g, \"]]\")}]`,\n detail: `${f.label} · ${f.type}`,\n description: f.name,\n insert: `[${f.name.replace(/\\]/g, \"]]\")}]`,\n }));\n if (fieldStart < 0) {\n options.push(\n ...FORMULA_FUNCTIONS.map((f) => ({\n label: f.name,\n detail: f.signature,\n description: f.description,\n insert: text[to] === \"(\" ? f.name : `${f.name}()`,\n caretOffset: f.name.length + 1,\n })),\n ...[\"TRUE\", \"FALSE\", \"NULL\", \"AND\", \"OR\", \"NOT\", \"IN\", \"BETWEEN\"].map(\n (label) => ({\n label,\n detail: \"Keyword\",\n description: \"\",\n insert: label,\n }),\n ),\n );\n }\n return {\n from,\n to,\n options: options.filter(\n (option) =>\n option.label.toLowerCase().startsWith(prefix) ||\n (fieldStart >= 0 &&\n option.detail.toLowerCase().includes(prefix.slice(1))),\n ),\n };\n}\n\nexport function signatureAt(text: string): string {\n const stack: { name: string; argument: number }[] = [];\n let quoted = false,\n field = false;\n for (let i = 0; i < text.length; i++) {\n const c = text[i];\n if (quoted) {\n if (c === \"\\\\\") i++;\n else if (c === '\"') quoted = false;\n continue;\n }\n if (field) {\n if (c === \"]\") {\n if (text[i + 1] === \"]\") i++;\n else field = false;\n }\n continue;\n }\n if (c === '\"') {\n quoted = true;\n continue;\n }\n if (c === \"[\") {\n field = true;\n continue;\n }\n if (c === \"(\")\n stack.push({\n name:\n text\n .slice(0, i)\n .match(/([A-Za-z_]+)\\s*$/)?.[1]\n ?.toUpperCase() ?? \"\",\n argument: 1,\n });\n else if (c === \")\") stack.pop();\n else if (c === \",\" && stack.length) stack[stack.length - 1]!.argument++;\n }\n const call = stack[stack.length - 1],\n spec = FORMULA_FUNCTIONS.find((f) => f.name === call?.name);\n return spec && call\n ? `${spec.signature} · Argument ${call.argument} — ${spec.description}`\n : FORMULA_EDITOR_HELP;\n}\n","import { useEffect, useId, useRef, useState } from \"react\";\nimport {\n FUNCTION_CATEGORIES,\n FUNCTION_RESULT_TYPES,\n findFormulaFunctions,\n functionCategory,\n functionResultLabel,\n type FunctionCategory,\n} from \"./formulaFunctionCatalogue\";\nimport type { FormulaFunction } from \"@pythia-software/query-table-core\";\n\nexport function FormulaFunctionBrowser({\n onInsert,\n}: {\n onInsert: (name: string) => void;\n}) {\n const id = useId();\n const list = useRef<HTMLDivElement>(null);\n const [search, setSearch] = useState(\"\");\n const [category, setCategory] = useState<FunctionCategory | \"all\">(\"all\");\n const [result, setResult] = useState<FormulaFunction[\"result\"] | \"all\">(\n \"all\",\n );\n const [active, setActive] = useState(0);\n const matches = findFormulaFunctions(search, category, result);\n const concept = FUNCTION_CATEGORIES.find((item) => item.id === category);\n\n useEffect(() => {\n // Scroll the list itself without moving the workbench while searching.\n const option = list.current?.children[active] as HTMLElement | undefined;\n const viewport = list.current;\n if (!option || !viewport) return;\n // The positioned list is the option’s offset parent.\n const top = option.offsetTop;\n if (top < viewport.scrollTop) viewport.scrollTop = top;\n else if (\n top + option.offsetHeight >\n viewport.scrollTop + viewport.clientHeight\n )\n viewport.scrollTop = top + option.offsetHeight - viewport.clientHeight;\n }, [active, search, category, result]);\n\n return (\n <aside className=\"qt-function-library\" aria-labelledby={`${id}-title`}>\n <h4 id={`${id}-title`}>Function library</h4>\n <p className=\"qt-muted\">Find a function by name or what it does.</p>\n <label className=\"qt-function-search\">\n Search functions\n <input\n className=\"qt-input\"\n type=\"search\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded={matches.length > 0}\n aria-controls={`${id}-results`}\n aria-activedescendant={\n matches[active] ? `${id}-${matches[active]!.name}` : undefined\n }\n placeholder=\"e.g. trim, minimum, date…\"\n value={search}\n onChange={(event) => {\n setSearch(event.target.value);\n setActive(0);\n }}\n onKeyDown={(event) => {\n if (\n event.nativeEvent.isComposing ||\n event.ctrlKey ||\n event.metaKey ||\n event.altKey ||\n !matches.length\n )\n return;\n if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n setActive(\n (index) =>\n (index +\n (event.key === \"ArrowDown\" ? 1 : matches.length - 1)) %\n matches.length,\n );\n } else if (event.key === \"Enter\") {\n event.preventDefault();\n const fn = matches[active];\n if (fn) onInsert(fn.name);\n }\n }}\n />\n </label>\n <div className=\"qt-function-filters\">\n <label>\n Browse by concept\n <select\n value={category}\n onChange={(event) => {\n setCategory(event.target.value as FunctionCategory | \"all\");\n setActive(0);\n }}\n >\n <option value=\"all\">All functions</option>\n {FUNCTION_CATEGORIES.map((item) => (\n <option key={item.id} value={item.id}>\n {item.label}\n </option>\n ))}\n </select>\n </label>\n <label>\n Return type\n <select\n value={result}\n onChange={(event) => {\n setResult(\n event.target.value as FormulaFunction[\"result\"] | \"all\",\n );\n setActive(0);\n }}\n >\n <option value=\"all\">Any type</option>\n {FUNCTION_RESULT_TYPES.map((type) => (\n <option key={type.value} value={type.value}>\n {type.label}\n </option>\n ))}\n </select>\n </label>\n </div>\n {concept && <p className=\"qt-function-concept\">{concept.description}</p>}\n <p className=\"qt-function-count\" aria-live=\"polite\">\n {matches.length} {matches.length === 1 ? \"function\" : \"functions\"}\n </p>\n <div\n ref={list}\n className=\"qt-function-list\"\n id={`${id}-results`}\n role=\"listbox\"\n aria-label=\"Function library results\"\n >\n {matches.map((fn, index) => (\n <button\n type=\"button\"\n role=\"option\"\n id={`${id}-${fn.name}`}\n key={fn.name}\n aria-selected={index === active}\n aria-label={`Insert ${fn.name}`}\n onMouseDown={(event) => event.preventDefault()}\n onFocus={() => setActive(index)}\n onClick={() => onInsert(fn.name)}\n >\n <span className=\"qt-function-card-title\">\n <strong>{fn.name}</strong>\n <span>{functionResultLabel(fn.result)}</span>\n </span>\n <code>{fn.signature}</code>\n <small>{fn.description}</small>\n <span className=\"qt-function-card-concept\">\n {\n FUNCTION_CATEGORIES.find(\n (item) => item.id === functionCategory(fn),\n )!.label\n }\n </span>\n </button>\n ))}\n </div>\n {!matches.length && (\n <p className=\"qt-function-empty\">\n No functions match. Try a shorter search or a different concept or\n return type.\n </p>\n )}\n <p className=\"qt-function-insert-hint\">\n Click a function or use ↑↓ and Enter in search to insert it at the\n cursor. Select formula text first to wrap it.\n </p>\n </aside>\n );\n}\n","import {\n FORMULA_FUNCTIONS,\n type FormulaFunction,\n type FormulaType,\n} from \"@pythia-software/query-table-core\";\n\nexport const FUNCTION_CATEGORIES = [\n {\n id: \"strings\",\n label: \"String functions\",\n description: \"Slice, combine, format, and clean text.\",\n keywords: \"string text characters whitespace case concatenate\",\n },\n {\n id: \"matching\",\n label: \"Matching & regex\",\n description: \"Find text, test patterns, and extract matches.\",\n keywords: \"search matching regex regular expression pattern capture\",\n },\n {\n id: \"numbers\",\n label: \"Numbers & statistics\",\n description: \"Round, transform, and compare numbers within a row.\",\n keywords: \"math numeric arithmetic statistics statistical rounding\",\n },\n {\n id: \"logic\",\n label: \"Conditions & missing values\",\n description: \"Choose results, handle errors, and work with empty values.\",\n keywords: \"logic boolean conditional null missing empty fallback error\",\n },\n {\n id: \"conversion\",\n label: \"Type conversion\",\n description: \"Convert values to text, numbers, booleans, or dates.\",\n keywords: \"convert conversion cast type parse\",\n },\n {\n id: \"arrays\",\n label: \"Array functions\",\n description: \"Split, join, count, and organize lists of text.\",\n keywords: \"array list collection elements unique deduplicate\",\n },\n {\n id: \"dates\",\n label: \"Dates & times\",\n description:\n \"Extract date parts, format timestamps, and calculate durations.\",\n keywords: \"date datetime time timestamp calendar duration UTC\",\n },\n] as const;\nexport type FunctionCategory = (typeof FUNCTION_CATEGORIES)[number][\"id\"];\n\nexport const FUNCTION_RESULT_TYPES: {\n value: FormulaFunction[\"result\"];\n label: string;\n}[] = [\n { value: \"text\", label: \"Text\" },\n { value: \"number\", label: \"Number\" },\n { value: \"bool\", label: \"Boolean\" },\n { value: \"datetime\", label: \"Date/time\" },\n { value: \"textarray\", label: \"Text array\" },\n { value: \"branch\", label: \"Depends on arguments\" },\n];\nexport function functionResultLabel(result: FormulaType | \"branch\"): string {\n return (\n FUNCTION_RESULT_TYPES.find((type) => type.value === result)?.label ?? result\n );\n}\n\n/** Concept is separate from return type: LENGTH is a string operation returning a number. */\nexport function functionCategory(fn: FormulaFunction): FunctionCategory {\n if (fn.name.startsWith(\"TO_\")) return \"conversion\";\n if (\n fn.name.startsWith(\"REGEX_\") ||\n [\"CONTAINS\", \"STARTS_WITH\", \"ENDS_WITH\"].includes(fn.name)\n )\n return \"matching\";\n if (fn.name.startsWith(\"ARRAY_\") || [\"SPLIT\", \"JOIN\"].includes(fn.name))\n return \"arrays\";\n if (\n fn.name.includes(\"DATE\") ||\n (fn.args !== \"any\" && fn.args.includes(\"datetime\"))\n )\n return \"dates\";\n if (fn.result === \"branch\" || fn.args === \"any\") return \"logic\";\n if (fn.args.includes(\"text\")) return \"strings\";\n return \"numbers\";\n}\n\nconst SEARCH_ALIASES: Record<string, string> = {\n LEFT: \"prefix beginning first characters\",\n RIGHT: \"suffix ending last characters\",\n SUBSTRING: \"slice middle characters\",\n LENGTH: \"size count characters\",\n LOWER: \"lowercase\",\n UPPER: \"uppercase\",\n CONCAT: \"concatenate combine append\",\n CONCAT_WS: \"concatenate separator combine append\",\n LEAST: \"minimum smallest min\",\n GREATEST: \"maximum largest max\",\n ABS: \"absolute magnitude\",\n SQRT: \"square root\",\n CEIL: \"ceiling round up\",\n FLOOR: \"round down\",\n IF: \"if else decision\",\n COALESCE: \"default first non-null missing fallback\",\n NULLIF: \"equal missing null\",\n IFERROR: \"recover error default fallback\",\n ARRAY_LENGTH: \"size count elements\",\n DATE_DIFF: \"difference elapsed duration\",\n};\nconst normalize = (text: string) =>\n text.toLowerCase().replace(/[_-]/g, \" \").trim();\n\nexport function findFormulaFunctions(\n search: string,\n category: FunctionCategory | \"all\" = \"all\",\n result: FormulaFunction[\"result\"] | \"all\" = \"all\",\n): FormulaFunction[] {\n const query = normalize(search);\n const terms = query.split(/\\s+/).filter(Boolean);\n const functions = FORMULA_FUNCTIONS.filter((fn) => {\n const concept = functionCategory(fn);\n if (\n (category !== \"all\" && concept !== category) ||\n (result !== \"all\" && fn.result !== result)\n )\n return false;\n const metadata = FUNCTION_CATEGORIES.find((item) => item.id === concept)!;\n const haystack = normalize(\n `${fn.name} ${fn.signature} ${fn.description} ${metadata.label} ${metadata.keywords} ${SEARCH_ALIASES[fn.name] ?? \"\"} ${functionResultLabel(fn.result)}`,\n );\n return terms.every((term) => haystack.includes(term));\n });\n // Exact/prefix name matches appear first; browsing stays alphabetical.\n const rank = (fn: FormulaFunction) =>\n !query\n ? 2\n : normalize(fn.name) === query\n ? 0\n : normalize(fn.name).startsWith(query)\n ? 1\n : 2;\n return functions.sort(\n (a, b) => rank(a) - rank(b) || a.name.localeCompare(b.name),\n );\n}\n"],"mappings":"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","names":["useEffect","useId","useRef","useState","FORMULA_FUNCTIONS","jsx","jsxs","useId","useRef","useState","useEffect","selection"]}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, SetStateAction } from 'react';
3
- import { FieldDef, QueryState, RowId, FieldStats, WhereClause } from '@pythia-software/query-table-core';
3
+ import { FieldDef, QueryState, RowId, AggregationClause, AggregationBucket, AggregationResultEntry, MetricValue, FieldStats, WhereClause, MetricValueFormat } from '@pythia-software/query-table-core';
4
4
  import * as react_jsx_runtime from 'react/jsx-runtime';
5
- import { SelectionApi, ColumnDragApi, QueryTableApi, AggregationsApi, SavedQueriesApi } from '@pythia-software/query-table-react';
5
+ import { SelectionApi, ColumnDragApi, QueryTableApi, AggregationsApi, SavedQueriesApi, RequestActivityApi } from '@pythia-software/query-table-react';
6
6
 
7
7
  interface CellContext<Row = any, V = unknown> {
8
8
  value: V;
@@ -25,6 +25,13 @@ declare const defaultRenderers: RenderRegistry;
25
25
  declare function resolveRenderer<Row>(field: FieldDef<Row>, registry: RenderRegistry<Row>): CellRenderer<Row>;
26
26
 
27
27
  declare const artwork: {
28
+ panelList: react_jsx_runtime.JSX.Element;
29
+ panelDefinition: react_jsx_runtime.JSX.Element;
30
+ panelReference: react_jsx_runtime.JSX.Element;
31
+ panelPreview: react_jsx_runtime.JSX.Element;
32
+ dashboard: react_jsx_runtime.JSX.Element;
33
+ resize: react_jsx_runtime.JSX.Element;
34
+ alert: react_jsx_runtime.JSX.Element;
28
35
  add: react_jsx_runtime.JSX.Element;
29
36
  close: react_jsx_runtime.JSX.Element;
30
37
  check: react_jsx_runtime.JSX.Element;
@@ -42,6 +49,7 @@ declare const artwork: {
42
49
  copy: react_jsx_runtime.JSX.Element;
43
50
  share: react_jsx_runtime.JSX.Element;
44
51
  refresh: react_jsx_runtime.JSX.Element;
52
+ stopwatch: react_jsx_runtime.JSX.Element;
45
53
  clock: react_jsx_runtime.JSX.Element;
46
54
  reset: react_jsx_runtime.JSX.Element;
47
55
  bookmark: react_jsx_runtime.JSX.Element;
@@ -126,7 +134,50 @@ interface DataTableProps<Row> {
126
134
  }
127
135
  declare function DataTable<Row>(props: DataTableProps<Row>): ReactNode;
128
136
 
137
+ /** Core owns computation and serialized presentation types. These aliases keep rendering imports focused. */
138
+
139
+ type MetricBucket = AggregationBucket;
140
+ type RenderingClause = AggregationClause;
141
+ /** Optional exact remainder for transports that truncate additive groups. */
142
+ type MetricRenderResult = Pick<AggregationResultEntry, "buckets" | "error" | "groupCount" | "coverage" | "scope" | "processedRows"> & {
143
+ other?: AggregationBucket;
144
+ };
145
+
146
+ type MetricToken = "accent" | "text" | "muted" | "grid" | "surface" | "other" | "null" | "error";
147
+ interface MetricTheme {
148
+ layers?: ReadonlyArray<{
149
+ id: string;
150
+ colors: ReadonlyArray<string>;
151
+ }>;
152
+ dimensions?: Readonly<Record<string, {
153
+ layer: string;
154
+ domain?: ReadonlyArray<MetricValue>;
155
+ overrides?: ReadonlyArray<{
156
+ value: MetricValue;
157
+ color: string;
158
+ }>;
159
+ identity?: string;
160
+ }>>;
161
+ tokens?: Partial<Record<MetricToken, string>>;
162
+ }
163
+ interface MetricClassNames {
164
+ root?: string;
165
+ metric?: string;
166
+ title?: string;
167
+ plot?: string;
168
+ axis?: string;
169
+ grid?: string;
170
+ legend?: string;
171
+ mark?: string;
172
+ }
173
+ declare function createMetricColorResolver(theme?: MetricTheme): {
174
+ token: (name: MetricToken) => string;
175
+ color(field: string, value: MetricValue): string;
176
+ };
177
+
129
178
  interface QueryBuilderProps<Row> {
179
+ /** Additional diagnostics shown inside request activity. */
180
+ requestActivityDetails?: ReactNode;
130
181
  /** The controller; QueryBuilder drives it through the intent helpers. */
131
182
  api: QueryTableApi<Row>;
132
183
  /** All schema fields (for the field picker / sort picker). */
@@ -142,10 +193,16 @@ interface QueryBuilderProps<Row> {
142
193
  onCollapsedChange?: (collapsed: boolean) => void;
143
194
  /** Initial collapse state for uncontrolled mode. */
144
195
  defaultCollapsed?: boolean;
196
+ metricTheme?: MetricTheme;
197
+ metricLocale?: string;
145
198
  }
146
- declare function QueryBuilder<Row>({ api, fields, total, running, classNames, collapsed, onCollapsedChange, defaultCollapsed, }: QueryBuilderProps<Row>): ReactNode;
199
+ declare function QueryBuilder<Row>({ api, fields, total, running, classNames, collapsed, onCollapsedChange, defaultCollapsed, metricTheme, metricLocale, requestActivityDetails, }: QueryBuilderProps<Row>): ReactNode;
147
200
 
148
201
  interface MetricsPanelProps<Row> {
202
+ /** Enables quick editing using the transactional metrics editor. */
203
+ api?: QueryTableApi<Row>;
204
+ onEditMetric?: (id: string) => void;
205
+ onEditDashboard?: () => void;
149
206
  /** The aggregation API from useQueryTable (clauses + results + status). */
150
207
  aggregations: AggregationsApi;
151
208
  /** All schema fields, to resolve labels + the measure field's renderer. */
@@ -154,12 +211,27 @@ interface MetricsPanelProps<Row> {
154
211
  renderers?: RenderRegistry<Row>;
155
212
  /** Hidden entirely when there are no metrics unless this is set. */
156
213
  emptyMessage?: string;
157
- classNames?: {
158
- root?: string;
159
- metric?: string;
160
- };
214
+ classNames?: MetricClassNames;
215
+ theme?: MetricTheme;
216
+ locale?: string;
217
+ onInspect?: (bucket: MetricBucket, clause: RenderingClause) => void;
218
+ }
219
+ declare function MetricsPanel<Row>({ aggregations, api, onEditMetric, onEditDashboard, fields, renderers, emptyMessage, classNames, theme, locale, onInspect, }: MetricsPanelProps<Row>): ReactNode;
220
+ interface MetricCardProps<Row> {
221
+ onEdit?: (() => void) | undefined;
222
+ clause: RenderingClause;
223
+ buckets?: MetricBucket[] | undefined;
224
+ result?: MetricRenderResult | undefined;
225
+ fields: FieldDef<Row>[];
226
+ loading?: boolean | undefined;
227
+ renderers?: RenderRegistry<Row> | undefined;
228
+ theme?: MetricTheme | undefined;
229
+ locale?: string | undefined;
230
+ className?: string | undefined;
231
+ classNames?: MetricClassNames | undefined;
232
+ onInspect?: ((bucket: MetricBucket, clause: RenderingClause) => void) | undefined;
161
233
  }
162
- declare function MetricsPanel<Row>({ aggregations, fields, renderers, emptyMessage, classNames, }: MetricsPanelProps<Row>): ReactNode;
234
+ declare function MetricCard<Row>({ clause, buckets: explicitBuckets, onEdit, result, fields, loading, renderers, theme, locale, className, classNames, onInspect, }: MetricCardProps<Row>): ReactNode;
163
235
 
164
236
  interface FieldPickerProps<Row> {
165
237
  fields: FieldDef<Row>[];
@@ -222,4 +294,37 @@ declare function PresentedFilterValue({ field, value, label }: {
222
294
  label?: string;
223
295
  }): react_jsx_runtime.JSX.Element;
224
296
 
225
- export { type CellContext, CellMenu, type CellMenuProps, type CellRenderer, DataTable, type DataTableProps, FieldPicker, type FieldPickerProps, type FilterValuePresentation, FilterValueProvider, Icon, type IconName, type IconProps, type MenuClassNames, MetricsPanel, type MetricsPanelProps, PresentedFilterValue, QueryBuilder, type QueryBuilderClassNames, type QueryBuilderProps, type RenderRegistry, SavedQueriesModal, type SavedQueriesModalProps, SelectColumnEditor, type SelectColumnEditorProps, SelectionToolbar, type SelectionToolbarProps, type TableClassNames, defaultRenderers, resolveRenderer, safeLinkHref };
297
+ interface MetricsEditorProps<Row> {
298
+ api: QueryTableApi<Row>;
299
+ fields: FieldDef<Row>[];
300
+ onClose: () => void;
301
+ initialMetricId?: string;
302
+ initialView?: "editor" | "dashboard";
303
+ theme?: MetricTheme;
304
+ locale?: string;
305
+ }
306
+ /** Transactional query-local authoring; previews never mutate the table query. */
307
+ declare function MetricsEditor<Row>({ api, fields, onClose, initialMetricId, initialView, theme, locale, }: MetricsEditorProps<Row>): ReactNode;
308
+
309
+ interface MetricFormattedValue {
310
+ text: string;
311
+ error?: string;
312
+ raw: MetricValue | undefined;
313
+ }
314
+ declare function validateMetricFormat(format: MetricValueFormat): string | undefined;
315
+ /** Strict coordinate parser shared by calendar formatting and chronological charts. */
316
+ declare function metricTimestamp(value: MetricValue, format?: MetricValueFormat): {
317
+ date: Date;
318
+ timeZone: string | undefined;
319
+ };
320
+ declare function formatMetricOutput(value: MetricValue | undefined, format?: MetricValueFormat, locale?: string): MetricFormattedValue;
321
+
322
+ interface RequestActivityProps {
323
+ activity: RequestActivityApi;
324
+ /** Optional host diagnostics, e.g. database and network timings. */
325
+ children?: ReactNode;
326
+ id?: string;
327
+ }
328
+ declare function RequestActivity({ activity, children, id, }: RequestActivityProps): react_jsx_runtime.JSX.Element;
329
+
330
+ export { type CellContext, CellMenu, type CellMenuProps, type CellRenderer, DataTable, type DataTableProps, FieldPicker, type FieldPickerProps, type FilterValuePresentation, FilterValueProvider, Icon, type IconName, type IconProps, type MenuClassNames, MetricCard, type MetricCardProps, type MetricClassNames, type MetricTheme, MetricsEditor, type MetricsEditorProps, MetricsPanel, type MetricsPanelProps, PresentedFilterValue, QueryBuilder, type QueryBuilderClassNames, type QueryBuilderProps, type RenderRegistry, RequestActivity, type RequestActivityProps, SavedQueriesModal, type SavedQueriesModalProps, SelectColumnEditor, type SelectColumnEditorProps, SelectionToolbar, type SelectionToolbarProps, type TableClassNames, createMetricColorResolver, defaultRenderers, formatMetricOutput, metricTimestamp, resolveRenderer, safeLinkHref, validateMetricFormat };