@pythia-software/query-table-ui 0.6.0 → 0.7.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.
@@ -2,10 +2,14 @@
2
2
  import {
3
3
  useEffect as useEffect2,
4
4
  useId as useId2,
5
+ useImperativeHandle,
5
6
  useRef as useRef2,
6
7
  useState as useState2
7
8
  } from "react";
8
- import { FormulaError } from "@pythia-software/query-table-core";
9
+ import {
10
+ FORMULA_FUNCTIONS as FORMULA_FUNCTIONS4,
11
+ FormulaError
12
+ } from "@pythia-software/query-table-core";
9
13
 
10
14
  // src/formulaEditorHelpers.ts
11
15
  import {
@@ -33,7 +37,7 @@ function formulaContext(text, end = text.length) {
33
37
  }
34
38
  return { quoted, fieldStart, escaped };
35
39
  }
36
- function formulaCompletions(text, start, end, fields, explicit = false) {
40
+ function formulaCompletions(text, start, end, fields, explicit = false, functions = FORMULA_FUNCTIONS) {
37
41
  const before = text.slice(0, start);
38
42
  const { quoted, fieldStart } = formulaContext(text, start);
39
43
  if (quoted || !explicit && start !== end) return null;
@@ -65,7 +69,7 @@ function formulaCompletions(text, start, end, fields, explicit = false) {
65
69
  }));
