@pythia-software/query-table-ui 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +373 -8
- package/dist/FormulaEditor-NZWSPFJQ.js +7 -0
- package/dist/FormulaEditor-NZWSPFJQ.js.map +1 -0
- package/dist/{FormulaEditor-FZPLQRWN.js → chunk-TC5KDYUF.js} +184 -145
- package/dist/chunk-TC5KDYUF.js.map +1 -0
- package/dist/index.d.ts +418 -10
- package/dist/index.js +12688 -7423
- package/dist/index.js.map +1 -1
- package/dist/theme.css +4025 -1659
- package/dist/theme.css.map +1 -1
- package/package.json +5 -4
- package/dist/FormulaEditor-FZPLQRWN.js.map +0 -1
- package/dist/theme.d.ts +0 -2
|
@@ -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 {
|
|
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
|
-
...
|
|
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 =
|
|
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 =
|
|
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 {
|
|
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(
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
"
|
|
275
|
-
|
|
276
|
-
"
|
|
277
|
-
|
|
278
|
-
className: "qt-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
"
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
719
|
-
|
|
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
|
-
|
|
775
|
+
FormulaFunctionBrowser,
|
|
776
|
+
FormulaEditor
|
|
738
777
|
};
|
|
739
|
-
//# sourceMappingURL=
|
|
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":";AAAA;AAAA,EACE,aAAAA;AAAA,EACA,SAAAC;AAAA,EACA;AAAA,EACA,UAAAC;AAAA,EACA,YAAAC;AAAA,OAIK;AACP;AAAA,EACE,qBAAAC;AAAA,EACA;AAAA,OAGK;;;ACfP;AAAA,EACE;AAAA,OAGK;AAEA,IAAM,sBACX;AAkBK,SAAS,eAAe,MAAc,MAAM,KAAK,QAAQ;AAC9D,MAAI,SAAS;AACb,MAAI,aAAa;AACjB,MAAI,UAAU;AACd,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,UAAM,IAAI,KAAK,CAAC;AAChB,QAAI,QAAQ;AACV,UAAI,MAAM,MAAM;AACd,kBAAU,IAAI,KAAK;AACnB;AAAA,MACF,WAAW,MAAM,IAAK,UAAS;AAAA,IACjC,WAAW,cAAc,GAAG;AAC1B,UAAI,MAAM,KAAK;AACb,YAAI,KAAK,IAAI,CAAC,MAAM,IAAK;AAAA,YACpB,cAAa;AAAA,MACpB;AAAA,IACF,WAAW,MAAM,IAAK,UAAS;AAAA,aACtB,MAAM,IAAK,cAAa;AAAA,EACnC;AACA,SAAO,EAAE,QAAQ,YAAY,QAAQ;AACvC;AAEO,SAAS,mBACd,MACA,OACA,KACA,QACA,WAAW,OACX,YAAwC,mBACb;AAC3B,QAAM,SAAS,KAAK,MAAM,GAAG,KAAK;AAClC,QAAM,EAAE,QAAQ,WAAW,IAAI,eAAe,MAAM,KAAK;AACzD,MAAI,UAAW,CAAC,YAAY,UAAU,IAAM,QAAO;AACnD,QAAM,OAAO,OAAO,MAAM,2BAA2B,IAAI,CAAC,KAAK;AAC/D,MAAI,CAAC,YAAY,aAAa,KAAK,CAAC,KAAM,QAAO;AACjD,QAAM,OACJ,UAAU,MAAM,QAAQ,cAAc,IAAI,aAAa,QAAQ,KAAK;AACtE,MAAI,KAAK;AACT,MAAI,UAAU,OAAO,cAAc,GAAG;AAEpC,aAAS,IAAI,aAAa,GAAG,IAAI,KAAK,QAAQ,KAAK;AACjD,UAAI,KAAK,CAAC,MAAM,KAAK;AACnB,YAAI,KAAK,IAAI,CAAC,MAAM,KAAK;AACvB;AACA;AAAA,QACF;AACA,aAAK,IAAI;AACT;AAAA,MACF;AACA,UAAI,KAAK,CAAC,MAAM,QAAQ,KAAK,CAAC,MAAM,IAAK;AAAA,IAC3C;AAAA,EACF,WAAW,UAAU,KAAK;AACxB,UAAM,KAAK,MAAM,GAAG,EAAE,MAAM,gBAAgB,IAAI,CAAC,EAAE,UAAU;AAAA,EAC/D;AACA,QAAM,SAAS,UAAU,MAAM,KAAK,OAAO,MAAM,IAAI,EAAE,YAAY;AACnE,QAAM,UAA+B,OAAO,IAAI,CAAC,OAAO;AAAA,IACtD,OAAO,IAAI,EAAE,KAAK,QAAQ,OAAO,IAAI,CAAC;AAAA,IACtC,QAAQ,GAAG,EAAE,KAAK,SAAM,EAAE,IAAI;AAAA,IAC9B,aAAa,EAAE;AAAA,IACf,QAAQ,IAAI,EAAE,KAAK,QAAQ,OAAO,IAAI,CAAC;AAAA,EACzC,EAAE;AACF,MAAI,aAAa,GAAG;AAClB,YAAQ;AAAA,MACN,GAAG,UAAU,IAAI,CAAC,OAAO;AAAA,QACvB,OAAO,EAAE;AAAA,QACT,QAAQ,EAAE;AAAA,QACV,aAAa,EAAE;AAAA,QACf,QAAQ,KAAK,EAAE,MAAM,MAAM,EAAE,OAAO,GAAG,EAAE,IAAI;AAAA,QAC7C,aAAa,EAAE,KAAK,SAAS;AAAA,MAC/B,EAAE;AAAA,MACF,GAAG,CAAC,QAAQ,SAAS,QAAQ,OAAO,MAAM,OAAO,MAAM,SAAS,EAAE;AAAA,QAChE,CAAC,WAAW;AAAA,UACV;AAAA,UACA,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,QAAQ;AAAA,MACf,CAAC,WACC,OAAO,MAAM,YAAY,EAAE,WAAW,MAAM,KAC3C,cAAc,KACb,OAAO,OAAO,YAAY,EAAE,SAAS,OAAO,MAAM,CAAC,CAAC;AAAA,IAC1D;AAAA,EACF;AACF;AAEO,SAAS,YACd,MACA,YAAwC,mBAChC;AACR,QAAM,QAA8C,CAAC;AACrD,MAAI,SAAS,OACX,QAAQ;AACV,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,IAAI,KAAK,CAAC;AAChB,QAAI,QAAQ;AACV,UAAI,MAAM,KAAM;AAAA,eACP,MAAM,IAAK,UAAS;AAC7B;AAAA,IACF;AACA,QAAI,OAAO;AACT,UAAI,MAAM,KAAK;AACb,YAAI,KAAK,IAAI,CAAC,MAAM,IAAK;AAAA,YACpB,SAAQ;AAAA,MACf;AACA;AAAA,IACF;AACA,QAAI,MAAM,KAAK;AACb,eAAS;AACT;AAAA,IACF;AACA,QAAI,MAAM,KAAK;AACb,cAAQ;AACR;AAAA,IACF;AACA,QAAI,MAAM;AACR,YAAM,KAAK;AAAA,QACT,MACE,KACG,MAAM,GAAG,CAAC,EACV,MAAM,kBAAkB,IAAI,CAAC,GAC5B,YAAY,KAAK;AAAA,QACvB,UAAU;AAAA,MACZ,CAAC;AAAA,aACM,MAAM,IAAK,OAAM,IAAI;AAAA,aACrB,MAAM,OAAO,MAAM,OAAQ,OAAM,MAAM,SAAS,CAAC,EAAG;AAAA,EAC/D;AACA,QAAM,OAAO,MAAM,MAAM,SAAS,CAAC,GACjC,OAAO,UAAU,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,IAAI;AACpD,SAAO,QAAQ,OACX,GAAG,KAAK,SAAS,kBAAe,KAAK,QAAQ,WAAM,KAAK,WAAW,KACnE;AACN;;;ACnKA,SAAS,WAAW,OAAO,QAAQ,gBAAgB;;;ACAnD;AAAA,EACE,qBAAAC;AAAA,OAGK;AAEA,IAAM,sBAAsB;AAAA,EACjC;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aACE;AAAA,IACF,UAAU;AAAA,EACZ;AACF;AAGO,IAAM,wBAGP;AAAA,EACJ,EAAE,OAAO,QAAQ,OAAO,OAAO;AAAA,EAC/B,EAAE,OAAO,UAAU,OAAO,SAAS;AAAA,EACnC,EAAE,OAAO,QAAQ,OAAO,UAAU;AAAA,EAClC,EAAE,OAAO,YAAY,OAAO,YAAY;AAAA,EACxC,EAAE,OAAO,aAAa,OAAO,aAAa;AAAA,EAC1C,EAAE,OAAO,UAAU,OAAO,uBAAuB;AACnD;AACO,SAAS,oBAAoB,QAAwC;AAC1E,SACE,sBAAsB,KAAK,CAAC,SAAS,KAAK,UAAU,MAAM,GAAG,SAAS;AAE1E;AAGO,SAAS,iBAAiB,IAAuC;AACtE,MACE,CAAC,SAAS,kBAAkB,OAAO,OAAO,OAAO,OAAO,QAAQ,EAAE;AAAA,IAChE,GAAG;AAAA,EACL;AAEA,WAAO;AACT,MAAI,GAAG,KAAK,WAAW,KAAK,EAAG,QAAO;AACtC,MACE,GAAG,KAAK,WAAW,QAAQ,KAC3B,CAAC,YAAY,eAAe,WAAW,EAAE,SAAS,GAAG,IAAI;AAEzD,WAAO;AACT,MAAI,GAAG,KAAK,WAAW,QAAQ,KAAK,CAAC,SAAS,MAAM,EAAE,SAAS,GAAG,IAAI;AACpE,WAAO;AACT,MACE,GAAG,KAAK,SAAS,MAAM,KACtB,GAAG,SAAS,SAAS,GAAG,KAAK,SAAS,UAAU;AAEjD,WAAO;AACT,MAAI,GAAG,WAAW,YAAY,GAAG,SAAS,MAAO,QAAO;AACxD,MAAI,GAAG,KAAK,SAAS,MAAM,EAAG,QAAO;AACrC,SAAO;AACT;AAEA,IAAM,iBAAyC;AAAA,EAC7C,MAAM;AAAA,EACN,OAAO;AAAA,EACP,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,OAAO;AAAA,EACP,UAAU;AAAA,EACV,KAAK;AAAA,EACL,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,cAAc;AAAA,EACd,WAAW;AACb;AACA,IAAM,YAAY,CAAC,SACjB,KAAK,YAAY,EAAE,QAAQ,SAAS,GAAG,EAAE,KAAK;AAEzC,SAAS,qBACd,QACA,WAAqC,OACrC,SAA4C,OAC5C,YAAwCA,oBACrB;AACnB,QAAM,QAAQ,UAAU,MAAM;AAC9B,QAAM,QAAQ,MAAM,MAAM,KAAK,EAAE,OAAO,OAAO;AAC/C,QAAM,YAAY,UAAU,OAAO,CAAC,OAAO;AACzC,UAAM,UAAU,iBAAiB,EAAE;AACnC,QACG,aAAa,SAAS,YAAY,YAClC,WAAW,SAAS,GAAG,WAAW;AAEnC,aAAO;AACT,UAAM,WAAW,oBAAoB,KAAK,CAAC,SAAS,KAAK,OAAO,OAAO;AACvE,UAAM,WAAW;AAAA,MACf,GAAG,GAAG,IAAI,IAAI,GAAG,SAAS,IAAI,GAAG,WAAW,IAAI,SAAS,KAAK,IAAI,SAAS,QAAQ,IAAI,eAAe,GAAG,IAAI,KAAK,EAAE,IAAI,oBAAoB,GAAG,MAAM,CAAC;AAAA,IACxJ;AACA,WAAO,MAAM,MAAM,CAAC,SAAS,SAAS,SAAS,IAAI,CAAC;AAAA,EACtD,CAAC;AAED,QAAM,OAAO,CAAC,OACZ,CAAC,QACG,IACA,UAAU,GAAG,IAAI,MAAM,QACrB,IACA,UAAU,GAAG,IAAI,EAAE,WAAW,KAAK,IACjC,IACA;AACV,SAAO,UAAU;AAAA,IACf,CAAC,GAAG,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,EAAE,KAAK,cAAc,EAAE,IAAI;AAAA,EAC5D;AACF;;;ADvJA;AAAA,EACE,qBAAAC;AAAA,OAEK;AA2CC,mBACE,KADF;AAzCD,SAAS,uBAAuB;AAAA,EACrC;AAAA,EACA,YAAYA;AAAA,EACZ,UAAU;AACZ,GAIG;AACD,QAAM,KAAK,MAAM;AACjB,QAAM,OAAO,OAAuB,IAAI;AACxC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,EAAE;AACvC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAmC,KAAK;AACxE,QAAM,CAAC,QAAQ,SAAS,IAAI;AAAA,IAC1B;AAAA,EACF;AACA,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,CAAC;AACtC,QAAM,UAAU,qBAAqB,QAAQ,UAAU,QAAQ,SAAS;AACxE,QAAM,UAAU,oBAAoB,KAAK,CAAC,SAAS,KAAK,OAAO,QAAQ;AAEvE,YAAU,MAAM;AAEd,UAAM,SAAS,KAAK,SAAS,SAAS,MAAM;AAC5C,UAAM,WAAW,KAAK;AACtB,QAAI,CAAC,UAAU,CAAC,SAAU;AAE1B,UAAM,MAAM,OAAO;AACnB,QAAI,MAAM,SAAS,UAAW,UAAS,YAAY;AAAA,aAEjD,MAAM,OAAO,eACb,SAAS,YAAY,SAAS;AAE9B,eAAS,YAAY,MAAM,OAAO,eAAe,SAAS;AAAA,EAC9D,GAAG,CAAC,QAAQ,QAAQ,UAAU,MAAM,CAAC;AAErC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,4CAA4C,OAAO;AAAA,MAC9D,cAAW;AAAA,MAEV;AAAA,oBAAY,UACX,iCACE;AAAA,8BAAC,QAAG,8BAAgB;AAAA,UACpB,oBAAC,OAAE,WAAU,YAAW,sDAAwC;AAAA,WAClE;AAAA,QAEF,qBAAC,WAAM,WAAU,sBAAqB;AAAA;AAAA,UAEpC;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,MAAK;AAAA,cACL,MAAK;AAAA,cACL,qBAAkB;AAAA,cAClB,iBAAe,QAAQ,SAAS;AAAA,cAChC,iBAAe,GAAG,EAAE;AAAA,cACpB,yBACE,QAAQ,MAAM,IAAI,GAAG,EAAE,IAAI,QAAQ,MAAM,EAAG,IAAI,KAAK;AAAA,cAEvD,aAAY;AAAA,cACZ,OAAO;AAAA,cACP,UAAU,CAAC,UAAU;AACnB,0BAAU,MAAM,OAAO,KAAK;AAC5B,0BAAU,CAAC;AAAA,cACb;AAAA,cACA,WAAW,CAAC,UAAU;AACpB,oBACE,MAAM,YAAY,eAClB,MAAM,WACN,MAAM,WACN,MAAM,UACN,CAAC,QAAQ;AAET;AACF,oBAAI,MAAM,QAAQ,eAAe,MAAM,QAAQ,WAAW;AACxD,wBAAM,eAAe;AACrB;AAAA,oBACE,CAAC,WACE,SACE,MAAM,QAAQ,cAAc,IAAI,QAAQ,SAAS,MACpD,QAAQ;AAAA,kBACZ;AAAA,gBACF,WAAW,MAAM,QAAQ,SAAS;AAChC,wBAAM,eAAe;AACrB,wBAAM,KAAK,QAAQ,MAAM;AACzB,sBAAI,GAAI,YAAW,GAAG,IAAI;AAAA,gBAC5B;AAAA,cACF;AAAA;AAAA,UACF;AAAA,WACF;AAAA,QACA,qBAAC,SAAI,WAAU,uBACb;AAAA,+BAAC,WAAM;AAAA;AAAA,YAEL;AAAA,cAAC;AAAA;AAAA,gBACC,OAAO;AAAA,gBACP,UAAU,CAAC,UAAU;AACnB,8BAAY,MAAM,OAAO,KAAiC;AAC1D,4BAAU,CAAC;AAAA,gBACb;AAAA,gBAEA;AAAA,sCAAC,YAAO,OAAM,OAAM,2BAAa;AAAA,kBAChC,oBAAoB;AAAA,oBAAO,CAAC,SAC3B,UAAU,KAAK,CAAC,OAAO,iBAAiB,EAAE,MAAM,KAAK,EAAE;AAAA,kBACzD,EAAE,IAAI,CAAC,SACL,oBAAC,YAAqB,OAAO,KAAK,IAC/B,eAAK,SADK,KAAK,EAElB,CACD;AAAA;AAAA;AAAA,YACH;AAAA,aACF;AAAA,UACA,qBAAC,WAAM;AAAA;AAAA,YAEL;AAAA,cAAC;AAAA;AAAA,gBACC,OAAO;AAAA,gBACP,UAAU,CAAC,UAAU;AACnB;AAAA,oBACE,MAAM,OAAO;AAAA,kBACf;AACA,4BAAU,CAAC;AAAA,gBACb;AAAA,gBAEA;AAAA,sCAAC,YAAO,OAAM,OAAM,sBAAQ;AAAA,kBAC3B,sBAAsB,IAAI,CAAC,SAC1B,oBAAC,YAAwB,OAAO,KAAK,OAClC,eAAK,SADK,KAAK,KAElB,CACD;AAAA;AAAA;AAAA,YACH;AAAA,aACF;AAAA,WACF;AAAA,QACC,WAAW,oBAAC,OAAE,WAAU,uBAAuB,kBAAQ,aAAY;AAAA,QACpE,qBAAC,OAAE,WAAU,qBAAoB,aAAU,UACxC;AAAA,kBAAQ;AAAA,UAAO;AAAA,UAAE,QAAQ,WAAW,IAAI,aAAa;AAAA,WACxD;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,WAAU;AAAA,YACV,IAAI,GAAG,EAAE;AAAA,YACT,MAAK;AAAA,YACL,cAAW;AAAA,YAEV,kBAAQ,IAAI,CAAC,IAAI,UAChB;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,MAAK;AAAA,gBACL,IAAI,GAAG,EAAE,IAAI,GAAG,IAAI;AAAA,gBAEpB,iBAAe,UAAU;AAAA,gBACzB,cAAY,WAAW,UAAU,GAAG,IAAI,KAAK,GAAG;AAAA,gBAChD,UAAU,CAAC;AAAA,gBACX,aAAa,CAAC,UAAU,MAAM,eAAe;AAAA,gBAC7C,SAAS,MAAM,UAAU,KAAK;AAAA,gBAC9B,SAAS,MAAM,WAAW,GAAG,IAAI;AAAA,gBAEjC;AAAA,uCAAC,UAAK,WAAU,0BACd;AAAA,wCAAC,YAAQ,aAAG,MAAK;AAAA,oBACjB,oBAAC,UAAM,8BAAoB,GAAG,MAAM,GAAE;AAAA,qBACxC;AAAA,kBACA,oBAAC,UAAM,aAAG,WAAU;AAAA,kBACpB,oBAAC,WAAO,aAAG,aAAY;AAAA,kBACvB,oBAAC,UAAK,WAAU,4BAEZ,8BAAoB;AAAA,oBAClB,CAAC,SAAS,KAAK,OAAO,iBAAiB,EAAE;AAAA,kBAC3C,EAAG,OAEP;AAAA;AAAA;AAAA,cApBK,GAAG;AAAA,YAqBV,CACD;AAAA;AAAA,QACH;AAAA,QACC,CAAC,QAAQ,UACR,oBAAC,OAAE,WAAU,qBAAoB,6FAGjC;AAAA,QAEF,oBAAC,OAAE,WAAU,2BACV,qBACG,+HACA,iDACN;AAAA;AAAA;AAAA,EACF;AAEJ;;;AFgIQ,gBAAAC,MA6DE,QAAAC,aA7DF;AArRO,SAAR,cAA+B;AAAA,EACpC;AAAA,EACA;AAAA,EACA,YAAYC;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,cAAc;AAAA,EACd,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AACT,GAAuB;AACrB,QAAM,KAAKC,OAAM;AACjB,QAAM,SAASC,QAAmC,IAAI;AACtD,QAAM,eAAeA,QAAO,KAAK;AACjC,QAAM,YAAYA,QAAO,KAAK;AAE9B,QAAM,SAASA,QAGZ,EAAE,OAAO,QAAQ,CAAC,EAAE,CAAC;AACxB,QAAM,YAAY,CAAC,SAAiB;AAClC,UAAM,WAAW,OAAO,QAAQ;AAChC,QAAI,aAAa,KAAM;AACvB,QAAI,OAAO;AACX,WACE,OAAO,SAAS,UAChB,OAAO,KAAK,UACZ,SAAS,IAAI,MAAM,KAAK,IAAI;AAE5B;AACF,QAAI,SAAS,SAAS,QACpB,SAAS,KAAK;AAChB,WACE,SAAS,QACT,SAAS,QACT,SAAS,SAAS,CAAC,MAAM,KAAK,SAAS,CAAC,GACxC;AACA;AACA;AAAA,IACF;AACA,UAAM,QAAQ,SAAS;AACvB,WAAO,QAAQ,SAAS,OAAO,QAAQ,OACpC;AAAA,MACC,CAAC,EAAE,MAAM,MAAM,MACb,EAAE,QAAQ,QAAQ,OAAO,WACzB,EAAE,SAAS,QAAQ,QAAQ;AAAA,IAC/B,EACC,IAAI,CAAC,EAAE,MAAM,MAAM,OAAO;AAAA,MACzB,MAAM,QAAQ,SAAS,OAAO,QAAQ;AAAA,MACtC,OAAO,SAAS,SAAS,QAAQ,QAAQ;AAAA,IAC3C,EAAE;AACJ,WAAO,QAAQ,QAAQ;AAAA,EACzB;AACA,QAAM,YAAYA,QAAO,EAAE,OAAO,GAAG,KAAK,EAAE,CAAC;AAC7C,QAAM,gBAAgBA,QAAO,OAAO;AACpC,gBAAc,UAAU;AACxB,QAAM,CAAC,OAAO,QAAQ,IAAIC,UAAS,CAAC;AACpC,QAAM,CAAC,aAAa,cAAc,IAAIA;AAAA,IACpC;AAAA,EACF;AACA,QAAM,CAAC,QAAQ,SAAS,IAAIA,UAAS,CAAC;AACtC,QAAM,CAAC,YAAY,aAAa,IAAIA,UAI1B,IAAI;AACd,QAAM,UAAU,cAAe,aAAa,QAAQ,MAAM,GAAG,EAAE,KAAK,CAAC,IAAK,CAAC;AAC3E,QAAM,OAAO,cAAc,YAAY,MAAM,MAAM,GAAG,KAAK,GAAG,SAAS,IAAI;AAE3E,EAAAC,WAAU,MAAM;AAEd,UAAM,QAAQ,OAAO;AACrB,QAAI,SAAS,MAAM,UAAU,OAAO;AAClC,YAAM,QAAQ;AACd,aAAO,UAAU,EAAE,OAAO,QAAQ,CAAC,EAAE;AACrC,eAAS,MAAM,cAAc;AAC7B,qBAAe,IAAI;AAAA,IACrB;AACA,kBAAc,IAAI;AAClB,UAAM,QAAQ,WAAW,MAAM;AAC7B,UAAI;AACF,sBAAc,QAAQ,KAAK;AAC3B,sBAAc,IAAI;AAAA,MACpB,SAAS,OAAO;AACd,cAAM,OAAO,iBAAiB,eAAe,MAAM,OAAO;AAC1D,cAAM,KAAK,iBAAiB,eAAe,MAAM,KAAK,MAAM;AAC5D,sBAAc;AAAA,UACZ,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,UAC9D,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,MAAM,MAAM,CAAC;AAAA,UAC9C,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,MAAM,MAAM,CAAC;AAAA,QAC5C,CAAC;AAAA,MACH;AAAA,IACF,GAAG,GAAG;AACN,WAAO,MAAM,aAAa,KAAK;AAAA,EACjC,GAAG,CAAC,KAAK,CAAC;AAEV,EAAAA,WAAU,MAAM;AACd,QAAI,OAAO,QAAS,WAAU,OAAO,OAAO;AAAA,EAC9C,GAAG,CAAC,OAAO,CAAC;AAEZ,EAAAA,WAAU,MAAM;AACd,aACG,eAAe,GAAG,EAAE,WAAW,MAAM,EAAE,GACtC,iBAAiB,EAAE,OAAO,UAAU,CAAC;AAAA,EAC3C,GAAG,CAAC,QAAQ,EAAE,CAAC;AAEf,QAAM,UAAU,CAAC,WAAW,UAAU;AACpC,UAAM,QAAQ,OAAO;AACrB,QAAI,CAAC,eAAe,CAAC,SAAS,UAAU,QAAS;AACjD,cAAU,UAAU;AAAA,MAClB,OAAO,MAAM;AAAA,MACb,KAAK,MAAM;AAAA,IACb;AACA,aAAS,MAAM,cAAc;AAC7B,cAAU,CAAC;AACX;AAAA,MACE;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,CACb,MACA,IACA,MACA,cAAc,KAAK,QACnB,SACG;AACH,UAAM,QAAQ,OAAO;AACrB,QAAI,CAAC,MAAO;AACZ,UAAM,MAAM;AACZ,UAAM,kBAAkB,MAAM,EAAE;AAChC,iBAAa,UAAU;AACvB,QAAI;AAGF,YAAM,SAAS,MAAM;AACrB,UAAI;AACF,iBAAS,cAAc,cAAc,OAAO,IAAI;AAAA,MAClD,QAAQ;AAAA,MAER;AACA,UAAI,MAAM,UAAU,OAAQ,OAAM,aAAa,MAAM,MAAM,IAAI,KAAK;AAAA,IACtE,UAAE;AACA,mBAAa,UAAU;AAAA,IACzB;AACA,cAAU,MAAM,KAAK;AACrB,QAAI;AACF,aAAO,QAAQ,OAAO,KAAK;AAAA,QACzB,MAAM,OAAO,KAAK;AAAA,QAClB,OAAO,OAAO,KAAK;AAAA,MACrB,CAAC;AACH,UAAM,kBAAkB,OAAO,aAAa,OAAO,WAAW;AAC9D,aAAS,MAAM,cAAc;AAC7B,mBAAe,IAAI;AACnB,aAAS,MAAM,KAAK;AAAA,EACtB;AAEA,QAAM,SAAS,CAAC,WAA8B;AAC5C,QAAI;AACF;AAAA,QACE,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO,OAAO,SAAS,IAAI,IACvB,EAAE,MAAM,OAAO,OAAO,SAAS,GAAG,OAAO,OAAO,OAAO,SAAS,EAAE,IAClE;AAAA,MACN;AAAA,EACJ;AAEA,QAAM,iBAAiB,CAAC,SAAiB;AACvC,UAAM,QAAQ,OAAO;AACrB,QAAI,CAAC,MAAO;AACZ,UAAM,OAAO,MAAM;AACnB,UAAM,KAAK,MAAM;AACjB,UAAM,WAAW,MAAM,MAAM,MAAM,MAAM,EAAE;AAC3C;AAAA,MACE;AAAA,MACA;AAAA,MACA,GAAG,IAAI,IAAI,QAAQ;AAAA,MACnB,KAAK,SAAS,SAAS,SAAS;AAAA,MAChC,EAAE,MAAM,KAAK,QAAQ,OAAO,KAAK,SAAS,SAAS,SAAS,EAAE;AAAA,IAChE;AAAA,EACF;AACA,sBAAoB,YAAY,OAAO,EAAE,eAAe,EAAE;AAE1D,QAAM,YAAY,CAAC,UAA8C;AAC/D,UAAM,QAAQ,MAAM;AACpB,QAAI,UAAU,WAAW,MAAM,YAAY,YAAa;AACxD,QAAI,eAAe,MAAM,WAAW,MAAM,SAAS,SAAS;AAC1D,YAAM,eAAe;AACrB,cAAQ,IAAI;AACZ;AAAA,IACF;AACA,QAAI,QAAQ,QAAQ;AAClB,UAAI,MAAM,QAAQ,UAAU;AAC1B,cAAM,eAAe;AACrB,cAAM,gBAAgB;AACtB,uBAAe,IAAI;AACnB;AAAA,MACF;AACA,UAAI,MAAM,QAAQ,eAAe,MAAM,QAAQ,WAAW;AACxD,cAAM,eAAe;AACrB;AAAA,UACE,CAAC,WACE,SAAS,MAAM,QAAQ,cAAc,IAAI,QAAQ,SAAS,MAC3D,QAAQ;AAAA,QACZ;AACA;AAAA,MACF;AACA,UAAI,MAAM,QAAQ,SAAS;AACzB,cAAM,eAAe;AACrB,cAAM,SAAS,QAAQ,MAAM;AAC7B,YAAI,OAAQ,QAAO,MAAM;AACzB;AAAA,MACF;AAAA,IACF;AACA,QAAI,MAAM,WAAW,MAAM,WAAW,MAAM,OAAQ;AACpD,UAAM,QAAQ,MAAM,gBAClB,MAAM,MAAM;AACd,UAAM,UAAU,eAAe,MAAM,OAAO,KAAK;AACjD,UAAM,QAAQ,MAAM,QAAQ,MAAM,MAAM,MAAM,QAAQ,MAAM,MAAM;AAClE,QACE,SACA,CAAC,QAAQ,UACT,QAAQ,aAAa,MACpB,UAAU,OACT,QAAQ,MAAM,MAAM,UACpB,WAAW,KAAK,MAAM,MAAM,GAAG,CAAE,IACnC;AACA,YAAM,eAAe;AACrB,YAAMC,aAAY,MAAM,MAAM,MAAM,OAAO,GAAG;AAC9C,aAAO,OAAO,KAAK,MAAM,MAAMA,aAAY,OAAOA,WAAU,SAAS,GAAG;AAAA,QACtE,MAAM;AAAA,QACN,OAAOA,WAAU,SAAS;AAAA,MAC5B,CAAC;AAAA,IACH,WACE,UAAU,OACV,MAAM,MAAM,KAAK,MAAM,MAAM,OAC7B,OAAO,QAAQ,OAAO,KAAK,CAAC,SAAS,KAAK,UAAU,KAAK,MACvD,MAAM,QAAQ,OAAO,CAAC,QAAQ,UAAU,QAAQ,aAAa,KAC5D,MAAM,QAAQ,OAAO,QAAQ,UAAU,CAAC,QAAQ,UACnD;AACA,YAAM,eAAe;AACrB,aAAO,QAAQ,SAAS,OAAO,QAAQ,OAAO;AAAA,QAC5C,CAAC,SAAS,KAAK,UAAU;AAAA,MAC3B;AACA,YAAM,kBAAkB,QAAQ,GAAG,QAAQ,CAAC;AAC5C,eAAS,QAAQ,CAAC;AAClB,qBAAe,IAAI;AAAA,IACrB,WACE,MAAM,QAAQ,eACd,UAAU,OACV,QAAQ,KACR,CAAC,MAAM,MAAM,IAAI,EAAE,SAAS,MAAM,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,MAClE,CAAC,QAAQ,UACP,MAAM,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,QAAQ,CAAC,QAAQ,UAChE;AACA,YAAM,eAAe;AACrB,aAAO,QAAQ,GAAG,QAAQ,GAAG,EAAE;AAAA,IACjC;AAAA,EACF;AAEA,SACE,gBAAAN,MAAC,SAAI,WAAU,qBACb;AAAA,oBAAAA,MAAC,SAAI,WAAU,qBACb;AAAA,sBAAAD;AAAA,QAAC;AAAA;AAAA,UACC,KAAK,CAAC,UAAU;AACd,mBAAO,UAAU;AACjB,gBAAI,SAAU,UAAS,UAAU;AAAA,UACnC;AAAA,UACA,WAAU;AAAA,UACV,cAAY;AAAA,UACZ,oBAAkB,GAAG,EAAE,QAAQ,aAAa,IAAI,EAAE,WAAW,EAAE;AAAA,UAC/D,gBAAc,aAAa,OAAO;AAAA,UAClC,qBAAmB,cAAc,SAAS;AAAA,UAC1C,iBAAe,QAAQ,SAAS,GAAG,EAAE,iBAAiB;AAAA,UACtD,yBACE,QAAQ,SAAS,GAAG,EAAE,WAAW,MAAM,KAAK;AAAA,UAE9C,cAAc;AAAA,UACd;AAAA,UACA,YAAY;AAAA,UACZ,gBAAe;AAAA,UACf,aAAY;AAAA,UACZ,UAAU,CAAC,UAAU;AACnB,gBAAI,aAAa,QAAS;AAC1B,sBAAU,MAAM,cAAc,KAAK;AACnC,qBAAS,MAAM,cAAc,KAAK;AAClC,gBAAI,CAAC,UAAU,QAAS,SAAQ;AAAA,UAClC;AAAA,UACA,UAAU,CAAC,UAAU;AACnB,qBAAS,MAAM,cAAc,cAAc;AAC3C,kBAAM,QAAQ,MAAM;AACpB,gBACE,CAAC,aAAa,YACb,MAAM,mBAAmB,UAAU,QAAQ,SAC1C,MAAM,iBAAiB,UAAU,QAAQ;AAE3C,6BAAe,IAAI;AAAA,UACvB;AAAA,UACA;AAAA,UACA,oBAAoB,MAAM;AACxB,sBAAU,UAAU;AACpB,2BAAe,IAAI;AAAA,UACrB;AAAA,UACA,kBAAkB,MAAM;AACtB,sBAAU,UAAU;AACpB,oBAAQ;AAAA,UACV;AAAA,UACA,QAAQ,MAAM,eAAe,IAAI;AAAA,UACjC;AAAA;AAAA,MACF;AAAA,MACA,gBAAAC,MAAC,SAAI,WAAU,oBACZ;AAAA,uBACC,gBAAAD;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,WAAU;AAAA,YACV,aAAa,CAAC,UAAU,MAAM,eAAe;AAAA,YAC7C,SAAS,MAAM;AACb,qBAAO,SAAS,MAAM;AACtB,sBAAQ,IAAI;AAAA,YACd;AAAA,YACD;AAAA;AAAA,QAED;AAAA,QAEF,gBAAAC,MAAC,UAAK;AAAA;AAAA,UACE,MAAM,MAAM,GAAG,KAAK,EAAE,MAAM,IAAI,EAAE;AAAA,UAAO;AAAA,UAAS;AAAA,UACvD,QAAQ,MAAM,MAAM,GAAG,KAAK,EAAE,YAAY,IAAI;AAAA,WACjD;AAAA,SACF;AAAA,MACC,QAAQ,SAAS,KAChB,gBAAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,+BAA4B;AAAA,UAE5B;AAAA,4BAAAD;AAAA,cAAC;AAAA;AAAA,gBACC,IAAI,GAAG,EAAE;AAAA,gBACT,MAAK;AAAA,gBACL,cAAW;AAAA,gBACX,aAAa,CAAC,UAAU,MAAM,eAAe;AAAA,gBAE5C,kBAAQ,IAAI,CAAC,QAAQ,UACpB,gBAAAC;AAAA,kBAAC;AAAA;AAAA,oBAEC,IAAI,GAAG,EAAE,WAAW,KAAK;AAAA,oBACzB,MAAK;AAAA,oBACL,iBAAe,UAAU;AAAA,oBACzB,OAAO,OAAO;AAAA,oBACd,SAAS,MAAM,OAAO,MAAM;AAAA,oBAE5B;AAAA,sCAAAD,KAAC,YAAQ,iBAAO,OAAM;AAAA,sBACtB,gBAAAA,KAAC,WAAO,iBAAO,QAAO;AAAA;AAAA;AAAA,kBARjB,OAAO;AAAA,gBASd,CACD;AAAA;AAAA,YACH;AAAA,YACA,gBAAAC,MAAC,OACE;AAAA,sBAAQ,MAAM,GAAG;AAAA,cAAY;AAAA,eAEhC;AAAA,YACC,eAAe,YAAY,QAAQ,SAAS,QAAQ,UACnD,gBAAAA,MAAC,OAAE;AAAA;AAAA,cAA0B,QAAQ;AAAA,cAAO;AAAA,eAAa;AAAA;AAAA;AAAA,MAE7D;AAAA,MAEF,gBAAAD,KAAC,OAAE,IAAI,GAAG,EAAE,SAAS,WAAU,mBAAkB,aAAU,UACxD,gBACH;AAAA,MACC,cACC,gBAAAC,MAAC,SAAI,IAAI,GAAG,EAAE,UAAU,WAAU,oBAAmB,MAAK,UACvD;AAAA,mBAAW;AAAA,QAAS;AAAA,QACrB,gBAAAD;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,WAAU;AAAA,YACV,SAAS,MAAM;AACb,qBAAO,SAAS,MAAM;AACtB,qBAAO,SAAS;AAAA,gBACd,WAAW;AAAA,gBACX,WAAW;AAAA,cACb;AACA,uBAAS,WAAW,IAAI;AAAA,YAC1B;AAAA,YACD;AAAA;AAAA,QAED;AAAA,SACF;AAAA,OAEJ;AAAA,IACC,eACC,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,UAAU;AAAA;AAAA,IACZ;AAAA,KAEJ;AAEJ;","names":["useEffect","useId","useRef","useState","FORMULA_FUNCTIONS","FORMULA_FUNCTIONS","FORMULA_FUNCTIONS","jsx","jsxs","FORMULA_FUNCTIONS","useId","useRef","useState","useEffect","selection"]}
|