@sustaina/shared-ui 1.19.0 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +92 -2
- package/dist/index.d.ts +92 -2
- package/dist/index.js +1009 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +994 -24
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -5
package/dist/index.js
CHANGED
|
@@ -21,11 +21,17 @@ var SheetPrimitive = require('@radix-ui/react-dialog');
|
|
|
21
21
|
var i18n = require('i18next');
|
|
22
22
|
var reactI18next = require('react-i18next');
|
|
23
23
|
var zustand = require('zustand');
|
|
24
|
+
var StarterKit = require('@tiptap/starter-kit');
|
|
25
|
+
var react = require('@tiptap/react');
|
|
26
|
+
var ReactDOM = require('react-dom/client');
|
|
27
|
+
var core = require('@tiptap/core');
|
|
28
|
+
var Suggestion = require('@tiptap/suggestion');
|
|
29
|
+
var prosemirrorState = require('prosemirror-state');
|
|
24
30
|
var zod$1 = require('@hookform/resolvers/zod');
|
|
25
31
|
var sortable = require('@dnd-kit/sortable');
|
|
26
32
|
var utilities = require('@dnd-kit/utilities');
|
|
27
33
|
var TooltipPrimitive = require('@radix-ui/react-tooltip');
|
|
28
|
-
var core = require('@dnd-kit/core');
|
|
34
|
+
var core$1 = require('@dnd-kit/core');
|
|
29
35
|
var modifiers = require('@dnd-kit/modifiers');
|
|
30
36
|
var zod = require('zod');
|
|
31
37
|
var RadioGroupPrimitive = require('@radix-ui/react-radio-group');
|
|
@@ -83,6 +89,9 @@ var CheckboxPrimitive__namespace = /*#__PURE__*/_interopNamespace(CheckboxPrimit
|
|
|
83
89
|
var CollapsiblePrimitive__namespace = /*#__PURE__*/_interopNamespace(CollapsiblePrimitive);
|
|
84
90
|
var SheetPrimitive__namespace = /*#__PURE__*/_interopNamespace(SheetPrimitive);
|
|
85
91
|
var i18n__default = /*#__PURE__*/_interopDefault(i18n);
|
|
92
|
+
var StarterKit__default = /*#__PURE__*/_interopDefault(StarterKit);
|
|
93
|
+
var ReactDOM__default = /*#__PURE__*/_interopDefault(ReactDOM);
|
|
94
|
+
var Suggestion__default = /*#__PURE__*/_interopDefault(Suggestion);
|
|
86
95
|
var TooltipPrimitive__namespace = /*#__PURE__*/_interopNamespace(TooltipPrimitive);
|
|
87
96
|
var RadioGroupPrimitive__namespace = /*#__PURE__*/_interopNamespace(RadioGroupPrimitive);
|
|
88
97
|
var SeparatorPrimitive__namespace = /*#__PURE__*/_interopNamespace(SeparatorPrimitive);
|
|
@@ -5120,7 +5129,8 @@ function DialogAlert({
|
|
|
5120
5129
|
}, [onCancel, onOpenChange]);
|
|
5121
5130
|
const handleConfirm = React4.useCallback(() => {
|
|
5122
5131
|
onConfirm?.();
|
|
5123
|
-
|
|
5132
|
+
onOpenChange(false);
|
|
5133
|
+
}, [onConfirm, onOpenChange]);
|
|
5124
5134
|
return /* @__PURE__ */ jsxRuntime.jsx(Dialog2, { open, onOpenChange: persistent ? () => {
|
|
5125
5135
|
} : onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(DialogContent2, { className: "max-w-md", showCloseButton: !persistent, children: [
|
|
5126
5136
|
/* @__PURE__ */ jsxRuntime.jsxs(DialogHeader2, { children: [
|
|
@@ -5382,6 +5392,989 @@ var getDialogAlertControls = () => ({
|
|
|
5382
5392
|
closeDialogAlert,
|
|
5383
5393
|
openErrorDialogAlert
|
|
5384
5394
|
});
|
|
5395
|
+
function LoadingOverlay({
|
|
5396
|
+
className,
|
|
5397
|
+
fullscreen = true,
|
|
5398
|
+
spinnerClassName,
|
|
5399
|
+
...props
|
|
5400
|
+
}) {
|
|
5401
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5402
|
+
"div",
|
|
5403
|
+
{
|
|
5404
|
+
"data-slot": "loading-overlay",
|
|
5405
|
+
className: cn(
|
|
5406
|
+
fullscreen ? "fixed z-100" : "absolute z-10",
|
|
5407
|
+
"inset-0 flex items-center justify-center transition-opacity duration-300",
|
|
5408
|
+
className
|
|
5409
|
+
),
|
|
5410
|
+
...props,
|
|
5411
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Spinner, { className: cn("size-50", spinnerClassName) })
|
|
5412
|
+
}
|
|
5413
|
+
);
|
|
5414
|
+
}
|
|
5415
|
+
|
|
5416
|
+
// src/components/formulaEditor/constants/formulaOperation.ts
|
|
5417
|
+
var defaultOperators = [
|
|
5418
|
+
{ value: "(", label: "(" },
|
|
5419
|
+
{ value: ")", label: ")" },
|
|
5420
|
+
{ value: "%", label: "%" },
|
|
5421
|
+
{ value: "&", label: "&" },
|
|
5422
|
+
{ value: "+", label: "+" },
|
|
5423
|
+
{ value: "-", label: "-" },
|
|
5424
|
+
{ value: "*", label: "\xD7" },
|
|
5425
|
+
{ value: "/", label: "\xF7" },
|
|
5426
|
+
{ value: "=", label: "=" },
|
|
5427
|
+
{ value: "\u2260", label: "\u2260" },
|
|
5428
|
+
{ value: "#NULL", label: "#NULL" }
|
|
5429
|
+
];
|
|
5430
|
+
var defaultOperatorShortcuts = {
|
|
5431
|
+
"(": "(",
|
|
5432
|
+
")": ")",
|
|
5433
|
+
"%": "%",
|
|
5434
|
+
"&": "&",
|
|
5435
|
+
"+": "+",
|
|
5436
|
+
"-": "-",
|
|
5437
|
+
"*": "*",
|
|
5438
|
+
"/": "/",
|
|
5439
|
+
"=": "=",
|
|
5440
|
+
"\u2260": "\u2260",
|
|
5441
|
+
"#NULL": "#NULL"
|
|
5442
|
+
};
|
|
5443
|
+
var DEFAULT_DEBOUNCE = 200;
|
|
5444
|
+
function useKeyboardNavigation(itemsLength, onSelect, isLocked) {
|
|
5445
|
+
const [selectedIndex, setSelectedIndex] = React4.useState(0);
|
|
5446
|
+
React4.useEffect(() => {
|
|
5447
|
+
const handler = (event) => {
|
|
5448
|
+
if (event.key === "ArrowDown") {
|
|
5449
|
+
event.preventDefault();
|
|
5450
|
+
setSelectedIndex((prev) => (prev + 1) % Math.max(itemsLength, 1));
|
|
5451
|
+
return;
|
|
5452
|
+
}
|
|
5453
|
+
if (event.key === "ArrowUp") {
|
|
5454
|
+
event.preventDefault();
|
|
5455
|
+
setSelectedIndex((prev) => (prev - 1 + Math.max(itemsLength, 1)) % Math.max(itemsLength, 1));
|
|
5456
|
+
return;
|
|
5457
|
+
}
|
|
5458
|
+
if (event.key === "Enter" || event.key === "Tab") {
|
|
5459
|
+
event.preventDefault();
|
|
5460
|
+
if (!isLocked) onSelect(selectedIndex);
|
|
5461
|
+
}
|
|
5462
|
+
};
|
|
5463
|
+
window.addEventListener("keydown", handler);
|
|
5464
|
+
return () => window.removeEventListener("keydown", handler);
|
|
5465
|
+
}, [itemsLength, onSelect, selectedIndex, isLocked]);
|
|
5466
|
+
return [selectedIndex, setSelectedIndex];
|
|
5467
|
+
}
|
|
5468
|
+
function useDropdownPosition(clientRect, itemsCount) {
|
|
5469
|
+
const [rect, setRect] = React4.useState(null);
|
|
5470
|
+
const [style, setStyle] = React4.useState({});
|
|
5471
|
+
const ref = React4.useRef(null);
|
|
5472
|
+
React4.useEffect(() => {
|
|
5473
|
+
if (!clientRect) return;
|
|
5474
|
+
const update = () => {
|
|
5475
|
+
const nextRect = clientRect();
|
|
5476
|
+
if (nextRect) setRect(nextRect);
|
|
5477
|
+
};
|
|
5478
|
+
update();
|
|
5479
|
+
window.addEventListener("scroll", update, true);
|
|
5480
|
+
window.addEventListener("resize", update);
|
|
5481
|
+
const resizeObserver = new ResizeObserver(update);
|
|
5482
|
+
resizeObserver.observe(document.body);
|
|
5483
|
+
return () => {
|
|
5484
|
+
window.removeEventListener("scroll", update, true);
|
|
5485
|
+
window.removeEventListener("resize", update);
|
|
5486
|
+
resizeObserver.disconnect();
|
|
5487
|
+
};
|
|
5488
|
+
}, [clientRect]);
|
|
5489
|
+
React4.useLayoutEffect(() => {
|
|
5490
|
+
if (!rect || !ref.current) return;
|
|
5491
|
+
const dropdown = ref.current;
|
|
5492
|
+
const dropdownRect = dropdown.getBoundingClientRect();
|
|
5493
|
+
const viewportHeight = window.innerHeight;
|
|
5494
|
+
const viewportWidth = window.innerWidth;
|
|
5495
|
+
let top = rect.bottom + window.scrollY + 6;
|
|
5496
|
+
let left = rect.left + window.scrollX;
|
|
5497
|
+
if (top + dropdownRect.height > viewportHeight + window.scrollY) {
|
|
5498
|
+
top = rect.top + window.scrollY - dropdownRect.height - 6;
|
|
5499
|
+
}
|
|
5500
|
+
if (left + dropdownRect.width > viewportWidth + window.scrollX) {
|
|
5501
|
+
left = Math.max(viewportWidth + window.scrollX - dropdownRect.width - 8, 0);
|
|
5502
|
+
}
|
|
5503
|
+
setStyle({ position: "absolute", zIndex: 50, top, left });
|
|
5504
|
+
}, [rect, itemsCount]);
|
|
5505
|
+
return { ref, style, rect };
|
|
5506
|
+
}
|
|
5507
|
+
var SuggestionList = ({
|
|
5508
|
+
clientRect,
|
|
5509
|
+
command,
|
|
5510
|
+
fetchItems,
|
|
5511
|
+
mapItem,
|
|
5512
|
+
normalizeToken,
|
|
5513
|
+
debounceMs = DEFAULT_DEBOUNCE,
|
|
5514
|
+
query
|
|
5515
|
+
}) => {
|
|
5516
|
+
const [items, setItems] = React4.useState([]);
|
|
5517
|
+
const [isLoading, setIsLoading] = React4.useState(false);
|
|
5518
|
+
const fetchId = React4.useRef(0);
|
|
5519
|
+
const debounceHandle = React4.useRef(null);
|
|
5520
|
+
const itemRefs = React4.useRef([]);
|
|
5521
|
+
const { ref, style, rect } = useDropdownPosition(clientRect, items.length);
|
|
5522
|
+
const normalizedMap = React4.useMemo(() => mapItem, [mapItem]);
|
|
5523
|
+
const normalizeItem = React4.useMemo(() => normalizeToken, [normalizeToken]);
|
|
5524
|
+
React4.useEffect(() => {
|
|
5525
|
+
const runFetch = (input) => {
|
|
5526
|
+
fetchId.current += 1;
|
|
5527
|
+
const currentId = fetchId.current;
|
|
5528
|
+
if (debounceHandle.current) clearTimeout(debounceHandle.current);
|
|
5529
|
+
debounceHandle.current = window.setTimeout(async () => {
|
|
5530
|
+
setIsLoading(true);
|
|
5531
|
+
try {
|
|
5532
|
+
const results = await fetchItems(input);
|
|
5533
|
+
if (fetchId.current !== currentId) return;
|
|
5534
|
+
const mapped = Array.isArray(results) ? results.map((item) => normalizeItem(normalizedMap(item))) : [];
|
|
5535
|
+
setItems(mapped);
|
|
5536
|
+
} catch {
|
|
5537
|
+
if (fetchId.current === currentId) {
|
|
5538
|
+
setItems([]);
|
|
5539
|
+
}
|
|
5540
|
+
} finally {
|
|
5541
|
+
if (fetchId.current === currentId) {
|
|
5542
|
+
setIsLoading(false);
|
|
5543
|
+
}
|
|
5544
|
+
}
|
|
5545
|
+
}, debounceMs);
|
|
5546
|
+
};
|
|
5547
|
+
runFetch(query ?? "");
|
|
5548
|
+
return () => {
|
|
5549
|
+
if (debounceHandle.current) {
|
|
5550
|
+
clearTimeout(debounceHandle.current);
|
|
5551
|
+
}
|
|
5552
|
+
fetchId.current += 1;
|
|
5553
|
+
};
|
|
5554
|
+
}, [query, fetchItems, normalizedMap, normalizeItem, debounceMs]);
|
|
5555
|
+
const handleSelect = React4.useMemo(
|
|
5556
|
+
() => (item) => {
|
|
5557
|
+
if (isLoading) return;
|
|
5558
|
+
command(item);
|
|
5559
|
+
},
|
|
5560
|
+
[command, isLoading]
|
|
5561
|
+
);
|
|
5562
|
+
const [selectedIndex, setSelectedIndex] = useKeyboardNavigation(
|
|
5563
|
+
items.length,
|
|
5564
|
+
(index) => {
|
|
5565
|
+
if (!items.length) return;
|
|
5566
|
+
handleSelect(items[index]);
|
|
5567
|
+
},
|
|
5568
|
+
isLoading
|
|
5569
|
+
);
|
|
5570
|
+
React4.useEffect(() => {
|
|
5571
|
+
setSelectedIndex(0);
|
|
5572
|
+
}, [items, setSelectedIndex]);
|
|
5573
|
+
React4.useEffect(() => {
|
|
5574
|
+
const element = itemRefs.current[selectedIndex];
|
|
5575
|
+
if (element) element.scrollIntoView({ block: "nearest" });
|
|
5576
|
+
}, [selectedIndex]);
|
|
5577
|
+
if (!rect) return null;
|
|
5578
|
+
const showEmptyState = !isLoading && items.length === 0;
|
|
5579
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5580
|
+
"div",
|
|
5581
|
+
{
|
|
5582
|
+
ref,
|
|
5583
|
+
style,
|
|
5584
|
+
className: "w-80 max-h-60 overflow-y-auto rounded-lg border bg-white p-1 shadow-lg",
|
|
5585
|
+
children: [
|
|
5586
|
+
isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: "Loading suggestions\u2026" }),
|
|
5587
|
+
showEmptyState && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: "No matches" }),
|
|
5588
|
+
items.map((item, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5589
|
+
"button",
|
|
5590
|
+
{
|
|
5591
|
+
ref: (el) => {
|
|
5592
|
+
itemRefs.current[idx] = el;
|
|
5593
|
+
},
|
|
5594
|
+
type: "button",
|
|
5595
|
+
onClick: () => handleSelect(item),
|
|
5596
|
+
onMouseMove: () => setSelectedIndex(idx),
|
|
5597
|
+
className: cn(
|
|
5598
|
+
"flex w-full items-center justify-between rounded-md px-3 py-2 text-left text-sm",
|
|
5599
|
+
selectedIndex === idx ? "bg-muted" : "hover:bg-muted",
|
|
5600
|
+
isLoading && "pointer-events-none opacity-60"
|
|
5601
|
+
),
|
|
5602
|
+
disabled: isLoading,
|
|
5603
|
+
children: [
|
|
5604
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: item.label ?? item.code }),
|
|
5605
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
5606
|
+
item.prefix ?? "",
|
|
5607
|
+
item.code ?? ""
|
|
5608
|
+
] })
|
|
5609
|
+
]
|
|
5610
|
+
},
|
|
5611
|
+
item.id
|
|
5612
|
+
))
|
|
5613
|
+
]
|
|
5614
|
+
}
|
|
5615
|
+
);
|
|
5616
|
+
};
|
|
5617
|
+
var DISALLOWED_MARKS = ["bold", "italic", "link"];
|
|
5618
|
+
var SUGGESTION_DEBOUNCE = 200;
|
|
5619
|
+
var DEFAULT_CHIP_CLASS = "outline-1 outline-muted bg-muted/40 text-foreground";
|
|
5620
|
+
var TokenView = ({ node, editor, getPos }) => {
|
|
5621
|
+
const [isFocused, setIsFocused] = React4__namespace.default.useState(false);
|
|
5622
|
+
React4__namespace.default.useEffect(() => {
|
|
5623
|
+
const handler = () => {
|
|
5624
|
+
const { from, to } = editor.state.selection;
|
|
5625
|
+
const position = getPos();
|
|
5626
|
+
if (position >= from && position + node.nodeSize <= to) {
|
|
5627
|
+
setIsFocused(true);
|
|
5628
|
+
} else {
|
|
5629
|
+
setIsFocused(false);
|
|
5630
|
+
}
|
|
5631
|
+
};
|
|
5632
|
+
editor.on("selectionUpdate", handler);
|
|
5633
|
+
return () => {
|
|
5634
|
+
editor.off("selectionUpdate", handler);
|
|
5635
|
+
};
|
|
5636
|
+
}, [editor, getPos, node.nodeSize]);
|
|
5637
|
+
const remove = () => {
|
|
5638
|
+
const pos = getPos();
|
|
5639
|
+
editor.chain().focus().deleteRange({ from: pos, to: pos + node.nodeSize }).run();
|
|
5640
|
+
};
|
|
5641
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5642
|
+
react.NodeViewWrapper,
|
|
5643
|
+
{
|
|
5644
|
+
as: "span",
|
|
5645
|
+
className: cn(
|
|
5646
|
+
"inline-flex items-center gap-1 rounded-sm px-2 py-0.5 mx-2 text-sm font-medium",
|
|
5647
|
+
DEFAULT_CHIP_CLASS,
|
|
5648
|
+
node.attrs.chipClassName,
|
|
5649
|
+
isFocused && "ring-2 ring-offset-1 ring-ring"
|
|
5650
|
+
),
|
|
5651
|
+
children: [
|
|
5652
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: node.attrs.label ?? node.attrs.code ?? node.attrs.rawValue }),
|
|
5653
|
+
(node.attrs.code || node.attrs.prefix) && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-normal opacity-80", children: [
|
|
5654
|
+
"(",
|
|
5655
|
+
node.attrs.prefix,
|
|
5656
|
+
node.attrs.code ?? "",
|
|
5657
|
+
")"
|
|
5658
|
+
] }),
|
|
5659
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5660
|
+
"button",
|
|
5661
|
+
{
|
|
5662
|
+
type: "button",
|
|
5663
|
+
onClick: remove,
|
|
5664
|
+
className: "ml-1 cursor-pointer rounded-sm px-1 text-xs leading-none hover:bg-sus-primary-1/30",
|
|
5665
|
+
"aria-label": `Remove ${node.attrs.label ?? node.attrs.code ?? "token"}`,
|
|
5666
|
+
children: "\xD7"
|
|
5667
|
+
}
|
|
5668
|
+
)
|
|
5669
|
+
]
|
|
5670
|
+
}
|
|
5671
|
+
);
|
|
5672
|
+
};
|
|
5673
|
+
function normalizeTokenAttrs(attrs, config) {
|
|
5674
|
+
const idSource = attrs.id ?? attrs.code ?? attrs.label ?? Math.random().toString(36).slice(2);
|
|
5675
|
+
const prefix = attrs.prefix ?? config.prefix;
|
|
5676
|
+
const suffix = attrs.code ?? attrs.label ?? "";
|
|
5677
|
+
const outputType = attrs.outputType ?? config.outputType ?? config.type;
|
|
5678
|
+
return {
|
|
5679
|
+
...attrs,
|
|
5680
|
+
id: String(idSource),
|
|
5681
|
+
type: config.type,
|
|
5682
|
+
prefix,
|
|
5683
|
+
chipClassName: attrs.chipClassName ?? config.chipClassName,
|
|
5684
|
+
rawValue: attrs.rawValue ?? (suffix ? `${prefix}${suffix}` : prefix),
|
|
5685
|
+
outputType
|
|
5686
|
+
};
|
|
5687
|
+
}
|
|
5688
|
+
var isPlainTextContext = (state, range) => {
|
|
5689
|
+
const { doc, schema } = state;
|
|
5690
|
+
const $from = doc.resolve(range.from);
|
|
5691
|
+
if ($from.parent.type.name !== "paragraph") return false;
|
|
5692
|
+
const marks = $from.marks();
|
|
5693
|
+
if (marks.some((mark) => DISALLOWED_MARKS.includes(mark.type.name))) return false;
|
|
5694
|
+
const linkMark = schema.marks.link;
|
|
5695
|
+
if (linkMark && linkMark.isInSet(marks)) return false;
|
|
5696
|
+
const nodeBefore = $from.nodeBefore;
|
|
5697
|
+
if (nodeBefore && nodeBefore.type.name === "token") return false;
|
|
5698
|
+
const nodeAfter = $from.nodeAfter;
|
|
5699
|
+
if (nodeAfter && nodeAfter.type.name === "token") return false;
|
|
5700
|
+
return true;
|
|
5701
|
+
};
|
|
5702
|
+
function suggestionPlugin({ config, onSelect, nodeName, editor, register }) {
|
|
5703
|
+
const pluginKey = new prosemirrorState.PluginKey(`formula-token-${config.type}-${config.prefix}`);
|
|
5704
|
+
const fetchItems = config.fetchItems ?? (async () => []);
|
|
5705
|
+
const mapItem = config.mapItem ?? ((item) => item);
|
|
5706
|
+
const normalizeForConfig = (attrs) => normalizeTokenAttrs(attrs, config);
|
|
5707
|
+
register({ key: pluginKey, config });
|
|
5708
|
+
return Suggestion__default.default({
|
|
5709
|
+
editor,
|
|
5710
|
+
char: config.prefix,
|
|
5711
|
+
pluginKey,
|
|
5712
|
+
allowSpaces: true,
|
|
5713
|
+
startOfLine: false,
|
|
5714
|
+
items: () => [],
|
|
5715
|
+
allow: ({ state, range }) => {
|
|
5716
|
+
if (editor.view.composing) return false;
|
|
5717
|
+
return isPlainTextContext(state, range);
|
|
5718
|
+
},
|
|
5719
|
+
render: () => {
|
|
5720
|
+
let reactRoot = null;
|
|
5721
|
+
let container = null;
|
|
5722
|
+
return {
|
|
5723
|
+
onStart: (props) => {
|
|
5724
|
+
container = document.createElement("div");
|
|
5725
|
+
document.body.appendChild(container);
|
|
5726
|
+
reactRoot = ReactDOM__default.default.createRoot(container);
|
|
5727
|
+
reactRoot.render(
|
|
5728
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5729
|
+
SuggestionList,
|
|
5730
|
+
{
|
|
5731
|
+
...props,
|
|
5732
|
+
editor,
|
|
5733
|
+
fetchItems,
|
|
5734
|
+
mapItem,
|
|
5735
|
+
normalizeToken: normalizeForConfig,
|
|
5736
|
+
debounceMs: SUGGESTION_DEBOUNCE
|
|
5737
|
+
}
|
|
5738
|
+
)
|
|
5739
|
+
);
|
|
5740
|
+
},
|
|
5741
|
+
onUpdate: (props) => {
|
|
5742
|
+
reactRoot?.render(
|
|
5743
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5744
|
+
SuggestionList,
|
|
5745
|
+
{
|
|
5746
|
+
...props,
|
|
5747
|
+
editor,
|
|
5748
|
+
fetchItems,
|
|
5749
|
+
mapItem,
|
|
5750
|
+
normalizeToken: normalizeForConfig,
|
|
5751
|
+
debounceMs: SUGGESTION_DEBOUNCE
|
|
5752
|
+
}
|
|
5753
|
+
)
|
|
5754
|
+
);
|
|
5755
|
+
},
|
|
5756
|
+
onKeyDown: ({ event }) => {
|
|
5757
|
+
if (event.key === "Escape" || event.key === "Enter" || event.key === "Tab") {
|
|
5758
|
+
event.preventDefault();
|
|
5759
|
+
return true;
|
|
5760
|
+
}
|
|
5761
|
+
return false;
|
|
5762
|
+
},
|
|
5763
|
+
onExit: () => {
|
|
5764
|
+
reactRoot?.unmount();
|
|
5765
|
+
reactRoot = null;
|
|
5766
|
+
container?.remove();
|
|
5767
|
+
container = null;
|
|
5768
|
+
}
|
|
5769
|
+
};
|
|
5770
|
+
},
|
|
5771
|
+
command: ({ range, props, editor: editor2 }) => {
|
|
5772
|
+
const attrs = normalizeTokenAttrs(props, config);
|
|
5773
|
+
const nextChar = editor2.state.doc.textBetween(range.to, range.to + 1, "\n", "\n");
|
|
5774
|
+
const shouldInsertSpace = !nextChar || !/\s/.test(nextChar);
|
|
5775
|
+
const content = shouldInsertSpace ? [
|
|
5776
|
+
{ type: nodeName, attrs },
|
|
5777
|
+
{ type: "text", text: " " }
|
|
5778
|
+
] : { type: nodeName, attrs };
|
|
5779
|
+
editor2.chain().focus().insertContentAt(range, content).run();
|
|
5780
|
+
onSelect?.(attrs, config);
|
|
5781
|
+
}
|
|
5782
|
+
});
|
|
5783
|
+
}
|
|
5784
|
+
var createSuggestionMonitorPlugin = (registrations) => new prosemirrorState.Plugin({
|
|
5785
|
+
key: new prosemirrorState.PluginKey("formula-token-suggestion-guard"),
|
|
5786
|
+
view(editorView) {
|
|
5787
|
+
const ensureValid = () => {
|
|
5788
|
+
registrations.forEach(({ key, config }) => {
|
|
5789
|
+
const state = key.getState(editorView.state);
|
|
5790
|
+
if (!state?.active) return;
|
|
5791
|
+
const currentText = editorView.state.doc.textBetween(
|
|
5792
|
+
state.range.from,
|
|
5793
|
+
state.range.to,
|
|
5794
|
+
"\n",
|
|
5795
|
+
"\n"
|
|
5796
|
+
);
|
|
5797
|
+
if (!currentText || !currentText.startsWith(config.prefix)) {
|
|
5798
|
+
const tr = editorView.state.tr.setMeta(key, { exit: true });
|
|
5799
|
+
editorView.dispatch(tr);
|
|
5800
|
+
}
|
|
5801
|
+
});
|
|
5802
|
+
};
|
|
5803
|
+
ensureValid();
|
|
5804
|
+
return {
|
|
5805
|
+
update: () => ensureValid()
|
|
5806
|
+
};
|
|
5807
|
+
}
|
|
5808
|
+
});
|
|
5809
|
+
var createTokenSpacingPlugin = (nodeName) => new prosemirrorState.Plugin({
|
|
5810
|
+
key: new prosemirrorState.PluginKey(`formula-token-spacing-${nodeName}`),
|
|
5811
|
+
appendTransaction(transactions, oldState, newState) {
|
|
5812
|
+
if (!transactions.some((tr2) => tr2.docChanged)) return null;
|
|
5813
|
+
const insertions = [];
|
|
5814
|
+
newState.doc.descendants((node, pos) => {
|
|
5815
|
+
if (node.type.name !== nodeName) return;
|
|
5816
|
+
const beforePos = pos - 1;
|
|
5817
|
+
if (beforePos >= 0) {
|
|
5818
|
+
const beforeChar = newState.doc.textBetween(beforePos, pos, "\n", "\n");
|
|
5819
|
+
if (beforeChar && !/\s/.test(beforeChar)) {
|
|
5820
|
+
insertions.push({ pos, text: " " });
|
|
5821
|
+
}
|
|
5822
|
+
}
|
|
5823
|
+
const afterPos = pos + node.nodeSize;
|
|
5824
|
+
if (afterPos < newState.doc.content.size) {
|
|
5825
|
+
const afterChar = newState.doc.textBetween(afterPos, afterPos + 1, "\n", "\n");
|
|
5826
|
+
if (afterChar && !/\s/.test(afterChar)) {
|
|
5827
|
+
insertions.push({ pos: afterPos, text: " " });
|
|
5828
|
+
}
|
|
5829
|
+
}
|
|
5830
|
+
});
|
|
5831
|
+
if (!insertions.length) return null;
|
|
5832
|
+
const tr = newState.tr;
|
|
5833
|
+
insertions.sort((a, b) => b.pos - a.pos).forEach(({ pos, text }) => {
|
|
5834
|
+
tr.insertText(text, pos);
|
|
5835
|
+
});
|
|
5836
|
+
return tr;
|
|
5837
|
+
}
|
|
5838
|
+
});
|
|
5839
|
+
var Token = core.Node.create({
|
|
5840
|
+
name: "token",
|
|
5841
|
+
inline: true,
|
|
5842
|
+
group: "inline",
|
|
5843
|
+
atom: true,
|
|
5844
|
+
selectable: false,
|
|
5845
|
+
draggable: false,
|
|
5846
|
+
addAttributes() {
|
|
5847
|
+
return {
|
|
5848
|
+
type: { default: "" },
|
|
5849
|
+
id: { default: "" },
|
|
5850
|
+
code: { default: "" },
|
|
5851
|
+
label: { default: "" },
|
|
5852
|
+
prefix: { default: "" },
|
|
5853
|
+
rawValue: { default: "" },
|
|
5854
|
+
chipClassName: { default: "" },
|
|
5855
|
+
outputType: { default: "" }
|
|
5856
|
+
};
|
|
5857
|
+
},
|
|
5858
|
+
parseHTML() {
|
|
5859
|
+
return [{ tag: "token-chip" }];
|
|
5860
|
+
},
|
|
5861
|
+
renderHTML({ HTMLAttributes }) {
|
|
5862
|
+
return ["token-chip", core.mergeAttributes(HTMLAttributes), HTMLAttributes.label || ""];
|
|
5863
|
+
},
|
|
5864
|
+
addNodeView() {
|
|
5865
|
+
return react.ReactNodeViewRenderer(TokenView);
|
|
5866
|
+
},
|
|
5867
|
+
addProseMirrorPlugins() {
|
|
5868
|
+
const configs = this.options.configs ?? [];
|
|
5869
|
+
const registrations = [];
|
|
5870
|
+
const suggestionPlugins = configs.map(
|
|
5871
|
+
(config) => suggestionPlugin({
|
|
5872
|
+
config,
|
|
5873
|
+
onSelect: this.options.onSelect,
|
|
5874
|
+
nodeName: this.name,
|
|
5875
|
+
editor: this.editor,
|
|
5876
|
+
register: (entry) => registrations.push(entry)
|
|
5877
|
+
})
|
|
5878
|
+
);
|
|
5879
|
+
return [
|
|
5880
|
+
...suggestionPlugins,
|
|
5881
|
+
createTokenSpacingPlugin(this.name),
|
|
5882
|
+
createSuggestionMonitorPlugin(registrations)
|
|
5883
|
+
];
|
|
5884
|
+
}
|
|
5885
|
+
});
|
|
5886
|
+
var Operator = core.Node.create({
|
|
5887
|
+
name: "operator",
|
|
5888
|
+
inline: true,
|
|
5889
|
+
group: "inline",
|
|
5890
|
+
atom: true,
|
|
5891
|
+
selectable: false,
|
|
5892
|
+
addAttributes() {
|
|
5893
|
+
return {
|
|
5894
|
+
value: { default: null }
|
|
5895
|
+
};
|
|
5896
|
+
},
|
|
5897
|
+
parseHTML() {
|
|
5898
|
+
return [{ tag: "span[operator]" }];
|
|
5899
|
+
},
|
|
5900
|
+
renderHTML({ HTMLAttributes }) {
|
|
5901
|
+
return ["span", core.mergeAttributes({ operator: HTMLAttributes.value }), HTMLAttributes.value];
|
|
5902
|
+
},
|
|
5903
|
+
addKeyboardShortcuts() {
|
|
5904
|
+
const shortcuts = this.options.shortcuts ?? {};
|
|
5905
|
+
return Object.fromEntries(
|
|
5906
|
+
Object.entries(shortcuts).map(([key, value]) => [
|
|
5907
|
+
key,
|
|
5908
|
+
({ editor }) => {
|
|
5909
|
+
editor.chain().insertContent({ type: "operator", attrs: { value } }).insertContent(" ").run();
|
|
5910
|
+
return true;
|
|
5911
|
+
}
|
|
5912
|
+
])
|
|
5913
|
+
);
|
|
5914
|
+
}
|
|
5915
|
+
});
|
|
5916
|
+
|
|
5917
|
+
// src/components/formulaEditor/utils/parseFormulaToken.ts
|
|
5918
|
+
var DEFAULT_PARENTHESIS_MAP = {
|
|
5919
|
+
"(": ")",
|
|
5920
|
+
"[": "]",
|
|
5921
|
+
"{": "}"
|
|
5922
|
+
};
|
|
5923
|
+
var createSortedPrefixes = (prefixMap) => Object.keys(prefixMap).sort((a, b) => b.length - a.length);
|
|
5924
|
+
var findPrefixAt = (value, start, prefixes) => prefixes.find((prefix) => value.startsWith(prefix, start));
|
|
5925
|
+
var pushVariableAndInner = (tokens, value, prefixMap, prefixes) => {
|
|
5926
|
+
if (!value) return;
|
|
5927
|
+
tokens.push({ type: "variable", value });
|
|
5928
|
+
for (let cursor = 0; cursor < value.length; ) {
|
|
5929
|
+
const prefix = findPrefixAt(value, cursor, prefixes);
|
|
5930
|
+
if (!prefix) {
|
|
5931
|
+
cursor += 1;
|
|
5932
|
+
continue;
|
|
5933
|
+
}
|
|
5934
|
+
cursor += prefix.length;
|
|
5935
|
+
const rest = value.slice(cursor);
|
|
5936
|
+
const codeMatch = rest.match(/^\w+/);
|
|
5937
|
+
if (codeMatch) {
|
|
5938
|
+
tokens.push({ type: prefixMap[prefix], code: codeMatch[0] });
|
|
5939
|
+
cursor += codeMatch[0].length;
|
|
5940
|
+
}
|
|
5941
|
+
}
|
|
5942
|
+
};
|
|
5943
|
+
var buildPrefixMap = (configs) => {
|
|
5944
|
+
return configs.reduce((acc, config) => {
|
|
5945
|
+
const outputType = config.outputType ?? config.type;
|
|
5946
|
+
if (!acc[config.prefix]) acc[config.prefix] = outputType;
|
|
5947
|
+
return acc;
|
|
5948
|
+
}, {});
|
|
5949
|
+
};
|
|
5950
|
+
var tokenizeFormulaString = (raw, prefixMap) => {
|
|
5951
|
+
const segments = [];
|
|
5952
|
+
if (!raw) return segments;
|
|
5953
|
+
const prefixes = createSortedPrefixes(prefixMap);
|
|
5954
|
+
let buffer = "";
|
|
5955
|
+
const flushBuffer = () => {
|
|
5956
|
+
if (buffer) {
|
|
5957
|
+
segments.push({ kind: "text", value: buffer });
|
|
5958
|
+
buffer = "";
|
|
5959
|
+
}
|
|
5960
|
+
};
|
|
5961
|
+
for (let i = 0; i < raw.length; ) {
|
|
5962
|
+
const prefix = findPrefixAt(raw, i, prefixes);
|
|
5963
|
+
if (prefix) {
|
|
5964
|
+
const rest = raw.slice(i + prefix.length);
|
|
5965
|
+
const codeMatch = rest.match(/^\w+/);
|
|
5966
|
+
if (codeMatch) {
|
|
5967
|
+
flushBuffer();
|
|
5968
|
+
segments.push({ kind: "token", prefix, code: codeMatch[0] });
|
|
5969
|
+
i += prefix.length + codeMatch[0].length;
|
|
5970
|
+
continue;
|
|
5971
|
+
}
|
|
5972
|
+
}
|
|
5973
|
+
buffer += raw[i];
|
|
5974
|
+
i += 1;
|
|
5975
|
+
}
|
|
5976
|
+
flushBuffer();
|
|
5977
|
+
return segments;
|
|
5978
|
+
};
|
|
5979
|
+
var parseFormulaToToken = (text, prefixMap) => {
|
|
5980
|
+
const tokens = [];
|
|
5981
|
+
if (!text) return tokens;
|
|
5982
|
+
const prefixes = createSortedPrefixes(prefixMap);
|
|
5983
|
+
const leadingPrefix = findPrefixAt(text, 0, prefixes);
|
|
5984
|
+
if (!leadingPrefix) {
|
|
5985
|
+
pushVariableAndInner(tokens, text, prefixMap, prefixes);
|
|
5986
|
+
return tokens;
|
|
5987
|
+
}
|
|
5988
|
+
const afterPrefix = text.slice(leadingPrefix.length);
|
|
5989
|
+
const codeMatch = afterPrefix.match(/^\w+/);
|
|
5990
|
+
if (!codeMatch) {
|
|
5991
|
+
pushVariableAndInner(tokens, text, prefixMap, prefixes);
|
|
5992
|
+
return tokens;
|
|
5993
|
+
}
|
|
5994
|
+
tokens.push({ type: prefixMap[leadingPrefix], code: codeMatch[0] });
|
|
5995
|
+
const remaining = afterPrefix.slice(codeMatch[0].length);
|
|
5996
|
+
if (remaining) {
|
|
5997
|
+
let variablePart = remaining;
|
|
5998
|
+
if (remaining.startsWith(":") && remaining.includes("(")) {
|
|
5999
|
+
let depth = 0;
|
|
6000
|
+
let endIndex = -1;
|
|
6001
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
6002
|
+
if (remaining[i] === "(") depth += 1;
|
|
6003
|
+
else if (remaining[i] === ")") depth -= 1;
|
|
6004
|
+
if (depth === 0 && remaining[i] === ")") {
|
|
6005
|
+
endIndex = i;
|
|
6006
|
+
break;
|
|
6007
|
+
}
|
|
6008
|
+
}
|
|
6009
|
+
if (endIndex !== -1) {
|
|
6010
|
+
variablePart = remaining.slice(0, endIndex + 1);
|
|
6011
|
+
}
|
|
6012
|
+
}
|
|
6013
|
+
pushVariableAndInner(tokens, variablePart, prefixMap, prefixes);
|
|
6014
|
+
}
|
|
6015
|
+
return tokens;
|
|
6016
|
+
};
|
|
6017
|
+
var splitOperators = (value, allowedOperators) => {
|
|
6018
|
+
const result = [];
|
|
6019
|
+
if (!value) return result;
|
|
6020
|
+
const sortedOperators = [...allowedOperators].sort((a, b) => b.length - a.length);
|
|
6021
|
+
let buffer = "";
|
|
6022
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
6023
|
+
let matched = false;
|
|
6024
|
+
for (const operator of sortedOperators) {
|
|
6025
|
+
if (operator && value.startsWith(operator, i)) {
|
|
6026
|
+
if (buffer) {
|
|
6027
|
+
result.push({ type: "variable", value: buffer });
|
|
6028
|
+
buffer = "";
|
|
6029
|
+
}
|
|
6030
|
+
result.push({ type: "operator", value: operator });
|
|
6031
|
+
i += operator.length - 1;
|
|
6032
|
+
matched = true;
|
|
6033
|
+
break;
|
|
6034
|
+
}
|
|
6035
|
+
}
|
|
6036
|
+
if (!matched) buffer += value[i];
|
|
6037
|
+
}
|
|
6038
|
+
if (buffer) result.push({ type: "variable", value: buffer });
|
|
6039
|
+
return result;
|
|
6040
|
+
};
|
|
6041
|
+
var parseFormula = (editorJson, prefixMap, allowedOperators) => {
|
|
6042
|
+
const rawParts = [];
|
|
6043
|
+
const tokens = [];
|
|
6044
|
+
const traverse = (nodes2 = []) => {
|
|
6045
|
+
nodes2.forEach((node) => {
|
|
6046
|
+
if (!node) return;
|
|
6047
|
+
switch (node.type) {
|
|
6048
|
+
case "text": {
|
|
6049
|
+
const text = node.text ?? "";
|
|
6050
|
+
if (!text) return;
|
|
6051
|
+
rawParts.push(text);
|
|
6052
|
+
if (text.trim()) {
|
|
6053
|
+
tokens.push({ type: "variable", value: text });
|
|
6054
|
+
}
|
|
6055
|
+
break;
|
|
6056
|
+
}
|
|
6057
|
+
case "operator": {
|
|
6058
|
+
rawParts.push(node.attrs?.value ?? "");
|
|
6059
|
+
tokens.push({ type: "operator", value: node.attrs?.value ?? "" });
|
|
6060
|
+
break;
|
|
6061
|
+
}
|
|
6062
|
+
case "op-library": {
|
|
6063
|
+
rawParts.push(node.attrs?.value ?? "");
|
|
6064
|
+
tokens.push(...splitOperators(node.attrs?.value ?? "", allowedOperators));
|
|
6065
|
+
break;
|
|
6066
|
+
}
|
|
6067
|
+
case "token": {
|
|
6068
|
+
const attrs = node.attrs ?? {};
|
|
6069
|
+
const rawValue = attrs.rawValue ?? `${attrs.prefix ?? ""}${attrs.code ?? ""}`;
|
|
6070
|
+
rawParts.push(rawValue);
|
|
6071
|
+
const outputType = attrs.outputType ?? attrs.type ?? prefixMap[attrs.prefix ?? ""];
|
|
6072
|
+
tokens.push({ ...attrs, type: outputType });
|
|
6073
|
+
break;
|
|
6074
|
+
}
|
|
6075
|
+
default:
|
|
6076
|
+
if (node.content) traverse(node.content);
|
|
6077
|
+
}
|
|
6078
|
+
});
|
|
6079
|
+
};
|
|
6080
|
+
traverse(editorJson?.content ?? []);
|
|
6081
|
+
return { raw: rawParts.join(""), token: tokens };
|
|
6082
|
+
};
|
|
6083
|
+
var validateTokenPrefixes = (rawFormula, prefixMap) => {
|
|
6084
|
+
const prefixes = Object.keys(prefixMap);
|
|
6085
|
+
if (prefixes.length === 0) return { isValid: true };
|
|
6086
|
+
const missingTypes = /* @__PURE__ */ new Set();
|
|
6087
|
+
for (let i = 0; i < rawFormula.length; i += 1) {
|
|
6088
|
+
const prefix = prefixes.find((key) => rawFormula.startsWith(key, i));
|
|
6089
|
+
if (prefix) {
|
|
6090
|
+
const nextChar = rawFormula[i + prefix.length];
|
|
6091
|
+
if (!nextChar || !/\w/.test(nextChar)) {
|
|
6092
|
+
missingTypes.add(prefixMap[prefix]);
|
|
6093
|
+
}
|
|
6094
|
+
i += prefix.length;
|
|
6095
|
+
}
|
|
6096
|
+
}
|
|
6097
|
+
if (missingTypes.size > 0) {
|
|
6098
|
+
return {
|
|
6099
|
+
isValid: false,
|
|
6100
|
+
message: `Invalid token for ${Array.from(missingTypes).join(" and ")}`
|
|
6101
|
+
};
|
|
6102
|
+
}
|
|
6103
|
+
return { isValid: true };
|
|
6104
|
+
};
|
|
6105
|
+
var isValidParentheses = (input) => {
|
|
6106
|
+
const stack = [];
|
|
6107
|
+
const openers = Object.keys(DEFAULT_PARENTHESIS_MAP);
|
|
6108
|
+
const closers = Object.values(DEFAULT_PARENTHESIS_MAP);
|
|
6109
|
+
for (const char of input) {
|
|
6110
|
+
if (openers.includes(char)) {
|
|
6111
|
+
stack.push(char);
|
|
6112
|
+
} else if (closers.includes(char)) {
|
|
6113
|
+
const last = stack.pop();
|
|
6114
|
+
if (!last || DEFAULT_PARENTHESIS_MAP[last] !== char) {
|
|
6115
|
+
return { valid: false };
|
|
6116
|
+
}
|
|
6117
|
+
}
|
|
6118
|
+
}
|
|
6119
|
+
if (stack.length > 0) {
|
|
6120
|
+
return { valid: false };
|
|
6121
|
+
}
|
|
6122
|
+
return { valid: true };
|
|
6123
|
+
};
|
|
6124
|
+
var mapTokensToOutput = (tokens, configs) => {
|
|
6125
|
+
const prefixLookup = /* @__PURE__ */ new Map();
|
|
6126
|
+
const typeLookup = /* @__PURE__ */ new Map();
|
|
6127
|
+
configs.forEach((config) => {
|
|
6128
|
+
prefixLookup.set(config.prefix, config);
|
|
6129
|
+
typeLookup.set(config.outputType ?? config.type, config);
|
|
6130
|
+
});
|
|
6131
|
+
return tokens.map((token) => {
|
|
6132
|
+
if (!token || typeof token !== "object") return token;
|
|
6133
|
+
const tokenPrefix = "prefix" in token ? token.prefix : void 0;
|
|
6134
|
+
const tokenType = "type" in token ? token.type : void 0;
|
|
6135
|
+
const config = (tokenPrefix ? prefixLookup.get(tokenPrefix) : void 0) ?? (tokenType ? typeLookup.get(String(tokenType)) : void 0);
|
|
6136
|
+
if (config?.mapOutput) {
|
|
6137
|
+
return config.mapOutput(token);
|
|
6138
|
+
}
|
|
6139
|
+
return token;
|
|
6140
|
+
});
|
|
6141
|
+
};
|
|
6142
|
+
var DEFAULT_TOKEN_CONFIGS = [
|
|
6143
|
+
{
|
|
6144
|
+
type: "position",
|
|
6145
|
+
prefix: "$",
|
|
6146
|
+
chipClassName: "text-black border border-sus-primary-1 outline-sus-primary-1",
|
|
6147
|
+
fetchItems: async () => []
|
|
6148
|
+
},
|
|
6149
|
+
{
|
|
6150
|
+
type: "impact",
|
|
6151
|
+
prefix: "#",
|
|
6152
|
+
chipClassName: "text-black border border-sus-primary-1 outline-sus-primary-1",
|
|
6153
|
+
fetchItems: async () => []
|
|
6154
|
+
}
|
|
6155
|
+
];
|
|
6156
|
+
var defaultMapItem = (item) => {
|
|
6157
|
+
const id = item?.id ?? item?.code ?? item?.label ?? Math.random().toString(36).slice(2);
|
|
6158
|
+
return {
|
|
6159
|
+
id: String(id),
|
|
6160
|
+
label: item?.label ?? item?.name ?? String(item?.code ?? item?.id ?? ""),
|
|
6161
|
+
code: item?.code ?? String(item?.id ?? "")
|
|
6162
|
+
};
|
|
6163
|
+
};
|
|
6164
|
+
var looksLikeHTML = (value) => /<\/?[a-z][\s\S]*>/i.test(value.trim());
|
|
6165
|
+
var tryParseJSON = (value) => {
|
|
6166
|
+
try {
|
|
6167
|
+
const parsed = JSON.parse(value);
|
|
6168
|
+
if (parsed && typeof parsed === "object") {
|
|
6169
|
+
return parsed;
|
|
6170
|
+
}
|
|
6171
|
+
} catch {
|
|
6172
|
+
}
|
|
6173
|
+
return null;
|
|
6174
|
+
};
|
|
6175
|
+
var buildDocFromRaw = (raw, prefixMap, configLookup) => {
|
|
6176
|
+
const lines = raw.split(/\r?\n/);
|
|
6177
|
+
let tokenCounter = 0;
|
|
6178
|
+
const paragraphs = lines.map((line) => {
|
|
6179
|
+
const segments = tokenizeFormulaString(line, prefixMap);
|
|
6180
|
+
const content = segments.map((segment) => {
|
|
6181
|
+
if (segment.kind === "text") {
|
|
6182
|
+
return segment.value ? { type: "text", text: segment.value } : null;
|
|
6183
|
+
}
|
|
6184
|
+
const config = configLookup.get(segment.prefix);
|
|
6185
|
+
const fallbackType = prefixMap[segment.prefix];
|
|
6186
|
+
const displayType = config?.type ?? fallbackType;
|
|
6187
|
+
const outputType = config?.outputType ?? fallbackType;
|
|
6188
|
+
if (!displayType && !outputType) {
|
|
6189
|
+
return { type: "text", text: `${segment.prefix}${segment.code}` };
|
|
6190
|
+
}
|
|
6191
|
+
const attrs = {
|
|
6192
|
+
id: `token-${tokenCounter++}`,
|
|
6193
|
+
type: displayType ?? outputType ?? "token",
|
|
6194
|
+
code: segment.code,
|
|
6195
|
+
prefix: segment.prefix,
|
|
6196
|
+
rawValue: `${segment.prefix}${segment.code}`,
|
|
6197
|
+
chipClassName: config?.chipClassName,
|
|
6198
|
+
label: segment.code,
|
|
6199
|
+
outputType: outputType ?? displayType
|
|
6200
|
+
};
|
|
6201
|
+
return { type: "token", attrs };
|
|
6202
|
+
}).filter((node) => Boolean(node));
|
|
6203
|
+
return {
|
|
6204
|
+
type: "paragraph",
|
|
6205
|
+
content: content.length > 0 ? content : [{ type: "text", text: "" }]
|
|
6206
|
+
};
|
|
6207
|
+
});
|
|
6208
|
+
return { type: "doc", content: paragraphs };
|
|
6209
|
+
};
|
|
6210
|
+
var FormulaEditor = ({
|
|
6211
|
+
value,
|
|
6212
|
+
disabled,
|
|
6213
|
+
loading = false,
|
|
6214
|
+
className,
|
|
6215
|
+
editorClassName,
|
|
6216
|
+
errorMessage,
|
|
6217
|
+
tokenConfigs,
|
|
6218
|
+
operators = defaultOperators,
|
|
6219
|
+
operatorShortcuts = defaultOperatorShortcuts,
|
|
6220
|
+
onChange,
|
|
6221
|
+
onSelectSuggestion,
|
|
6222
|
+
field,
|
|
6223
|
+
fieldState
|
|
6224
|
+
}) => {
|
|
6225
|
+
const [isExpanded, setIsExpanded] = React4.useState(false);
|
|
6226
|
+
const lastEmittedValueRef = React4.useRef(null);
|
|
6227
|
+
const ignorePropValueRef = React4.useRef(false);
|
|
6228
|
+
const normalizedConfigs = React4.useMemo(() => {
|
|
6229
|
+
const configsToUse = tokenConfigs?.length ? tokenConfigs : DEFAULT_TOKEN_CONFIGS;
|
|
6230
|
+
return configsToUse.map((config) => ({
|
|
6231
|
+
...config,
|
|
6232
|
+
fetchItems: config.fetchItems ?? (async () => []),
|
|
6233
|
+
mapItem: config.mapItem ?? ((item) => ({ ...item, ...defaultMapItem(item) })),
|
|
6234
|
+
outputType: config.outputType ?? config.type
|
|
6235
|
+
}));
|
|
6236
|
+
}, [tokenConfigs]);
|
|
6237
|
+
const prefixMap = React4.useMemo(() => buildPrefixMap(normalizedConfigs), [normalizedConfigs]);
|
|
6238
|
+
const configLookup = React4.useMemo(() => {
|
|
6239
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
6240
|
+
normalizedConfigs.forEach((config) => {
|
|
6241
|
+
lookup.set(config.prefix, config);
|
|
6242
|
+
});
|
|
6243
|
+
return lookup;
|
|
6244
|
+
}, [normalizedConfigs]);
|
|
6245
|
+
const allowedOperators = React4.useMemo(() => operators.map((operator) => operator.value), [operators]);
|
|
6246
|
+
const displayError = errorMessage ?? fieldState?.error?.message;
|
|
6247
|
+
const hasError = Boolean(displayError);
|
|
6248
|
+
const isInteractionDisabled = Boolean(disabled || loading);
|
|
6249
|
+
const convertValueToContent = React4.useCallback(
|
|
6250
|
+
(input) => {
|
|
6251
|
+
if (!input) return "";
|
|
6252
|
+
const trimmed = input.trim();
|
|
6253
|
+
if (!trimmed) return "";
|
|
6254
|
+
const parsedJSON = tryParseJSON(trimmed);
|
|
6255
|
+
if (parsedJSON && parsedJSON.type === "doc") {
|
|
6256
|
+
return parsedJSON;
|
|
6257
|
+
}
|
|
6258
|
+
if (looksLikeHTML(trimmed)) {
|
|
6259
|
+
return input;
|
|
6260
|
+
}
|
|
6261
|
+
return buildDocFromRaw(input, prefixMap, configLookup);
|
|
6262
|
+
},
|
|
6263
|
+
[configLookup, prefixMap]
|
|
6264
|
+
);
|
|
6265
|
+
const resolvedContent = React4.useMemo(() => convertValueToContent(value), [convertValueToContent, value]);
|
|
6266
|
+
const extensions = React4.useMemo(
|
|
6267
|
+
() => [
|
|
6268
|
+
StarterKit__default.default.configure({ bold: false, italic: false }),
|
|
6269
|
+
Token.configure({ configs: normalizedConfigs, onSelect: onSelectSuggestion }),
|
|
6270
|
+
Operator.configure({ shortcuts: operatorShortcuts })
|
|
6271
|
+
],
|
|
6272
|
+
[normalizedConfigs, onSelectSuggestion, operatorShortcuts]
|
|
6273
|
+
);
|
|
6274
|
+
const editor = react.useEditor({
|
|
6275
|
+
extensions,
|
|
6276
|
+
content: resolvedContent ?? "",
|
|
6277
|
+
onUpdate: ({ editor: nextEditor }) => {
|
|
6278
|
+
const { raw, token } = parseFormula(nextEditor.getJSON(), prefixMap, allowedOperators);
|
|
6279
|
+
const mappedTokens = mapTokensToOutput(token, normalizedConfigs);
|
|
6280
|
+
lastEmittedValueRef.current = raw;
|
|
6281
|
+
ignorePropValueRef.current = true;
|
|
6282
|
+
onChange?.(raw, mappedTokens);
|
|
6283
|
+
},
|
|
6284
|
+
editorProps: {
|
|
6285
|
+
attributes: {
|
|
6286
|
+
class: cn(
|
|
6287
|
+
isExpanded ? "min-h-[300px] max-h-[300px]" : "min-h-[150px] max-h-[150px]",
|
|
6288
|
+
hasError ? "border border-destructive" : "border focus-visible:border-ring",
|
|
6289
|
+
"w-full rounded-lg bg-white px-4 py-3",
|
|
6290
|
+
"overflow-y-auto whitespace-pre-wrap wrap-break-word focus:outline-none",
|
|
6291
|
+
isInteractionDisabled && "pointer-events-none opacity-60",
|
|
6292
|
+
editorClassName
|
|
6293
|
+
),
|
|
6294
|
+
...loading ? { "aria-busy": "true" } : {}
|
|
6295
|
+
}
|
|
6296
|
+
}
|
|
6297
|
+
});
|
|
6298
|
+
React4.useEffect(() => {
|
|
6299
|
+
if (!editor) return;
|
|
6300
|
+
editor.setEditable(!isInteractionDisabled);
|
|
6301
|
+
}, [editor, isInteractionDisabled]);
|
|
6302
|
+
React4.useEffect(() => {
|
|
6303
|
+
if (!editor || resolvedContent === void 0) return;
|
|
6304
|
+
if (ignorePropValueRef.current && typeof value === "string" && value === lastEmittedValueRef.current) {
|
|
6305
|
+
ignorePropValueRef.current = false;
|
|
6306
|
+
return;
|
|
6307
|
+
}
|
|
6308
|
+
ignorePropValueRef.current = false;
|
|
6309
|
+
if (typeof resolvedContent === "string") {
|
|
6310
|
+
const currentHTML = editor.getHTML();
|
|
6311
|
+
if (resolvedContent !== currentHTML) {
|
|
6312
|
+
editor.commands.setContent(resolvedContent, { emitUpdate: false });
|
|
6313
|
+
}
|
|
6314
|
+
return;
|
|
6315
|
+
}
|
|
6316
|
+
const currentJSON = JSON.stringify(editor.getJSON());
|
|
6317
|
+
const nextJSON = JSON.stringify(resolvedContent);
|
|
6318
|
+
if (currentJSON !== nextJSON) {
|
|
6319
|
+
editor.commands.setContent(resolvedContent, { emitUpdate: false });
|
|
6320
|
+
}
|
|
6321
|
+
}, [editor, resolvedContent, value]);
|
|
6322
|
+
const insertOperator = (operator) => {
|
|
6323
|
+
editor?.chain().focus().insertContent({ type: "operator", attrs: { value: operator } }).insertContent(" ").run();
|
|
6324
|
+
};
|
|
6325
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("w-full space-y-2", className), children: [
|
|
6326
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6327
|
+
"div",
|
|
6328
|
+
{
|
|
6329
|
+
ref: field?.ref,
|
|
6330
|
+
onBlur: field?.onBlur,
|
|
6331
|
+
tabIndex: 0,
|
|
6332
|
+
className: "relative",
|
|
6333
|
+
"aria-busy": loading,
|
|
6334
|
+
onFocus: () => {
|
|
6335
|
+
if (editor && !editor.isFocused) {
|
|
6336
|
+
editor.chain().focus().run();
|
|
6337
|
+
}
|
|
6338
|
+
},
|
|
6339
|
+
children: [
|
|
6340
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.EditorContent, { editor }),
|
|
6341
|
+
loading && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6342
|
+
LoadingOverlay,
|
|
6343
|
+
{
|
|
6344
|
+
fullscreen: false,
|
|
6345
|
+
className: "rounded-lg bg-white/80 backdrop-blur-sm",
|
|
6346
|
+
spinnerClassName: "size-6 text-sus-blue-3"
|
|
6347
|
+
}
|
|
6348
|
+
),
|
|
6349
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6350
|
+
Button,
|
|
6351
|
+
{
|
|
6352
|
+
type: "button",
|
|
6353
|
+
variant: "ghost",
|
|
6354
|
+
size: "icon",
|
|
6355
|
+
className: "absolute bottom-2 right-4 h-6 w-6 rounded-full bg-white shadow",
|
|
6356
|
+
disabled: isInteractionDisabled,
|
|
6357
|
+
onClick: () => setIsExpanded((prev) => !prev),
|
|
6358
|
+
children: isExpanded ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minimize2, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2, { className: "h-4 w-4" })
|
|
6359
|
+
}
|
|
6360
|
+
)
|
|
6361
|
+
]
|
|
6362
|
+
}
|
|
6363
|
+
),
|
|
6364
|
+
hasError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", role: "alert", children: displayError }),
|
|
6365
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap justify-end gap-2 py-2", children: operators.map((operator) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
6366
|
+
Button,
|
|
6367
|
+
{
|
|
6368
|
+
type: "button",
|
|
6369
|
+
onClick: () => insertOperator(operator.value),
|
|
6370
|
+
className: "min-w-10 rounded-sm px-3 bg-sus-blue-3",
|
|
6371
|
+
disabled: isInteractionDisabled,
|
|
6372
|
+
children: operator.label
|
|
6373
|
+
},
|
|
6374
|
+
operator.value
|
|
6375
|
+
)) })
|
|
6376
|
+
] });
|
|
6377
|
+
};
|
|
5385
6378
|
function TooltipProvider({
|
|
5386
6379
|
delayDuration = 0,
|
|
5387
6380
|
...props
|
|
@@ -5597,7 +6590,7 @@ var GridSettingsModal = ({
|
|
|
5597
6590
|
onSaveColumns({ ordering, visibility, pinning }, data.columns);
|
|
5598
6591
|
}
|
|
5599
6592
|
};
|
|
5600
|
-
const sensors = core.useSensors(core.useSensor(core.PointerSensor, { activationConstraint: { distance: 5 } }));
|
|
6593
|
+
const sensors = core$1.useSensors(core$1.useSensor(core$1.PointerSensor, { activationConstraint: { distance: 5 } }));
|
|
5601
6594
|
function handleDragEnd(event) {
|
|
5602
6595
|
const { active, over } = event;
|
|
5603
6596
|
if (!over || active.id === over.id) return;
|
|
@@ -5661,10 +6654,10 @@ var GridSettingsModal = ({
|
|
|
5661
6654
|
fields[0]?.fieldId
|
|
5662
6655
|
) }),
|
|
5663
6656
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3 [&_button:not([disabled])]:cursor-pointer", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5664
|
-
core.DndContext,
|
|
6657
|
+
core$1.DndContext,
|
|
5665
6658
|
{
|
|
5666
6659
|
sensors,
|
|
5667
|
-
collisionDetection: core.closestCenter,
|
|
6660
|
+
collisionDetection: core$1.closestCenter,
|
|
5668
6661
|
modifiers: [modifiers.restrictToParentElement, modifiers.restrictToVerticalAxis],
|
|
5669
6662
|
onDragStart: () => setIsDragging(true),
|
|
5670
6663
|
onDragEnd: (event) => {
|
|
@@ -5781,26 +6774,6 @@ var useGridSettingsStore = zustand.create(
|
|
|
5781
6774
|
})
|
|
5782
6775
|
);
|
|
5783
6776
|
var useGridSettingsStore_default = useGridSettingsStore;
|
|
5784
|
-
function LoadingOverlay({
|
|
5785
|
-
className,
|
|
5786
|
-
fullscreen = true,
|
|
5787
|
-
spinnerClassName,
|
|
5788
|
-
...props
|
|
5789
|
-
}) {
|
|
5790
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5791
|
-
"div",
|
|
5792
|
-
{
|
|
5793
|
-
"data-slot": "loading-overlay",
|
|
5794
|
-
className: cn(
|
|
5795
|
-
fullscreen ? "fixed z-100" : "absolute z-10",
|
|
5796
|
-
"inset-0 flex items-center justify-center transition-opacity duration-300",
|
|
5797
|
-
className
|
|
5798
|
-
),
|
|
5799
|
-
...props,
|
|
5800
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(Spinner, { className: cn("size-50", spinnerClassName) })
|
|
5801
|
-
}
|
|
5802
|
-
);
|
|
5803
|
-
}
|
|
5804
6777
|
var DEVICE_SIZES = [320, 420, 640, 768, 1024, 1280, 1536, 1920];
|
|
5805
6778
|
var IMAGE_SIZES = [16, 32, 48, 64, 96, 128, 256, 384];
|
|
5806
6779
|
var defaultLoader = ({ src }) => src;
|
|
@@ -8819,6 +9792,7 @@ exports.FormField = FormField;
|
|
|
8819
9792
|
exports.FormItem = FormItem;
|
|
8820
9793
|
exports.FormLabel = FormLabel;
|
|
8821
9794
|
exports.FormMessage = FormMessage;
|
|
9795
|
+
exports.FormulaEditor = FormulaEditor;
|
|
8822
9796
|
exports.GridSettingsModal = GridSettingsModal_default;
|
|
8823
9797
|
exports.HeaderCell = HeaderCell_default;
|
|
8824
9798
|
exports.Image = Image2;
|
|
@@ -8906,20 +9880,29 @@ exports.TooltipTrigger = TooltipTrigger;
|
|
|
8906
9880
|
exports.Truncated = truncated_default;
|
|
8907
9881
|
exports.UI = ui_exports;
|
|
8908
9882
|
exports.booleanToSelectValue = booleanToSelectValue;
|
|
9883
|
+
exports.buildPrefixMap = buildPrefixMap;
|
|
8909
9884
|
exports.buttonVariants = buttonVariants;
|
|
8910
9885
|
exports.cn = cn;
|
|
8911
9886
|
exports.compareAlphanumeric = compareAlphanumeric;
|
|
8912
9887
|
exports.debounce = debounce;
|
|
9888
|
+
exports.defaultOperatorShortcuts = defaultOperatorShortcuts;
|
|
9889
|
+
exports.defaultOperators = defaultOperators;
|
|
8913
9890
|
exports.formatISODate = formatISODate;
|
|
8914
9891
|
exports.getDialogAlertControls = getDialogAlertControls;
|
|
8915
9892
|
exports.getDialogTemplates = getDialogTemplates;
|
|
8916
9893
|
exports.inputVariants = inputVariants;
|
|
8917
9894
|
exports.isDefined = isDefined;
|
|
8918
9895
|
exports.isEmptyObject = isEmptyObject;
|
|
9896
|
+
exports.isValidParentheses = isValidParentheses;
|
|
9897
|
+
exports.mapTokensToOutput = mapTokensToOutput;
|
|
9898
|
+
exports.parseFormula = parseFormula;
|
|
9899
|
+
exports.parseFormulaToToken = parseFormulaToToken;
|
|
8919
9900
|
exports.selectValueToBoolean = selectValueToBoolean;
|
|
8920
9901
|
exports.spinnerVariants = spinnerVariants;
|
|
9902
|
+
exports.splitOperators = splitOperators;
|
|
8921
9903
|
exports.stripNullishObject = stripNullishObject;
|
|
8922
9904
|
exports.throttle = throttle;
|
|
9905
|
+
exports.tokenizeFormulaString = tokenizeFormulaString;
|
|
8923
9906
|
exports.useFormField = useFormField;
|
|
8924
9907
|
exports.useGridSettingsStore = useGridSettingsStore_default;
|
|
8925
9908
|
exports.useHover = useHover_default;
|
|
@@ -8930,5 +9913,6 @@ exports.usePreventPageLeaveStore = usePreventPageLeaveStore_default;
|
|
|
8930
9913
|
exports.useScreenSize = useScreenSize_default;
|
|
8931
9914
|
exports.useSidebar = useSidebar;
|
|
8932
9915
|
exports.useTruncated = useTruncated_default;
|
|
9916
|
+
exports.validateTokenPrefixes = validateTokenPrefixes;
|
|
8933
9917
|
//# sourceMappingURL=index.js.map
|
|
8934
9918
|
//# sourceMappingURL=index.js.map
|