66
70
  if (fieldStart < 0) {
67
71
  options.push(
68
- ...FORMULA_FUNCTIONS.map((f) => ({
72
+ ...functions.map((f) => ({
69
73
  label: f.name,
70
74
  detail: f.signature,
71
75
  description: f.description,
@@ -90,7 +94,7 @@ function formulaCompletions(text, start, end, fields, explicit = false) {
90
94
  )
91
95
  };
92
96
  }
93
- function signatureAt(text) {
97
+ function signatureAt(text, functions = FORMULA_FUNCTIONS) {
94
98
  const stack = [];
95
99
  let quoted = false, field = false;
96
100
  for (let i = 0; i < text.length; i++) {
@@ -123,7 +127,7 @@ function signatureAt(text) {
123
127
  else if (c === ")") stack.pop();
124
128
  else if (c === "," && stack.length) stack[stack.length - 1].argument++;
125
129
  }
126
- const call = stack[stack.length - 1], spec = FORMULA_FUNCTIONS.find((f) => f.name === call?.name);
130
+ const call = stack[stack.length - 1], spec = functions.find((f) => f.name === call?.name);
127
131
  return spec && call ? `${spec.signature} \xB7 Argument ${call.argument} \u2014 ${spec.description}` : FORMULA_EDITOR_HELP;
128
132
  }
129
133
 
@@ -135,6 +139,12 @@ import {
135
139
  FORMULA_FUNCTIONS as FORMULA_FUNCTIONS2
136
140
  } from "@pythia-software/query-table-core";
137
141
  var FUNCTION_CATEGORIES = [
142
+ {
143
+ id: "aggregates",
144
+ label: "Aggregate functions",
145
+ description: "Combine values from the rows in each group.",
146
+ keywords: "aggregate summary group median average count sum"
147
+ },
138
148
  {
139
149
  id: "strings",
140
150
  label: "String functions",
@@ -190,6 +200,10 @@ function functionResultLabel(result) {
190
200
  return FUNCTION_RESULT_TYPES.find((type) => type.value === result)?.label ?? result;
191
201
  }
192
202
  function functionCategory(fn) {
203
+ if (["COUNT", "COUNT_DISTINCT", "SUM", "AVG", "MIN", "MAX", "MEDIAN"].includes(
204
+ fn.name
205
+ ))
206
+ return "aggregates";
193
207
  if (fn.name.startsWith("TO_")) return "conversion";
194
208
  if (fn.name.startsWith("REGEX_") || ["CONTAINS", "STARTS_WITH", "ENDS_WITH"].includes(fn.name))
195
209
  return "matching";
@@ -224,10 +238,10 @@ var SEARCH_ALIASES = {
224
238
  DATE_DIFF: "difference elapsed duration"
225
239
  };
226
240
  var normalize = (text) => text.toLowerCase().replace(/[_-]/g, " ").trim();
227
- function findFormulaFunctions(search, category = "all", result = "all") {
241
+ function findFormulaFunctions(search, category = "all", result = "all", catalogue = FORMULA_FUNCTIONS2) {
228
242
  const query = normalize(search);
229
243
  const terms = query.split(/\s+/).filter(Boolean);
230
- const functions = FORMULA_FUNCTIONS2.filter((fn) => {
244
+ const functions = catalogue.filter((fn) => {
231
245
  const concept = functionCategory(fn);
232
246
  if (category !== "all" && concept !== category || result !== "all" && fn.result !== result)
233
247
  return false;
@@ -244,9 +258,14 @@ function findFormulaFunctions(search, category = "all", result = "all") {
244
258
  }
245
259
 
246
260
  // src/FormulaFunctionBrowser.tsx
247
- import { jsx, jsxs } from "react/jsx-runtime";
261
+ import {
262
+ FORMULA_FUNCTIONS as FORMULA_FUNCTIONS3
263
+ } from "@pythia-software/query-table-core";
264
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
248
265
  function FormulaFunctionBrowser({
249
- onInsert
266
+ onInsert,
267
+ functions = FORMULA_FUNCTIONS3,
268
+ variant = "card"
250
269
  }) {
251
270
  const id = useId();
252
271
  const list = useRef(null);
@@ -256,7 +275,7 @@ function FormulaFunctionBrowser({
256
275
  "all"
257
276
  );
258
277
  const [active, setActive] = useState(0);
259
- const matches = findFormulaFunctions(search, category, result);
278
+ const matches = findFormulaFunctions(search, category, result, functions);
260
279
  const concept = FUNCTION_CATEGORIES.find((item) => item.id === category);
261
280
  useEffect(() => {
262
281
  const option = list.current?.children[active];
@@ -267,132 +286,146 @@ function FormulaFunctionBrowser({
267
286
  else if (top + option.offsetHeight > viewport.scrollTop + viewport.clientHeight)
268
287
  viewport.scrollTop = top + option.offsetHeight - viewport.clientHeight;
269
288
  }, [active, search, category, result]);
270
- return /* @__PURE__ */ jsxs("aside", { className: "qt-function-library", "aria-labelledby": `${id}-title`, children: [
271
- /* @__PURE__ */ jsx("h4", { id: `${id}-title`, children: "Function library" }),
272
- /* @__PURE__ */ jsx("p", { className: "qt-muted", children: "Find a function by name or what it does." }),
273
- /* @__PURE__ */ jsxs("label", { className: "qt-function-search", children: [
274
- "Search functions",
275
- /* @__PURE__ */ jsx(
276
- "input",
277
- {
278
- className: "qt-input",
279
- type: "search",
280
- role: "combobox",
281
- "aria-autocomplete": "list",
282
- "aria-expanded": matches.length > 0,
283
- "aria-controls": `${id}-results`,
284
- "aria-activedescendant": matches[active] ? `${id}-${matches[active].name}` : void 0,
285
- placeholder: "e.g. trim, minimum, date\u2026",
286
- value: search,
287
- onChange: (event) => {
288
- setSearch(event.target.value);
289
- setActive(0);
290
- },
291
- onKeyDown: (event) => {
292
- if (event.nativeEvent.isComposing || event.ctrlKey || event.metaKey || event.altKey || !matches.length)
293
- return;
294
- if (event.key === "ArrowDown" || event.key === "ArrowUp") {
295
- event.preventDefault();
296
- setActive(
297
- (index) => (index + (event.key === "ArrowDown" ? 1 : matches.length - 1)) % matches.length
298
- );
299
- } else if (event.key === "Enter") {
300
- event.preventDefault();
301
- const fn = matches[active];
302
- if (fn) onInsert(fn.name);
289
+ return /* @__PURE__ */ jsxs(
290
+ "aside",
291
+ {
292
+ className: `qt-function-library qt-function-library--${variant}`,
293
+ "aria-label": "Function library",
294
+ children: [
295
+ variant === "card" && /* @__PURE__ */ jsxs(Fragment, { children: [
296
+ /* @__PURE__ */ jsx("h4", { children: "Function library" }),
297
+ /* @__PURE__ */ jsx("p", { className: "qt-muted", children: "Find a function by name or what it does." })
298
+ ] }),
299
+ /* @__PURE__ */ jsxs("label", { className: "qt-function-search", children: [
300
+ "Search functions",
301
+ /* @__PURE__ */ jsx(
302
+ "input",
303
+ {
304
+ className: "qt-input",
305
+ type: "search",
306
+ role: "combobox",
307
+ "aria-autocomplete": "list",
308
+ "aria-expanded": matches.length > 0,
309
+ "aria-controls": `${id}-results`,
310
+ "aria-activedescendant": matches[active] ? `${id}-${matches[active].name}` : void 0,
311
+ placeholder: "e.g. trim, minimum, date\u2026",
312
+ value: search,
313
+ onChange: (event) => {
314
+ setSearch(event.target.value);
315
+ setActive(0);
316
+ },
317
+ onKeyDown: (event) => {
318
+ if (event.nativeEvent.isComposing || event.ctrlKey || event.metaKey || event.altKey || !matches.length)
319
+ return;
320
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
321
+ event.preventDefault();
322
+ setActive(
323
+ (index) => (index + (event.key === "ArrowDown" ? 1 : matches.length - 1)) % matches.length
324
+ );
325
+ } else if (event.key === "Enter") {
326
+ event.preventDefault();
327
+ const fn = matches[active];
328
+ if (fn) onInsert?.(fn.name);
329
+ }
330
+ }
303
331
  }
304
- }
305
- }
306
- )
307
- ] }),
308
- /* @__PURE__ */ jsxs("div", { className: "qt-function-filters", children: [
309
- /* @__PURE__ */ jsxs("label", { children: [
310
- "Browse by concept",
311
- /* @__PURE__ */ jsxs(
312
- "select",
313
- {
314
- value: category,
315
- onChange: (event) => {
316
- setCategory(event.target.value);
317
- setActive(0);
318
- },
319
- children: [
320
- /* @__PURE__ */ jsx("option", { value: "all", children: "All functions" }),
321
- FUNCTION_CATEGORIES.map((item) => /* @__PURE__ */ jsx("option", { value: item.id, children: item.label }, item.id))
322
- ]
323
- }
324
- )
325
- ] }),
326
- /* @__PURE__ */ jsxs("label", { children: [
327
- "Return type",
328
- /* @__PURE__ */ jsxs(
329
- "select",
332
+ )
333
+ ] }),
334
+ /* @__PURE__ */ jsxs("div", { className: "qt-function-filters", children: [
335
+ /* @__PURE__ */ jsxs("label", { children: [
336
+ "Browse by concept",
337
+ /* @__PURE__ */ jsxs(
338
+ "select",
339
+ {
340
+ value: category,
341
+ onChange: (event) => {
342
+ setCategory(event.target.value);
343
+ setActive(0);
344
+ },
345
+ children: [
346
+ /* @__PURE__ */ jsx("option", { value: "all", children: "All functions" }),
347
+ FUNCTION_CATEGORIES.filter(
348
+ (item) => functions.some((fn) => functionCategory(fn) === item.id)
349
+ ).map((item) => /* @__PURE__ */ jsx("option", { value: item.id, children: item.label }, item.id))
350
+ ]
351
+ }
352
+ )
353
+ ] }),
354
+ /* @__PURE__ */ jsxs("label", { children: [
355
+ "Return type",
356
+ /* @__PURE__ */ jsxs(
357
+ "select",
358
+ {
359
+ value: result,
360
+ onChange: (event) => {
361
+ setResult(
362
+ event.target.value
363
+ );
364
+ setActive(0);
365
+ },
366
+ children: [
367
+ /* @__PURE__ */ jsx("option", { value: "all", children: "Any type" }),
368
+ FUNCTION_RESULT_TYPES.map((type) => /* @__PURE__ */ jsx("option", { value: type.value, children: type.label }, type.value))
369
+ ]
370
+ }
371
+ )
372
+ ] })
373
+ ] }),
374
+ concept && /* @__PURE__ */ jsx("p", { className: "qt-function-concept", children: concept.description }),
375
+ /* @__PURE__ */ jsxs("p", { className: "qt-function-count", "aria-live": "polite", children: [
376
+ matches.length,
377
+ " ",
378
+ matches.length === 1 ? "function" : "functions"
379
+ ] }),
380
+ /* @__PURE__ */ jsx(
381
+ "div",
330
382
  {
331
- value: result,
332
- onChange: (event) => {
333
- setResult(
334
- event.target.value
335
- );
336
- setActive(0);
337
- },
338
- children: [
339
- /* @__PURE__ */ jsx("option", { value: "all", children: "Any type" }),
340
- FUNCTION_RESULT_TYPES.map((type) => /* @__PURE__ */ jsx("option", { value: type.value, children: type.label }, type.value))
341
- ]
383
+ ref: list,
384
+ className: "qt-function-list",
385
+ id: `${id}-results`,
386
+ role: "listbox",
387
+ "aria-label": "Function library results",
388
+ children: matches.map((fn, index) => /* @__PURE__ */ jsxs(
389
+ "button",
390
+ {
391
+ type: "button",
392
+ role: "option",
393
+ id: `${id}-${fn.name}`,
394
+ "aria-selected": index === active,
395
+ "aria-label": onInsert ? `Insert ${fn.name}` : fn.name,
396
+ disabled: !onInsert,
397
+ onMouseDown: (event) => event.preventDefault(),
398
+ onFocus: () => setActive(index),
399
+ onClick: () => onInsert?.(fn.name),
400
+ children: [
401
+ /* @__PURE__ */ jsxs("span", { className: "qt-function-card-title", children: [
402
+ /* @__PURE__ */ jsx("strong", { children: fn.name }),
403
+ /* @__PURE__ */ jsx("span", { children: functionResultLabel(fn.result) })
404
+ ] }),
405
+ /* @__PURE__ */ jsx("code", { children: fn.signature }),
406
+ /* @__PURE__ */ jsx("small", { children: fn.description }),
407
+ /* @__PURE__ */ jsx("span", { className: "qt-function-card-concept", children: FUNCTION_CATEGORIES.find(
408
+ (item) => item.id === functionCategory(fn)
409
+ ).label })
410
+ ]
411
+ },
412
+ fn.name
413
+ ))
342
414
  }
343
- )
344
- ] })
345
- ] }),
346
- concept && /* @__PURE__ */ jsx("p", { className: "qt-function-concept", children: concept.description }),
347
- /* @__PURE__ */ jsxs("p", { className: "qt-function-count", "aria-live": "polite", children: [
348
- matches.length,
349
- " ",
350
- matches.length === 1 ? "function" : "functions"
351
- ] }),
352
- /* @__PURE__ */ jsx(
353
- "div",
354
- {
355
- ref: list,
356
- className: "qt-function-list",
357
- id: `${id}-results`,
358
- role: "listbox",
359
- "aria-label": "Function library results",
360
- children: matches.map((fn, index) => /* @__PURE__ */ jsxs(
361
- "button",
362
- {
363
- type: "button",
364
- role: "option",
365
- id: `${id}-${fn.name}`,
366
- "aria-selected": index === active,
367
- "aria-label": `Insert ${fn.name}`,
368
- onMouseDown: (event) => event.preventDefault(),
369
- onFocus: () => setActive(index),
370
- onClick: () => onInsert(fn.name),
371
- children: [
372
- /* @__PURE__ */ jsxs("span", { className: "qt-function-card-title", children: [
373
- /* @__PURE__ */ jsx("strong", { children: fn.name }),
374
- /* @__PURE__ */ jsx("span", { children: functionResultLabel(fn.result) })
375
- ] }),
376
- /* @__PURE__ */ jsx("code", { children: fn.signature }),
377
- /* @__PURE__ */ jsx("small", { children: fn.description }),
378
- /* @__PURE__ */ jsx("span", { className: "qt-function-card-concept", children: FUNCTION_CATEGORIES.find(
379
- (item) => item.id === functionCategory(fn)
380
- ).label })
381
- ]
382
- },
383
- fn.name
384
- ))
385
- }
386
- ),
387
- !matches.length && /* @__PURE__ */ jsx("p", { className: "qt-function-empty", children: "No functions match. Try a shorter search or a different concept or return type." }),
388
- /* @__PURE__ */ jsx("p", { className: "qt-function-insert-hint", children: "Click a function or use \u2191\u2193 and Enter in search to insert it at the cursor. Select formula text first to wrap it." })
389
- ] });
415
+ ),
416
+ !matches.length && /* @__PURE__ */ jsx("p", { className: "qt-function-empty", children: "No functions match. Try a shorter search or a different concept or return type." }),
417
+ /* @__PURE__ */ jsx("p", { className: "qt-function-insert-hint", children: onInsert ? "Click a function or use \u2191\u2193 and Enter in search to insert it at the cursor. Select formula text first to wrap it." : "Select a formula column to insert a function." })
418
+ ]
419
+ }
420
+ );
390
421
  }
391
422
 
392
423
  // src/FormulaEditor.tsx
393
424
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
394
425
  function FormulaEditor({
395
426
  value,
427
+ actionsRef,
428
+ functions = FORMULA_FUNCTIONS4,
396
429
  onChange,
397
430
  fields,
398
431
  compile,
@@ -439,7 +472,7 @@ function FormulaEditor({
439
472
  const [active, setActive] = useState2(0);
440
473
  const [diagnostic, setDiagnostic] = useState2(null);
441
474
  const options = suggestions ? completions?.options.slice(0, 50) ?? [] : [];
442
- const hint = suggestions ? signatureAt(value.slice(0, caret)) : "";
475
+ const hint = suggestions ? signatureAt(value.slice(0, caret), functions) : "";
443
476
  useEffect2(() => {
444
477
  const input = editor.current;
445
478
  if (input && input.value !== value) {
@@ -486,7 +519,8 @@ function FormulaEditor({
486
519
  input.selectionStart,
487
520
  input.selectionEnd,
488
521
  fields,
489
- explicit
522
+ explicit,
523
+ functions
490
524
  )
491
525
  );
492
526
  };
@@ -527,6 +561,21 @@ function FormulaEditor({
527
561
  option.insert.endsWith("()") ? { open: option.insert.length - 2, close: option.insert.length - 1 } : void 0
528
562
  );
529
563
  };
564
+ const insertFunction = (name) => {
565
+ const input = editor.current;
566
+ if (!input) return;
567
+ const from = input.selectionStart;
568
+ const to = input.selectionEnd;
569
+ const selected = input.value.slice(from, to);
570
+ insert(
571
+ from,
572
+ to,
573
+ `${name}(${selected})`,
574
+ name.length + selected.length + 1,
575
+ { open: name.length, close: name.length + selected.length + 1 }
576
+ );
577
+ };
578
+ useImperativeHandle(actionsRef, () => ({ insertFunction }));
530
579
  const onKeyDown = (event) => {
531
580
  const input = event.currentTarget;
532
581
  if (composing.current || event.nativeEvent.isComposing) return;
@@ -715,25 +764,15 @@ function FormulaEditor({
715
764
  showLibrary && /* @__PURE__ */ jsx2(
716
765
  FormulaFunctionBrowser,
717
766
  {
718
- onInsert: (name) => {
719
- const input = editor.current;
720
- if (!input) return;
721
- const from = input.selectionStart;
722
- const to = input.selectionEnd;
723
- const selected = input.value.slice(from, to);
724
- insert(
725
- from,
726
- to,
727
- `${name}(${selected})`,
728
- name.length + selected.length + 1,
729
- { open: name.length, close: name.length + selected.length + 1 }
730
- );
731
- }
767
+ functions,
768
+ onInsert: insertFunction
732
769
  }
733
770
  )
734
771
  ] });
735
772
  }
773
+
736
774
  export {
737
- FormulaEditor as default
775
+ FormulaFunctionBrowser,
776
+ FormulaEditor
738
777
  };
739
- //# sourceMappingURL=FormulaEditor-FZPLQRWN.js.map
778
+ //# sourceMappingURL=chunk-TC5KDYUF.js.map
@@ -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 useImperativeHandle,\n useRef,\n useState,\n type KeyboardEvent,\n type MutableRefObject,\n type Ref,\n} from \"react\";\nimport {\n FORMULA_FUNCTIONS,\n FormulaError,\n type FormulaFunction,\n type FieldDef,\n} 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 FormulaEditorActions {\n insertFunction: (name: string) => void;\n}\n\nexport interface FormulaEditorProps {\n actionsRef?: Ref<FormulaEditorActions>;\n functions?: readonly FormulaFunction[];\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 actionsRef,\n functions = FORMULA_FUNCTIONS,\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), functions) : \"\";\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 functions,\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 insertFunction = (name: string) => {\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 useImperativeHandle(actionsRef, () => ({ insertFunction }));\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 functions={functions}\n onInsert={insertFunction}\n />\n )}\n </div>\n );\n}\n","import {\n FORMULA_FUNCTIONS,\n type FieldDef,\n type FormulaFunction,\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 functions: readonly FormulaFunction[] = FORMULA_FUNCTIONS,\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 ...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(\n text: string,\n functions: readonly FormulaFunction[] = FORMULA_FUNCTIONS,\n): 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 = 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 {\n FORMULA_FUNCTIONS,\n type FormulaFunction,\n} from \"@pythia-software/query-table-core\";\n\nexport function FormulaFunctionBrowser({\n onInsert,\n functions = FORMULA_FUNCTIONS,\n variant = \"card\",\n}: {\n onInsert?: (name: string) => void;\n functions?: readonly FormulaFunction[];\n variant?: \"card\" | \"panel\";\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, functions);\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\n className={`qt-function-library qt-function-library--${variant}`}\n aria-label=\"Function library\"\n >\n {variant === \"card\" && (\n <>\n <h4>Function library</h4>\n <p className=\"qt-muted\">Find a function by name or what it does.</p>\n </>\n )}\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.filter((item) =>\n functions.some((fn) => functionCategory(fn) === item.id),\n ).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={onInsert ? `Insert ${fn.name}` : fn.name}\n disabled={!onInsert}\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 {onInsert\n ? \"Click a function or use ↑↓ and Enter in search to insert it at the cursor. Select formula text first to wrap it.\"\n : \"Select a formula column to insert a function.\"}\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: \"aggregates\",\n label: \"Aggregate functions\",\n description: \"Combine values from the rows in each group.\",\n keywords: \"aggregate summary group median average count sum\",\n },\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 (\n [\"COUNT\", \"COUNT_DISTINCT\", \"SUM\", \"AVG\", \"MIN\", \"MAX\", \"MEDIAN\"].includes(\n fn.name,\n )\n )\n return \"aggregates\";\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 catalogue: readonly FormulaFunction[] = FORMULA_FUNCTIONS,\n): FormulaFunction[] {\n const query = normalize(search);\n const terms = query.split(/\\s+/).filter(Boolean);\n const functions = catalogue.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","FORMULA_FUNCTIONS","FORMULA_FUNCTIONS","jsx","jsxs","FORMULA_FUNCTIONS","useId","useRef","useState","useEffect","selection"]}