@octaviaflow/core 3.1.0-beta.102 → 3.1.0-beta.104
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/dist/components/ExpressionField/ExpressionField.d.ts +18 -7
- package/dist/components/ExpressionField/ExpressionField.d.ts.map +1 -1
- package/dist/components/ExpressionField/parseExpression.d.ts +52 -0
- package/dist/components/ExpressionField/parseExpression.d.ts.map +1 -0
- package/dist/index.cjs +569 -80
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +612 -122
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -41339,11 +41339,173 @@ ToolCard.displayName = "ToolCard";
|
|
|
41339
41339
|
|
|
41340
41340
|
// src/components/ExpressionField/ExpressionField.tsx
|
|
41341
41341
|
var import_react188 = require("react");
|
|
41342
|
+
|
|
41343
|
+
// src/components/ExpressionField/parseExpression.ts
|
|
41344
|
+
var CALL_NAME_SOURCE = "(?:[A-Z_][A-Z0-9_]*|user\\.[A-Za-z_][A-Za-z0-9_]*)";
|
|
41345
|
+
var CALL_HEAD = new RegExp(`^(${CALL_NAME_SOURCE})\\s*\\(`);
|
|
41346
|
+
function wrappedRegions(value) {
|
|
41347
|
+
const out = [];
|
|
41348
|
+
let i = 0;
|
|
41349
|
+
while (i < value.length) {
|
|
41350
|
+
const open = value.indexOf("{{", i);
|
|
41351
|
+
if (open === -1) break;
|
|
41352
|
+
let depth = 0;
|
|
41353
|
+
let quote = null;
|
|
41354
|
+
let close = -1;
|
|
41355
|
+
for (let j = open; j < value.length; j++) {
|
|
41356
|
+
const ch = value[j];
|
|
41357
|
+
if (quote) {
|
|
41358
|
+
if (ch === quote) quote = null;
|
|
41359
|
+
continue;
|
|
41360
|
+
}
|
|
41361
|
+
if (ch === '"' || ch === "'") {
|
|
41362
|
+
quote = ch;
|
|
41363
|
+
continue;
|
|
41364
|
+
}
|
|
41365
|
+
if (ch === "{" && value[j + 1] === "{") {
|
|
41366
|
+
depth++;
|
|
41367
|
+
j++;
|
|
41368
|
+
continue;
|
|
41369
|
+
}
|
|
41370
|
+
if (ch === "}" && value[j + 1] === "}") {
|
|
41371
|
+
depth--;
|
|
41372
|
+
j++;
|
|
41373
|
+
if (depth === 0) {
|
|
41374
|
+
close = j;
|
|
41375
|
+
break;
|
|
41376
|
+
}
|
|
41377
|
+
}
|
|
41378
|
+
}
|
|
41379
|
+
if (close === -1) break;
|
|
41380
|
+
out.push({ start: open, end: close });
|
|
41381
|
+
i = close + 1;
|
|
41382
|
+
}
|
|
41383
|
+
return out;
|
|
41384
|
+
}
|
|
41385
|
+
function splitArguments(argsText) {
|
|
41386
|
+
const trimmed = argsText.trim();
|
|
41387
|
+
if (!trimmed) return [];
|
|
41388
|
+
const args = [];
|
|
41389
|
+
let current = "";
|
|
41390
|
+
let depth = 0;
|
|
41391
|
+
let braces = 0;
|
|
41392
|
+
let quote = null;
|
|
41393
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
41394
|
+
const ch = trimmed[i];
|
|
41395
|
+
if (quote) {
|
|
41396
|
+
current += ch;
|
|
41397
|
+
if (ch === quote) quote = null;
|
|
41398
|
+
continue;
|
|
41399
|
+
}
|
|
41400
|
+
if (ch === '"' || ch === "'") {
|
|
41401
|
+
quote = ch;
|
|
41402
|
+
current += ch;
|
|
41403
|
+
continue;
|
|
41404
|
+
}
|
|
41405
|
+
if (ch === "{" && trimmed[i + 1] === "{") {
|
|
41406
|
+
braces++;
|
|
41407
|
+
current += "{{";
|
|
41408
|
+
i++;
|
|
41409
|
+
continue;
|
|
41410
|
+
}
|
|
41411
|
+
if (ch === "}" && trimmed[i + 1] === "}") {
|
|
41412
|
+
braces--;
|
|
41413
|
+
current += "}}";
|
|
41414
|
+
i++;
|
|
41415
|
+
continue;
|
|
41416
|
+
}
|
|
41417
|
+
if (ch === "(") depth++;
|
|
41418
|
+
if (ch === ")") depth--;
|
|
41419
|
+
if (ch === "," && depth === 0 && braces === 0) {
|
|
41420
|
+
args.push(current.trim());
|
|
41421
|
+
current = "";
|
|
41422
|
+
continue;
|
|
41423
|
+
}
|
|
41424
|
+
current += ch;
|
|
41425
|
+
}
|
|
41426
|
+
if (current.trim()) args.push(current.trim());
|
|
41427
|
+
return args;
|
|
41428
|
+
}
|
|
41429
|
+
function parseCall(raw) {
|
|
41430
|
+
const inner = raw.startsWith("{{") && raw.endsWith("}}") ? raw.slice(2, -2).trim() : raw.trim();
|
|
41431
|
+
const head = CALL_HEAD.exec(inner);
|
|
41432
|
+
if (!head) return void 0;
|
|
41433
|
+
const open = inner.indexOf("(");
|
|
41434
|
+
const close = inner.lastIndexOf(")");
|
|
41435
|
+
if (open === -1 || close < open) return void 0;
|
|
41436
|
+
return { name: head[1], args: splitArguments(inner.slice(open + 1, close)) };
|
|
41437
|
+
}
|
|
41438
|
+
function formatCall(name, args) {
|
|
41439
|
+
return `{{${name}(${args.filter((a) => a.trim() !== "").join(", ")})}}`;
|
|
41440
|
+
}
|
|
41441
|
+
function parseExpression(value, pillEveryToken = false) {
|
|
41442
|
+
const segments = [];
|
|
41443
|
+
let cursor = 0;
|
|
41444
|
+
const pushText = (raw) => {
|
|
41445
|
+
if (raw) segments.push({ kind: "text", raw });
|
|
41446
|
+
};
|
|
41447
|
+
for (const { start, end } of wrappedRegions(value)) {
|
|
41448
|
+
pushText(value.slice(cursor, start));
|
|
41449
|
+
const raw = value.slice(start, end + 1);
|
|
41450
|
+
const call = parseCall(raw);
|
|
41451
|
+
if (call) {
|
|
41452
|
+
segments.push({ kind: "call", raw, name: call.name, args: call.args });
|
|
41453
|
+
} else if (raw.startsWith("{{ods://") || pillEveryToken) {
|
|
41454
|
+
segments.push({ kind: "ref", raw });
|
|
41455
|
+
} else {
|
|
41456
|
+
pushText(raw);
|
|
41457
|
+
}
|
|
41458
|
+
cursor = end + 1;
|
|
41459
|
+
}
|
|
41460
|
+
const tail = value.slice(cursor);
|
|
41461
|
+
let last = 0;
|
|
41462
|
+
for (const m of tail.matchAll(/\$\{[^}]+\}/g)) {
|
|
41463
|
+
pushText(tail.slice(last, m.index));
|
|
41464
|
+
segments.push({ kind: "ref", raw: m[0] });
|
|
41465
|
+
last = (m.index ?? 0) + m[0].length;
|
|
41466
|
+
}
|
|
41467
|
+
pushText(tail.slice(last));
|
|
41468
|
+
return segments;
|
|
41469
|
+
}
|
|
41470
|
+
function callAt(raw, path) {
|
|
41471
|
+
let current = parseCall(raw);
|
|
41472
|
+
for (const index of path) {
|
|
41473
|
+
if (!current) return void 0;
|
|
41474
|
+
const arg = current.args[index];
|
|
41475
|
+
if (arg === void 0) return void 0;
|
|
41476
|
+
current = parseCall(arg);
|
|
41477
|
+
}
|
|
41478
|
+
return current;
|
|
41479
|
+
}
|
|
41480
|
+
function formatNestedCall(name, args) {
|
|
41481
|
+
return `${name}(${args.filter((a) => a.trim() !== "").join(", ")})`;
|
|
41482
|
+
}
|
|
41483
|
+
function replaceArgument(raw, path, next) {
|
|
41484
|
+
if (path.length === 0) return next;
|
|
41485
|
+
const [index, ...rest] = path;
|
|
41486
|
+
const call = parseCall(raw);
|
|
41487
|
+
if (!call || index === void 0 || index > call.args.length) return raw;
|
|
41488
|
+
const args = [...call.args];
|
|
41489
|
+
if (args[index] === void 0) args[index] = "";
|
|
41490
|
+
args[index] = rest.length === 0 ? next : replaceArgument(args[index], rest, next);
|
|
41491
|
+
const wrapped = raw.trim().startsWith("{{");
|
|
41492
|
+
return wrapped ? formatCall(call.name, args) : formatNestedCall(call.name, args);
|
|
41493
|
+
}
|
|
41494
|
+
function argumentKind(arg) {
|
|
41495
|
+
const trimmed = arg.trim();
|
|
41496
|
+
if (!trimmed) return "literal";
|
|
41497
|
+
if (parseCall(trimmed)) return "call";
|
|
41498
|
+
if (trimmed.startsWith("{{") || trimmed.startsWith("ods://") || trimmed.startsWith("${")) {
|
|
41499
|
+
return "reference";
|
|
41500
|
+
}
|
|
41501
|
+
return "literal";
|
|
41502
|
+
}
|
|
41503
|
+
|
|
41504
|
+
// src/components/ExpressionField/ExpressionField.tsx
|
|
41342
41505
|
var import_jsx_runtime174 = require("react/jsx-runtime");
|
|
41343
|
-
var REF_TOKEN = /(\{\{ods:\/\/[^}]+\}\}|\$\{[^}]+\})/g;
|
|
41344
|
-
var ANY_TOKEN = /(\{\{[^}]+\}\}|\$\{[^}]+\})/g;
|
|
41345
|
-
var CALL_TOKEN = /^\{\{\s*(?:[A-Z_][A-Z0-9_]*|user\.[A-Za-z_][A-Za-z0-9_]*)\s*\(/;
|
|
41346
41506
|
var ZERO_WIDTH = "\u200B";
|
|
41507
|
+
var PILL = "ods-expression-field__pill";
|
|
41508
|
+
var CHIP = "ods-expression-field__chip";
|
|
41347
41509
|
function shortReferenceLabel(token) {
|
|
41348
41510
|
if (token.startsWith("{{ods://")) {
|
|
41349
41511
|
const path = token.slice(8, -2);
|
|
@@ -41365,7 +41527,7 @@ function serialize(el) {
|
|
|
41365
41527
|
}
|
|
41366
41528
|
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
|
41367
41529
|
const elem = node;
|
|
41368
|
-
if (elem.classList.contains(
|
|
41530
|
+
if (elem.classList.contains(PILL) || elem.classList.contains(CHIP)) {
|
|
41369
41531
|
out += elem.getAttribute("data-value") || "";
|
|
41370
41532
|
} else if (elem.tagName === "BR") {
|
|
41371
41533
|
out += "\n";
|
|
@@ -41396,6 +41558,19 @@ function insideCallArguments(textBeforeCaret) {
|
|
|
41396
41558
|
}
|
|
41397
41559
|
return depth > 0;
|
|
41398
41560
|
}
|
|
41561
|
+
function argSummary(arg, nameOf, depth = 2) {
|
|
41562
|
+
const trimmed = arg.trim();
|
|
41563
|
+
const call = parseCall(trimmed);
|
|
41564
|
+
if (call) {
|
|
41565
|
+
if (depth <= 0) return `${nameOf(call.name)}(\u2026)`;
|
|
41566
|
+
const inner = call.args.map((a) => argSummary(a, nameOf, depth - 1)).filter(Boolean).join(", ");
|
|
41567
|
+
return `${nameOf(call.name)}(${inner})`;
|
|
41568
|
+
}
|
|
41569
|
+
if (trimmed.startsWith("{{") || trimmed.startsWith("ods://") || trimmed.startsWith("${")) {
|
|
41570
|
+
return shortReferenceLabel(trimmed.startsWith("ods://") ? `{{${trimmed}}}` : trimmed);
|
|
41571
|
+
}
|
|
41572
|
+
return trimmed;
|
|
41573
|
+
}
|
|
41399
41574
|
function ExpressionField({
|
|
41400
41575
|
value,
|
|
41401
41576
|
onChange,
|
|
@@ -41408,6 +41583,7 @@ function ExpressionField({
|
|
|
41408
41583
|
className,
|
|
41409
41584
|
style,
|
|
41410
41585
|
resolveLabel,
|
|
41586
|
+
resolveCall,
|
|
41411
41587
|
pillEveryToken = false,
|
|
41412
41588
|
"aria-label": ariaLabel
|
|
41413
41589
|
}) {
|
|
@@ -41416,28 +41592,42 @@ function ExpressionField({
|
|
|
41416
41592
|
const generatedId = (0, import_react188.useId)();
|
|
41417
41593
|
const fieldId = id ?? generatedId;
|
|
41418
41594
|
const helperId = `${fieldId}-helper`;
|
|
41419
|
-
const
|
|
41420
|
-
const isPillToken = (0, import_react188.useCallback)(
|
|
41421
|
-
(part) => {
|
|
41422
|
-
if (CALL_TOKEN.test(part)) return false;
|
|
41423
|
-
return pillEveryToken ? /^(\{\{[^}]+\}\}|\$\{[^}]+\})$/.test(part) : /^(\{\{ods:\/\/[^}]+\}\}|\$\{[^}]+\})$/.test(part);
|
|
41424
|
-
},
|
|
41425
|
-
[pillEveryToken]
|
|
41426
|
-
);
|
|
41595
|
+
const [editing, setEditing] = (0, import_react188.useState)(null);
|
|
41427
41596
|
const labelFor = (0, import_react188.useCallback)(
|
|
41428
41597
|
(token) => resolveLabel?.(token) ?? shortReferenceLabel(token),
|
|
41429
41598
|
[resolveLabel]
|
|
41430
41599
|
);
|
|
41431
|
-
const
|
|
41432
|
-
(
|
|
41433
|
-
|
|
41434
|
-
|
|
41435
|
-
|
|
41436
|
-
|
|
41437
|
-
|
|
41438
|
-
return
|
|
41600
|
+
const callLabel = (0, import_react188.useCallback)(
|
|
41601
|
+
(name) => resolveCall?.(name)?.label ?? (name.startsWith("user.") ? name.slice(5) : name),
|
|
41602
|
+
[resolveCall]
|
|
41603
|
+
);
|
|
41604
|
+
const chipText = (0, import_react188.useCallback)(
|
|
41605
|
+
(name, args) => {
|
|
41606
|
+
const summary = args.map((a) => argSummary(a, callLabel)).filter(Boolean).join(", ");
|
|
41607
|
+
return `${callLabel(name)}(${summary})`;
|
|
41439
41608
|
},
|
|
41440
|
-
[
|
|
41609
|
+
[callLabel]
|
|
41610
|
+
);
|
|
41611
|
+
const makeToken = (0, import_react188.useCallback)(
|
|
41612
|
+
(segment) => {
|
|
41613
|
+
if (segment.kind === "text") return null;
|
|
41614
|
+
const el = document.createElement("span");
|
|
41615
|
+
el.contentEditable = "false";
|
|
41616
|
+
el.setAttribute("data-value", segment.raw);
|
|
41617
|
+
el.tabIndex = -1;
|
|
41618
|
+
if (segment.kind === "ref") {
|
|
41619
|
+
el.className = PILL;
|
|
41620
|
+
el.textContent = labelFor(segment.raw);
|
|
41621
|
+
el.title = segment.raw;
|
|
41622
|
+
} else {
|
|
41623
|
+
el.className = CHIP;
|
|
41624
|
+
el.setAttribute("data-call", segment.name);
|
|
41625
|
+
el.textContent = chipText(segment.name, segment.args);
|
|
41626
|
+
el.title = segment.raw;
|
|
41627
|
+
}
|
|
41628
|
+
return el;
|
|
41629
|
+
},
|
|
41630
|
+
[labelFor, chipText]
|
|
41441
41631
|
);
|
|
41442
41632
|
const render = (0, import_react188.useCallback)(
|
|
41443
41633
|
(raw) => {
|
|
@@ -41445,17 +41635,19 @@ function ExpressionField({
|
|
|
41445
41635
|
if (!el) return;
|
|
41446
41636
|
el.innerHTML = "";
|
|
41447
41637
|
if (!raw) return;
|
|
41448
|
-
|
|
41449
|
-
if (
|
|
41450
|
-
|
|
41451
|
-
|
|
41638
|
+
for (const segment of parseExpression(raw, pillEveryToken)) {
|
|
41639
|
+
if (segment.kind === "text") {
|
|
41640
|
+
el.appendChild(document.createTextNode(segment.raw));
|
|
41641
|
+
continue;
|
|
41642
|
+
}
|
|
41643
|
+
const token = makeToken(segment);
|
|
41644
|
+
if (token) {
|
|
41645
|
+
el.appendChild(token);
|
|
41452
41646
|
el.appendChild(document.createTextNode(ZERO_WIDTH));
|
|
41453
|
-
} else {
|
|
41454
|
-
el.appendChild(document.createTextNode(part));
|
|
41455
41647
|
}
|
|
41456
|
-
}
|
|
41648
|
+
}
|
|
41457
41649
|
},
|
|
41458
|
-
[
|
|
41650
|
+
[pillEveryToken, makeToken]
|
|
41459
41651
|
);
|
|
41460
41652
|
(0, import_react188.useEffect)(() => {
|
|
41461
41653
|
if (fromSelf.current) {
|
|
@@ -41476,9 +41668,8 @@ function ExpressionField({
|
|
|
41476
41668
|
if (!el || !sel || sel.rangeCount === 0 || !el.contains(sel.anchorNode)) return "";
|
|
41477
41669
|
const range = sel.getRangeAt(0).cloneRange();
|
|
41478
41670
|
range.setStart(el, 0);
|
|
41479
|
-
const fragment = range.cloneContents();
|
|
41480
41671
|
const holder = document.createElement("div");
|
|
41481
|
-
holder.appendChild(
|
|
41672
|
+
holder.appendChild(range.cloneContents());
|
|
41482
41673
|
return serialize(holder);
|
|
41483
41674
|
}, []);
|
|
41484
41675
|
const insert = (0, import_react188.useCallback)(
|
|
@@ -41488,9 +41679,19 @@ function ExpressionField({
|
|
|
41488
41679
|
const sel = window.getSelection();
|
|
41489
41680
|
const hasCaret = !!sel && sel.rangeCount > 0 && el.contains(sel.anchorNode);
|
|
41490
41681
|
const asArgument = hasCaret && insideCallArguments(textBeforeCaret());
|
|
41491
|
-
const
|
|
41492
|
-
const
|
|
41493
|
-
const
|
|
41682
|
+
const call = parseCall(raw);
|
|
41683
|
+
const isCall = kind === "function" || !!call;
|
|
41684
|
+
const text = isCall || asArgument ? raw : raw.startsWith("ods://") ? `{{${raw}}}` : raw;
|
|
41685
|
+
let node;
|
|
41686
|
+
if (asArgument) {
|
|
41687
|
+
node = document.createTextNode(text);
|
|
41688
|
+
} else if (isCall && call) {
|
|
41689
|
+
node = makeToken({ kind: "call", raw: text, name: call.name, args: call.args });
|
|
41690
|
+
} else if (text.startsWith("{{") || text.startsWith("${")) {
|
|
41691
|
+
node = makeToken({ kind: "ref", raw: text });
|
|
41692
|
+
} else {
|
|
41693
|
+
node = document.createTextNode(text);
|
|
41694
|
+
}
|
|
41494
41695
|
const range = hasCaret && sel ? sel.getRangeAt(0) : (() => {
|
|
41495
41696
|
const r = document.createRange();
|
|
41496
41697
|
r.selectNodeContents(el);
|
|
@@ -41502,27 +41703,113 @@ function ExpressionField({
|
|
|
41502
41703
|
const after = document.createTextNode(ZERO_WIDTH);
|
|
41503
41704
|
range.setStartAfter(node);
|
|
41504
41705
|
range.insertNode(after);
|
|
41505
|
-
|
|
41506
|
-
|
|
41507
|
-
const close = text.lastIndexOf(")");
|
|
41508
|
-
if (open !== -1 && close === open + 1) {
|
|
41509
|
-
range.setStart(node, open + 1);
|
|
41510
|
-
range.collapse(true);
|
|
41511
|
-
} else {
|
|
41512
|
-
range.setStartAfter(after);
|
|
41513
|
-
range.collapse(true);
|
|
41514
|
-
}
|
|
41515
|
-
} else {
|
|
41516
|
-
range.setStartAfter(after);
|
|
41517
|
-
range.collapse(true);
|
|
41518
|
-
}
|
|
41706
|
+
range.setStartAfter(after);
|
|
41707
|
+
range.collapse(true);
|
|
41519
41708
|
el.focus();
|
|
41520
41709
|
const selection = window.getSelection();
|
|
41521
41710
|
selection?.removeAllRanges();
|
|
41522
41711
|
selection?.addRange(range);
|
|
41523
41712
|
commit();
|
|
41713
|
+
if (isCall && call && node instanceof HTMLElement && call.args.length === 0) {
|
|
41714
|
+
openEditor(node);
|
|
41715
|
+
}
|
|
41716
|
+
},
|
|
41717
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
41718
|
+
[commit, disabled, makeToken, textBeforeCaret]
|
|
41719
|
+
);
|
|
41720
|
+
const selected = (0, import_react188.useRef)(null);
|
|
41721
|
+
const selectToken = (0, import_react188.useCallback)((token) => {
|
|
41722
|
+
selected.current = token;
|
|
41723
|
+
const range = document.createRange();
|
|
41724
|
+
range.selectNode(token);
|
|
41725
|
+
const sel = window.getSelection();
|
|
41726
|
+
sel?.removeAllRanges();
|
|
41727
|
+
sel?.addRange(range);
|
|
41728
|
+
fieldRef.current?.focus();
|
|
41729
|
+
}, []);
|
|
41730
|
+
const openEditor = (0, import_react188.useCallback)((chip) => {
|
|
41731
|
+
const raw = chip.getAttribute("data-value") ?? "";
|
|
41732
|
+
if (!parseCall(raw)) return;
|
|
41733
|
+
const box = chip.getBoundingClientRect();
|
|
41734
|
+
const host = fieldRef.current?.getBoundingClientRect();
|
|
41735
|
+
setEditing({
|
|
41736
|
+
el: chip,
|
|
41737
|
+
raw,
|
|
41738
|
+
rect: {
|
|
41739
|
+
top: box.bottom - (host?.top ?? 0) + 4,
|
|
41740
|
+
left: Math.max(0, box.left - (host?.left ?? 0)),
|
|
41741
|
+
width: Math.max(300, box.width)
|
|
41742
|
+
}
|
|
41743
|
+
});
|
|
41744
|
+
}, []);
|
|
41745
|
+
const applyEditor = (0, import_react188.useCallback)(
|
|
41746
|
+
(raw) => {
|
|
41747
|
+
if (!editing) return;
|
|
41748
|
+
const call = parseCall(raw);
|
|
41749
|
+
if (!call) return;
|
|
41750
|
+
editing.el.setAttribute("data-value", raw);
|
|
41751
|
+
editing.el.textContent = chipText(call.name, call.args);
|
|
41752
|
+
editing.el.title = raw;
|
|
41753
|
+
setEditing(null);
|
|
41754
|
+
commit();
|
|
41755
|
+
fieldRef.current?.focus();
|
|
41756
|
+
},
|
|
41757
|
+
[editing, chipText, commit]
|
|
41758
|
+
);
|
|
41759
|
+
const handleClick = (0, import_react188.useCallback)(
|
|
41760
|
+
(e) => {
|
|
41761
|
+
const target = e.target.closest(`.${PILL}, .${CHIP}`);
|
|
41762
|
+
if (!target) return;
|
|
41763
|
+
selectToken(target);
|
|
41764
|
+
if (!disabled && target.classList.contains(CHIP) && e.detail >= 2) {
|
|
41765
|
+
openEditor(target);
|
|
41766
|
+
}
|
|
41767
|
+
},
|
|
41768
|
+
[disabled, openEditor, selectToken]
|
|
41769
|
+
);
|
|
41770
|
+
const handleCopy = (0, import_react188.useCallback)(
|
|
41771
|
+
(e, cut = false) => {
|
|
41772
|
+
const sel = window.getSelection();
|
|
41773
|
+
if (!sel || sel.rangeCount === 0) return;
|
|
41774
|
+
const holder = document.createElement("div");
|
|
41775
|
+
holder.appendChild(sel.getRangeAt(0).cloneContents());
|
|
41776
|
+
const token = selected.current;
|
|
41777
|
+
const text = serialize(holder) || (token && fieldRef.current?.contains(token) && sel.containsNode(token, true) ? token.getAttribute("data-value") ?? "" : "");
|
|
41778
|
+
if (!text) return;
|
|
41779
|
+
e.preventDefault();
|
|
41780
|
+
e.clipboardData.setData("text/plain", text);
|
|
41781
|
+
if (cut && !disabled) {
|
|
41782
|
+
if (serialize(holder)) sel.getRangeAt(0).deleteContents();
|
|
41783
|
+
else token?.remove();
|
|
41784
|
+
commit();
|
|
41785
|
+
}
|
|
41786
|
+
},
|
|
41787
|
+
[commit, disabled]
|
|
41788
|
+
);
|
|
41789
|
+
const handlePaste = (0, import_react188.useCallback)(
|
|
41790
|
+
(e) => {
|
|
41791
|
+
e.preventDefault();
|
|
41792
|
+
const text = e.clipboardData.getData("text/plain");
|
|
41793
|
+
if (!text) return;
|
|
41794
|
+
document.execCommand("insertText", false, text);
|
|
41795
|
+
const next = serialize(fieldRef.current);
|
|
41796
|
+
fromSelf.current = false;
|
|
41797
|
+
onChange(next);
|
|
41524
41798
|
},
|
|
41525
|
-
[
|
|
41799
|
+
[onChange]
|
|
41800
|
+
);
|
|
41801
|
+
const handleKeyDown = (0, import_react188.useCallback)(
|
|
41802
|
+
(e) => {
|
|
41803
|
+
if (e.key !== "Enter" || !e.metaKey) return;
|
|
41804
|
+
const sel = window.getSelection();
|
|
41805
|
+
const node = sel?.anchorNode;
|
|
41806
|
+
const chip = node instanceof HTMLElement ? node.closest(`.${CHIP}`) : node?.parentElement?.closest(`.${CHIP}`) ?? null;
|
|
41807
|
+
if (chip) {
|
|
41808
|
+
e.preventDefault();
|
|
41809
|
+
openEditor(chip);
|
|
41810
|
+
}
|
|
41811
|
+
},
|
|
41812
|
+
[openEditor]
|
|
41526
41813
|
);
|
|
41527
41814
|
const handleDragOver = (0, import_react188.useCallback)(
|
|
41528
41815
|
(e) => {
|
|
@@ -41549,42 +41836,244 @@ function ExpressionField({
|
|
|
41549
41836
|
},
|
|
41550
41837
|
[disabled, insert]
|
|
41551
41838
|
);
|
|
41552
|
-
const
|
|
41553
|
-
|
|
41554
|
-
|
|
41555
|
-
|
|
41556
|
-
|
|
41839
|
+
const handleBlur = (0, import_react188.useCallback)(() => {
|
|
41840
|
+
const el = fieldRef.current;
|
|
41841
|
+
if (!el) return;
|
|
41842
|
+
const next = serialize(el);
|
|
41843
|
+
if (next !== value) {
|
|
41844
|
+
fromSelf.current = true;
|
|
41845
|
+
onChange(next);
|
|
41846
|
+
}
|
|
41847
|
+
if (!editing) render(next);
|
|
41848
|
+
}, [editing, onChange, render, value]);
|
|
41557
41849
|
return /* @__PURE__ */ (0, import_jsx_runtime174.jsxs)("div", { className: cn("ods-expression-field", className), style, children: [
|
|
41558
41850
|
label && /* @__PURE__ */ (0, import_jsx_runtime174.jsx)("label", { className: "ods-expression-field__label", htmlFor: fieldId, children: label }),
|
|
41559
|
-
/* @__PURE__ */ (0, import_jsx_runtime174.
|
|
41560
|
-
|
|
41561
|
-
|
|
41562
|
-
|
|
41563
|
-
|
|
41564
|
-
|
|
41565
|
-
|
|
41566
|
-
|
|
41567
|
-
|
|
41568
|
-
|
|
41569
|
-
|
|
41570
|
-
|
|
41571
|
-
|
|
41572
|
-
|
|
41573
|
-
|
|
41574
|
-
|
|
41575
|
-
|
|
41576
|
-
|
|
41577
|
-
|
|
41578
|
-
|
|
41579
|
-
|
|
41580
|
-
|
|
41581
|
-
|
|
41582
|
-
|
|
41583
|
-
|
|
41584
|
-
|
|
41851
|
+
/* @__PURE__ */ (0, import_jsx_runtime174.jsxs)("div", { className: "ods-expression-field__body", children: [
|
|
41852
|
+
/* @__PURE__ */ (0, import_jsx_runtime174.jsx)(
|
|
41853
|
+
"div",
|
|
41854
|
+
{
|
|
41855
|
+
id: fieldId,
|
|
41856
|
+
ref: fieldRef,
|
|
41857
|
+
role: "textbox",
|
|
41858
|
+
"aria-multiline": "true",
|
|
41859
|
+
"aria-label": ariaLabel ?? (typeof label === "string" ? label : void 0),
|
|
41860
|
+
"aria-describedby": helperText ? helperId : void 0,
|
|
41861
|
+
"aria-disabled": disabled || void 0,
|
|
41862
|
+
contentEditable: !disabled,
|
|
41863
|
+
suppressContentEditableWarning: true,
|
|
41864
|
+
"data-placeholder": placeholder,
|
|
41865
|
+
className: cn(
|
|
41866
|
+
"ods-expression-field__input",
|
|
41867
|
+
disabled && "ods-expression-field__input--disabled"
|
|
41868
|
+
),
|
|
41869
|
+
style: { minHeight: `${Math.max(1, rows) * 1.5 + 1}rem` },
|
|
41870
|
+
onInput: commit,
|
|
41871
|
+
onBlur: handleBlur,
|
|
41872
|
+
onClick: handleClick,
|
|
41873
|
+
onKeyDown: handleKeyDown,
|
|
41874
|
+
onCopy: handleCopy,
|
|
41875
|
+
onCut: (e) => handleCopy(e, true),
|
|
41876
|
+
onPaste: handlePaste,
|
|
41877
|
+
onDragOver: handleDragOver,
|
|
41878
|
+
onDragLeave: handleDragLeave,
|
|
41879
|
+
onDrop: handleDrop
|
|
41880
|
+
}
|
|
41881
|
+
),
|
|
41882
|
+
editing && /* @__PURE__ */ (0, import_jsx_runtime174.jsx)(
|
|
41883
|
+
CallEditor,
|
|
41884
|
+
{
|
|
41885
|
+
raw: editing.raw,
|
|
41886
|
+
rect: editing.rect,
|
|
41887
|
+
nameOf: callLabel,
|
|
41888
|
+
labelFor,
|
|
41889
|
+
resolveCall,
|
|
41890
|
+
onCancel: () => setEditing(null),
|
|
41891
|
+
onApply: applyEditor
|
|
41892
|
+
}
|
|
41893
|
+
)
|
|
41894
|
+
] }),
|
|
41585
41895
|
helperText && /* @__PURE__ */ (0, import_jsx_runtime174.jsx)("span", { id: helperId, className: "ods-expression-field__helper", children: helperText })
|
|
41586
41896
|
] });
|
|
41587
41897
|
}
|
|
41898
|
+
function CallEditor({
|
|
41899
|
+
raw,
|
|
41900
|
+
rect,
|
|
41901
|
+
nameOf,
|
|
41902
|
+
labelFor,
|
|
41903
|
+
resolveCall,
|
|
41904
|
+
onApply,
|
|
41905
|
+
onCancel
|
|
41906
|
+
}) {
|
|
41907
|
+
const [draft, setDraft] = (0, import_react188.useState)(raw);
|
|
41908
|
+
const [path, setPath] = (0, import_react188.useState)([]);
|
|
41909
|
+
const [rawMode, setRawMode] = (0, import_react188.useState)(false);
|
|
41910
|
+
const panelRef = (0, import_react188.useRef)(null);
|
|
41911
|
+
(0, import_react188.useEffect)(() => {
|
|
41912
|
+
const onKey = (e) => {
|
|
41913
|
+
if (e.key === "Escape") onCancel();
|
|
41914
|
+
};
|
|
41915
|
+
document.addEventListener("keydown", onKey);
|
|
41916
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
41917
|
+
}, [onCancel]);
|
|
41918
|
+
const current = callAt(draft, path);
|
|
41919
|
+
const signature = current ? resolveCall?.(current.name) : void 0;
|
|
41920
|
+
const names = signature?.args ?? [];
|
|
41921
|
+
const args = current ? current.args.length ? current.args : [""] : [];
|
|
41922
|
+
const crumbs = [];
|
|
41923
|
+
for (let i = 0; i <= path.length; i++) {
|
|
41924
|
+
const at = callAt(draft, path.slice(0, i));
|
|
41925
|
+
if (at) crumbs.push({ label: nameOf(at.name), path: path.slice(0, i) });
|
|
41926
|
+
}
|
|
41927
|
+
const setArg = (index, next) => setDraft((d) => replaceArgument(d, [...path, index], next));
|
|
41928
|
+
const dropInto = (index) => (e) => {
|
|
41929
|
+
const dropped = e.dataTransfer.getData("application/x-fx-insert");
|
|
41930
|
+
if (!dropped) return;
|
|
41931
|
+
e.preventDefault();
|
|
41932
|
+
e.stopPropagation();
|
|
41933
|
+
const kind = e.dataTransfer.getData("application/x-fx-type");
|
|
41934
|
+
const bare = dropped.startsWith("{{") && dropped.endsWith("}}") ? dropped.slice(2, -2).trim() : dropped;
|
|
41935
|
+
setDraft((d) => replaceArgument(d, [...path, index], bare));
|
|
41936
|
+
if (kind === "function" || parseCall(bare)) setPath((pth) => [...pth, index]);
|
|
41937
|
+
};
|
|
41938
|
+
return /* @__PURE__ */ (0, import_jsx_runtime174.jsxs)(
|
|
41939
|
+
"div",
|
|
41940
|
+
{
|
|
41941
|
+
ref: panelRef,
|
|
41942
|
+
className: "ods-expression-field__editor",
|
|
41943
|
+
style: { top: rect.top, left: rect.left, width: rect.width },
|
|
41944
|
+
role: "dialog",
|
|
41945
|
+
"aria-label": `Edit ${crumbs[0]?.label ?? "function"}`,
|
|
41946
|
+
children: [
|
|
41947
|
+
/* @__PURE__ */ (0, import_jsx_runtime174.jsxs)("div", { className: "ods-expression-field__editor-head", children: [
|
|
41948
|
+
/* @__PURE__ */ (0, import_jsx_runtime174.jsx)("nav", { className: "ods-expression-field__editor-crumbs", "aria-label": "Nested functions", children: crumbs.map((crumb, i) => /* @__PURE__ */ (0, import_jsx_runtime174.jsxs)("span", { children: [
|
|
41949
|
+
i > 0 && /* @__PURE__ */ (0, import_jsx_runtime174.jsx)("span", { "aria-hidden": "true", children: " \u203A " }),
|
|
41950
|
+
/* @__PURE__ */ (0, import_jsx_runtime174.jsx)(
|
|
41951
|
+
"button",
|
|
41952
|
+
{
|
|
41953
|
+
type: "button",
|
|
41954
|
+
disabled: i === crumbs.length - 1,
|
|
41955
|
+
onClick: () => setPath(crumb.path),
|
|
41956
|
+
children: crumb.label
|
|
41957
|
+
}
|
|
41958
|
+
)
|
|
41959
|
+
] }, crumb.path.join(".") || "root")) }),
|
|
41960
|
+
/* @__PURE__ */ (0, import_jsx_runtime174.jsx)(
|
|
41961
|
+
"button",
|
|
41962
|
+
{
|
|
41963
|
+
type: "button",
|
|
41964
|
+
className: "ods-expression-field__editor-link",
|
|
41965
|
+
onClick: () => setRawMode((r) => !r),
|
|
41966
|
+
children: rawMode ? "Back to fields" : "Edit as text"
|
|
41967
|
+
}
|
|
41968
|
+
)
|
|
41969
|
+
] }),
|
|
41970
|
+
rawMode ? /* @__PURE__ */ (0, import_jsx_runtime174.jsx)(
|
|
41971
|
+
"textarea",
|
|
41972
|
+
{
|
|
41973
|
+
className: "ods-expression-field__editor-raw",
|
|
41974
|
+
value: draft,
|
|
41975
|
+
rows: 3,
|
|
41976
|
+
"aria-label": "Expression",
|
|
41977
|
+
onChange: (e) => setDraft(e.target.value)
|
|
41978
|
+
}
|
|
41979
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime174.jsxs)("div", { className: "ods-expression-field__editor-rows", children: [
|
|
41980
|
+
args.map((arg, index) => {
|
|
41981
|
+
const kind = argumentKind(arg);
|
|
41982
|
+
const rowLabel = names[index] ?? `argument ${index + 1}`;
|
|
41983
|
+
const nested = kind === "call" ? parseCall(arg) : void 0;
|
|
41984
|
+
return (
|
|
41985
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
41986
|
+
/* @__PURE__ */ (0, import_jsx_runtime174.jsxs)("div", { className: "ods-expression-field__editor-row", children: [
|
|
41987
|
+
/* @__PURE__ */ (0, import_jsx_runtime174.jsx)("span", { id: `arg-${index}-label`, children: rowLabel }),
|
|
41988
|
+
nested ? /* @__PURE__ */ (0, import_jsx_runtime174.jsxs)(
|
|
41989
|
+
"div",
|
|
41990
|
+
{
|
|
41991
|
+
className: "ods-expression-field__editor-nested",
|
|
41992
|
+
onDragOver: (e) => {
|
|
41993
|
+
if (e.dataTransfer.types.includes("application/x-fx-insert")) e.preventDefault();
|
|
41994
|
+
},
|
|
41995
|
+
onDrop: dropInto(index),
|
|
41996
|
+
children: [
|
|
41997
|
+
/* @__PURE__ */ (0, import_jsx_runtime174.jsx)("code", { children: argSummary(arg, nameOf) }),
|
|
41998
|
+
/* @__PURE__ */ (0, import_jsx_runtime174.jsxs)("span", { children: [
|
|
41999
|
+
/* @__PURE__ */ (0, import_jsx_runtime174.jsx)("button", { type: "button", onClick: () => setPath([...path, index]), children: "Open" }),
|
|
42000
|
+
/* @__PURE__ */ (0, import_jsx_runtime174.jsx)("button", { type: "button", onClick: () => setArg(index, ""), "aria-label": `Clear ${rowLabel}`, children: "Clear" })
|
|
42001
|
+
] })
|
|
42002
|
+
]
|
|
42003
|
+
}
|
|
42004
|
+
) : kind === "reference" ? (
|
|
42005
|
+
// A reference is a value from somewhere else — named, never
|
|
42006
|
+
// its raw `ods://…` path, here as much as in the field.
|
|
42007
|
+
/* @__PURE__ */ (0, import_jsx_runtime174.jsxs)(
|
|
42008
|
+
"div",
|
|
42009
|
+
{
|
|
42010
|
+
className: "ods-expression-field__editor-ref",
|
|
42011
|
+
onDragOver: (e) => {
|
|
42012
|
+
if (e.dataTransfer.types.includes("application/x-fx-insert")) e.preventDefault();
|
|
42013
|
+
},
|
|
42014
|
+
onDrop: dropInto(index),
|
|
42015
|
+
children: [
|
|
42016
|
+
/* @__PURE__ */ (0, import_jsx_runtime174.jsx)(
|
|
42017
|
+
"span",
|
|
42018
|
+
{
|
|
42019
|
+
className: "ods-expression-field__pill",
|
|
42020
|
+
title: arg,
|
|
42021
|
+
"aria-labelledby": `arg-${index}-label`,
|
|
42022
|
+
children: labelFor(arg.startsWith("ods://") ? `{{${arg}}}` : arg)
|
|
42023
|
+
}
|
|
42024
|
+
),
|
|
42025
|
+
/* @__PURE__ */ (0, import_jsx_runtime174.jsx)("button", { type: "button", onClick: () => setArg(index, ""), "aria-label": `Clear ${rowLabel}`, children: "Clear" })
|
|
42026
|
+
]
|
|
42027
|
+
}
|
|
42028
|
+
)
|
|
42029
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime174.jsx)(
|
|
42030
|
+
"input",
|
|
42031
|
+
{
|
|
42032
|
+
"aria-labelledby": `arg-${index}-label`,
|
|
42033
|
+
value: arg,
|
|
42034
|
+
placeholder: 'a value, "text", or drag one in',
|
|
42035
|
+
"data-kind": kind,
|
|
42036
|
+
onChange: (e) => setArg(index, e.target.value),
|
|
42037
|
+
onDragOver: (e) => {
|
|
42038
|
+
if (e.dataTransfer.types.includes("application/x-fx-insert")) e.preventDefault();
|
|
42039
|
+
},
|
|
42040
|
+
onDrop: dropInto(index)
|
|
42041
|
+
}
|
|
42042
|
+
)
|
|
42043
|
+
] }, index)
|
|
42044
|
+
);
|
|
42045
|
+
}),
|
|
42046
|
+
/* @__PURE__ */ (0, import_jsx_runtime174.jsx)(
|
|
42047
|
+
"button",
|
|
42048
|
+
{
|
|
42049
|
+
type: "button",
|
|
42050
|
+
className: "ods-expression-field__editor-link",
|
|
42051
|
+
onClick: () => setDraft((d) => {
|
|
42052
|
+
const at = callAt(d, path);
|
|
42053
|
+
if (!at) return d;
|
|
42054
|
+
const next = [...at.args, '""'];
|
|
42055
|
+
return path.length === 0 ? formatCall(at.name, next) : replaceArgument(d, path, `${at.name}(${next.join(", ")})`);
|
|
42056
|
+
}),
|
|
42057
|
+
children: "+ argument"
|
|
42058
|
+
}
|
|
42059
|
+
)
|
|
42060
|
+
] }),
|
|
42061
|
+
/* @__PURE__ */ (0, import_jsx_runtime174.jsxs)("div", { className: "ods-expression-field__editor-foot", children: [
|
|
42062
|
+
/* @__PURE__ */ (0, import_jsx_runtime174.jsx)("button", { type: "button", onClick: onCancel, children: "Cancel" }),
|
|
42063
|
+
/* @__PURE__ */ (0, import_jsx_runtime174.jsx)(
|
|
42064
|
+
"button",
|
|
42065
|
+
{
|
|
42066
|
+
type: "button",
|
|
42067
|
+
className: "ods-expression-field__editor-primary",
|
|
42068
|
+
onClick: () => onApply(draft),
|
|
42069
|
+
children: "Apply"
|
|
42070
|
+
}
|
|
42071
|
+
)
|
|
42072
|
+
] })
|
|
42073
|
+
]
|
|
42074
|
+
}
|
|
42075
|
+
);
|
|
42076
|
+
}
|
|
41588
42077
|
|
|
41589
42078
|
// src/components/TopBar/TopBar.tsx
|
|
41590
42079
|
var import_jsx_runtime175 = require("react/jsx-runtime");
|