@papernote/ui 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -23,6 +23,21 @@ function _interopNamespaceDefault(e) {
23
23
  return Object.freeze(n);
24
24
  }
25
25
 
26
+ function _mergeNamespaces(n, m) {
27
+ m.forEach(function (e) {
28
+ e && typeof e !== 'string' && !Array.isArray(e) && Object.keys(e).forEach(function (k) {
29
+ if (k !== 'default' && !(k in n)) {
30
+ var d = Object.getOwnPropertyDescriptor(e, k);
31
+ Object.defineProperty(n, k, d.get ? d : {
32
+ enumerable: true,
33
+ get: function () { return e[k]; }
34
+ });
35
+ }
36
+ });
37
+ });
38
+ return Object.freeze(n);
39
+ }
40
+
26
41
  var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
27
42
 
28
43
  /**
@@ -765,13 +780,15 @@ const optionSizeClasses = {
765
780
  * ```
766
781
  */
767
782
  const Select = React.forwardRef((props, ref) => {
768
- const { options = [], groups = [], value, onChange, placeholder = 'Select an option', searchable = false, disabled = false, label, helperText, error, loading = false, clearable = false, creatable = false, onCreateOption, virtualized = false, virtualHeight = '300px', virtualItemHeight = 42, size = 'md', mobileMode = 'auto', } = props;
783
+ const { options = [], groups = [], value, onChange, placeholder = 'Select an option', searchable = false, disabled = false, label, helperText, error, loading = false, clearable = false, creatable = false, onCreateOption, virtualized = false, virtualHeight = '300px', virtualItemHeight = 42, size = 'md', mobileMode = 'auto', usePortal = true, } = props;
769
784
  const [isOpen, setIsOpen] = React.useState(false);
770
785
  const [searchQuery, setSearchQuery] = React.useState('');
771
786
  const [scrollTop, setScrollTop] = React.useState(0);
772
787
  const [activeDescendant] = React.useState(undefined);
788
+ const [dropdownPosition, setDropdownPosition] = React.useState(null);
773
789
  const selectRef = React.useRef(null);
774
790
  const buttonRef = React.useRef(null);
791
+ const dropdownRef = React.useRef(null);
775
792
  const searchInputRef = React.useRef(null);
776
793
  const mobileSearchInputRef = React.useRef(null);
777
794
  const listRef = React.useRef(null);
@@ -859,7 +876,11 @@ const Select = React.forwardRef((props, ref) => {
859
876
  if (useMobileSheet)
860
877
  return; // Mobile sheet handles its own closing
861
878
  const handleClickOutside = (event) => {
862
- if (selectRef.current && !selectRef.current.contains(event.target)) {
879
+ const target = event.target;
880
+ // Check if click is outside both the select trigger and the dropdown portal
881
+ const isOutsideSelect = selectRef.current && !selectRef.current.contains(target);
882
+ const isOutsideDropdown = dropdownRef.current && !dropdownRef.current.contains(target);
883
+ if (isOutsideSelect && isOutsideDropdown) {
863
884
  setIsOpen(false);
864
885
  setSearchQuery('');
865
886
  }
@@ -883,6 +904,46 @@ const Select = React.forwardRef((props, ref) => {
883
904
  }
884
905
  }
885
906
  }, [isOpen, searchable, useMobileSheet]);
907
+ // Calculate dropdown position with collision detection and scroll/resize handling
908
+ React.useEffect(() => {
909
+ if (!isOpen || useMobileSheet || !usePortal) {
910
+ setDropdownPosition(null);
911
+ return;
912
+ }
913
+ const updatePosition = () => {
914
+ if (!buttonRef.current)
915
+ return;
916
+ const rect = buttonRef.current.getBoundingClientRect();
917
+ const dropdownHeight = 240; // max-h-60 = 15rem = 240px
918
+ const gap = 2; // Small gap to visually connect to trigger
919
+ const viewportHeight = window.innerHeight;
920
+ // Check if there's enough space below
921
+ const spaceBelow = viewportHeight - rect.bottom;
922
+ const spaceAbove = rect.top;
923
+ const hasSpaceBelow = spaceBelow >= dropdownHeight + gap;
924
+ const hasSpaceAbove = spaceAbove >= dropdownHeight + gap;
925
+ // Prefer bottom placement, flip to top if not enough space below but enough above
926
+ const placement = hasSpaceBelow || !hasSpaceAbove ? 'bottom' : 'top';
927
+ const top = placement === 'bottom'
928
+ ? rect.bottom + gap
929
+ : rect.top - dropdownHeight - gap;
930
+ setDropdownPosition({
931
+ top,
932
+ left: rect.left,
933
+ width: rect.width,
934
+ placement,
935
+ });
936
+ };
937
+ // Initial position calculation
938
+ updatePosition();
939
+ // Listen for scroll events on all scrollable ancestors
940
+ window.addEventListener('scroll', updatePosition, true);
941
+ window.addEventListener('resize', updatePosition);
942
+ return () => {
943
+ window.removeEventListener('scroll', updatePosition, true);
944
+ window.removeEventListener('resize', updatePosition);
945
+ };
946
+ }, [isOpen, useMobileSheet, usePortal]);
886
947
  // Lock body scroll when mobile sheet is open
887
948
  React.useEffect(() => {
888
949
  if (useMobileSheet && isOpen) {
@@ -959,16 +1020,22 @@ const Select = React.forwardRef((props, ref) => {
959
1020
  ${disabled ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer'}
960
1021
  `, "aria-labelledby": label ? labelId : undefined, "aria-invalid": error ? 'true' : undefined, "aria-describedby": error ? errorId : (helperText ? helperTextId : undefined), children: [jsxRuntime.jsx("option", { value: "", disabled: true, children: placeholder }), options.map((opt) => (jsxRuntime.jsx("option", { value: opt.value, disabled: opt.disabled, children: opt.label }, opt.value))), groups.map((group) => (jsxRuntime.jsx("optgroup", { label: group.label, children: group.options.map((opt) => (jsxRuntime.jsx("option", { value: opt.value, disabled: opt.disabled, children: opt.label }, opt.value))) }, group.label)))] }), jsxRuntime.jsx(lucideReact.ChevronDown, { className: "absolute right-3 top-1/2 -translate-y-1/2 h-5 w-5 text-ink-500 pointer-events-none" })] }), error && (jsxRuntime.jsx("p", { id: errorId, className: "mt-2 text-xs text-error-600", role: "alert", "aria-live": "assertive", children: error })), helperText && !error && (jsxRuntime.jsx("p", { id: helperTextId, className: "mt-2 text-xs text-ink-600", children: helperText }))] }));
961
1022
  }
962
- return (jsxRuntime.jsxs("div", { className: "w-full", children: [label && (jsxRuntime.jsx("label", { id: labelId, className: "label", children: label })), jsxRuntime.jsxs("div", { ref: selectRef, className: "relative", children: [jsxRuntime.jsxs("button", { ref: buttonRef, type: "button", onClick: () => !disabled && setIsOpen(!isOpen), disabled: disabled, className: `
1023
+ return (jsxRuntime.jsxs("div", { className: "w-full", children: [label && (jsxRuntime.jsx("label", { id: labelId, className: "label", children: label })), jsxRuntime.jsx("div", { ref: selectRef, className: "relative", children: jsxRuntime.jsxs("button", { ref: buttonRef, type: "button", onClick: () => !disabled && setIsOpen(!isOpen), disabled: disabled, className: `
963
1024
  input w-full flex items-center justify-between px-3
964
1025
  ${sizeClasses$a[effectiveSize]}
965
1026
  ${error ? 'border-error-400 focus:border-error-400 focus:ring-error-400' : ''}
966
1027
  ${disabled ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer'}
967
1028
  `, role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": listboxId, "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? placeholder : undefined, "aria-activedescendant": activeDescendant, "aria-invalid": error ? 'true' : undefined, "aria-describedby": error ? errorId : (helperText ? helperTextId : undefined), "aria-disabled": disabled, children: [jsxRuntime.jsxs("span", { className: `flex items-center gap-2 ${selectedOption ? 'text-ink-800' : 'text-ink-400'}`, children: [loading && jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin text-ink-500" }), !loading && selectedOption?.icon && jsxRuntime.jsx("span", { children: selectedOption.icon }), selectedOption ? selectedOption.label : placeholder] }), jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [clearable && value && (jsxRuntime.jsx("button", { type: "button", onClick: (e) => {
968
- e.stopPropagation();
969
- onChange?.('');
970
- setIsOpen(false);
971
- }, className: "text-ink-400 hover:text-ink-600 transition-colors p-0.5", "aria-label": "Clear selection", children: jsxRuntime.jsx(lucideReact.X, { className: `${effectiveSize === 'lg' ? 'h-5 w-5' : 'h-4 w-4'}` }) })), jsxRuntime.jsx(lucideReact.ChevronDown, { className: `${effectiveSize === 'lg' ? 'h-5 w-5' : 'h-4 w-4'} text-ink-500 transition-transform ${isOpen ? 'rotate-180' : ''}` })] })] }), isOpen && !useMobileSheet && (jsxRuntime.jsxs("div", { className: "absolute z-50 w-full mt-2 bg-white bg-subtle-grain rounded-lg shadow-lg border border-paper-200 max-h-60 overflow-hidden animate-fade-in", children: [searchable && (jsxRuntime.jsx("div", { className: "p-2 border-b border-paper-200", children: jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-ink-400" }), jsxRuntime.jsx("input", { ref: searchInputRef, type: "text", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), placeholder: "Search...", className: "w-full pl-9 pr-3 py-2 text-sm border border-paper-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-400 focus:border-accent-400", role: "searchbox", "aria-label": "Search options", "aria-autocomplete": "list", "aria-controls": listboxId })] }) })), jsxRuntime.jsx("div", { ref: listRef, id: listboxId, className: "overflow-y-auto", style: { maxHeight: useVirtualScrolling ? virtualHeight : '12rem' }, onScroll: (e) => useVirtualScrolling && setScrollTop(e.currentTarget.scrollTop), role: "listbox", "aria-label": "Available options", "aria-multiselectable": "false", children: renderOptionsContent(false) })] }))] }), isOpen && useMobileSheet && reactDom.createPortal(jsxRuntime.jsxs("div", { className: "fixed inset-0 z-50 flex items-end", onClick: (e) => e.target === e.currentTarget && handleClose(), role: "dialog", "aria-modal": "true", "aria-labelledby": label ? `mobile-${labelId}` : undefined, children: [jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/50 animate-fade-in" }), jsxRuntime.jsxs("div", { className: "relative w-full bg-white rounded-t-2xl shadow-2xl animate-slide-up max-h-[85vh] flex flex-col", style: { paddingBottom: 'env(safe-area-inset-bottom)' }, children: [jsxRuntime.jsx("div", { className: "py-3 cursor-grab", children: jsxRuntime.jsx("div", { className: "w-12 h-1.5 bg-ink-300 rounded-full mx-auto" }) }), jsxRuntime.jsxs("div", { className: "px-4 pb-3 border-b border-paper-200 flex items-center justify-between", children: [label && (jsxRuntime.jsx("h2", { id: `mobile-${labelId}`, className: "text-lg font-semibold text-ink-900", children: label })), !label && (jsxRuntime.jsx("h2", { className: "text-lg font-semibold text-ink-900", children: placeholder })), jsxRuntime.jsx("button", { onClick: handleClose, className: "text-ink-400 hover:text-ink-600 transition-colors p-2 -mr-2", "aria-label": "Close", children: jsxRuntime.jsx(lucideReact.X, { className: "h-5 w-5" }) })] }), searchable && (jsxRuntime.jsx("div", { className: "p-3 border-b border-paper-200", children: jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-4 top-1/2 -translate-y-1/2 h-5 w-5 text-ink-400" }), jsxRuntime.jsx("input", { ref: mobileSearchInputRef, type: "text", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), placeholder: "Search...", inputMode: "search", enterKeyHint: "search", className: "w-full pl-12 pr-4 py-3 text-base border border-paper-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-accent-400 focus:border-accent-400", role: "searchbox", "aria-label": "Search options" })] }) })), jsxRuntime.jsx("div", { id: listboxId, className: "overflow-y-auto flex-1", role: "listbox", "aria-label": "Available options", "aria-multiselectable": "false", children: renderOptionsContent(true) })] })] }), document.body), error && (jsxRuntime.jsx("p", { id: errorId, className: "mt-2 text-xs text-error-600", role: "alert", "aria-live": "assertive", children: error })), helperText && !error && (jsxRuntime.jsx("p", { id: helperTextId, className: "mt-2 text-xs text-ink-600", children: helperText }))] }));
1029
+ e.stopPropagation();
1030
+ onChange?.('');
1031
+ setIsOpen(false);
1032
+ }, className: "text-ink-400 hover:text-ink-600 transition-colors p-0.5", "aria-label": "Clear selection", children: jsxRuntime.jsx(lucideReact.X, { className: `${effectiveSize === 'lg' ? 'h-5 w-5' : 'h-4 w-4'}` }) })), jsxRuntime.jsx(lucideReact.ChevronDown, { className: `${effectiveSize === 'lg' ? 'h-5 w-5' : 'h-4 w-4'} text-ink-500 transition-transform ${isOpen ? 'rotate-180' : ''}` })] })] }) }), isOpen && !useMobileSheet && (usePortal ? dropdownPosition : true) && (usePortal ? reactDom.createPortal(jsxRuntime.jsxs("div", { ref: dropdownRef, className: `fixed z-[9999] bg-white bg-subtle-grain rounded-lg shadow-lg border border-paper-200 max-h-60 overflow-hidden animate-fade-in ${dropdownPosition?.placement === 'top' ? 'origin-bottom' : 'origin-top'}`, style: {
1033
+ top: dropdownPosition.top,
1034
+ left: dropdownPosition.left,
1035
+ width: dropdownPosition.width,
1036
+ }, children: [searchable && (jsxRuntime.jsx("div", { className: "p-2 border-b border-paper-200", children: jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-ink-400" }), jsxRuntime.jsx("input", { ref: searchInputRef, type: "text", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), placeholder: "Search...", className: "w-full pl-9 pr-3 py-2 text-sm border border-paper-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-400 focus:border-accent-400", role: "searchbox", "aria-label": "Search options", "aria-autocomplete": "list", "aria-controls": listboxId })] }) })), jsxRuntime.jsx("div", { ref: listRef, id: listboxId, className: "overflow-y-auto", style: { maxHeight: useVirtualScrolling ? virtualHeight : '12rem' }, onScroll: (e) => useVirtualScrolling && setScrollTop(e.currentTarget.scrollTop), role: "listbox", "aria-label": "Available options", "aria-multiselectable": "false", children: renderOptionsContent(false) })] }), document.body) : (
1037
+ // Non-portal dropdown (inline, relative positioning)
1038
+ jsxRuntime.jsxs("div", { ref: dropdownRef, className: "absolute z-50 mt-1 w-full bg-white bg-subtle-grain rounded-lg shadow-lg border border-paper-200 max-h-60 overflow-hidden animate-fade-in", children: [searchable && (jsxRuntime.jsx("div", { className: "p-2 border-b border-paper-200", children: jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-ink-400" }), jsxRuntime.jsx("input", { ref: searchInputRef, type: "text", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), placeholder: "Search...", className: "w-full pl-9 pr-3 py-2 text-sm border border-paper-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-400 focus:border-accent-400", role: "searchbox", "aria-label": "Search options", "aria-autocomplete": "list", "aria-controls": listboxId })] }) })), jsxRuntime.jsx("div", { ref: listRef, id: listboxId, className: "overflow-y-auto", style: { maxHeight: useVirtualScrolling ? virtualHeight : '12rem' }, onScroll: (e) => useVirtualScrolling && setScrollTop(e.currentTarget.scrollTop), role: "listbox", "aria-label": "Available options", "aria-multiselectable": "false", children: renderOptionsContent(false) })] }))), isOpen && useMobileSheet && reactDom.createPortal(jsxRuntime.jsxs("div", { className: "fixed inset-0 z-50 flex items-end", onClick: (e) => e.target === e.currentTarget && handleClose(), role: "dialog", "aria-modal": "true", "aria-labelledby": label ? `mobile-${labelId}` : undefined, children: [jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/50 animate-fade-in" }), jsxRuntime.jsxs("div", { className: "relative w-full bg-white rounded-t-2xl shadow-2xl animate-slide-up max-h-[85vh] flex flex-col", style: { paddingBottom: 'env(safe-area-inset-bottom)' }, children: [jsxRuntime.jsx("div", { className: "py-3 cursor-grab", children: jsxRuntime.jsx("div", { className: "w-12 h-1.5 bg-ink-300 rounded-full mx-auto" }) }), jsxRuntime.jsxs("div", { className: "px-4 pb-3 border-b border-paper-200 flex items-center justify-between", children: [label && (jsxRuntime.jsx("h2", { id: `mobile-${labelId}`, className: "text-lg font-semibold text-ink-900", children: label })), !label && (jsxRuntime.jsx("h2", { className: "text-lg font-semibold text-ink-900", children: placeholder })), jsxRuntime.jsx("button", { onClick: handleClose, className: "text-ink-400 hover:text-ink-600 transition-colors p-2 -mr-2", "aria-label": "Close", children: jsxRuntime.jsx(lucideReact.X, { className: "h-5 w-5" }) })] }), searchable && (jsxRuntime.jsx("div", { className: "p-3 border-b border-paper-200", children: jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-4 top-1/2 -translate-y-1/2 h-5 w-5 text-ink-400" }), jsxRuntime.jsx("input", { ref: mobileSearchInputRef, type: "text", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), placeholder: "Search...", inputMode: "search", enterKeyHint: "search", className: "w-full pl-12 pr-4 py-3 text-base border border-paper-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-accent-400 focus:border-accent-400", role: "searchbox", "aria-label": "Search options" })] }) })), jsxRuntime.jsx("div", { id: listboxId, className: "overflow-y-auto flex-1", role: "listbox", "aria-label": "Available options", "aria-multiselectable": "false", children: renderOptionsContent(true) })] })] }), document.body), error && (jsxRuntime.jsx("p", { id: errorId, className: "mt-2 text-xs text-error-600", role: "alert", "aria-live": "assertive", children: error })), helperText && !error && (jsxRuntime.jsx("p", { id: helperTextId, className: "mt-2 text-xs text-ink-600", children: helperText }))] }));
972
1039
  });
973
1040
  Select.displayName = 'Select';
974
1041
 
@@ -10121,256 +10188,6 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
10121
10188
  return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [renderPaginationControls(), shouldShowCardView ? cardViewContent : finalContent, contextMenuState.isOpen && contextMenuState.item && (jsxRuntime.jsx(Menu, { items: convertActionsToMenuItems(contextMenuState.item), position: contextMenuState.position, onClose: () => setContextMenuState({ isOpen: false, position: { x: 0, y: 0 }, item: null }) }))] }));
10122
10189
  }
10123
10190
 
10124
- // Color mapping for action buttons
10125
- const colorClasses = {
10126
- primary: 'bg-accent-500 text-white',
10127
- success: 'bg-success-500 text-white',
10128
- warning: 'bg-warning-500 text-white',
10129
- error: 'bg-error-500 text-white',
10130
- default: 'bg-paper-500 text-white',
10131
- };
10132
- /**
10133
- * SwipeActions - Touch-based swipe actions for list items
10134
- *
10135
- * Wraps any content with swipe-to-reveal actions, commonly used in mobile
10136
- * list items for quick actions like delete, archive, edit, etc.
10137
- *
10138
- * Features:
10139
- * - Left and right swipe actions
10140
- * - Full swipe to trigger primary action
10141
- * - Spring-back animation
10142
- * - Touch and mouse support
10143
- * - Customizable thresholds
10144
- *
10145
- * @example Basic delete action
10146
- * ```tsx
10147
- * <SwipeActions
10148
- * leftActions={[
10149
- * {
10150
- * id: 'delete',
10151
- * label: 'Delete',
10152
- * icon: <Trash className="h-5 w-5" />,
10153
- * color: 'error',
10154
- * onClick: () => handleDelete(item),
10155
- * primary: true,
10156
- * },
10157
- * ]}
10158
- * >
10159
- * <div className="p-4 bg-white">
10160
- * List item content
10161
- * </div>
10162
- * </SwipeActions>
10163
- * ```
10164
- *
10165
- * @example Multiple actions on both sides
10166
- * ```tsx
10167
- * <SwipeActions
10168
- * leftActions={[
10169
- * { id: 'delete', label: 'Delete', icon: <Trash />, color: 'error', onClick: handleDelete },
10170
- * { id: 'archive', label: 'Archive', icon: <Archive />, color: 'warning', onClick: handleArchive },
10171
- * ]}
10172
- * rightActions={[
10173
- * { id: 'edit', label: 'Edit', icon: <Edit />, color: 'primary', onClick: handleEdit },
10174
- * ]}
10175
- * fullSwipe
10176
- * >
10177
- * <ListItem />
10178
- * </SwipeActions>
10179
- * ```
10180
- */
10181
- function SwipeActions({ children, leftActions = [], rightActions = [], threshold = 80, fullSwipeThreshold = 0.5, fullSwipe = false, disabled = false, onSwipeChange, className = '', }) {
10182
- const containerRef = React.useRef(null);
10183
- const contentRef = React.useRef(null);
10184
- // Swipe state
10185
- const [translateX, setTranslateX] = React.useState(0);
10186
- const [isDragging, setIsDragging] = React.useState(false);
10187
- const [activeDirection, setActiveDirection] = React.useState(null);
10188
- // Touch/mouse tracking
10189
- const startX = React.useRef(0);
10190
- const currentX = React.useRef(0);
10191
- const startTime = React.useRef(0);
10192
- // Calculate action widths
10193
- const leftActionsWidth = leftActions.length * 72; // 72px per action
10194
- const rightActionsWidth = rightActions.length * 72;
10195
- // Reset position
10196
- const resetPosition = React.useCallback(() => {
10197
- setTranslateX(0);
10198
- setActiveDirection(null);
10199
- onSwipeChange?.(null);
10200
- }, [onSwipeChange]);
10201
- // Handle touch/mouse start
10202
- const handleStart = React.useCallback((clientX) => {
10203
- if (disabled)
10204
- return;
10205
- startX.current = clientX;
10206
- currentX.current = clientX;
10207
- startTime.current = Date.now();
10208
- setIsDragging(true);
10209
- }, [disabled]);
10210
- // Handle touch/mouse move
10211
- const handleMove = React.useCallback((clientX) => {
10212
- if (!isDragging || disabled)
10213
- return;
10214
- const deltaX = clientX - startX.current;
10215
- currentX.current = clientX;
10216
- // Determine direction and apply resistance at boundaries
10217
- let newTranslateX = deltaX;
10218
- // Swiping left (reveals left actions on right side)
10219
- if (deltaX < 0) {
10220
- if (leftActions.length === 0) {
10221
- newTranslateX = deltaX * 0.2; // Heavy resistance if no actions
10222
- }
10223
- else {
10224
- const maxSwipe = fullSwipe
10225
- ? -(containerRef.current?.offsetWidth || 300)
10226
- : -leftActionsWidth;
10227
- newTranslateX = Math.max(maxSwipe, deltaX);
10228
- // Apply resistance past the action buttons
10229
- if (newTranslateX < -leftActionsWidth) {
10230
- const overSwipe = newTranslateX + leftActionsWidth;
10231
- newTranslateX = -leftActionsWidth + overSwipe * 0.3;
10232
- }
10233
- }
10234
- setActiveDirection('left');
10235
- onSwipeChange?.('left');
10236
- }
10237
- // Swiping right (reveals right actions on left side)
10238
- else if (deltaX > 0) {
10239
- if (rightActions.length === 0) {
10240
- newTranslateX = deltaX * 0.2; // Heavy resistance if no actions
10241
- }
10242
- else {
10243
- const maxSwipe = fullSwipe
10244
- ? (containerRef.current?.offsetWidth || 300)
10245
- : rightActionsWidth;
10246
- newTranslateX = Math.min(maxSwipe, deltaX);
10247
- // Apply resistance past the action buttons
10248
- if (newTranslateX > rightActionsWidth) {
10249
- const overSwipe = newTranslateX - rightActionsWidth;
10250
- newTranslateX = rightActionsWidth + overSwipe * 0.3;
10251
- }
10252
- }
10253
- setActiveDirection('right');
10254
- onSwipeChange?.('right');
10255
- }
10256
- setTranslateX(newTranslateX);
10257
- }, [isDragging, disabled, leftActions.length, rightActions.length, leftActionsWidth, rightActionsWidth, fullSwipe, onSwipeChange]);
10258
- // Handle touch/mouse end
10259
- const handleEnd = React.useCallback(() => {
10260
- if (!isDragging)
10261
- return;
10262
- setIsDragging(false);
10263
- const deltaX = currentX.current - startX.current;
10264
- const velocity = Math.abs(deltaX) / (Date.now() - startTime.current);
10265
- const containerWidth = containerRef.current?.offsetWidth || 300;
10266
- // Check for full swipe trigger
10267
- if (fullSwipe) {
10268
- const swipePercentage = Math.abs(translateX) / containerWidth;
10269
- if (swipePercentage >= fullSwipeThreshold || velocity > 0.5) {
10270
- // Find primary action and trigger it
10271
- if (translateX < 0 && leftActions.length > 0) {
10272
- const primaryAction = leftActions.find(a => a.primary) || leftActions[0];
10273
- primaryAction.onClick();
10274
- resetPosition();
10275
- return;
10276
- }
10277
- else if (translateX > 0 && rightActions.length > 0) {
10278
- const primaryAction = rightActions.find(a => a.primary) || rightActions[0];
10279
- primaryAction.onClick();
10280
- resetPosition();
10281
- return;
10282
- }
10283
- }
10284
- }
10285
- // Snap to open or closed position
10286
- if (Math.abs(translateX) >= threshold || velocity > 0.3) {
10287
- // Snap open
10288
- if (translateX < 0 && leftActions.length > 0) {
10289
- setTranslateX(-leftActionsWidth);
10290
- setActiveDirection('left');
10291
- onSwipeChange?.('left');
10292
- }
10293
- else if (translateX > 0 && rightActions.length > 0) {
10294
- setTranslateX(rightActionsWidth);
10295
- setActiveDirection('right');
10296
- onSwipeChange?.('right');
10297
- }
10298
- else {
10299
- resetPosition();
10300
- }
10301
- }
10302
- else {
10303
- // Snap closed
10304
- resetPosition();
10305
- }
10306
- }, [isDragging, translateX, threshold, fullSwipe, fullSwipeThreshold, leftActions, rightActions, leftActionsWidth, rightActionsWidth, resetPosition, onSwipeChange]);
10307
- // Touch event handlers
10308
- const handleTouchStart = (e) => {
10309
- handleStart(e.touches[0].clientX);
10310
- };
10311
- const handleTouchMove = (e) => {
10312
- handleMove(e.touches[0].clientX);
10313
- };
10314
- const handleTouchEnd = () => {
10315
- handleEnd();
10316
- };
10317
- // Mouse event handlers (for testing/desktop)
10318
- const handleMouseDown = (e) => {
10319
- handleStart(e.clientX);
10320
- };
10321
- const handleMouseMove = (e) => {
10322
- handleMove(e.clientX);
10323
- };
10324
- const handleMouseUp = () => {
10325
- handleEnd();
10326
- };
10327
- // Close on outside click
10328
- React.useEffect(() => {
10329
- if (activeDirection === null)
10330
- return;
10331
- const handleClickOutside = (e) => {
10332
- if (containerRef.current && !containerRef.current.contains(e.target)) {
10333
- resetPosition();
10334
- }
10335
- };
10336
- document.addEventListener('mousedown', handleClickOutside);
10337
- return () => document.removeEventListener('mousedown', handleClickOutside);
10338
- }, [activeDirection, resetPosition]);
10339
- // Handle mouse leave during drag
10340
- React.useEffect(() => {
10341
- if (!isDragging)
10342
- return;
10343
- const handleMouseLeave = () => {
10344
- handleEnd();
10345
- };
10346
- document.addEventListener('mouseup', handleMouseLeave);
10347
- return () => document.removeEventListener('mouseup', handleMouseLeave);
10348
- }, [isDragging, handleEnd]);
10349
- // Render action button
10350
- const renderActionButton = (action) => {
10351
- const colorClass = colorClasses[action.color || 'default'];
10352
- return (jsxRuntime.jsxs("button", { onClick: (e) => {
10353
- e.stopPropagation();
10354
- action.onClick();
10355
- resetPosition();
10356
- }, className: `
10357
- flex flex-col items-center justify-center
10358
- w-18 h-full min-w-[72px]
10359
- ${colorClass}
10360
- transition-transform duration-150
10361
- `, style: {
10362
- transform: isDragging ? 'scale(1)' : 'scale(1)',
10363
- }, children: [action.icon && (jsxRuntime.jsx("div", { className: "mb-1", children: action.icon })), jsxRuntime.jsx("span", { className: "text-xs font-medium", children: action.label })] }, action.id));
10364
- };
10365
- return (jsxRuntime.jsxs("div", { ref: containerRef, className: `relative overflow-hidden ${className}`, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, onMouseDown: handleMouseDown, onMouseMove: isDragging ? handleMouseMove : undefined, onMouseUp: handleMouseUp, onMouseLeave: isDragging ? handleEnd : undefined, children: [rightActions.length > 0 && (jsxRuntime.jsx("div", { className: "absolute left-0 top-0 bottom-0 flex", style: { width: rightActionsWidth }, children: rightActions.map((action) => renderActionButton(action)) })), leftActions.length > 0 && (jsxRuntime.jsx("div", { className: "absolute right-0 top-0 bottom-0 flex", style: { width: leftActionsWidth }, children: leftActions.map((action) => renderActionButton(action)) })), jsxRuntime.jsx("div", { ref: contentRef, className: `
10366
- relative bg-white
10367
- ${isDragging ? '' : 'transition-transform duration-200 ease-out'}
10368
- `, style: {
10369
- transform: `translateX(${translateX}px)`,
10370
- touchAction: 'pan-y', // Allow vertical scrolling
10371
- }, children: children })] }));
10372
- }
10373
-
10374
10191
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
10375
10192
 
10376
10193
  function getDefaultExportFromCjs (x) {
@@ -10402,86 +10219,6 @@ function getAugmentedNamespace(n) {
10402
10219
  return a;
10403
10220
  }
10404
10221
 
10405
- var classnames = {exports: {}};
10406
-
10407
- /*!
10408
- Copyright (c) 2018 Jed Watson.
10409
- Licensed under the MIT License (MIT), see
10410
- http://jedwatson.github.io/classnames
10411
- */
10412
-
10413
- (function (module) {
10414
- /* global define */
10415
-
10416
- (function () {
10417
-
10418
- var hasOwn = {}.hasOwnProperty;
10419
-
10420
- function classNames () {
10421
- var classes = '';
10422
-
10423
- for (var i = 0; i < arguments.length; i++) {
10424
- var arg = arguments[i];
10425
- if (arg) {
10426
- classes = appendClass(classes, parseValue(arg));
10427
- }
10428
- }
10429
-
10430
- return classes;
10431
- }
10432
-
10433
- function parseValue (arg) {
10434
- if (typeof arg === 'string' || typeof arg === 'number') {
10435
- return arg;
10436
- }
10437
-
10438
- if (typeof arg !== 'object') {
10439
- return '';
10440
- }
10441
-
10442
- if (Array.isArray(arg)) {
10443
- return classNames.apply(null, arg);
10444
- }
10445
-
10446
- if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes('[native code]')) {
10447
- return arg.toString();
10448
- }
10449
-
10450
- var classes = '';
10451
-
10452
- for (var key in arg) {
10453
- if (hasOwn.call(arg, key) && arg[key]) {
10454
- classes = appendClass(classes, key);
10455
- }
10456
- }
10457
-
10458
- return classes;
10459
- }
10460
-
10461
- function appendClass (value, newClass) {
10462
- if (!newClass) {
10463
- return value;
10464
- }
10465
-
10466
- if (value) {
10467
- return value + ' ' + newClass;
10468
- }
10469
-
10470
- return value + newClass;
10471
- }
10472
-
10473
- if (module.exports) {
10474
- classNames.default = classNames;
10475
- module.exports = classNames;
10476
- } else {
10477
- window.classNames = classNames;
10478
- }
10479
- }());
10480
- } (classnames));
10481
-
10482
- var classnamesExports = classnames.exports;
10483
- var classNames = /*@__PURE__*/getDefaultExportFromCjs(classnamesExports);
10484
-
10485
10222
  /**
10486
10223
  * Represents unions.
10487
10224
  * (A1, A1:C5, ...)
@@ -32241,7 +31978,7 @@ const Utils$2 = utils$2;
32241
31978
  /**
32242
31979
  * A Excel Formula Parser & Evaluator
32243
31980
  */
32244
- let FormulaParser$2 = class FormulaParser {
31981
+ let FormulaParser$3 = class FormulaParser {
32245
31982
 
32246
31983
  /**
32247
31984
  * @param {{functions: {}, functionsNeedContext: {}, onVariable: function, onCell: function, onRange: function}} [config]
@@ -32572,7 +32309,7 @@ let FormulaParser$2 = class FormulaParser {
32572
32309
  };
32573
32310
 
32574
32311
  var hooks$1 = {
32575
- FormulaParser: FormulaParser$2};
32312
+ FormulaParser: FormulaParser$3};
32576
32313
 
32577
32314
  const FormulaError$2 = requireError();
32578
32315
  const {FormulaHelpers: FormulaHelpers$1, Types, Address} = requireHelpers();
@@ -33028,7 +32765,7 @@ var hooks = {
33028
32765
  DepParser: DepParser$1,
33029
32766
  };
33030
32767
 
33031
- const {FormulaParser} = hooks$1;
32768
+ const {FormulaParser: FormulaParser$1} = hooks$1;
33032
32769
  const {DepParser} = hooks;
33033
32770
  const SSF = ssf$1;
33034
32771
  const FormulaError = requireError();
@@ -33038,16 +32775,2271 @@ const FormulaError = requireError();
33038
32775
  // `\nTotal: ${funs.length}/477, ${funs.length/477*100}% implemented.`);
33039
32776
 
33040
32777
 
33041
- Object.assign(FormulaParser, {
32778
+ Object.assign(FormulaParser$1, {
33042
32779
  MAX_ROW: 1048576,
33043
32780
  MAX_COLUMN: 16384,
33044
32781
  SSF,
33045
32782
  DepParser,
33046
32783
  FormulaError, ...requireHelpers()
33047
32784
  });
33048
- var fastFormulaParser = FormulaParser;
32785
+ var fastFormulaParser = FormulaParser$1;
32786
+
32787
+ var FormulaParser$2 = /*@__PURE__*/getDefaultExportFromCjs(fastFormulaParser);
33049
32788
 
33050
- var FormulaParser$1 = /*@__PURE__*/getDefaultExportFromCjs(fastFormulaParser);
32789
+ var FormulaParserModule = /*#__PURE__*/_mergeNamespaces({
32790
+ __proto__: null,
32791
+ default: FormulaParser$2
32792
+ }, [fastFormulaParser]);
32793
+
32794
+ /**
32795
+ * Formula definitions for DataGrid intellisense
32796
+ * Based on fast-formula-parser supported functions
32797
+ */
32798
+ const FORMULA_DEFINITIONS = [
32799
+ // ==================== MATH ====================
32800
+ {
32801
+ name: 'SUM',
32802
+ category: 'Math',
32803
+ description: 'Adds all numbers in a range of cells',
32804
+ syntax: 'SUM(number1, [number2], ...)',
32805
+ parameters: [
32806
+ { name: 'number1', description: 'First number or range to add' },
32807
+ { name: 'number2', description: 'Additional numbers or ranges', optional: true },
32808
+ ],
32809
+ example: '=SUM(A1:A10)',
32810
+ },
32811
+ {
32812
+ name: 'AVERAGE',
32813
+ category: 'Math',
32814
+ description: 'Returns the average of the arguments',
32815
+ syntax: 'AVERAGE(number1, [number2], ...)',
32816
+ parameters: [
32817
+ { name: 'number1', description: 'First number or range' },
32818
+ { name: 'number2', description: 'Additional numbers or ranges', optional: true },
32819
+ ],
32820
+ example: '=AVERAGE(B1:B20)',
32821
+ },
32822
+ {
32823
+ name: 'MIN',
32824
+ category: 'Math',
32825
+ description: 'Returns the minimum value in a list of arguments',
32826
+ syntax: 'MIN(number1, [number2], ...)',
32827
+ parameters: [
32828
+ { name: 'number1', description: 'First number or range' },
32829
+ { name: 'number2', description: 'Additional numbers or ranges', optional: true },
32830
+ ],
32831
+ example: '=MIN(A1:A100)',
32832
+ },
32833
+ {
32834
+ name: 'MAX',
32835
+ category: 'Math',
32836
+ description: 'Returns the maximum value in a list of arguments',
32837
+ syntax: 'MAX(number1, [number2], ...)',
32838
+ parameters: [
32839
+ { name: 'number1', description: 'First number or range' },
32840
+ { name: 'number2', description: 'Additional numbers or ranges', optional: true },
32841
+ ],
32842
+ example: '=MAX(A1:A100)',
32843
+ },
32844
+ {
32845
+ name: 'COUNT',
32846
+ category: 'Math',
32847
+ description: 'Counts the number of cells that contain numbers',
32848
+ syntax: 'COUNT(value1, [value2], ...)',
32849
+ parameters: [
32850
+ { name: 'value1', description: 'First value or range' },
32851
+ { name: 'value2', description: 'Additional values or ranges', optional: true },
32852
+ ],
32853
+ example: '=COUNT(A1:A50)',
32854
+ },
32855
+ {
32856
+ name: 'COUNTA',
32857
+ category: 'Math',
32858
+ description: 'Counts the number of non-empty cells',
32859
+ syntax: 'COUNTA(value1, [value2], ...)',
32860
+ parameters: [
32861
+ { name: 'value1', description: 'First value or range' },
32862
+ { name: 'value2', description: 'Additional values or ranges', optional: true },
32863
+ ],
32864
+ example: '=COUNTA(A1:A50)',
32865
+ },
32866
+ {
32867
+ name: 'ROUND',
32868
+ category: 'Math',
32869
+ description: 'Rounds a number to a specified number of digits',
32870
+ syntax: 'ROUND(number, num_digits)',
32871
+ parameters: [
32872
+ { name: 'number', description: 'The number to round' },
32873
+ { name: 'num_digits', description: 'Number of decimal places' },
32874
+ ],
32875
+ example: '=ROUND(3.14159, 2)',
32876
+ },
32877
+ {
32878
+ name: 'ROUNDUP',
32879
+ category: 'Math',
32880
+ description: 'Rounds a number up, away from zero',
32881
+ syntax: 'ROUNDUP(number, num_digits)',
32882
+ parameters: [
32883
+ { name: 'number', description: 'The number to round up' },
32884
+ { name: 'num_digits', description: 'Number of decimal places' },
32885
+ ],
32886
+ example: '=ROUNDUP(3.14159, 2)',
32887
+ },
32888
+ {
32889
+ name: 'ROUNDDOWN',
32890
+ category: 'Math',
32891
+ description: 'Rounds a number down, toward zero',
32892
+ syntax: 'ROUNDDOWN(number, num_digits)',
32893
+ parameters: [
32894
+ { name: 'number', description: 'The number to round down' },
32895
+ { name: 'num_digits', description: 'Number of decimal places' },
32896
+ ],
32897
+ example: '=ROUNDDOWN(3.14159, 2)',
32898
+ },
32899
+ {
32900
+ name: 'ABS',
32901
+ category: 'Math',
32902
+ description: 'Returns the absolute value of a number',
32903
+ syntax: 'ABS(number)',
32904
+ parameters: [{ name: 'number', description: 'The number to get absolute value of' }],
32905
+ example: '=ABS(-5)',
32906
+ },
32907
+ {
32908
+ name: 'SQRT',
32909
+ category: 'Math',
32910
+ description: 'Returns the square root of a number',
32911
+ syntax: 'SQRT(number)',
32912
+ parameters: [{ name: 'number', description: 'The number to get square root of' }],
32913
+ example: '=SQRT(16)',
32914
+ },
32915
+ {
32916
+ name: 'POWER',
32917
+ category: 'Math',
32918
+ description: 'Returns the result of a number raised to a power',
32919
+ syntax: 'POWER(number, power)',
32920
+ parameters: [
32921
+ { name: 'number', description: 'The base number' },
32922
+ { name: 'power', description: 'The exponent' },
32923
+ ],
32924
+ example: '=POWER(2, 8)',
32925
+ },
32926
+ {
32927
+ name: 'MOD',
32928
+ category: 'Math',
32929
+ description: 'Returns the remainder after division',
32930
+ syntax: 'MOD(number, divisor)',
32931
+ parameters: [
32932
+ { name: 'number', description: 'The number to divide' },
32933
+ { name: 'divisor', description: 'The number to divide by' },
32934
+ ],
32935
+ example: '=MOD(10, 3)',
32936
+ },
32937
+ {
32938
+ name: 'INT',
32939
+ category: 'Math',
32940
+ description: 'Rounds a number down to the nearest integer',
32941
+ syntax: 'INT(number)',
32942
+ parameters: [{ name: 'number', description: 'The number to round down' }],
32943
+ example: '=INT(3.7)',
32944
+ },
32945
+ {
32946
+ name: 'CEILING',
32947
+ category: 'Math',
32948
+ description: 'Rounds a number up to the nearest multiple of significance',
32949
+ syntax: 'CEILING(number, significance)',
32950
+ parameters: [
32951
+ { name: 'number', description: 'The number to round' },
32952
+ { name: 'significance', description: 'The multiple to round to' },
32953
+ ],
32954
+ example: '=CEILING(4.3, 1)',
32955
+ },
32956
+ {
32957
+ name: 'FLOOR',
32958
+ category: 'Math',
32959
+ description: 'Rounds a number down to the nearest multiple of significance',
32960
+ syntax: 'FLOOR(number, significance)',
32961
+ parameters: [
32962
+ { name: 'number', description: 'The number to round' },
32963
+ { name: 'significance', description: 'The multiple to round to' },
32964
+ ],
32965
+ example: '=FLOOR(4.7, 1)',
32966
+ },
32967
+ {
32968
+ name: 'PRODUCT',
32969
+ category: 'Math',
32970
+ description: 'Multiplies all the numbers given as arguments',
32971
+ syntax: 'PRODUCT(number1, [number2], ...)',
32972
+ parameters: [
32973
+ { name: 'number1', description: 'First number or range' },
32974
+ { name: 'number2', description: 'Additional numbers or ranges', optional: true },
32975
+ ],
32976
+ example: '=PRODUCT(A1:A5)',
32977
+ },
32978
+ {
32979
+ name: 'SUMPRODUCT',
32980
+ category: 'Math',
32981
+ description: 'Returns the sum of the products of corresponding array components',
32982
+ syntax: 'SUMPRODUCT(array1, [array2], ...)',
32983
+ parameters: [
32984
+ { name: 'array1', description: 'First array' },
32985
+ { name: 'array2', description: 'Additional arrays', optional: true },
32986
+ ],
32987
+ example: '=SUMPRODUCT(A1:A5, B1:B5)',
32988
+ },
32989
+ {
32990
+ name: 'RAND',
32991
+ category: 'Math',
32992
+ description: 'Returns a random number between 0 and 1',
32993
+ syntax: 'RAND()',
32994
+ parameters: [],
32995
+ example: '=RAND()',
32996
+ },
32997
+ {
32998
+ name: 'RANDBETWEEN',
32999
+ category: 'Math',
33000
+ description: 'Returns a random integer between the specified values',
33001
+ syntax: 'RANDBETWEEN(bottom, top)',
33002
+ parameters: [
33003
+ { name: 'bottom', description: 'Minimum value' },
33004
+ { name: 'top', description: 'Maximum value' },
33005
+ ],
33006
+ example: '=RANDBETWEEN(1, 100)',
33007
+ },
33008
+ // ==================== STATISTICAL ====================
33009
+ {
33010
+ name: 'COUNTIF',
33011
+ category: 'Statistical',
33012
+ description: 'Counts cells that meet a single criterion',
33013
+ syntax: 'COUNTIF(range, criteria)',
33014
+ parameters: [
33015
+ { name: 'range', description: 'Range of cells to count' },
33016
+ { name: 'criteria', description: 'Condition to match' },
33017
+ ],
33018
+ example: '=COUNTIF(A1:A10, ">5")',
33019
+ },
33020
+ {
33021
+ name: 'COUNTIFS',
33022
+ category: 'Statistical',
33023
+ description: 'Counts cells that meet multiple criteria',
33024
+ syntax: 'COUNTIFS(range1, criteria1, [range2], [criteria2], ...)',
33025
+ parameters: [
33026
+ { name: 'range1', description: 'First range to evaluate' },
33027
+ { name: 'criteria1', description: 'First condition' },
33028
+ { name: 'range2', description: 'Additional range', optional: true },
33029
+ { name: 'criteria2', description: 'Additional condition', optional: true },
33030
+ ],
33031
+ example: '=COUNTIFS(A:A, ">5", B:B, "<10")',
33032
+ },
33033
+ {
33034
+ name: 'SUMIF',
33035
+ category: 'Statistical',
33036
+ description: 'Sums cells that meet a single criterion',
33037
+ syntax: 'SUMIF(range, criteria, [sum_range])',
33038
+ parameters: [
33039
+ { name: 'range', description: 'Range to evaluate' },
33040
+ { name: 'criteria', description: 'Condition to match' },
33041
+ { name: 'sum_range', description: 'Actual cells to sum', optional: true },
33042
+ ],
33043
+ example: '=SUMIF(A1:A10, ">5", B1:B10)',
33044
+ },
33045
+ {
33046
+ name: 'SUMIFS',
33047
+ category: 'Statistical',
33048
+ description: 'Sums cells that meet multiple criteria',
33049
+ syntax: 'SUMIFS(sum_range, range1, criteria1, [range2], [criteria2], ...)',
33050
+ parameters: [
33051
+ { name: 'sum_range', description: 'Cells to sum' },
33052
+ { name: 'range1', description: 'First range to evaluate' },
33053
+ { name: 'criteria1', description: 'First condition' },
33054
+ { name: 'range2', description: 'Additional range', optional: true },
33055
+ { name: 'criteria2', description: 'Additional condition', optional: true },
33056
+ ],
33057
+ example: '=SUMIFS(C:C, A:A, ">5", B:B, "<10")',
33058
+ },
33059
+ {
33060
+ name: 'AVERAGEIF',
33061
+ category: 'Statistical',
33062
+ description: 'Averages cells that meet a single criterion',
33063
+ syntax: 'AVERAGEIF(range, criteria, [average_range])',
33064
+ parameters: [
33065
+ { name: 'range', description: 'Range to evaluate' },
33066
+ { name: 'criteria', description: 'Condition to match' },
33067
+ { name: 'average_range', description: 'Actual cells to average', optional: true },
33068
+ ],
33069
+ example: '=AVERAGEIF(A1:A10, ">5", B1:B10)',
33070
+ },
33071
+ {
33072
+ name: 'MEDIAN',
33073
+ category: 'Statistical',
33074
+ description: 'Returns the median of the given numbers',
33075
+ syntax: 'MEDIAN(number1, [number2], ...)',
33076
+ parameters: [
33077
+ { name: 'number1', description: 'First number or range' },
33078
+ { name: 'number2', description: 'Additional numbers or ranges', optional: true },
33079
+ ],
33080
+ example: '=MEDIAN(A1:A100)',
33081
+ },
33082
+ {
33083
+ name: 'MODE',
33084
+ category: 'Statistical',
33085
+ description: 'Returns the most frequently occurring value',
33086
+ syntax: 'MODE(number1, [number2], ...)',
33087
+ parameters: [
33088
+ { name: 'number1', description: 'First number or range' },
33089
+ { name: 'number2', description: 'Additional numbers or ranges', optional: true },
33090
+ ],
33091
+ example: '=MODE(A1:A100)',
33092
+ },
33093
+ {
33094
+ name: 'STDEV',
33095
+ category: 'Statistical',
33096
+ description: 'Estimates standard deviation based on a sample',
33097
+ syntax: 'STDEV(number1, [number2], ...)',
33098
+ parameters: [
33099
+ { name: 'number1', description: 'First number or range' },
33100
+ { name: 'number2', description: 'Additional numbers or ranges', optional: true },
33101
+ ],
33102
+ example: '=STDEV(A1:A100)',
33103
+ },
33104
+ {
33105
+ name: 'VAR',
33106
+ category: 'Statistical',
33107
+ description: 'Estimates variance based on a sample',
33108
+ syntax: 'VAR(number1, [number2], ...)',
33109
+ parameters: [
33110
+ { name: 'number1', description: 'First number or range' },
33111
+ { name: 'number2', description: 'Additional numbers or ranges', optional: true },
33112
+ ],
33113
+ example: '=VAR(A1:A100)',
33114
+ },
33115
+ {
33116
+ name: 'LARGE',
33117
+ category: 'Statistical',
33118
+ description: 'Returns the k-th largest value in a data set',
33119
+ syntax: 'LARGE(array, k)',
33120
+ parameters: [
33121
+ { name: 'array', description: 'Range to search' },
33122
+ { name: 'k', description: 'Position from largest' },
33123
+ ],
33124
+ example: '=LARGE(A1:A100, 3)',
33125
+ },
33126
+ {
33127
+ name: 'SMALL',
33128
+ category: 'Statistical',
33129
+ description: 'Returns the k-th smallest value in a data set',
33130
+ syntax: 'SMALL(array, k)',
33131
+ parameters: [
33132
+ { name: 'array', description: 'Range to search' },
33133
+ { name: 'k', description: 'Position from smallest' },
33134
+ ],
33135
+ example: '=SMALL(A1:A100, 3)',
33136
+ },
33137
+ {
33138
+ name: 'RANK',
33139
+ category: 'Statistical',
33140
+ description: 'Returns the rank of a number in a list',
33141
+ syntax: 'RANK(number, ref, [order])',
33142
+ parameters: [
33143
+ { name: 'number', description: 'The number to rank' },
33144
+ { name: 'ref', description: 'Range of numbers' },
33145
+ { name: 'order', description: '0 for descending, non-zero for ascending', optional: true },
33146
+ ],
33147
+ example: '=RANK(A1, A1:A100)',
33148
+ },
33149
+ {
33150
+ name: 'PERCENTILE',
33151
+ category: 'Statistical',
33152
+ description: 'Returns the k-th percentile of values',
33153
+ syntax: 'PERCENTILE(array, k)',
33154
+ parameters: [
33155
+ { name: 'array', description: 'Range of data' },
33156
+ { name: 'k', description: 'Percentile value (0 to 1)' },
33157
+ ],
33158
+ example: '=PERCENTILE(A1:A100, 0.9)',
33159
+ },
33160
+ // ==================== LOOKUP ====================
33161
+ {
33162
+ name: 'VLOOKUP',
33163
+ category: 'Lookup',
33164
+ description: 'Looks for a value in the leftmost column and returns a value in the same row from a specified column',
33165
+ syntax: 'VLOOKUP(lookup_value, table_array, col_index_num, [range_lookup])',
33166
+ parameters: [
33167
+ { name: 'lookup_value', description: 'Value to search for' },
33168
+ { name: 'table_array', description: 'Table range to search' },
33169
+ { name: 'col_index_num', description: 'Column number to return (1-based)' },
33170
+ { name: 'range_lookup', description: 'FALSE for exact match, TRUE for approximate', optional: true },
33171
+ ],
33172
+ example: '=VLOOKUP(A1, B1:D10, 2, FALSE)',
33173
+ },
33174
+ {
33175
+ name: 'HLOOKUP',
33176
+ category: 'Lookup',
33177
+ description: 'Looks for a value in the top row and returns a value in the same column from a specified row',
33178
+ syntax: 'HLOOKUP(lookup_value, table_array, row_index_num, [range_lookup])',
33179
+ parameters: [
33180
+ { name: 'lookup_value', description: 'Value to search for' },
33181
+ { name: 'table_array', description: 'Table range to search' },
33182
+ { name: 'row_index_num', description: 'Row number to return (1-based)' },
33183
+ { name: 'range_lookup', description: 'FALSE for exact match, TRUE for approximate', optional: true },
33184
+ ],
33185
+ example: '=HLOOKUP(A1, A1:Z3, 2, FALSE)',
33186
+ },
33187
+ {
33188
+ name: 'INDEX',
33189
+ category: 'Lookup',
33190
+ description: 'Returns a value from a specific position in a range',
33191
+ syntax: 'INDEX(array, row_num, [col_num])',
33192
+ parameters: [
33193
+ { name: 'array', description: 'Range of cells' },
33194
+ { name: 'row_num', description: 'Row position' },
33195
+ { name: 'col_num', description: 'Column position', optional: true },
33196
+ ],
33197
+ example: '=INDEX(A1:C10, 5, 2)',
33198
+ },
33199
+ {
33200
+ name: 'MATCH',
33201
+ category: 'Lookup',
33202
+ description: 'Returns the relative position of an item in a range',
33203
+ syntax: 'MATCH(lookup_value, lookup_array, [match_type])',
33204
+ parameters: [
33205
+ { name: 'lookup_value', description: 'Value to find' },
33206
+ { name: 'lookup_array', description: 'Range to search' },
33207
+ { name: 'match_type', description: '0 for exact, 1 for less than, -1 for greater than', optional: true },
33208
+ ],
33209
+ example: '=MATCH("Apple", A1:A10, 0)',
33210
+ },
33211
+ {
33212
+ name: 'LOOKUP',
33213
+ category: 'Lookup',
33214
+ description: 'Looks up a value in a vector or array',
33215
+ syntax: 'LOOKUP(lookup_value, lookup_vector, [result_vector])',
33216
+ parameters: [
33217
+ { name: 'lookup_value', description: 'Value to find' },
33218
+ { name: 'lookup_vector', description: 'Range to search' },
33219
+ { name: 'result_vector', description: 'Range to return from', optional: true },
33220
+ ],
33221
+ example: '=LOOKUP(5, A1:A10, B1:B10)',
33222
+ },
33223
+ {
33224
+ name: 'CHOOSE',
33225
+ category: 'Lookup',
33226
+ description: 'Returns a value from a list based on an index',
33227
+ syntax: 'CHOOSE(index_num, value1, [value2], ...)',
33228
+ parameters: [
33229
+ { name: 'index_num', description: 'Which value to return (1-based)' },
33230
+ { name: 'value1', description: 'First value' },
33231
+ { name: 'value2', description: 'Additional values', optional: true },
33232
+ ],
33233
+ example: '=CHOOSE(2, "Red", "Blue", "Green")',
33234
+ },
33235
+ {
33236
+ name: 'ROW',
33237
+ category: 'Lookup',
33238
+ description: 'Returns the row number of a reference',
33239
+ syntax: 'ROW([reference])',
33240
+ parameters: [{ name: 'reference', description: 'Cell reference', optional: true }],
33241
+ example: '=ROW(A5)',
33242
+ },
33243
+ {
33244
+ name: 'COLUMN',
33245
+ category: 'Lookup',
33246
+ description: 'Returns the column number of a reference',
33247
+ syntax: 'COLUMN([reference])',
33248
+ parameters: [{ name: 'reference', description: 'Cell reference', optional: true }],
33249
+ example: '=COLUMN(C1)',
33250
+ },
33251
+ {
33252
+ name: 'ROWS',
33253
+ category: 'Lookup',
33254
+ description: 'Returns the number of rows in a reference',
33255
+ syntax: 'ROWS(array)',
33256
+ parameters: [{ name: 'array', description: 'Range reference' }],
33257
+ example: '=ROWS(A1:A10)',
33258
+ },
33259
+ {
33260
+ name: 'COLUMNS',
33261
+ category: 'Lookup',
33262
+ description: 'Returns the number of columns in a reference',
33263
+ syntax: 'COLUMNS(array)',
33264
+ parameters: [{ name: 'array', description: 'Range reference' }],
33265
+ example: '=COLUMNS(A1:D1)',
33266
+ },
33267
+ {
33268
+ name: 'OFFSET',
33269
+ category: 'Lookup',
33270
+ description: 'Returns a reference offset from a starting point',
33271
+ syntax: 'OFFSET(reference, rows, cols, [height], [width])',
33272
+ parameters: [
33273
+ { name: 'reference', description: 'Starting cell' },
33274
+ { name: 'rows', description: 'Rows to offset' },
33275
+ { name: 'cols', description: 'Columns to offset' },
33276
+ { name: 'height', description: 'Height of result', optional: true },
33277
+ { name: 'width', description: 'Width of result', optional: true },
33278
+ ],
33279
+ example: '=OFFSET(A1, 2, 3)',
33280
+ },
33281
+ {
33282
+ name: 'INDIRECT',
33283
+ category: 'Lookup',
33284
+ description: 'Returns the reference specified by a text string',
33285
+ syntax: 'INDIRECT(ref_text, [a1])',
33286
+ parameters: [
33287
+ { name: 'ref_text', description: 'Reference as text' },
33288
+ { name: 'a1', description: 'TRUE for A1 style, FALSE for R1C1', optional: true },
33289
+ ],
33290
+ example: '=INDIRECT("A" & B1)',
33291
+ },
33292
+ // ==================== TEXT ====================
33293
+ {
33294
+ name: 'CONCATENATE',
33295
+ category: 'Text',
33296
+ description: 'Joins several text strings into one',
33297
+ syntax: 'CONCATENATE(text1, [text2], ...)',
33298
+ parameters: [
33299
+ { name: 'text1', description: 'First text' },
33300
+ { name: 'text2', description: 'Additional text', optional: true },
33301
+ ],
33302
+ example: '=CONCATENATE(A1, " ", B1)',
33303
+ },
33304
+ {
33305
+ name: 'CONCAT',
33306
+ category: 'Text',
33307
+ description: 'Joins text from multiple ranges',
33308
+ syntax: 'CONCAT(text1, [text2], ...)',
33309
+ parameters: [
33310
+ { name: 'text1', description: 'First text or range' },
33311
+ { name: 'text2', description: 'Additional text or ranges', optional: true },
33312
+ ],
33313
+ example: '=CONCAT(A1:A5)',
33314
+ },
33315
+ {
33316
+ name: 'LEFT',
33317
+ category: 'Text',
33318
+ description: 'Returns the leftmost characters from a text string',
33319
+ syntax: 'LEFT(text, [num_chars])',
33320
+ parameters: [
33321
+ { name: 'text', description: 'Text string' },
33322
+ { name: 'num_chars', description: 'Number of characters', optional: true },
33323
+ ],
33324
+ example: '=LEFT(A1, 5)',
33325
+ },
33326
+ {
33327
+ name: 'RIGHT',
33328
+ category: 'Text',
33329
+ description: 'Returns the rightmost characters from a text string',
33330
+ syntax: 'RIGHT(text, [num_chars])',
33331
+ parameters: [
33332
+ { name: 'text', description: 'Text string' },
33333
+ { name: 'num_chars', description: 'Number of characters', optional: true },
33334
+ ],
33335
+ example: '=RIGHT(A1, 5)',
33336
+ },
33337
+ {
33338
+ name: 'MID',
33339
+ category: 'Text',
33340
+ description: 'Returns characters from the middle of a text string',
33341
+ syntax: 'MID(text, start_num, num_chars)',
33342
+ parameters: [
33343
+ { name: 'text', description: 'Text string' },
33344
+ { name: 'start_num', description: 'Starting position' },
33345
+ { name: 'num_chars', description: 'Number of characters' },
33346
+ ],
33347
+ example: '=MID(A1, 3, 5)',
33348
+ },
33349
+ {
33350
+ name: 'LEN',
33351
+ category: 'Text',
33352
+ description: 'Returns the number of characters in a text string',
33353
+ syntax: 'LEN(text)',
33354
+ parameters: [{ name: 'text', description: 'Text string' }],
33355
+ example: '=LEN(A1)',
33356
+ },
33357
+ {
33358
+ name: 'UPPER',
33359
+ category: 'Text',
33360
+ description: 'Converts text to uppercase',
33361
+ syntax: 'UPPER(text)',
33362
+ parameters: [{ name: 'text', description: 'Text to convert' }],
33363
+ example: '=UPPER(A1)',
33364
+ },
33365
+ {
33366
+ name: 'LOWER',
33367
+ category: 'Text',
33368
+ description: 'Converts text to lowercase',
33369
+ syntax: 'LOWER(text)',
33370
+ parameters: [{ name: 'text', description: 'Text to convert' }],
33371
+ example: '=LOWER(A1)',
33372
+ },
33373
+ {
33374
+ name: 'PROPER',
33375
+ category: 'Text',
33376
+ description: 'Capitalizes the first letter of each word',
33377
+ syntax: 'PROPER(text)',
33378
+ parameters: [{ name: 'text', description: 'Text to convert' }],
33379
+ example: '=PROPER(A1)',
33380
+ },
33381
+ {
33382
+ name: 'TRIM',
33383
+ category: 'Text',
33384
+ description: 'Removes extra spaces from text',
33385
+ syntax: 'TRIM(text)',
33386
+ parameters: [{ name: 'text', description: 'Text to trim' }],
33387
+ example: '=TRIM(A1)',
33388
+ },
33389
+ {
33390
+ name: 'CLEAN',
33391
+ category: 'Text',
33392
+ description: 'Removes non-printable characters from text',
33393
+ syntax: 'CLEAN(text)',
33394
+ parameters: [{ name: 'text', description: 'Text to clean' }],
33395
+ example: '=CLEAN(A1)',
33396
+ },
33397
+ {
33398
+ name: 'FIND',
33399
+ category: 'Text',
33400
+ description: 'Finds one text string within another (case-sensitive)',
33401
+ syntax: 'FIND(find_text, within_text, [start_num])',
33402
+ parameters: [
33403
+ { name: 'find_text', description: 'Text to find' },
33404
+ { name: 'within_text', description: 'Text to search in' },
33405
+ { name: 'start_num', description: 'Starting position', optional: true },
33406
+ ],
33407
+ example: '=FIND("@", A1)',
33408
+ },
33409
+ {
33410
+ name: 'SEARCH',
33411
+ category: 'Text',
33412
+ description: 'Finds one text string within another (case-insensitive)',
33413
+ syntax: 'SEARCH(find_text, within_text, [start_num])',
33414
+ parameters: [
33415
+ { name: 'find_text', description: 'Text to find' },
33416
+ { name: 'within_text', description: 'Text to search in' },
33417
+ { name: 'start_num', description: 'Starting position', optional: true },
33418
+ ],
33419
+ example: '=SEARCH("test", A1)',
33420
+ },
33421
+ {
33422
+ name: 'REPLACE',
33423
+ category: 'Text',
33424
+ description: 'Replaces part of a text string with different text',
33425
+ syntax: 'REPLACE(old_text, start_num, num_chars, new_text)',
33426
+ parameters: [
33427
+ { name: 'old_text', description: 'Original text' },
33428
+ { name: 'start_num', description: 'Starting position' },
33429
+ { name: 'num_chars', description: 'Number of characters to replace' },
33430
+ { name: 'new_text', description: 'Replacement text' },
33431
+ ],
33432
+ example: '=REPLACE(A1, 1, 3, "New")',
33433
+ },
33434
+ {
33435
+ name: 'SUBSTITUTE',
33436
+ category: 'Text',
33437
+ description: 'Substitutes new text for old text in a string',
33438
+ syntax: 'SUBSTITUTE(text, old_text, new_text, [instance_num])',
33439
+ parameters: [
33440
+ { name: 'text', description: 'Original text' },
33441
+ { name: 'old_text', description: 'Text to replace' },
33442
+ { name: 'new_text', description: 'Replacement text' },
33443
+ { name: 'instance_num', description: 'Which occurrence to replace', optional: true },
33444
+ ],
33445
+ example: '=SUBSTITUTE(A1, "old", "new")',
33446
+ },
33447
+ {
33448
+ name: 'TEXT',
33449
+ category: 'Text',
33450
+ description: 'Formats a number as text with a specified format',
33451
+ syntax: 'TEXT(value, format_text)',
33452
+ parameters: [
33453
+ { name: 'value', description: 'Value to format' },
33454
+ { name: 'format_text', description: 'Format code' },
33455
+ ],
33456
+ example: '=TEXT(A1, "$#,##0.00")',
33457
+ },
33458
+ {
33459
+ name: 'VALUE',
33460
+ category: 'Text',
33461
+ description: 'Converts a text string that represents a number to a number',
33462
+ syntax: 'VALUE(text)',
33463
+ parameters: [{ name: 'text', description: 'Text to convert' }],
33464
+ example: '=VALUE("123.45")',
33465
+ },
33466
+ {
33467
+ name: 'REPT',
33468
+ category: 'Text',
33469
+ description: 'Repeats text a specified number of times',
33470
+ syntax: 'REPT(text, number_times)',
33471
+ parameters: [
33472
+ { name: 'text', description: 'Text to repeat' },
33473
+ { name: 'number_times', description: 'Number of repetitions' },
33474
+ ],
33475
+ example: '=REPT("*", 5)',
33476
+ },
33477
+ {
33478
+ name: 'EXACT',
33479
+ category: 'Text',
33480
+ description: 'Checks if two text strings are exactly the same',
33481
+ syntax: 'EXACT(text1, text2)',
33482
+ parameters: [
33483
+ { name: 'text1', description: 'First text' },
33484
+ { name: 'text2', description: 'Second text' },
33485
+ ],
33486
+ example: '=EXACT(A1, B1)',
33487
+ },
33488
+ // ==================== LOGICAL ====================
33489
+ {
33490
+ name: 'IF',
33491
+ category: 'Logical',
33492
+ description: 'Returns one value if a condition is true, another if false',
33493
+ syntax: 'IF(logical_test, value_if_true, [value_if_false])',
33494
+ parameters: [
33495
+ { name: 'logical_test', description: 'Condition to test' },
33496
+ { name: 'value_if_true', description: 'Value if condition is true' },
33497
+ { name: 'value_if_false', description: 'Value if condition is false', optional: true },
33498
+ ],
33499
+ example: '=IF(A1>10, "High", "Low")',
33500
+ },
33501
+ {
33502
+ name: 'IFS',
33503
+ category: 'Logical',
33504
+ description: 'Checks multiple conditions and returns the first TRUE result',
33505
+ syntax: 'IFS(condition1, value1, [condition2, value2], ...)',
33506
+ parameters: [
33507
+ { name: 'condition1', description: 'First condition' },
33508
+ { name: 'value1', description: 'Value if first condition is true' },
33509
+ { name: 'condition2', description: 'Second condition', optional: true },
33510
+ { name: 'value2', description: 'Value if second condition is true', optional: true },
33511
+ ],
33512
+ example: '=IFS(A1>90, "A", A1>80, "B", A1>70, "C")',
33513
+ },
33514
+ {
33515
+ name: 'AND',
33516
+ category: 'Logical',
33517
+ description: 'Returns TRUE if all arguments are true',
33518
+ syntax: 'AND(logical1, [logical2], ...)',
33519
+ parameters: [
33520
+ { name: 'logical1', description: 'First condition' },
33521
+ { name: 'logical2', description: 'Additional conditions', optional: true },
33522
+ ],
33523
+ example: '=AND(A1>5, A1<10)',
33524
+ },
33525
+ {
33526
+ name: 'OR',
33527
+ category: 'Logical',
33528
+ description: 'Returns TRUE if any argument is true',
33529
+ syntax: 'OR(logical1, [logical2], ...)',
33530
+ parameters: [
33531
+ { name: 'logical1', description: 'First condition' },
33532
+ { name: 'logical2', description: 'Additional conditions', optional: true },
33533
+ ],
33534
+ example: '=OR(A1="Yes", A1="Y")',
33535
+ },
33536
+ {
33537
+ name: 'NOT',
33538
+ category: 'Logical',
33539
+ description: 'Reverses the logic of its argument',
33540
+ syntax: 'NOT(logical)',
33541
+ parameters: [{ name: 'logical', description: 'Value to reverse' }],
33542
+ example: '=NOT(A1>10)',
33543
+ },
33544
+ {
33545
+ name: 'XOR',
33546
+ category: 'Logical',
33547
+ description: 'Returns TRUE if an odd number of arguments are true',
33548
+ syntax: 'XOR(logical1, [logical2], ...)',
33549
+ parameters: [
33550
+ { name: 'logical1', description: 'First condition' },
33551
+ { name: 'logical2', description: 'Additional conditions', optional: true },
33552
+ ],
33553
+ example: '=XOR(A1>5, B1>5)',
33554
+ },
33555
+ {
33556
+ name: 'IFERROR',
33557
+ category: 'Logical',
33558
+ description: 'Returns a value if an expression results in an error',
33559
+ syntax: 'IFERROR(value, value_if_error)',
33560
+ parameters: [
33561
+ { name: 'value', description: 'Expression to check' },
33562
+ { name: 'value_if_error', description: 'Value to return on error' },
33563
+ ],
33564
+ example: '=IFERROR(A1/B1, 0)',
33565
+ },
33566
+ {
33567
+ name: 'IFNA',
33568
+ category: 'Logical',
33569
+ description: 'Returns a value if an expression results in #N/A',
33570
+ syntax: 'IFNA(value, value_if_na)',
33571
+ parameters: [
33572
+ { name: 'value', description: 'Expression to check' },
33573
+ { name: 'value_if_na', description: 'Value to return if #N/A' },
33574
+ ],
33575
+ example: '=IFNA(VLOOKUP(A1, B:C, 2, FALSE), "Not found")',
33576
+ },
33577
+ {
33578
+ name: 'TRUE',
33579
+ category: 'Logical',
33580
+ description: 'Returns the logical value TRUE',
33581
+ syntax: 'TRUE()',
33582
+ parameters: [],
33583
+ example: '=TRUE()',
33584
+ },
33585
+ {
33586
+ name: 'FALSE',
33587
+ category: 'Logical',
33588
+ description: 'Returns the logical value FALSE',
33589
+ syntax: 'FALSE()',
33590
+ parameters: [],
33591
+ example: '=FALSE()',
33592
+ },
33593
+ {
33594
+ name: 'SWITCH',
33595
+ category: 'Logical',
33596
+ description: 'Evaluates an expression against a list of values',
33597
+ syntax: 'SWITCH(expression, value1, result1, [value2, result2], ..., [default])',
33598
+ parameters: [
33599
+ { name: 'expression', description: 'Value to compare' },
33600
+ { name: 'value1', description: 'First value to match' },
33601
+ { name: 'result1', description: 'Result if first value matches' },
33602
+ { name: 'default', description: 'Default result if no match', optional: true },
33603
+ ],
33604
+ example: '=SWITCH(A1, 1, "One", 2, "Two", "Other")',
33605
+ },
33606
+ // ==================== DATE ====================
33607
+ {
33608
+ name: 'DATE',
33609
+ category: 'Date',
33610
+ description: 'Creates a date from year, month, and day',
33611
+ syntax: 'DATE(year, month, day)',
33612
+ parameters: [
33613
+ { name: 'year', description: 'Year number' },
33614
+ { name: 'month', description: 'Month number (1-12)' },
33615
+ { name: 'day', description: 'Day number' },
33616
+ ],
33617
+ example: '=DATE(2025, 1, 15)',
33618
+ },
33619
+ {
33620
+ name: 'TODAY',
33621
+ category: 'Date',
33622
+ description: 'Returns the current date',
33623
+ syntax: 'TODAY()',
33624
+ parameters: [],
33625
+ example: '=TODAY()',
33626
+ },
33627
+ {
33628
+ name: 'NOW',
33629
+ category: 'Date',
33630
+ description: 'Returns the current date and time',
33631
+ syntax: 'NOW()',
33632
+ parameters: [],
33633
+ example: '=NOW()',
33634
+ },
33635
+ {
33636
+ name: 'YEAR',
33637
+ category: 'Date',
33638
+ description: 'Returns the year of a date',
33639
+ syntax: 'YEAR(serial_number)',
33640
+ parameters: [{ name: 'serial_number', description: 'Date value' }],
33641
+ example: '=YEAR(A1)',
33642
+ },
33643
+ {
33644
+ name: 'MONTH',
33645
+ category: 'Date',
33646
+ description: 'Returns the month of a date (1-12)',
33647
+ syntax: 'MONTH(serial_number)',
33648
+ parameters: [{ name: 'serial_number', description: 'Date value' }],
33649
+ example: '=MONTH(A1)',
33650
+ },
33651
+ {
33652
+ name: 'DAY',
33653
+ category: 'Date',
33654
+ description: 'Returns the day of a date (1-31)',
33655
+ syntax: 'DAY(serial_number)',
33656
+ parameters: [{ name: 'serial_number', description: 'Date value' }],
33657
+ example: '=DAY(A1)',
33658
+ },
33659
+ {
33660
+ name: 'HOUR',
33661
+ category: 'Date',
33662
+ description: 'Returns the hour of a time (0-23)',
33663
+ syntax: 'HOUR(serial_number)',
33664
+ parameters: [{ name: 'serial_number', description: 'Time value' }],
33665
+ example: '=HOUR(A1)',
33666
+ },
33667
+ {
33668
+ name: 'MINUTE',
33669
+ category: 'Date',
33670
+ description: 'Returns the minute of a time (0-59)',
33671
+ syntax: 'MINUTE(serial_number)',
33672
+ parameters: [{ name: 'serial_number', description: 'Time value' }],
33673
+ example: '=MINUTE(A1)',
33674
+ },
33675
+ {
33676
+ name: 'SECOND',
33677
+ category: 'Date',
33678
+ description: 'Returns the second of a time (0-59)',
33679
+ syntax: 'SECOND(serial_number)',
33680
+ parameters: [{ name: 'serial_number', description: 'Time value' }],
33681
+ example: '=SECOND(A1)',
33682
+ },
33683
+ {
33684
+ name: 'WEEKDAY',
33685
+ category: 'Date',
33686
+ description: 'Returns the day of the week (1-7)',
33687
+ syntax: 'WEEKDAY(serial_number, [return_type])',
33688
+ parameters: [
33689
+ { name: 'serial_number', description: 'Date value' },
33690
+ { name: 'return_type', description: 'Number system to use', optional: true },
33691
+ ],
33692
+ example: '=WEEKDAY(A1)',
33693
+ },
33694
+ {
33695
+ name: 'WEEKNUM',
33696
+ category: 'Date',
33697
+ description: 'Returns the week number of the year',
33698
+ syntax: 'WEEKNUM(serial_number, [return_type])',
33699
+ parameters: [
33700
+ { name: 'serial_number', description: 'Date value' },
33701
+ { name: 'return_type', description: 'Week calculation method', optional: true },
33702
+ ],
33703
+ example: '=WEEKNUM(A1)',
33704
+ },
33705
+ {
33706
+ name: 'DATEDIF',
33707
+ category: 'Date',
33708
+ description: 'Calculates the difference between two dates',
33709
+ syntax: 'DATEDIF(start_date, end_date, unit)',
33710
+ parameters: [
33711
+ { name: 'start_date', description: 'Start date' },
33712
+ { name: 'end_date', description: 'End date' },
33713
+ { name: 'unit', description: '"Y", "M", "D", "YM", "YD", or "MD"' },
33714
+ ],
33715
+ example: '=DATEDIF(A1, B1, "D")',
33716
+ },
33717
+ {
33718
+ name: 'EDATE',
33719
+ category: 'Date',
33720
+ description: 'Returns a date a specified number of months before or after',
33721
+ syntax: 'EDATE(start_date, months)',
33722
+ parameters: [
33723
+ { name: 'start_date', description: 'Starting date' },
33724
+ { name: 'months', description: 'Number of months' },
33725
+ ],
33726
+ example: '=EDATE(A1, 3)',
33727
+ },
33728
+ {
33729
+ name: 'EOMONTH',
33730
+ category: 'Date',
33731
+ description: 'Returns the last day of the month, months before or after',
33732
+ syntax: 'EOMONTH(start_date, months)',
33733
+ parameters: [
33734
+ { name: 'start_date', description: 'Starting date' },
33735
+ { name: 'months', description: 'Number of months' },
33736
+ ],
33737
+ example: '=EOMONTH(A1, 0)',
33738
+ },
33739
+ {
33740
+ name: 'NETWORKDAYS',
33741
+ category: 'Date',
33742
+ description: 'Returns the number of working days between two dates',
33743
+ syntax: 'NETWORKDAYS(start_date, end_date, [holidays])',
33744
+ parameters: [
33745
+ { name: 'start_date', description: 'Start date' },
33746
+ { name: 'end_date', description: 'End date' },
33747
+ { name: 'holidays', description: 'Range of holiday dates', optional: true },
33748
+ ],
33749
+ example: '=NETWORKDAYS(A1, B1)',
33750
+ },
33751
+ {
33752
+ name: 'WORKDAY',
33753
+ category: 'Date',
33754
+ description: 'Returns a date a specified number of workdays before or after',
33755
+ syntax: 'WORKDAY(start_date, days, [holidays])',
33756
+ parameters: [
33757
+ { name: 'start_date', description: 'Starting date' },
33758
+ { name: 'days', description: 'Number of workdays' },
33759
+ { name: 'holidays', description: 'Range of holiday dates', optional: true },
33760
+ ],
33761
+ example: '=WORKDAY(A1, 10)',
33762
+ },
33763
+ {
33764
+ name: 'TIME',
33765
+ category: 'Date',
33766
+ description: 'Creates a time from hour, minute, and second',
33767
+ syntax: 'TIME(hour, minute, second)',
33768
+ parameters: [
33769
+ { name: 'hour', description: 'Hour (0-23)' },
33770
+ { name: 'minute', description: 'Minute (0-59)' },
33771
+ { name: 'second', description: 'Second (0-59)' },
33772
+ ],
33773
+ example: '=TIME(14, 30, 0)',
33774
+ },
33775
+ // ==================== INFORMATION ====================
33776
+ {
33777
+ name: 'ISBLANK',
33778
+ category: 'Information',
33779
+ description: 'Returns TRUE if the cell is empty',
33780
+ syntax: 'ISBLANK(value)',
33781
+ parameters: [{ name: 'value', description: 'Cell to check' }],
33782
+ example: '=ISBLANK(A1)',
33783
+ },
33784
+ {
33785
+ name: 'ISNUMBER',
33786
+ category: 'Information',
33787
+ description: 'Returns TRUE if the value is a number',
33788
+ syntax: 'ISNUMBER(value)',
33789
+ parameters: [{ name: 'value', description: 'Value to check' }],
33790
+ example: '=ISNUMBER(A1)',
33791
+ },
33792
+ {
33793
+ name: 'ISTEXT',
33794
+ category: 'Information',
33795
+ description: 'Returns TRUE if the value is text',
33796
+ syntax: 'ISTEXT(value)',
33797
+ parameters: [{ name: 'value', description: 'Value to check' }],
33798
+ example: '=ISTEXT(A1)',
33799
+ },
33800
+ {
33801
+ name: 'ISERROR',
33802
+ category: 'Information',
33803
+ description: 'Returns TRUE if the value is any error',
33804
+ syntax: 'ISERROR(value)',
33805
+ parameters: [{ name: 'value', description: 'Value to check' }],
33806
+ example: '=ISERROR(A1)',
33807
+ },
33808
+ {
33809
+ name: 'ISNA',
33810
+ category: 'Information',
33811
+ description: 'Returns TRUE if the value is #N/A',
33812
+ syntax: 'ISNA(value)',
33813
+ parameters: [{ name: 'value', description: 'Value to check' }],
33814
+ example: '=ISNA(A1)',
33815
+ },
33816
+ {
33817
+ name: 'ISLOGICAL',
33818
+ category: 'Information',
33819
+ description: 'Returns TRUE if the value is a logical value',
33820
+ syntax: 'ISLOGICAL(value)',
33821
+ parameters: [{ name: 'value', description: 'Value to check' }],
33822
+ example: '=ISLOGICAL(A1)',
33823
+ },
33824
+ {
33825
+ name: 'ISEVEN',
33826
+ category: 'Information',
33827
+ description: 'Returns TRUE if the number is even',
33828
+ syntax: 'ISEVEN(number)',
33829
+ parameters: [{ name: 'number', description: 'Number to check' }],
33830
+ example: '=ISEVEN(A1)',
33831
+ },
33832
+ {
33833
+ name: 'ISODD',
33834
+ category: 'Information',
33835
+ description: 'Returns TRUE if the number is odd',
33836
+ syntax: 'ISODD(number)',
33837
+ parameters: [{ name: 'number', description: 'Number to check' }],
33838
+ example: '=ISODD(A1)',
33839
+ },
33840
+ {
33841
+ name: 'TYPE',
33842
+ category: 'Information',
33843
+ description: 'Returns a number indicating the data type',
33844
+ syntax: 'TYPE(value)',
33845
+ parameters: [{ name: 'value', description: 'Value to check' }],
33846
+ example: '=TYPE(A1)',
33847
+ },
33848
+ {
33849
+ name: 'N',
33850
+ category: 'Information',
33851
+ description: 'Returns a value converted to a number',
33852
+ syntax: 'N(value)',
33853
+ parameters: [{ name: 'value', description: 'Value to convert' }],
33854
+ example: '=N(A1)',
33855
+ },
33856
+ {
33857
+ name: 'NA',
33858
+ category: 'Information',
33859
+ description: 'Returns the error value #N/A',
33860
+ syntax: 'NA()',
33861
+ parameters: [],
33862
+ example: '=NA()',
33863
+ },
33864
+ // ==================== FINANCIAL ====================
33865
+ {
33866
+ name: 'PMT',
33867
+ category: 'Financial',
33868
+ description: 'Calculates the payment for a loan',
33869
+ syntax: 'PMT(rate, nper, pv, [fv], [type])',
33870
+ parameters: [
33871
+ { name: 'rate', description: 'Interest rate per period' },
33872
+ { name: 'nper', description: 'Total number of payments' },
33873
+ { name: 'pv', description: 'Present value (loan amount)' },
33874
+ { name: 'fv', description: 'Future value', optional: true },
33875
+ { name: 'type', description: '0 = end of period, 1 = beginning', optional: true },
33876
+ ],
33877
+ example: '=PMT(0.05/12, 60, 10000)',
33878
+ },
33879
+ {
33880
+ name: 'PV',
33881
+ category: 'Financial',
33882
+ description: 'Returns the present value of an investment',
33883
+ syntax: 'PV(rate, nper, pmt, [fv], [type])',
33884
+ parameters: [
33885
+ { name: 'rate', description: 'Interest rate per period' },
33886
+ { name: 'nper', description: 'Total number of periods' },
33887
+ { name: 'pmt', description: 'Payment per period' },
33888
+ { name: 'fv', description: 'Future value', optional: true },
33889
+ { name: 'type', description: '0 = end, 1 = beginning', optional: true },
33890
+ ],
33891
+ example: '=PV(0.05/12, 60, -100)',
33892
+ },
33893
+ {
33894
+ name: 'FV',
33895
+ category: 'Financial',
33896
+ description: 'Returns the future value of an investment',
33897
+ syntax: 'FV(rate, nper, pmt, [pv], [type])',
33898
+ parameters: [
33899
+ { name: 'rate', description: 'Interest rate per period' },
33900
+ { name: 'nper', description: 'Total number of periods' },
33901
+ { name: 'pmt', description: 'Payment per period' },
33902
+ { name: 'pv', description: 'Present value', optional: true },
33903
+ { name: 'type', description: '0 = end, 1 = beginning', optional: true },
33904
+ ],
33905
+ example: '=FV(0.05/12, 60, -100)',
33906
+ },
33907
+ {
33908
+ name: 'NPV',
33909
+ category: 'Financial',
33910
+ description: 'Returns the net present value of an investment',
33911
+ syntax: 'NPV(rate, value1, [value2], ...)',
33912
+ parameters: [
33913
+ { name: 'rate', description: 'Discount rate' },
33914
+ { name: 'value1', description: 'First cash flow' },
33915
+ { name: 'value2', description: 'Additional cash flows', optional: true },
33916
+ ],
33917
+ example: '=NPV(0.1, -10000, 3000, 4200, 6800)',
33918
+ },
33919
+ {
33920
+ name: 'IRR',
33921
+ category: 'Financial',
33922
+ description: 'Returns the internal rate of return',
33923
+ syntax: 'IRR(values, [guess])',
33924
+ parameters: [
33925
+ { name: 'values', description: 'Range of cash flows' },
33926
+ { name: 'guess', description: 'Initial guess for rate', optional: true },
33927
+ ],
33928
+ example: '=IRR(A1:A5)',
33929
+ },
33930
+ {
33931
+ name: 'RATE',
33932
+ category: 'Financial',
33933
+ description: 'Returns the interest rate per period',
33934
+ syntax: 'RATE(nper, pmt, pv, [fv], [type], [guess])',
33935
+ parameters: [
33936
+ { name: 'nper', description: 'Total number of periods' },
33937
+ { name: 'pmt', description: 'Payment per period' },
33938
+ { name: 'pv', description: 'Present value' },
33939
+ { name: 'fv', description: 'Future value', optional: true },
33940
+ { name: 'type', description: '0 = end, 1 = beginning', optional: true },
33941
+ { name: 'guess', description: 'Initial guess', optional: true },
33942
+ ],
33943
+ example: '=RATE(60, -100, 5000)',
33944
+ },
33945
+ {
33946
+ name: 'NPER',
33947
+ category: 'Financial',
33948
+ description: 'Returns the number of periods for an investment',
33949
+ syntax: 'NPER(rate, pmt, pv, [fv], [type])',
33950
+ parameters: [
33951
+ { name: 'rate', description: 'Interest rate per period' },
33952
+ { name: 'pmt', description: 'Payment per period' },
33953
+ { name: 'pv', description: 'Present value' },
33954
+ { name: 'fv', description: 'Future value', optional: true },
33955
+ { name: 'type', description: '0 = end, 1 = beginning', optional: true },
33956
+ ],
33957
+ example: '=NPER(0.05/12, -100, 5000)',
33958
+ },
33959
+ ];
33960
+ // Get all formula names
33961
+ const FORMULA_NAMES = FORMULA_DEFINITIONS.map((f) => f.name);
33962
+ // Get formulas by category
33963
+ const getFormulasByCategory = (category) => FORMULA_DEFINITIONS.filter((f) => f.category === category);
33964
+ // Search formulas by name prefix
33965
+ const searchFormulas = (query) => {
33966
+ const upperQuery = query.toUpperCase();
33967
+ return FORMULA_DEFINITIONS.filter((f) => f.name.startsWith(upperQuery));
33968
+ };
33969
+ // Get formula by exact name
33970
+ const getFormula = (name) => FORMULA_DEFINITIONS.find((f) => f.name === name.toUpperCase());
33971
+ // Get all categories
33972
+ const FORMULA_CATEGORIES = [
33973
+ 'Math',
33974
+ 'Statistical',
33975
+ 'Lookup',
33976
+ 'Text',
33977
+ 'Logical',
33978
+ 'Date',
33979
+ 'Information',
33980
+ 'Financial',
33981
+ ];
33982
+
33983
+ /**
33984
+ * FormulaAutocomplete - Input with formula intellisense
33985
+ *
33986
+ * Features:
33987
+ * - Autocomplete dropdown when typing after '='
33988
+ * - Function signature hints while typing parameters
33989
+ * - Category-based browsing
33990
+ * - Keyboard navigation
33991
+ */
33992
+ const FormulaAutocomplete = ({ value, onChange, onComplete, onCancel, anchorRect, autoFocus = true, className = '', }) => {
33993
+ const inputRef = React.useRef(null);
33994
+ const dropdownRef = React.useRef(null);
33995
+ const [selectedIndex, setSelectedIndex] = React.useState(0);
33996
+ const [showDropdown, setShowDropdown] = React.useState(false);
33997
+ const [showHint, setShowHint] = React.useState(false);
33998
+ const [hint, setHint] = React.useState(null);
33999
+ const [activeCategory, setActiveCategory] = React.useState(null);
34000
+ // Parse the current formula context
34001
+ const formulaContext = React.useMemo(() => {
34002
+ if (!value.startsWith('=')) {
34003
+ return { isFormula: false, query: '', inFunction: false, functionName: '', paramIndex: 0 };
34004
+ }
34005
+ const formulaText = value.substring(1);
34006
+ // Check if we're typing a function name (before opening paren)
34007
+ const functionMatch = formulaText.match(/^([A-Z]+)$/i);
34008
+ if (functionMatch) {
34009
+ return {
34010
+ isFormula: true,
34011
+ query: functionMatch[1].toUpperCase(),
34012
+ inFunction: false,
34013
+ functionName: '',
34014
+ paramIndex: 0,
34015
+ };
34016
+ }
34017
+ // Check if we're inside a function (after opening paren)
34018
+ const insideFunctionMatch = formulaText.match(/^([A-Z]+)\((.*)$/i);
34019
+ if (insideFunctionMatch) {
34020
+ const functionName = insideFunctionMatch[1].toUpperCase();
34021
+ const params = insideFunctionMatch[2];
34022
+ // Count commas to determine parameter index (accounting for nested parens)
34023
+ let paramIndex = 0;
34024
+ let parenDepth = 0;
34025
+ for (const char of params) {
34026
+ if (char === '(')
34027
+ parenDepth++;
34028
+ else if (char === ')')
34029
+ parenDepth--;
34030
+ else if (char === ',' && parenDepth === 0)
34031
+ paramIndex++;
34032
+ }
34033
+ return {
34034
+ isFormula: true,
34035
+ query: '',
34036
+ inFunction: true,
34037
+ functionName,
34038
+ paramIndex,
34039
+ };
34040
+ }
34041
+ // Just '=' or other expression
34042
+ return {
34043
+ isFormula: true,
34044
+ query: formulaText.toUpperCase(),
34045
+ inFunction: false,
34046
+ functionName: '',
34047
+ paramIndex: 0,
34048
+ };
34049
+ }, [value]);
34050
+ // Get matching formulas for dropdown
34051
+ const matchingFormulas = React.useMemo(() => {
34052
+ if (!formulaContext.isFormula || formulaContext.inFunction)
34053
+ return [];
34054
+ if (activeCategory) {
34055
+ const categoryFormulas = FORMULA_DEFINITIONS.filter(f => f.category === activeCategory);
34056
+ if (formulaContext.query) {
34057
+ return categoryFormulas.filter(f => f.name.startsWith(formulaContext.query));
34058
+ }
34059
+ return categoryFormulas;
34060
+ }
34061
+ if (formulaContext.query) {
34062
+ return searchFormulas(formulaContext.query);
34063
+ }
34064
+ // Show all formulas grouped by first letter when just '='
34065
+ return FORMULA_DEFINITIONS.slice(0, 20); // Show first 20 as default
34066
+ }, [formulaContext, activeCategory]);
34067
+ // Update hint when inside a function
34068
+ React.useEffect(() => {
34069
+ if (formulaContext.inFunction && formulaContext.functionName) {
34070
+ const formula = getFormula(formulaContext.functionName);
34071
+ if (formula) {
34072
+ setHint({
34073
+ formula,
34074
+ currentParamIndex: formulaContext.paramIndex,
34075
+ });
34076
+ setShowHint(true);
34077
+ setShowDropdown(false);
34078
+ }
34079
+ else {
34080
+ setShowHint(false);
34081
+ setHint(null);
34082
+ }
34083
+ }
34084
+ else if (formulaContext.isFormula && !formulaContext.inFunction) {
34085
+ setShowDropdown(true);
34086
+ setShowHint(false);
34087
+ setHint(null);
34088
+ }
34089
+ else {
34090
+ setShowDropdown(false);
34091
+ setShowHint(false);
34092
+ setHint(null);
34093
+ }
34094
+ }, [formulaContext]);
34095
+ // Reset selected index when matches change
34096
+ React.useEffect(() => {
34097
+ setSelectedIndex(0);
34098
+ }, [matchingFormulas.length]);
34099
+ // Auto-focus
34100
+ React.useEffect(() => {
34101
+ if (autoFocus && inputRef.current) {
34102
+ inputRef.current.focus();
34103
+ inputRef.current.select();
34104
+ }
34105
+ }, [autoFocus]);
34106
+ // Scroll selected item into view
34107
+ React.useEffect(() => {
34108
+ if (dropdownRef.current && showDropdown) {
34109
+ const selectedItem = dropdownRef.current.querySelector(`[data-index="${selectedIndex}"]`);
34110
+ if (selectedItem) {
34111
+ selectedItem.scrollIntoView({ block: 'nearest' });
34112
+ }
34113
+ }
34114
+ }, [selectedIndex, showDropdown]);
34115
+ // Handle keyboard navigation
34116
+ const handleKeyDown = React.useCallback((e) => {
34117
+ if (showDropdown && matchingFormulas.length > 0) {
34118
+ switch (e.key) {
34119
+ case 'ArrowDown':
34120
+ e.preventDefault();
34121
+ setSelectedIndex((prev) => Math.min(prev + 1, matchingFormulas.length - 1));
34122
+ break;
34123
+ case 'ArrowUp':
34124
+ e.preventDefault();
34125
+ setSelectedIndex((prev) => Math.max(prev - 1, 0));
34126
+ break;
34127
+ case 'Tab':
34128
+ case 'Enter':
34129
+ e.preventDefault();
34130
+ insertFormula(matchingFormulas[selectedIndex]);
34131
+ break;
34132
+ case 'Escape':
34133
+ e.preventDefault();
34134
+ if (showDropdown) {
34135
+ setShowDropdown(false);
34136
+ }
34137
+ else {
34138
+ onCancel();
34139
+ }
34140
+ break;
34141
+ }
34142
+ }
34143
+ else {
34144
+ if (e.key === 'Enter') {
34145
+ e.preventDefault();
34146
+ onComplete();
34147
+ }
34148
+ else if (e.key === 'Escape') {
34149
+ e.preventDefault();
34150
+ onCancel();
34151
+ }
34152
+ }
34153
+ }, [showDropdown, matchingFormulas, selectedIndex, onComplete, onCancel]);
34154
+ // Insert selected formula
34155
+ const insertFormula = React.useCallback((formula) => {
34156
+ // Replace the current query with the formula name and open paren
34157
+ const newValue = `=${formula.name}(`;
34158
+ onChange(newValue);
34159
+ setShowDropdown(false);
34160
+ inputRef.current?.focus();
34161
+ }, [onChange]);
34162
+ // Calculate dropdown position
34163
+ const dropdownPosition = React.useMemo(() => {
34164
+ if (!anchorRect)
34165
+ return { top: 0, left: 0 };
34166
+ return {
34167
+ top: anchorRect.bottom + 2,
34168
+ left: anchorRect.left,
34169
+ };
34170
+ }, [anchorRect]);
34171
+ // Prevent blur when clicking inside dropdown
34172
+ const handleDropdownMouseDown = React.useCallback((e) => {
34173
+ e.preventDefault(); // Prevents input from losing focus
34174
+ }, []);
34175
+ // Render parameter hint
34176
+ const renderHint = () => {
34177
+ if (!showHint || !hint)
34178
+ return null;
34179
+ return reactDom.createPortal(jsxRuntime.jsxs("div", { className: "fixed z-[9999] bg-white border border-stone-200 rounded-lg shadow-lg p-3 max-w-md", style: {
34180
+ top: dropdownPosition.top,
34181
+ left: dropdownPosition.left,
34182
+ }, onMouseDown: handleDropdownMouseDown, children: [jsxRuntime.jsxs("div", { className: "font-mono text-sm mb-2", children: [jsxRuntime.jsx("span", { className: "text-primary-600 font-semibold", children: hint.formula.name }), jsxRuntime.jsx("span", { className: "text-ink-500", children: "(" }), hint.formula.parameters.map((param, idx) => (jsxRuntime.jsxs("span", { children: [idx > 0 && jsxRuntime.jsx("span", { className: "text-ink-500", children: ", " }), jsxRuntime.jsx("span", { className: `${idx === hint.currentParamIndex
34183
+ ? 'bg-primary-100 text-primary-700 px-1 rounded font-semibold'
34184
+ : param.optional
34185
+ ? 'text-ink-400'
34186
+ : 'text-ink-600'}`, children: param.optional ? `[${param.name}]` : param.name })] }, param.name))), jsxRuntime.jsx("span", { className: "text-ink-500", children: ")" })] }), jsxRuntime.jsx("div", { className: "text-xs text-ink-600 mb-2", children: hint.formula.description }), hint.formula.parameters[hint.currentParamIndex] && (jsxRuntime.jsxs("div", { className: "text-xs bg-paper-50 p-2 rounded border border-stone-100", children: [jsxRuntime.jsxs("span", { className: "font-semibold text-primary-600", children: [hint.formula.parameters[hint.currentParamIndex].name, ":"] }), ' ', jsxRuntime.jsx("span", { className: "text-ink-600", children: hint.formula.parameters[hint.currentParamIndex].description })] }))] }), document.body);
34187
+ };
34188
+ // Render autocomplete dropdown
34189
+ const renderDropdown = () => {
34190
+ if (!showDropdown || matchingFormulas.length === 0)
34191
+ return null;
34192
+ return reactDom.createPortal(jsxRuntime.jsxs("div", { ref: dropdownRef, className: "fixed z-[9999] bg-white border border-stone-200 rounded-lg shadow-lg overflow-hidden", style: {
34193
+ top: dropdownPosition.top,
34194
+ left: dropdownPosition.left,
34195
+ minWidth: 320,
34196
+ maxWidth: 450,
34197
+ maxHeight: 300,
34198
+ }, onMouseDown: handleDropdownMouseDown, children: [jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-1 p-2 border-b border-stone-100 bg-paper-50", children: [jsxRuntime.jsx("button", { className: `px-2 py-1 text-xs rounded ${activeCategory === null
34199
+ ? 'bg-primary-500 text-white'
34200
+ : 'bg-white text-ink-600 hover:bg-stone-100'}`, onClick: () => {
34201
+ setActiveCategory(null);
34202
+ inputRef.current?.focus();
34203
+ }, children: "All" }), FORMULA_CATEGORIES.map((cat) => (jsxRuntime.jsx("button", { className: `px-2 py-1 text-xs rounded ${activeCategory === cat
34204
+ ? 'bg-primary-500 text-white'
34205
+ : 'bg-white text-ink-600 hover:bg-stone-100'}`, onClick: () => {
34206
+ setActiveCategory(cat);
34207
+ inputRef.current?.focus();
34208
+ }, children: cat }, cat)))] }), jsxRuntime.jsx("div", { className: "overflow-y-auto", style: { maxHeight: 220 }, children: matchingFormulas.map((formula, index) => (jsxRuntime.jsxs("div", { "data-index": index, className: `px-3 py-2 cursor-pointer border-b border-stone-50 ${index === selectedIndex ? 'bg-primary-50' : 'hover:bg-paper-50'}`, onClick: () => insertFormula(formula), onMouseEnter: () => setSelectedIndex(index), children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsx("span", { className: "font-mono font-semibold text-primary-600 text-sm", children: formula.name }), jsxRuntime.jsx("span", { className: "text-xs text-ink-400 bg-stone-100 px-1.5 py-0.5 rounded", children: formula.category })] }), jsxRuntime.jsx("div", { className: "text-xs text-ink-500 mt-0.5 truncate", children: formula.description }), jsxRuntime.jsx("div", { className: "font-mono text-xs text-ink-400 mt-0.5", children: formula.syntax })] }, formula.name))) }), jsxRuntime.jsxs("div", { className: "px-3 py-1.5 bg-paper-50 border-t border-stone-100 text-xs text-ink-400", children: [jsxRuntime.jsx("span", { className: "font-medium", children: "\u2191\u2193" }), " navigate", jsxRuntime.jsx("span", { className: "mx-2", children: "\u00B7" }), jsxRuntime.jsx("span", { className: "font-medium", children: "Tab/Enter" }), " insert", jsxRuntime.jsx("span", { className: "mx-2", children: "\u00B7" }), jsxRuntime.jsx("span", { className: "font-medium", children: "Esc" }), " close"] })] }), document.body);
34209
+ };
34210
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("input", { ref: inputRef, type: "text", value: value, onChange: (e) => onChange(e.target.value), onKeyDown: handleKeyDown, onBlur: () => {
34211
+ // Delay to allow click on dropdown
34212
+ setTimeout(() => {
34213
+ setShowDropdown(false);
34214
+ setShowHint(false);
34215
+ }, 150);
34216
+ }, className: `w-full h-full border-none outline-none bg-transparent font-mono text-sm ${className}`, style: { margin: '-4px', padding: '4px' } }), renderDropdown(), renderHint()] }));
34217
+ };
34218
+
34219
+ // Handle both ESM default export and CommonJS module.exports
34220
+ // @ts-ignore
34221
+ const FormulaParser = FormulaParser$2 || FormulaParserModule;
34222
+ /**
34223
+ * Convert column index to Excel-style letter (0 = A, 1 = B, ..., 26 = AA)
34224
+ */
34225
+ const colIndexToLetter = (index) => {
34226
+ let result = '';
34227
+ let num = index;
34228
+ while (num >= 0) {
34229
+ result = String.fromCharCode((num % 26) + 65) + result;
34230
+ num = Math.floor(num / 26) - 1;
34231
+ }
34232
+ return result;
34233
+ };
34234
+ // Note: parseRef is available for future formula reference parsing
34235
+ // const parseRef = (ref: string): { row: number; col: number } | null => { ... }
34236
+ /**
34237
+ * DataGrid - Excel-like data grid component with formulas
34238
+ *
34239
+ * A grid-based spreadsheet component that provides:
34240
+ * - Cell-level editing with formula support (280+ Excel formulas)
34241
+ * - Sorting and filtering
34242
+ * - Frozen rows and columns
34243
+ * - Zebra striping
34244
+ * - CSV export
34245
+ * - Keyboard navigation
34246
+ *
34247
+ * Uses fast-formula-parser (MIT licensed) for formula evaluation.
34248
+ *
34249
+ * @example Basic usage
34250
+ * ```tsx
34251
+ * const columns = [
34252
+ * { key: 'name', header: 'Name' },
34253
+ * { key: 'q1', header: 'Q1', type: 'number' },
34254
+ * { key: 'q2', header: 'Q2', type: 'number' },
34255
+ * { key: 'total', header: 'Total', type: 'number' },
34256
+ * ];
34257
+ *
34258
+ * const data = [
34259
+ * [{ value: 'Widget A' }, { value: 100 }, { value: 150 }, { value: 0, formula: '=SUM(B1:C1)' }],
34260
+ * [{ value: 'Widget B' }, { value: 200 }, { value: 250 }, { value: 0, formula: '=SUM(B2:C2)' }],
34261
+ * ];
34262
+ *
34263
+ * <DataGrid
34264
+ * data={data}
34265
+ * columns={columns}
34266
+ * formulas
34267
+ * zebraStripes
34268
+ * frozenRows={1}
34269
+ * />
34270
+ * ```
34271
+ */
34272
+ const DataGrid = React.forwardRef(({ data: initialData, columns, onChange, rowHeaders = false, frozenRows: frozenRowsProp = 'none', frozenColumns = 0, showFreezeRowToggle = false, zebraStripes = false, formulas = false, readOnly = false, height = 400, width = '100%', showToolbar = false, title, enableExport = false, exportFileName = 'export.csv', enableSave = false, onSave, toolbarActions, className = '', density = 'normal', }, ref) => {
34273
+ // State
34274
+ const [data, setData] = React.useState(initialData);
34275
+ const [editingCell, setEditingCell] = React.useState(null);
34276
+ const [editValue, setEditValue] = React.useState('');
34277
+ const [sortConfig, setSortConfig] = React.useState(null);
34278
+ const [filters, setFilters] = React.useState([]);
34279
+ const [activeFilter, setActiveFilter] = React.useState(null);
34280
+ const [filterValue, setFilterValue] = React.useState('');
34281
+ const [isSaving, setIsSaving] = React.useState(false);
34282
+ const [selectedCell, setSelectedCell] = React.useState(null);
34283
+ const [frozenRowsState, setFrozenRowsState] = React.useState(frozenRowsProp);
34284
+ const [editingCellRect, setEditingCellRect] = React.useState(null);
34285
+ // Update frozen rows when prop changes
34286
+ React.useEffect(() => {
34287
+ setFrozenRowsState(frozenRowsProp);
34288
+ }, [frozenRowsProp]);
34289
+ const tableRef = React.useRef(null);
34290
+ const inputRef = React.useRef(null);
34291
+ // Compute actual number of frozen rows based on mode
34292
+ const frozenRows = React.useMemo(() => {
34293
+ if (frozenRowsState === 'none')
34294
+ return 0;
34295
+ if (frozenRowsState === 'first')
34296
+ return 1;
34297
+ if (frozenRowsState === 'selected') {
34298
+ // Return selected row + 1 (to include it), or 0 if nothing selected
34299
+ return selectedCell ? selectedCell.row + 1 : 0;
34300
+ }
34301
+ if (typeof frozenRowsState === 'number')
34302
+ return frozenRowsState;
34303
+ return 0;
34304
+ }, [frozenRowsState, selectedCell]);
34305
+ // Check if a specific row is frozen
34306
+ const isRowFrozen = React.useCallback((rowIndex) => {
34307
+ if (frozenRowsState === 'none')
34308
+ return false;
34309
+ if (frozenRowsState === 'first')
34310
+ return rowIndex === 0;
34311
+ if (frozenRowsState === 'selected') {
34312
+ return selectedCell ? rowIndex === selectedCell.row : false;
34313
+ }
34314
+ if (typeof frozenRowsState === 'number')
34315
+ return rowIndex < frozenRowsState;
34316
+ return false;
34317
+ }, [frozenRowsState, selectedCell]);
34318
+ // Update data when initialData changes
34319
+ React.useEffect(() => {
34320
+ setData(initialData);
34321
+ }, [initialData]);
34322
+ // Get computed data with formulas evaluated
34323
+ // Uses a cache to handle formula dependencies (formulas referencing other formulas)
34324
+ const computedData = React.useMemo(() => {
34325
+ if (!formulas)
34326
+ return data;
34327
+ // Cache for computed cell values to handle dependencies
34328
+ const computedCache = new Map();
34329
+ // Recursive function to get cell value, evaluating formulas as needed
34330
+ const getCellValue = (r, c) => {
34331
+ const cacheKey = `${r},${c}`;
34332
+ if (computedCache.has(cacheKey)) {
34333
+ return computedCache.get(cacheKey);
34334
+ }
34335
+ if (r < 0 || r >= data.length || c < 0 || c >= (data[r]?.length || 0)) {
34336
+ return null;
34337
+ }
34338
+ const cell = data[r][c];
34339
+ if (!cell?.formula) {
34340
+ return cell?.value ?? null;
34341
+ }
34342
+ // Mark as computing to detect circular references
34343
+ computedCache.set(cacheKey, '#CIRCULAR');
34344
+ try {
34345
+ const result = parser.parse(cell.formula.substring(1));
34346
+ computedCache.set(cacheKey, result);
34347
+ return result;
34348
+ }
34349
+ catch (error) {
34350
+ computedCache.set(cacheKey, '#ERROR');
34351
+ return '#ERROR';
34352
+ }
34353
+ };
34354
+ // Create parser with callbacks that resolve formula dependencies
34355
+ const parser = new FormulaParser({
34356
+ onCell: ({ row, col }) => {
34357
+ // row and col are 1-indexed in the parser
34358
+ return getCellValue(row - 1, col - 1);
34359
+ },
34360
+ onRange: ({ from, to }) => {
34361
+ const result = [];
34362
+ for (let r = from.row - 1; r <= to.row - 1; r++) {
34363
+ const rowData = [];
34364
+ for (let c = from.col - 1; c <= to.col - 1; c++) {
34365
+ rowData.push(getCellValue(r, c));
34366
+ }
34367
+ result.push(rowData);
34368
+ }
34369
+ return result;
34370
+ },
34371
+ });
34372
+ // Compute all cells
34373
+ return data.map((row, rowIndex) => row.map((cell, colIndex) => {
34374
+ if (cell?.formula) {
34375
+ return {
34376
+ ...cell,
34377
+ value: getCellValue(rowIndex, colIndex),
34378
+ };
34379
+ }
34380
+ return cell;
34381
+ }));
34382
+ }, [formulas, data]);
34383
+ // Apply sorting
34384
+ const sortedData = React.useMemo(() => {
34385
+ if (!sortConfig)
34386
+ return computedData;
34387
+ const colIndex = columns.findIndex((c) => c.key === sortConfig.key);
34388
+ if (colIndex === -1)
34389
+ return computedData;
34390
+ // Keep frozen rows at top
34391
+ const frozenData = computedData.slice(0, frozenRows);
34392
+ const sortableData = [...computedData.slice(frozenRows)];
34393
+ sortableData.sort((a, b) => {
34394
+ const aVal = a[colIndex]?.value ?? '';
34395
+ const bVal = b[colIndex]?.value ?? '';
34396
+ if (typeof aVal === 'number' && typeof bVal === 'number') {
34397
+ return sortConfig.direction === 'asc' ? aVal - bVal : bVal - aVal;
34398
+ }
34399
+ const aStr = String(aVal).toLowerCase();
34400
+ const bStr = String(bVal).toLowerCase();
34401
+ const cmp = aStr.localeCompare(bStr);
34402
+ return sortConfig.direction === 'asc' ? cmp : -cmp;
34403
+ });
34404
+ return [...frozenData, ...sortableData];
34405
+ }, [computedData, sortConfig, columns, frozenRows]);
34406
+ // Apply filters
34407
+ const filteredData = React.useMemo(() => {
34408
+ if (filters.length === 0)
34409
+ return sortedData;
34410
+ // Keep frozen rows
34411
+ const frozenData = sortedData.slice(0, frozenRows);
34412
+ const filterableData = sortedData.slice(frozenRows);
34413
+ const filtered = filterableData.filter((row) => {
34414
+ return filters.every((filter) => {
34415
+ const colIndex = columns.findIndex((c) => c.key === filter.key);
34416
+ if (colIndex === -1)
34417
+ return true;
34418
+ const cellValue = String(row[colIndex]?.value ?? '').toLowerCase();
34419
+ return cellValue.includes(filter.value.toLowerCase());
34420
+ });
34421
+ });
34422
+ return [...frozenData, ...filtered];
34423
+ }, [sortedData, filters, columns, frozenRows]);
34424
+ // Handle cell edit start
34425
+ const handleCellDoubleClick = React.useCallback((rowIndex, colIndex, cellElement) => {
34426
+ if (readOnly)
34427
+ return;
34428
+ const column = columns[colIndex];
34429
+ if (column?.readOnly)
34430
+ return;
34431
+ const cell = data[rowIndex]?.[colIndex];
34432
+ if (cell?.readOnly)
34433
+ return;
34434
+ setEditingCell({ row: rowIndex, col: colIndex });
34435
+ setEditValue(cell?.formula || String(cell?.value ?? ''));
34436
+ // Capture cell position for formula autocomplete dropdown
34437
+ if (cellElement) {
34438
+ setEditingCellRect(cellElement.getBoundingClientRect());
34439
+ }
34440
+ setTimeout(() => inputRef.current?.focus(), 0);
34441
+ }, [readOnly, columns, data]);
34442
+ // Handle cell edit complete
34443
+ const handleEditComplete = React.useCallback(() => {
34444
+ if (!editingCell)
34445
+ return;
34446
+ const { row, col } = editingCell;
34447
+ const newData = [...data];
34448
+ if (!newData[row])
34449
+ newData[row] = [];
34450
+ const isFormula = editValue.startsWith('=');
34451
+ const numValue = parseFloat(editValue);
34452
+ const value = isFormula ? 0 : !isNaN(numValue) ? numValue : editValue;
34453
+ newData[row][col] = {
34454
+ ...newData[row][col],
34455
+ value,
34456
+ formula: isFormula ? editValue : undefined,
34457
+ };
34458
+ setData(newData);
34459
+ setEditingCell(null);
34460
+ setEditValue('');
34461
+ if (onChange) {
34462
+ onChange(newData, row, col);
34463
+ }
34464
+ }, [editingCell, editValue, data, onChange]);
34465
+ // Handle cell edit cancel
34466
+ const handleEditCancel = React.useCallback(() => {
34467
+ setEditingCell(null);
34468
+ setEditValue('');
34469
+ }, []);
34470
+ // Handle key down in edit mode
34471
+ const handleEditKeyDown = React.useCallback((e) => {
34472
+ if (e.key === 'Enter') {
34473
+ e.preventDefault();
34474
+ handleEditComplete();
34475
+ }
34476
+ else if (e.key === 'Escape') {
34477
+ handleEditCancel();
34478
+ }
34479
+ else if (e.key === 'Tab') {
34480
+ e.preventDefault();
34481
+ handleEditComplete();
34482
+ // Move to next cell
34483
+ if (editingCell) {
34484
+ const nextCol = e.shiftKey ? editingCell.col - 1 : editingCell.col + 1;
34485
+ if (nextCol >= 0 && nextCol < columns.length) {
34486
+ handleCellDoubleClick(editingCell.row, nextCol);
34487
+ }
34488
+ }
34489
+ }
34490
+ }, [handleEditComplete, handleEditCancel, editingCell, columns.length, handleCellDoubleClick]);
34491
+ // Handle cell click
34492
+ const handleCellClick = React.useCallback((rowIndex, colIndex) => {
34493
+ setSelectedCell({ row: rowIndex, col: colIndex });
34494
+ }, []);
34495
+ // Handle keyboard navigation
34496
+ const handleKeyDown = React.useCallback((e) => {
34497
+ if (editingCell)
34498
+ return;
34499
+ if (!selectedCell)
34500
+ return;
34501
+ const { row, col } = selectedCell;
34502
+ let newRow = row;
34503
+ let newCol = col;
34504
+ switch (e.key) {
34505
+ case 'ArrowUp':
34506
+ newRow = Math.max(0, row - 1);
34507
+ break;
34508
+ case 'ArrowDown':
34509
+ newRow = Math.min(filteredData.length - 1, row + 1);
34510
+ break;
34511
+ case 'ArrowLeft':
34512
+ newCol = Math.max(0, col - 1);
34513
+ break;
34514
+ case 'ArrowRight':
34515
+ newCol = Math.min(columns.length - 1, col + 1);
34516
+ break;
34517
+ case 'Enter':
34518
+ case 'F2':
34519
+ handleCellDoubleClick(row, col);
34520
+ e.preventDefault();
34521
+ return;
34522
+ default:
34523
+ return;
34524
+ }
34525
+ if (newRow !== row || newCol !== col) {
34526
+ setSelectedCell({ row: newRow, col: newCol });
34527
+ e.preventDefault();
34528
+ }
34529
+ }, [editingCell, selectedCell, filteredData.length, columns.length, handleCellDoubleClick]);
34530
+ // Handle sort
34531
+ const handleSort = React.useCallback((key) => {
34532
+ setSortConfig((prev) => {
34533
+ if (prev?.key === key) {
34534
+ if (prev.direction === 'asc') {
34535
+ return { key, direction: 'desc' };
34536
+ }
34537
+ return null; // Clear sort
34538
+ }
34539
+ return { key, direction: 'asc' };
34540
+ });
34541
+ }, []);
34542
+ // Handle filter
34543
+ const handleFilter = React.useCallback((key) => {
34544
+ setActiveFilter((prev) => (prev === key ? null : key));
34545
+ const existing = filters.find((f) => f.key === key);
34546
+ setFilterValue(existing?.value || '');
34547
+ }, [filters]);
34548
+ // Apply filter
34549
+ const applyFilter = React.useCallback(() => {
34550
+ if (!activeFilter)
34551
+ return;
34552
+ setFilters((prev) => {
34553
+ const existing = prev.findIndex((f) => f.key === activeFilter);
34554
+ if (filterValue) {
34555
+ if (existing >= 0) {
34556
+ const newFilters = [...prev];
34557
+ newFilters[existing] = { key: activeFilter, value: filterValue };
34558
+ return newFilters;
34559
+ }
34560
+ return [...prev, { key: activeFilter, value: filterValue }];
34561
+ }
34562
+ else {
34563
+ return prev.filter((f) => f.key !== activeFilter);
34564
+ }
34565
+ });
34566
+ setActiveFilter(null);
34567
+ }, [activeFilter, filterValue]);
34568
+ // Clear filter
34569
+ const clearFilter = React.useCallback((key) => {
34570
+ setFilters((prev) => prev.filter((f) => f.key !== key));
34571
+ }, []);
34572
+ // Export to CSV
34573
+ const exportToCSV = React.useCallback(() => {
34574
+ const headers = columns.map((c) => c.header).join(',');
34575
+ const rows = filteredData.map((row) => row
34576
+ .map((cell) => {
34577
+ const val = String(cell?.value ?? '');
34578
+ // Escape quotes and wrap in quotes if contains comma
34579
+ if (val.includes(',') || val.includes('"') || val.includes('\n')) {
34580
+ return `"${val.replace(/"/g, '""')}"`;
34581
+ }
34582
+ return val;
34583
+ })
34584
+ .join(','));
34585
+ const csv = [headers, ...rows].join('\n');
34586
+ const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
34587
+ const url = URL.createObjectURL(blob);
34588
+ const link = document.createElement('a');
34589
+ link.href = url;
34590
+ link.download = exportFileName;
34591
+ link.click();
34592
+ URL.revokeObjectURL(url);
34593
+ addSuccessMessage('Exported to CSV successfully');
34594
+ }, [columns, filteredData, exportFileName]);
34595
+ // Save handler
34596
+ const handleSave = React.useCallback(async () => {
34597
+ if (!onSave)
34598
+ return;
34599
+ setIsSaving(true);
34600
+ try {
34601
+ await onSave(data);
34602
+ addSuccessMessage('Data saved successfully');
34603
+ }
34604
+ catch (error) {
34605
+ console.error('Save failed:', error);
34606
+ addErrorMessage('Failed to save data');
34607
+ }
34608
+ finally {
34609
+ setIsSaving(false);
34610
+ }
34611
+ }, [onSave, data]);
34612
+ // Toggle freeze first row
34613
+ const toggleFreezeFirstRow = React.useCallback(() => {
34614
+ setFrozenRowsState((prev) => (prev === 'first' || prev === 1 ? 'none' : 'first'));
34615
+ }, []);
34616
+ // Toggle freeze selected row
34617
+ const toggleFreezeSelectedRow = React.useCallback(() => {
34618
+ setFrozenRowsState((prev) => (prev === 'selected' ? 'none' : 'selected'));
34619
+ }, []);
34620
+ // Expose imperative handle
34621
+ React.useImperativeHandle(ref, () => ({
34622
+ getData: () => data,
34623
+ setCell: (rowIndex, colIndex, value) => {
34624
+ const newData = [...data];
34625
+ if (!newData[rowIndex])
34626
+ newData[rowIndex] = [];
34627
+ newData[rowIndex][colIndex] =
34628
+ typeof value === 'object' && value !== null ? value : { value: value };
34629
+ setData(newData);
34630
+ },
34631
+ clearFilters: () => setFilters([]),
34632
+ clearSort: () => setSortConfig(null),
34633
+ exportToCSV,
34634
+ toggleFreezeFirstRow,
34635
+ toggleFreezeSelectedRow,
34636
+ setFrozenRows: setFrozenRowsState,
34637
+ }));
34638
+ // Format cell value for display
34639
+ const formatValue = React.useCallback((value, column) => {
34640
+ if (value === null || value === undefined)
34641
+ return '';
34642
+ if (typeof value === 'boolean')
34643
+ return value ? 'Yes' : 'No';
34644
+ const numVal = typeof value === 'number' ? value : parseFloat(String(value));
34645
+ if (column.type === 'currency' && !isNaN(numVal)) {
34646
+ const decimals = column.format?.decimals ?? 2;
34647
+ const prefix = column.format?.prefix ?? '$';
34648
+ return `${prefix}${numVal.toFixed(decimals)}`;
34649
+ }
34650
+ if (column.type === 'percent' && !isNaN(numVal)) {
34651
+ const decimals = column.format?.decimals ?? 1;
34652
+ return `${(numVal * 100).toFixed(decimals)}%`;
34653
+ }
34654
+ if (column.type === 'number' && !isNaN(numVal)) {
34655
+ const decimals = column.format?.decimals;
34656
+ if (decimals !== undefined) {
34657
+ return numVal.toFixed(decimals);
34658
+ }
34659
+ }
34660
+ return String(value);
34661
+ }, []);
34662
+ // Density classes
34663
+ const densityClasses = {
34664
+ compact: 'py-1 px-2 text-xs',
34665
+ normal: 'py-2 px-3 text-sm',
34666
+ comfortable: 'py-3 px-4 text-sm',
34667
+ };
34668
+ const cellPadding = densityClasses[density];
34669
+ return (jsxRuntime.jsxs("div", { className: `data-grid ${className}`, style: { width }, children: [showToolbar && (jsxRuntime.jsxs(Stack, { direction: "horizontal", spacing: "md", align: "center", className: "mb-3 px-1", children: [title && (jsxRuntime.jsx("div", { className: "text-lg font-medium text-ink-900 flex-1", children: title })), showFreezeRowToggle && (jsxRuntime.jsx("div", { className: "relative", children: jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", icon: frozenRowsState !== 'none' ? (jsxRuntime.jsx(lucideReact.Pin, { className: "h-4 w-4 text-primary-600" })) : (jsxRuntime.jsx(lucideReact.PinOff, { className: "h-4 w-4" })), onClick: toggleFreezeFirstRow, title: frozenRowsState === 'first' || frozenRowsState === 1
34670
+ ? 'Unfreeze first row'
34671
+ : 'Freeze first row', children: frozenRowsState === 'first' || frozenRowsState === 1
34672
+ ? 'Unfreeze Row'
34673
+ : 'Freeze Row' }) })), enableExport && (jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", icon: jsxRuntime.jsx(lucideReact.Download, { className: "h-4 w-4" }), onClick: exportToCSV, children: "Export" })), enableSave && onSave && (jsxRuntime.jsx(Button, { variant: "primary", size: "sm", icon: jsxRuntime.jsx(lucideReact.Save, { className: "h-4 w-4" }), onClick: handleSave, loading: isSaving, children: "Save" })), toolbarActions] })), filters.length > 0 && (jsxRuntime.jsx(Stack, { direction: "horizontal", spacing: "sm", className: "mb-2 px-1 flex-wrap", children: filters.map((filter) => {
34674
+ const column = columns.find((c) => c.key === filter.key);
34675
+ return (jsxRuntime.jsxs("div", { className: "inline-flex items-center gap-1 px-2 py-1 bg-primary-100 text-primary-700 rounded text-xs", children: [jsxRuntime.jsxs("span", { className: "font-medium", children: [column?.header, ":"] }), jsxRuntime.jsx("span", { children: filter.value }), jsxRuntime.jsx("button", { onClick: () => clearFilter(filter.key), className: "ml-1 hover:bg-primary-200 rounded p-0.5", children: jsxRuntime.jsx(lucideReact.X, { className: "h-3 w-3" }) })] }, filter.key));
34676
+ }) })), jsxRuntime.jsx("div", { ref: tableRef, className: "relative overflow-auto border border-stone-200 rounded-lg bg-white", style: { height }, onKeyDown: handleKeyDown, tabIndex: 0, children: jsxRuntime.jsxs("table", { className: "border-collapse", style: { tableLayout: 'auto' }, children: [jsxRuntime.jsx("thead", { className: "sticky top-0 z-20 bg-stone-100", children: jsxRuntime.jsxs("tr", { children: [rowHeaders && (jsxRuntime.jsx("th", { className: `${cellPadding} border-b border-r border-stone-200 bg-stone-100 text-left font-semibold text-ink-600 sticky left-0 z-30`, style: { width: 50, minWidth: 50, maxWidth: 50 }, children: "#" })), columns.map((column, colIndex) => {
34677
+ const isFrozen = colIndex < frozenColumns;
34678
+ const leftOffset = rowHeaders ? 50 + columns.slice(0, colIndex).reduce((sum, c) => sum + (c.width || 100), 0) : columns.slice(0, colIndex).reduce((sum, c) => sum + (c.width || 100), 0);
34679
+ return (jsxRuntime.jsx("th", { className: `${cellPadding} border-b border-r border-stone-200 bg-stone-100 font-semibold text-ink-600 text-${column.align || 'left'} ${isFrozen ? 'sticky z-30' : ''}`, style: {
34680
+ width: column.width,
34681
+ minWidth: column.minWidth || 80,
34682
+ left: isFrozen ? leftOffset : undefined,
34683
+ }, children: jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("span", { className: "flex-1", children: column.header }), column.sortable && (jsxRuntime.jsx("button", { onClick: () => handleSort(column.key), className: "p-0.5 hover:bg-stone-200 rounded", children: sortConfig?.key === column.key ? (sortConfig.direction === 'asc' ? (jsxRuntime.jsx(lucideReact.ChevronUp, { className: "h-4 w-4 text-primary-600" })) : (jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4 text-primary-600" }))) : (jsxRuntime.jsx(lucideReact.ArrowUpDown, { className: "h-4 w-4 text-ink-400" })) })), column.filterable && (jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsx("button", { onClick: () => handleFilter(column.key), className: `p-0.5 hover:bg-stone-200 rounded ${filters.some((f) => f.key === column.key)
34684
+ ? 'text-primary-600'
34685
+ : 'text-ink-400'}`, children: jsxRuntime.jsx(lucideReact.Filter, { className: "h-4 w-4" }) }), activeFilter === column.key && (jsxRuntime.jsxs("div", { className: "absolute top-full right-0 mt-1 p-2 bg-white border border-stone-200 rounded-lg shadow-lg z-50 min-w-48", children: [jsxRuntime.jsx(Input, { size: "sm", placeholder: `Filter ${column.header}...`, value: filterValue, onChange: (e) => setFilterValue(e.target.value), onKeyDown: (e) => {
34686
+ if (e.key === 'Enter')
34687
+ applyFilter();
34688
+ if (e.key === 'Escape')
34689
+ setActiveFilter(null);
34690
+ }, autoFocus: true }), jsxRuntime.jsxs(Stack, { direction: "horizontal", spacing: "sm", className: "mt-2", children: [jsxRuntime.jsx(Button, { size: "sm", variant: "ghost", onClick: () => setActiveFilter(null), children: "Cancel" }), jsxRuntime.jsx(Button, { size: "sm", variant: "primary", onClick: applyFilter, children: "Apply" })] })] }))] }))] }) }, column.key));
34691
+ })] }) }), jsxRuntime.jsx("tbody", { children: filteredData.map((row, rowIndex) => {
34692
+ const isFrozen = isRowFrozen(rowIndex);
34693
+ const isZebra = zebraStripes && rowIndex % 2 === 1;
34694
+ return (jsxRuntime.jsxs("tr", { className: `${isZebra ? 'bg-paper-50' : 'bg-white'} ${isFrozen ? 'sticky z-10' : ''} ${isFrozen ? 'shadow-sm' : ''}`, style: {
34695
+ top: isFrozen ? `${40 + rowIndex * 40}px` : undefined,
34696
+ }, children: [rowHeaders && (jsxRuntime.jsx("td", { className: `${cellPadding} border-b border-r border-stone-200 bg-stone-50 text-ink-500 font-medium sticky left-0 z-10`, style: { width: 50, minWidth: 50, maxWidth: 50 }, children: Array.isArray(rowHeaders) ? rowHeaders[rowIndex] : rowIndex + 1 })), row.map((cell, colIndex) => {
34697
+ const column = columns[colIndex];
34698
+ const isFrozenCol = colIndex < frozenColumns;
34699
+ const isEditing = editingCell?.row === rowIndex && editingCell?.col === colIndex;
34700
+ const isSelected = selectedCell?.row === rowIndex && selectedCell?.col === colIndex;
34701
+ const hasFormula = !!cell?.formula;
34702
+ const leftOffset = rowHeaders
34703
+ ? 50 + columns.slice(0, colIndex).reduce((sum, c) => sum + (c.width || 100), 0)
34704
+ : columns.slice(0, colIndex).reduce((sum, c) => sum + (c.width || 100), 0);
34705
+ return (jsxRuntime.jsx("td", { className: `${cellPadding} border-b border-r border-stone-200 text-${column?.align || 'left'} ${isFrozenCol ? 'sticky z-10' : ''} ${isZebra && isFrozenCol ? 'bg-paper-50' : isFrozenCol ? 'bg-white' : ''} ${isSelected ? 'ring-2 ring-inset ring-primary-500' : ''} ${hasFormula ? 'bg-blue-50' : ''} ${cell?.className || ''}`, style: {
34706
+ left: isFrozenCol ? leftOffset : undefined,
34707
+ minWidth: column?.minWidth || 80,
34708
+ }, onClick: () => handleCellClick(rowIndex, colIndex), onDoubleClick: (e) => handleCellDoubleClick(rowIndex, colIndex, e.currentTarget), children: isEditing ? (formulas ? (jsxRuntime.jsx(FormulaAutocomplete, { value: editValue, onChange: setEditValue, onComplete: handleEditComplete, onCancel: handleEditCancel, anchorRect: editingCellRect, autoFocus: true })) : (jsxRuntime.jsx("input", { ref: inputRef, type: "text", value: editValue, onChange: (e) => setEditValue(e.target.value), onBlur: handleEditComplete, onKeyDown: handleEditKeyDown, className: "w-full h-full border-none outline-none bg-transparent", style: { margin: '-4px', padding: '4px' } }))) : (formatValue(cell?.value, column)) }, colIndex));
34709
+ })] }, rowIndex));
34710
+ }) })] }) }), jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-2 py-1 text-xs text-ink-500 border-t border-stone-200 bg-stone-50 rounded-b-lg", children: [jsxRuntime.jsxs("span", { children: [filteredData.length, " row", filteredData.length !== 1 ? 's' : '', filters.length > 0 && ` (filtered)`] }), selectedCell && (jsxRuntime.jsxs("span", { children: [colIndexToLetter(selectedCell.col), selectedCell.row + 1, data[selectedCell.row]?.[selectedCell.col]?.formula && (jsxRuntime.jsx("span", { className: "ml-2 text-blue-600", children: data[selectedCell.row][selectedCell.col].formula }))] }))] })] }));
34711
+ });
34712
+ DataGrid.displayName = 'DataGrid';
34713
+
34714
+ // Color mapping for action buttons
34715
+ const colorClasses = {
34716
+ primary: 'bg-accent-500 text-white',
34717
+ success: 'bg-success-500 text-white',
34718
+ warning: 'bg-warning-500 text-white',
34719
+ error: 'bg-error-500 text-white',
34720
+ default: 'bg-paper-500 text-white',
34721
+ };
34722
+ /**
34723
+ * SwipeActions - Touch-based swipe actions for list items
34724
+ *
34725
+ * Wraps any content with swipe-to-reveal actions, commonly used in mobile
34726
+ * list items for quick actions like delete, archive, edit, etc.
34727
+ *
34728
+ * Features:
34729
+ * - Left and right swipe actions
34730
+ * - Full swipe to trigger primary action
34731
+ * - Spring-back animation
34732
+ * - Touch and mouse support
34733
+ * - Customizable thresholds
34734
+ *
34735
+ * @example Basic delete action
34736
+ * ```tsx
34737
+ * <SwipeActions
34738
+ * leftActions={[
34739
+ * {
34740
+ * id: 'delete',
34741
+ * label: 'Delete',
34742
+ * icon: <Trash className="h-5 w-5" />,
34743
+ * color: 'error',
34744
+ * onClick: () => handleDelete(item),
34745
+ * primary: true,
34746
+ * },
34747
+ * ]}
34748
+ * >
34749
+ * <div className="p-4 bg-white">
34750
+ * List item content
34751
+ * </div>
34752
+ * </SwipeActions>
34753
+ * ```
34754
+ *
34755
+ * @example Multiple actions on both sides
34756
+ * ```tsx
34757
+ * <SwipeActions
34758
+ * leftActions={[
34759
+ * { id: 'delete', label: 'Delete', icon: <Trash />, color: 'error', onClick: handleDelete },
34760
+ * { id: 'archive', label: 'Archive', icon: <Archive />, color: 'warning', onClick: handleArchive },
34761
+ * ]}
34762
+ * rightActions={[
34763
+ * { id: 'edit', label: 'Edit', icon: <Edit />, color: 'primary', onClick: handleEdit },
34764
+ * ]}
34765
+ * fullSwipe
34766
+ * >
34767
+ * <ListItem />
34768
+ * </SwipeActions>
34769
+ * ```
34770
+ */
34771
+ function SwipeActions({ children, leftActions = [], rightActions = [], threshold = 80, fullSwipeThreshold = 0.5, fullSwipe = false, disabled = false, onSwipeChange, className = '', }) {
34772
+ const containerRef = React.useRef(null);
34773
+ const contentRef = React.useRef(null);
34774
+ // Swipe state
34775
+ const [translateX, setTranslateX] = React.useState(0);
34776
+ const [isDragging, setIsDragging] = React.useState(false);
34777
+ const [activeDirection, setActiveDirection] = React.useState(null);
34778
+ // Touch/mouse tracking
34779
+ const startX = React.useRef(0);
34780
+ const currentX = React.useRef(0);
34781
+ const startTime = React.useRef(0);
34782
+ // Calculate action widths
34783
+ const leftActionsWidth = leftActions.length * 72; // 72px per action
34784
+ const rightActionsWidth = rightActions.length * 72;
34785
+ // Reset position
34786
+ const resetPosition = React.useCallback(() => {
34787
+ setTranslateX(0);
34788
+ setActiveDirection(null);
34789
+ onSwipeChange?.(null);
34790
+ }, [onSwipeChange]);
34791
+ // Handle touch/mouse start
34792
+ const handleStart = React.useCallback((clientX) => {
34793
+ if (disabled)
34794
+ return;
34795
+ startX.current = clientX;
34796
+ currentX.current = clientX;
34797
+ startTime.current = Date.now();
34798
+ setIsDragging(true);
34799
+ }, [disabled]);
34800
+ // Handle touch/mouse move
34801
+ const handleMove = React.useCallback((clientX) => {
34802
+ if (!isDragging || disabled)
34803
+ return;
34804
+ const deltaX = clientX - startX.current;
34805
+ currentX.current = clientX;
34806
+ // Determine direction and apply resistance at boundaries
34807
+ let newTranslateX = deltaX;
34808
+ // Swiping left (reveals left actions on right side)
34809
+ if (deltaX < 0) {
34810
+ if (leftActions.length === 0) {
34811
+ newTranslateX = deltaX * 0.2; // Heavy resistance if no actions
34812
+ }
34813
+ else {
34814
+ const maxSwipe = fullSwipe
34815
+ ? -(containerRef.current?.offsetWidth || 300)
34816
+ : -leftActionsWidth;
34817
+ newTranslateX = Math.max(maxSwipe, deltaX);
34818
+ // Apply resistance past the action buttons
34819
+ if (newTranslateX < -leftActionsWidth) {
34820
+ const overSwipe = newTranslateX + leftActionsWidth;
34821
+ newTranslateX = -leftActionsWidth + overSwipe * 0.3;
34822
+ }
34823
+ }
34824
+ setActiveDirection('left');
34825
+ onSwipeChange?.('left');
34826
+ }
34827
+ // Swiping right (reveals right actions on left side)
34828
+ else if (deltaX > 0) {
34829
+ if (rightActions.length === 0) {
34830
+ newTranslateX = deltaX * 0.2; // Heavy resistance if no actions
34831
+ }
34832
+ else {
34833
+ const maxSwipe = fullSwipe
34834
+ ? (containerRef.current?.offsetWidth || 300)
34835
+ : rightActionsWidth;
34836
+ newTranslateX = Math.min(maxSwipe, deltaX);
34837
+ // Apply resistance past the action buttons
34838
+ if (newTranslateX > rightActionsWidth) {
34839
+ const overSwipe = newTranslateX - rightActionsWidth;
34840
+ newTranslateX = rightActionsWidth + overSwipe * 0.3;
34841
+ }
34842
+ }
34843
+ setActiveDirection('right');
34844
+ onSwipeChange?.('right');
34845
+ }
34846
+ setTranslateX(newTranslateX);
34847
+ }, [isDragging, disabled, leftActions.length, rightActions.length, leftActionsWidth, rightActionsWidth, fullSwipe, onSwipeChange]);
34848
+ // Handle touch/mouse end
34849
+ const handleEnd = React.useCallback(() => {
34850
+ if (!isDragging)
34851
+ return;
34852
+ setIsDragging(false);
34853
+ const deltaX = currentX.current - startX.current;
34854
+ const velocity = Math.abs(deltaX) / (Date.now() - startTime.current);
34855
+ const containerWidth = containerRef.current?.offsetWidth || 300;
34856
+ // Check for full swipe trigger
34857
+ if (fullSwipe) {
34858
+ const swipePercentage = Math.abs(translateX) / containerWidth;
34859
+ if (swipePercentage >= fullSwipeThreshold || velocity > 0.5) {
34860
+ // Find primary action and trigger it
34861
+ if (translateX < 0 && leftActions.length > 0) {
34862
+ const primaryAction = leftActions.find(a => a.primary) || leftActions[0];
34863
+ primaryAction.onClick();
34864
+ resetPosition();
34865
+ return;
34866
+ }
34867
+ else if (translateX > 0 && rightActions.length > 0) {
34868
+ const primaryAction = rightActions.find(a => a.primary) || rightActions[0];
34869
+ primaryAction.onClick();
34870
+ resetPosition();
34871
+ return;
34872
+ }
34873
+ }
34874
+ }
34875
+ // Snap to open or closed position
34876
+ if (Math.abs(translateX) >= threshold || velocity > 0.3) {
34877
+ // Snap open
34878
+ if (translateX < 0 && leftActions.length > 0) {
34879
+ setTranslateX(-leftActionsWidth);
34880
+ setActiveDirection('left');
34881
+ onSwipeChange?.('left');
34882
+ }
34883
+ else if (translateX > 0 && rightActions.length > 0) {
34884
+ setTranslateX(rightActionsWidth);
34885
+ setActiveDirection('right');
34886
+ onSwipeChange?.('right');
34887
+ }
34888
+ else {
34889
+ resetPosition();
34890
+ }
34891
+ }
34892
+ else {
34893
+ // Snap closed
34894
+ resetPosition();
34895
+ }
34896
+ }, [isDragging, translateX, threshold, fullSwipe, fullSwipeThreshold, leftActions, rightActions, leftActionsWidth, rightActionsWidth, resetPosition, onSwipeChange]);
34897
+ // Touch event handlers
34898
+ const handleTouchStart = (e) => {
34899
+ handleStart(e.touches[0].clientX);
34900
+ };
34901
+ const handleTouchMove = (e) => {
34902
+ handleMove(e.touches[0].clientX);
34903
+ };
34904
+ const handleTouchEnd = () => {
34905
+ handleEnd();
34906
+ };
34907
+ // Mouse event handlers (for testing/desktop)
34908
+ const handleMouseDown = (e) => {
34909
+ handleStart(e.clientX);
34910
+ };
34911
+ const handleMouseMove = (e) => {
34912
+ handleMove(e.clientX);
34913
+ };
34914
+ const handleMouseUp = () => {
34915
+ handleEnd();
34916
+ };
34917
+ // Close on outside click
34918
+ React.useEffect(() => {
34919
+ if (activeDirection === null)
34920
+ return;
34921
+ const handleClickOutside = (e) => {
34922
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
34923
+ resetPosition();
34924
+ }
34925
+ };
34926
+ document.addEventListener('mousedown', handleClickOutside);
34927
+ return () => document.removeEventListener('mousedown', handleClickOutside);
34928
+ }, [activeDirection, resetPosition]);
34929
+ // Handle mouse leave during drag
34930
+ React.useEffect(() => {
34931
+ if (!isDragging)
34932
+ return;
34933
+ const handleMouseLeave = () => {
34934
+ handleEnd();
34935
+ };
34936
+ document.addEventListener('mouseup', handleMouseLeave);
34937
+ return () => document.removeEventListener('mouseup', handleMouseLeave);
34938
+ }, [isDragging, handleEnd]);
34939
+ // Render action button
34940
+ const renderActionButton = (action) => {
34941
+ const colorClass = colorClasses[action.color || 'default'];
34942
+ return (jsxRuntime.jsxs("button", { onClick: (e) => {
34943
+ e.stopPropagation();
34944
+ action.onClick();
34945
+ resetPosition();
34946
+ }, className: `
34947
+ flex flex-col items-center justify-center
34948
+ w-18 h-full min-w-[72px]
34949
+ ${colorClass}
34950
+ transition-transform duration-150
34951
+ `, style: {
34952
+ transform: isDragging ? 'scale(1)' : 'scale(1)',
34953
+ }, children: [action.icon && (jsxRuntime.jsx("div", { className: "mb-1", children: action.icon })), jsxRuntime.jsx("span", { className: "text-xs font-medium", children: action.label })] }, action.id));
34954
+ };
34955
+ return (jsxRuntime.jsxs("div", { ref: containerRef, className: `relative overflow-hidden ${className}`, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, onMouseDown: handleMouseDown, onMouseMove: isDragging ? handleMouseMove : undefined, onMouseUp: handleMouseUp, onMouseLeave: isDragging ? handleEnd : undefined, children: [rightActions.length > 0 && (jsxRuntime.jsx("div", { className: "absolute left-0 top-0 bottom-0 flex", style: { width: rightActionsWidth }, children: rightActions.map((action) => renderActionButton(action)) })), leftActions.length > 0 && (jsxRuntime.jsx("div", { className: "absolute right-0 top-0 bottom-0 flex", style: { width: leftActionsWidth }, children: leftActions.map((action) => renderActionButton(action)) })), jsxRuntime.jsx("div", { ref: contentRef, className: `
34956
+ relative bg-white
34957
+ ${isDragging ? '' : 'transition-transform duration-200 ease-out'}
34958
+ `, style: {
34959
+ transform: `translateX(${translateX}px)`,
34960
+ touchAction: 'pan-y', // Allow vertical scrolling
34961
+ }, children: children })] }));
34962
+ }
34963
+
34964
+ var classnames = {exports: {}};
34965
+
34966
+ /*!
34967
+ Copyright (c) 2018 Jed Watson.
34968
+ Licensed under the MIT License (MIT), see
34969
+ http://jedwatson.github.io/classnames
34970
+ */
34971
+
34972
+ (function (module) {
34973
+ /* global define */
34974
+
34975
+ (function () {
34976
+
34977
+ var hasOwn = {}.hasOwnProperty;
34978
+
34979
+ function classNames () {
34980
+ var classes = '';
34981
+
34982
+ for (var i = 0; i < arguments.length; i++) {
34983
+ var arg = arguments[i];
34984
+ if (arg) {
34985
+ classes = appendClass(classes, parseValue(arg));
34986
+ }
34987
+ }
34988
+
34989
+ return classes;
34990
+ }
34991
+
34992
+ function parseValue (arg) {
34993
+ if (typeof arg === 'string' || typeof arg === 'number') {
34994
+ return arg;
34995
+ }
34996
+
34997
+ if (typeof arg !== 'object') {
34998
+ return '';
34999
+ }
35000
+
35001
+ if (Array.isArray(arg)) {
35002
+ return classNames.apply(null, arg);
35003
+ }
35004
+
35005
+ if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes('[native code]')) {
35006
+ return arg.toString();
35007
+ }
35008
+
35009
+ var classes = '';
35010
+
35011
+ for (var key in arg) {
35012
+ if (hasOwn.call(arg, key) && arg[key]) {
35013
+ classes = appendClass(classes, key);
35014
+ }
35015
+ }
35016
+
35017
+ return classes;
35018
+ }
35019
+
35020
+ function appendClass (value, newClass) {
35021
+ if (!newClass) {
35022
+ return value;
35023
+ }
35024
+
35025
+ if (value) {
35026
+ return value + ' ' + newClass;
35027
+ }
35028
+
35029
+ return value + newClass;
35030
+ }
35031
+
35032
+ if (module.exports) {
35033
+ classNames.default = classNames;
35034
+ module.exports = classNames;
35035
+ } else {
35036
+ window.classNames = classNames;
35037
+ }
35038
+ }());
35039
+ } (classnames));
35040
+
35041
+ var classnamesExports = classnames.exports;
35042
+ var classNames = /*@__PURE__*/getDefaultExportFromCjs(classnamesExports);
33051
35043
 
33052
35044
  var scheduler = {exports: {}};
33053
35045
 
@@ -35011,7 +37003,7 @@ function extractFormula(value) {
35011
37003
  return value.slice(1);
35012
37004
  }
35013
37005
  function createFormulaParser(data, config) {
35014
- return new FormulaParser$1(__assign(__assign({}, config), { onCell: function (ref) {
37006
+ return new FormulaParser$2(__assign(__assign({}, config), { onCell: function (ref) {
35015
37007
  var point = {
35016
37008
  row: ref.row - 1,
35017
37009
  column: ref.col - 1,
@@ -54859,6 +56851,7 @@ exports.CurrencyInput = CurrencyInput;
54859
56851
  exports.Dashboard = Dashboard;
54860
56852
  exports.DashboardContent = DashboardContent;
54861
56853
  exports.DashboardHeader = DashboardHeader;
56854
+ exports.DataGrid = DataGrid;
54862
56855
  exports.DataTable = DataTable;
54863
56856
  exports.DataTableCardView = DataTableCardView;
54864
56857
  exports.DateDisplay = DateDisplay;
@@ -54880,6 +56873,9 @@ exports.ExpandableRowButton = ExpandableRowButton;
54880
56873
  exports.ExpandableToolbar = ExpandableToolbar;
54881
56874
  exports.ExpandedRowEditForm = ExpandedRowEditForm;
54882
56875
  exports.ExportButton = ExportButton;
56876
+ exports.FORMULA_CATEGORIES = FORMULA_CATEGORIES;
56877
+ exports.FORMULA_DEFINITIONS = FORMULA_DEFINITIONS;
56878
+ exports.FORMULA_NAMES = FORMULA_NAMES;
54883
56879
  exports.FieldArray = FieldArray;
54884
56880
  exports.FileUpload = FileUpload;
54885
56881
  exports.FilterBar = FilterBar;
@@ -54981,11 +56977,14 @@ exports.exportDataTableToExcel = exportDataTableToExcel;
54981
56977
  exports.exportToExcel = exportToExcel;
54982
56978
  exports.formatStatisticValue = formatStatisticValue;
54983
56979
  exports.formatStatistics = formatStatistics;
56980
+ exports.getFormula = getFormula;
56981
+ exports.getFormulasByCategory = getFormulasByCategory;
54984
56982
  exports.loadColumnOrder = loadColumnOrder;
54985
56983
  exports.loadColumnWidths = loadColumnWidths;
54986
56984
  exports.reorderArray = reorderArray;
54987
56985
  exports.saveColumnOrder = saveColumnOrder;
54988
56986
  exports.saveColumnWidths = saveColumnWidths;
56987
+ exports.searchFormulas = searchFormulas;
54989
56988
  exports.statusManager = statusManager;
54990
56989
  exports.useBreakpoint = useBreakpoint;
54991
56990
  exports.useBreakpointValue = useBreakpointValue;