react-open-source-grid 1.6.5 → 1.6.6

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/lib/index.js CHANGED
@@ -2356,7 +2356,16 @@ var GridBody = ({
2356
2356
  onKeyDown: (e) => handleKeyDown(e, rowIndex, columnIndex),
2357
2357
  tabIndex: isCellFocused ? 0 : -1
2358
2358
  },
2359
- isEditing ? /* @__PURE__ */ React6.createElement(
2359
+ isEditing ? column.editor ? column.editor({
2360
+ value: editState.value,
2361
+ row,
2362
+ column,
2363
+ onChange: handleEditChange,
2364
+ onCommit: handleEditComplete,
2365
+ onCancel: () => dispatch({ type: "END_EDIT" }),
2366
+ autoFocus: true,
2367
+ ...column.editorParams
2368
+ }) : /* @__PURE__ */ React6.createElement(
2360
2369
  "input",
2361
2370
  {
2362
2371
  ref: editInputRef,
@@ -5027,7 +5036,7 @@ var LayoutPresetsManager = ({
5027
5036
  setLoading(false);
5028
5037
  }
5029
5038
  };
5030
- const formatDate = (timestamp) => {
5039
+ const formatDate2 = (timestamp) => {
5031
5040
  return new Date(timestamp).toLocaleString();
5032
5041
  };
5033
5042
  return /* @__PURE__ */ React14.createElement("div", { className: "relative inline-block" }, /* @__PURE__ */ React14.createElement(
@@ -5141,7 +5150,7 @@ var LayoutPresetsManager = ({
5141
5150
  if (buttons) buttons.style.opacity = "0";
5142
5151
  }
5143
5152
  },
5144
- /* @__PURE__ */ React14.createElement("div", { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between" } }, /* @__PURE__ */ React14.createElement("div", { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React14.createElement("h4", { style: { fontSize: "14px", fontWeight: "500", color: "#111827", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, preset.name), preset.description && /* @__PURE__ */ React14.createElement("p", { style: { fontSize: "12px", color: "#6b7280", marginTop: "4px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, preset.description), /* @__PURE__ */ React14.createElement("p", { style: { fontSize: "12px", color: "#9ca3af", marginTop: "4px" } }, "Updated: ", formatDate(preset.updatedAt))), /* @__PURE__ */ React14.createElement("div", { className: "preset-actions", style: { display: "flex", gap: "4px", marginLeft: "8px", opacity: 0, transition: "opacity 0.2s" } }, /* @__PURE__ */ React14.createElement(
5153
+ /* @__PURE__ */ React14.createElement("div", { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between" } }, /* @__PURE__ */ React14.createElement("div", { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React14.createElement("h4", { style: { fontSize: "14px", fontWeight: "500", color: "#111827", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, preset.name), preset.description && /* @__PURE__ */ React14.createElement("p", { style: { fontSize: "12px", color: "#6b7280", marginTop: "4px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, preset.description), /* @__PURE__ */ React14.createElement("p", { style: { fontSize: "12px", color: "#9ca3af", marginTop: "4px" } }, "Updated: ", formatDate2(preset.updatedAt))), /* @__PURE__ */ React14.createElement("div", { className: "preset-actions", style: { display: "flex", gap: "4px", marginLeft: "8px", opacity: 0, transition: "opacity 0.2s" } }, /* @__PURE__ */ React14.createElement(
5145
5154
  "button",
5146
5155
  {
5147
5156
  onClick: (e) => handleUpdatePreset(preset, e),
@@ -12232,6 +12241,1280 @@ var PivotToolbar = ({
12232
12241
  } }, /* @__PURE__ */ React29.createElement("strong", null, "\u{1F4A1} Tip:"), " Select a Row Group column to organize data by, a Pivot column whose values become new columns, and a Value column to aggregate.")
12233
12242
  );
12234
12243
  };
12244
+
12245
+ // src/editors/editorUtils.ts
12246
+ import { useEffect as useEffect14, useRef as useRef15, useCallback as useCallback11 } from "react";
12247
+ function useEditorKeyboardNavigation(handlers, config = {}) {
12248
+ const {
12249
+ commitOnEnter = true,
12250
+ cancelOnEscape = true,
12251
+ commitOnTab = true,
12252
+ preventDefault = true,
12253
+ stopPropagation = true
12254
+ } = config;
12255
+ const handleKeyDown = useCallback11(
12256
+ (event) => {
12257
+ let handled = false;
12258
+ switch (event.key) {
12259
+ case "Enter":
12260
+ if (commitOnEnter && handlers.onEnter) {
12261
+ handlers.onEnter();
12262
+ handled = true;
12263
+ }
12264
+ break;
12265
+ case "Escape":
12266
+ if (cancelOnEscape && handlers.onEscape) {
12267
+ handlers.onEscape();
12268
+ handled = true;
12269
+ }
12270
+ break;
12271
+ case "Tab":
12272
+ if (commitOnTab && handlers.onTab) {
12273
+ handlers.onTab(event.shiftKey);
12274
+ handled = true;
12275
+ }
12276
+ break;
12277
+ case "ArrowUp":
12278
+ if (handlers.onArrowUp) {
12279
+ handlers.onArrowUp();
12280
+ handled = true;
12281
+ }
12282
+ break;
12283
+ case "ArrowDown":
12284
+ if (handlers.onArrowDown) {
12285
+ handlers.onArrowDown();
12286
+ handled = true;
12287
+ }
12288
+ break;
12289
+ }
12290
+ if (handled) {
12291
+ if (preventDefault) {
12292
+ event.preventDefault();
12293
+ }
12294
+ if (stopPropagation) {
12295
+ event.stopPropagation();
12296
+ }
12297
+ }
12298
+ },
12299
+ [
12300
+ handlers,
12301
+ commitOnEnter,
12302
+ cancelOnEscape,
12303
+ commitOnTab,
12304
+ preventDefault,
12305
+ stopPropagation
12306
+ ]
12307
+ );
12308
+ return { handleKeyDown };
12309
+ }
12310
+ function useEditorAutoFocus(autoFocus = true, selectAll = false) {
12311
+ const elementRef = useRef15(null);
12312
+ useEffect14(() => {
12313
+ if (autoFocus && elementRef.current) {
12314
+ elementRef.current.focus();
12315
+ if (selectAll && elementRef.current instanceof HTMLInputElement) {
12316
+ elementRef.current.select();
12317
+ }
12318
+ }
12319
+ }, [autoFocus, selectAll]);
12320
+ return elementRef;
12321
+ }
12322
+ function useEditorClickOutside(ref, onClickOutside, enabled = true) {
12323
+ useEffect14(() => {
12324
+ if (!enabled) return;
12325
+ const handleClickOutside = (event) => {
12326
+ if (ref.current && !ref.current.contains(event.target)) {
12327
+ onClickOutside();
12328
+ }
12329
+ };
12330
+ const timeoutId = setTimeout(() => {
12331
+ document.addEventListener("mousedown", handleClickOutside);
12332
+ }, 0);
12333
+ return () => {
12334
+ clearTimeout(timeoutId);
12335
+ document.removeEventListener("mousedown", handleClickOutside);
12336
+ };
12337
+ }, [ref, onClickOutside, enabled]);
12338
+ }
12339
+ function usePopupPosition(anchorRef, popupRef, isOpen, placement = "auto") {
12340
+ useEffect14(() => {
12341
+ if (!isOpen || !anchorRef.current || !popupRef.current) return;
12342
+ const anchor = anchorRef.current;
12343
+ const popup = popupRef.current;
12344
+ const anchorRect = anchor.getBoundingClientRect();
12345
+ const popupRect = popup.getBoundingClientRect();
12346
+ const viewportHeight = window.innerHeight;
12347
+ const viewportWidth = window.innerWidth;
12348
+ let top = 0;
12349
+ let left = 0;
12350
+ let actualPlacement = placement;
12351
+ if (placement === "auto") {
12352
+ const spaceBelow = viewportHeight - anchorRect.bottom;
12353
+ const spaceAbove = anchorRect.top;
12354
+ const spaceRight = viewportWidth - anchorRect.right;
12355
+ const spaceLeft = anchorRect.left;
12356
+ if (spaceBelow >= popupRect.height || spaceBelow >= spaceAbove) {
12357
+ actualPlacement = "bottom";
12358
+ } else if (spaceAbove >= popupRect.height) {
12359
+ actualPlacement = "top";
12360
+ } else if (spaceRight >= popupRect.width) {
12361
+ actualPlacement = "right";
12362
+ } else if (spaceLeft >= popupRect.width) {
12363
+ actualPlacement = "left";
12364
+ } else {
12365
+ actualPlacement = "bottom";
12366
+ }
12367
+ }
12368
+ switch (actualPlacement) {
12369
+ case "bottom":
12370
+ top = anchorRect.bottom + window.scrollY;
12371
+ left = anchorRect.left + window.scrollX;
12372
+ break;
12373
+ case "top":
12374
+ top = anchorRect.top + window.scrollY - popupRect.height;
12375
+ left = anchorRect.left + window.scrollX;
12376
+ break;
12377
+ case "right":
12378
+ top = anchorRect.top + window.scrollY;
12379
+ left = anchorRect.right + window.scrollX;
12380
+ break;
12381
+ case "left":
12382
+ top = anchorRect.top + window.scrollY;
12383
+ left = anchorRect.left + window.scrollX - popupRect.width;
12384
+ break;
12385
+ }
12386
+ const margin = 8;
12387
+ if (left + popupRect.width > viewportWidth) {
12388
+ left = viewportWidth - popupRect.width - margin;
12389
+ }
12390
+ if (left < margin) {
12391
+ left = margin;
12392
+ }
12393
+ if (top + popupRect.height > viewportHeight + window.scrollY) {
12394
+ top = viewportHeight + window.scrollY - popupRect.height - margin;
12395
+ }
12396
+ if (top < window.scrollY + margin) {
12397
+ top = window.scrollY + margin;
12398
+ }
12399
+ popup.style.top = `${top}px`;
12400
+ popup.style.left = `${left}px`;
12401
+ popup.style.minWidth = `${anchorRect.width}px`;
12402
+ }, [isOpen, anchorRef, popupRef, placement]);
12403
+ }
12404
+ function debounce2(func, wait) {
12405
+ let timeout = null;
12406
+ return function executedFunction(...args) {
12407
+ const later = () => {
12408
+ timeout = null;
12409
+ func(...args);
12410
+ };
12411
+ if (timeout) {
12412
+ clearTimeout(timeout);
12413
+ }
12414
+ timeout = setTimeout(later, wait);
12415
+ };
12416
+ }
12417
+ function formatNumber(value, decimals = 0, thousandsSeparator = ",", decimalSeparator = ".") {
12418
+ const fixed = value.toFixed(decimals);
12419
+ const [integerPart, decimalPart] = fixed.split(".");
12420
+ const formattedInteger = integerPart.replace(
12421
+ /\B(?=(\d{3})+(?!\d))/g,
12422
+ thousandsSeparator
12423
+ );
12424
+ return decimalPart ? `${formattedInteger}${decimalSeparator}${decimalPart}` : formattedInteger;
12425
+ }
12426
+ function parseFormattedNumber(value, thousandsSeparator = ",", decimalSeparator = ".") {
12427
+ if (!value || typeof value !== "string") return null;
12428
+ const normalized = value.replace(new RegExp(`\\${thousandsSeparator}`, "g"), "").replace(new RegExp(`\\${decimalSeparator}`), ".");
12429
+ const parsed = parseFloat(normalized);
12430
+ return isNaN(parsed) ? null : parsed;
12431
+ }
12432
+ function filterOptions(options, searchQuery) {
12433
+ if (!searchQuery.trim()) return options;
12434
+ const lowerQuery = searchQuery.toLowerCase();
12435
+ return options.filter(
12436
+ (option) => option.label.toLowerCase().includes(lowerQuery)
12437
+ );
12438
+ }
12439
+
12440
+ // src/editors/RichSelectEditor.tsx
12441
+ import React30, { useState as useState21, useRef as useRef16, useEffect as useEffect15, useMemo as useMemo9 } from "react";
12442
+ function RichSelectEditor(props) {
12443
+ const {
12444
+ value,
12445
+ onChange,
12446
+ onCommit,
12447
+ onCancel,
12448
+ autoFocus = true,
12449
+ options,
12450
+ placeholder = "Select...",
12451
+ allowClear = false,
12452
+ filterable = true,
12453
+ renderOptionLabel,
12454
+ maxDropdownHeight = 300
12455
+ } = props;
12456
+ const [isOpen] = useState21(true);
12457
+ const [searchQuery, setSearchQuery] = useState21("");
12458
+ const [focusedIndex, setFocusedIndex] = useState21(-1);
12459
+ const containerRef = useRef16(null);
12460
+ const inputRef = useEditorAutoFocus(autoFocus, true);
12461
+ const dropdownRef = useRef16(null);
12462
+ const optionRefs = useRef16([]);
12463
+ const filteredOptions = useMemo9(
12464
+ () => filterable ? filterOptions(options, searchQuery) : options,
12465
+ [options, searchQuery, filterable]
12466
+ );
12467
+ const selectedOption = useMemo9(
12468
+ () => options.find((opt) => opt.value === value),
12469
+ [options, value]
12470
+ );
12471
+ useEffect15(() => {
12472
+ if (selectedOption) {
12473
+ const index = filteredOptions.findIndex(
12474
+ (opt) => opt.value === selectedOption.value
12475
+ );
12476
+ if (index !== -1 && focusedIndex === -1) {
12477
+ queueMicrotask(() => setFocusedIndex(index));
12478
+ }
12479
+ }
12480
+ }, [selectedOption, filteredOptions]);
12481
+ useEffect15(() => {
12482
+ var _a;
12483
+ if (focusedIndex >= 0 && optionRefs.current[focusedIndex]) {
12484
+ (_a = optionRefs.current[focusedIndex]) == null ? void 0 : _a.scrollIntoView({
12485
+ block: "nearest",
12486
+ behavior: "smooth"
12487
+ });
12488
+ }
12489
+ }, [focusedIndex]);
12490
+ usePopupPosition(containerRef, dropdownRef, isOpen, "auto");
12491
+ useEditorClickOutside(
12492
+ containerRef,
12493
+ () => {
12494
+ if (isOpen) {
12495
+ onCommit();
12496
+ }
12497
+ },
12498
+ true
12499
+ );
12500
+ const handleSelectOption = (option) => {
12501
+ if (!option.disabled) {
12502
+ onChange(option.value);
12503
+ onCommit();
12504
+ }
12505
+ };
12506
+ const handleClear = (e) => {
12507
+ e.stopPropagation();
12508
+ onChange(null);
12509
+ setSearchQuery("");
12510
+ setFocusedIndex(-1);
12511
+ };
12512
+ const handleArrowDown = () => {
12513
+ setFocusedIndex((prev) => {
12514
+ var _a;
12515
+ let next = prev + 1;
12516
+ while (next < filteredOptions.length && ((_a = filteredOptions[next]) == null ? void 0 : _a.disabled)) {
12517
+ next++;
12518
+ }
12519
+ return next < filteredOptions.length ? next : prev;
12520
+ });
12521
+ };
12522
+ const handleArrowUp = () => {
12523
+ setFocusedIndex((prev) => {
12524
+ var _a;
12525
+ let next = prev - 1;
12526
+ while (next >= 0 && ((_a = filteredOptions[next]) == null ? void 0 : _a.disabled)) {
12527
+ next--;
12528
+ }
12529
+ return next >= 0 ? next : prev;
12530
+ });
12531
+ };
12532
+ const handleEnter = () => {
12533
+ if (focusedIndex >= 0 && focusedIndex < filteredOptions.length) {
12534
+ const option = filteredOptions[focusedIndex];
12535
+ if (option && !option.disabled) {
12536
+ handleSelectOption(option);
12537
+ }
12538
+ } else {
12539
+ onCommit();
12540
+ }
12541
+ };
12542
+ const { handleKeyDown } = useEditorKeyboardNavigation(
12543
+ {
12544
+ onEnter: handleEnter,
12545
+ onEscape: onCancel,
12546
+ onArrowUp: handleArrowUp,
12547
+ onArrowDown: handleArrowDown
12548
+ },
12549
+ {
12550
+ commitOnTab: true,
12551
+ commitOnBlur: false
12552
+ }
12553
+ );
12554
+ const renderLabel = (option) => {
12555
+ if (renderOptionLabel) {
12556
+ return renderOptionLabel(option);
12557
+ }
12558
+ return /* @__PURE__ */ React30.createElement("div", { className: "editor-option-content" }, option.icon && /* @__PURE__ */ React30.createElement("span", { className: "editor-option-icon" }, option.icon), /* @__PURE__ */ React30.createElement("div", { className: "editor-option-text" }, /* @__PURE__ */ React30.createElement("div", { className: "editor-option-label" }, option.label), option.description && /* @__PURE__ */ React30.createElement("div", { className: "editor-option-description" }, option.description)));
12559
+ };
12560
+ return /* @__PURE__ */ React30.createElement(
12561
+ "div",
12562
+ {
12563
+ ref: containerRef,
12564
+ className: "editor-container editor-richselect-container",
12565
+ onKeyDown: handleKeyDown
12566
+ },
12567
+ /* @__PURE__ */ React30.createElement("div", { className: "editor-input-wrapper" }, /* @__PURE__ */ React30.createElement(
12568
+ "input",
12569
+ {
12570
+ ref: inputRef,
12571
+ type: "text",
12572
+ className: "editor-input editor-richselect-input",
12573
+ value: filterable ? searchQuery : (selectedOption == null ? void 0 : selectedOption.label) || "",
12574
+ onChange: (e) => {
12575
+ if (filterable) {
12576
+ setSearchQuery(e.target.value);
12577
+ setFocusedIndex(-1);
12578
+ }
12579
+ },
12580
+ placeholder: (selectedOption == null ? void 0 : selectedOption.label) || placeholder,
12581
+ "aria-label": "Select option",
12582
+ "aria-expanded": isOpen,
12583
+ "aria-autocomplete": "list",
12584
+ "aria-controls": "richselect-dropdown",
12585
+ autoComplete: "off"
12586
+ }
12587
+ ), /* @__PURE__ */ React30.createElement("div", { className: "editor-input-actions" }, allowClear && value !== null && value !== void 0 && /* @__PURE__ */ React30.createElement(
12588
+ "button",
12589
+ {
12590
+ type: "button",
12591
+ className: "editor-clear-btn",
12592
+ onClick: handleClear,
12593
+ "aria-label": "Clear selection",
12594
+ tabIndex: -1
12595
+ },
12596
+ "\xD7"
12597
+ ), /* @__PURE__ */ React30.createElement("span", { className: "editor-dropdown-icon", "aria-hidden": "true" }, "\u25BC"))),
12598
+ isOpen && /* @__PURE__ */ React30.createElement(
12599
+ "div",
12600
+ {
12601
+ ref: dropdownRef,
12602
+ id: "richselect-dropdown",
12603
+ className: "editor-dropdown",
12604
+ role: "listbox",
12605
+ style: { maxHeight: maxDropdownHeight }
12606
+ },
12607
+ filteredOptions.length === 0 ? /* @__PURE__ */ React30.createElement("div", { className: "editor-dropdown-empty" }, "No options found") : filteredOptions.map((option, index) => /* @__PURE__ */ React30.createElement(
12608
+ "div",
12609
+ {
12610
+ key: option.value,
12611
+ ref: (el) => {
12612
+ optionRefs.current[index] = el;
12613
+ },
12614
+ className: `editor-dropdown-option ${option.value === value ? "selected" : ""} ${option.disabled ? "disabled" : ""} ${index === focusedIndex ? "focused" : ""}`,
12615
+ role: "option",
12616
+ "aria-selected": option.value === value,
12617
+ "aria-disabled": option.disabled,
12618
+ onClick: () => handleSelectOption(option),
12619
+ onMouseEnter: () => !option.disabled && setFocusedIndex(index)
12620
+ },
12621
+ renderLabel(option)
12622
+ ))
12623
+ )
12624
+ );
12625
+ }
12626
+ RichSelectEditor.displayName = "RichSelectEditor";
12627
+
12628
+ // src/editors/DateEditor.tsx
12629
+ import React31, { useState as useState22, useRef as useRef17, useMemo as useMemo10 } from "react";
12630
+ function DateEditor(props) {
12631
+ const {
12632
+ value,
12633
+ onChange,
12634
+ onCommit,
12635
+ onCancel,
12636
+ autoFocus = true,
12637
+ dateFormat = "yyyy-MM-dd",
12638
+ showTime = false,
12639
+ minDate,
12640
+ maxDate,
12641
+ autoCommit = false
12642
+ } = props;
12643
+ const [isOpen] = useState22(true);
12644
+ const [inputValue, setInputValue] = useState22("");
12645
+ const [viewDate, setViewDate] = useState22(/* @__PURE__ */ new Date());
12646
+ const containerRef = useRef17(null);
12647
+ const inputRef = useEditorAutoFocus(autoFocus, true);
12648
+ const calendarRef = useRef17(null);
12649
+ const parsedValue = useMemo10(() => {
12650
+ if (!value) return null;
12651
+ if (value instanceof Date) return value;
12652
+ const parsed = new Date(value);
12653
+ return isNaN(parsed.getTime()) ? null : parsed;
12654
+ }, [value]);
12655
+ React31.useEffect(() => {
12656
+ if (parsedValue) {
12657
+ setInputValue(formatDate(parsedValue, dateFormat, showTime));
12658
+ setViewDate(parsedValue);
12659
+ }
12660
+ }, [parsedValue, dateFormat, showTime]);
12661
+ usePopupPosition(containerRef, calendarRef, isOpen, "auto");
12662
+ useEditorClickOutside(containerRef, () => {
12663
+ if (isOpen) {
12664
+ onCommit();
12665
+ }
12666
+ }, true);
12667
+ const handleSelectDate = (date) => {
12668
+ const newDate = new Date(date);
12669
+ if (parsedValue && !showTime) {
12670
+ newDate.setHours(parsedValue.getHours());
12671
+ newDate.setMinutes(parsedValue.getMinutes());
12672
+ newDate.setSeconds(parsedValue.getSeconds());
12673
+ }
12674
+ onChange(newDate);
12675
+ setInputValue(formatDate(newDate, dateFormat, showTime));
12676
+ if (autoCommit && !showTime) {
12677
+ onCommit();
12678
+ }
12679
+ };
12680
+ const handleTimeChange = (hours, minutes) => {
12681
+ const newDate = parsedValue ? new Date(parsedValue) : /* @__PURE__ */ new Date();
12682
+ newDate.setHours(hours);
12683
+ newDate.setMinutes(minutes);
12684
+ onChange(newDate);
12685
+ setInputValue(formatDate(newDate, dateFormat, showTime));
12686
+ };
12687
+ const handleInputChange = (e) => {
12688
+ const newValue = e.target.value;
12689
+ setInputValue(newValue);
12690
+ const parsed = parseDate(newValue, dateFormat);
12691
+ if (parsed && !isNaN(parsed.getTime())) {
12692
+ onChange(parsed);
12693
+ setViewDate(parsed);
12694
+ }
12695
+ };
12696
+ const handleInputBlur = () => {
12697
+ if (parsedValue) {
12698
+ setInputValue(formatDate(parsedValue, dateFormat, showTime));
12699
+ }
12700
+ };
12701
+ const handlePrevMonth = () => {
12702
+ setViewDate((prev) => {
12703
+ const newDate = new Date(prev);
12704
+ newDate.setMonth(newDate.getMonth() - 1);
12705
+ return newDate;
12706
+ });
12707
+ };
12708
+ const handleNextMonth = () => {
12709
+ setViewDate((prev) => {
12710
+ const newDate = new Date(prev);
12711
+ newDate.setMonth(newDate.getMonth() + 1);
12712
+ return newDate;
12713
+ });
12714
+ };
12715
+ const { handleKeyDown } = useEditorKeyboardNavigation(
12716
+ {
12717
+ onEnter: onCommit,
12718
+ onEscape: onCancel
12719
+ },
12720
+ {
12721
+ commitOnTab: true,
12722
+ commitOnBlur: false
12723
+ }
12724
+ );
12725
+ const calendarDays = useMemo10(() => {
12726
+ return generateCalendarDays(viewDate, parsedValue, minDate, maxDate);
12727
+ }, [viewDate, parsedValue, minDate, maxDate]);
12728
+ return /* @__PURE__ */ React31.createElement(
12729
+ "div",
12730
+ {
12731
+ ref: containerRef,
12732
+ className: "editor-container editor-date-container",
12733
+ onKeyDown: handleKeyDown
12734
+ },
12735
+ /* @__PURE__ */ React31.createElement("div", { className: "editor-input-wrapper" }, /* @__PURE__ */ React31.createElement(
12736
+ "input",
12737
+ {
12738
+ ref: inputRef,
12739
+ type: "text",
12740
+ className: "editor-input editor-date-input",
12741
+ value: inputValue,
12742
+ onChange: handleInputChange,
12743
+ onBlur: handleInputBlur,
12744
+ placeholder: dateFormat,
12745
+ "aria-label": "Date input",
12746
+ autoComplete: "off"
12747
+ }
12748
+ ), /* @__PURE__ */ React31.createElement("span", { className: "editor-calendar-icon", "aria-hidden": "true" }, "\u{1F4C5}")),
12749
+ isOpen && /* @__PURE__ */ React31.createElement("div", { ref: calendarRef, className: "editor-dropdown editor-calendar" }, /* @__PURE__ */ React31.createElement("div", { className: "editor-calendar-header" }, /* @__PURE__ */ React31.createElement(
12750
+ "button",
12751
+ {
12752
+ type: "button",
12753
+ className: "editor-calendar-nav",
12754
+ onClick: handlePrevMonth,
12755
+ "aria-label": "Previous month"
12756
+ },
12757
+ "\u2039"
12758
+ ), /* @__PURE__ */ React31.createElement("div", { className: "editor-calendar-title" }, viewDate.toLocaleDateString("en-US", {
12759
+ month: "long",
12760
+ year: "numeric"
12761
+ })), /* @__PURE__ */ React31.createElement(
12762
+ "button",
12763
+ {
12764
+ type: "button",
12765
+ className: "editor-calendar-nav",
12766
+ onClick: handleNextMonth,
12767
+ "aria-label": "Next month"
12768
+ },
12769
+ "\u203A"
12770
+ )), /* @__PURE__ */ React31.createElement("div", { className: "editor-calendar-weekdays" }, ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"].map((day) => /* @__PURE__ */ React31.createElement("div", { key: day, className: "editor-calendar-weekday" }, day))), /* @__PURE__ */ React31.createElement("div", { className: "editor-calendar-days" }, calendarDays.map((day, index) => /* @__PURE__ */ React31.createElement(
12771
+ "button",
12772
+ {
12773
+ key: index,
12774
+ type: "button",
12775
+ className: `editor-calendar-day ${day.className}`,
12776
+ onClick: () => day.date && handleSelectDate(day.date),
12777
+ disabled: day.disabled,
12778
+ "aria-label": day.date ? day.date.toDateString() : ""
12779
+ },
12780
+ day.label
12781
+ ))), showTime && /* @__PURE__ */ React31.createElement("div", { className: "editor-calendar-time" }, /* @__PURE__ */ React31.createElement(
12782
+ "input",
12783
+ {
12784
+ type: "time",
12785
+ className: "editor-time-input",
12786
+ value: parsedValue ? `${String(parsedValue.getHours()).padStart(2, "0")}:${String(
12787
+ parsedValue.getMinutes()
12788
+ ).padStart(2, "0")}` : "00:00",
12789
+ onChange: (e) => {
12790
+ const [hours, minutes] = e.target.value.split(":").map(Number);
12791
+ handleTimeChange(hours, minutes);
12792
+ },
12793
+ "aria-label": "Time input"
12794
+ }
12795
+ )))
12796
+ );
12797
+ }
12798
+ DateEditor.displayName = "DateEditor";
12799
+ function formatDate(date, format, showTime) {
12800
+ const year = date.getFullYear();
12801
+ const month = String(date.getMonth() + 1).padStart(2, "0");
12802
+ const day = String(date.getDate()).padStart(2, "0");
12803
+ const hours = String(date.getHours()).padStart(2, "0");
12804
+ const minutes = String(date.getMinutes()).padStart(2, "0");
12805
+ let formatted = format.replace("yyyy", String(year)).replace("MM", month).replace("dd", day);
12806
+ if (showTime) {
12807
+ formatted += ` ${hours}:${minutes}`;
12808
+ }
12809
+ return formatted;
12810
+ }
12811
+ function parseDate(value, format) {
12812
+ if (!value) return null;
12813
+ try {
12814
+ const parts = value.split(/[-/\s:]/);
12815
+ if (parts.length < 3) return null;
12816
+ let year, month, day;
12817
+ let hours = 0, minutes = 0;
12818
+ if (format.startsWith("yyyy")) {
12819
+ year = parseInt(parts[0], 10);
12820
+ month = parseInt(parts[1], 10) - 1;
12821
+ day = parseInt(parts[2], 10);
12822
+ } else if (format.startsWith("MM")) {
12823
+ month = parseInt(parts[0], 10) - 1;
12824
+ day = parseInt(parts[1], 10);
12825
+ year = parseInt(parts[2], 10);
12826
+ } else {
12827
+ day = parseInt(parts[0], 10);
12828
+ month = parseInt(parts[1], 10) - 1;
12829
+ year = parseInt(parts[2], 10);
12830
+ }
12831
+ if (parts.length >= 5) {
12832
+ hours = parseInt(parts[3], 10);
12833
+ minutes = parseInt(parts[4], 10);
12834
+ }
12835
+ const date = new Date(year, month, day, hours, minutes);
12836
+ return isNaN(date.getTime()) ? null : date;
12837
+ } catch {
12838
+ return null;
12839
+ }
12840
+ }
12841
+ function generateCalendarDays(viewDate, selectedDate, minDate, maxDate) {
12842
+ const year = viewDate.getFullYear();
12843
+ const month = viewDate.getMonth();
12844
+ const firstDay = new Date(year, month, 1);
12845
+ const lastDay = new Date(year, month + 1, 0);
12846
+ const daysInMonth = lastDay.getDate();
12847
+ const startingDayOfWeek = firstDay.getDay();
12848
+ const days = [];
12849
+ const prevMonth = new Date(year, month, 0);
12850
+ const prevMonthDays = prevMonth.getDate();
12851
+ for (let i = startingDayOfWeek - 1; i >= 0; i--) {
12852
+ const date = new Date(year, month - 1, prevMonthDays - i);
12853
+ days.push({
12854
+ date,
12855
+ label: String(prevMonthDays - i),
12856
+ className: "other-month",
12857
+ disabled: isDateDisabled(date, minDate, maxDate)
12858
+ });
12859
+ }
12860
+ for (let i = 1; i <= daysInMonth; i++) {
12861
+ const date = new Date(year, month, i);
12862
+ const isSelected = selectedDate && isSameDay(date, selectedDate);
12863
+ const isToday = isSameDay(date, /* @__PURE__ */ new Date());
12864
+ const disabled = isDateDisabled(date, minDate, maxDate);
12865
+ days.push({
12866
+ date,
12867
+ label: String(i),
12868
+ className: `${isSelected ? "selected" : ""} ${isToday ? "today" : ""} ${disabled ? "disabled" : ""}`.trim(),
12869
+ disabled
12870
+ });
12871
+ }
12872
+ const remainingDays = 42 - days.length;
12873
+ for (let i = 1; i <= remainingDays; i++) {
12874
+ const date = new Date(year, month + 1, i);
12875
+ days.push({
12876
+ date,
12877
+ label: String(i),
12878
+ className: "other-month",
12879
+ disabled: isDateDisabled(date, minDate, maxDate)
12880
+ });
12881
+ }
12882
+ return days;
12883
+ }
12884
+ function isSameDay(date1, date2) {
12885
+ return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate();
12886
+ }
12887
+ function isDateDisabled(date, minDate, maxDate) {
12888
+ if (minDate && date < minDate) return true;
12889
+ if (maxDate && date > maxDate) return true;
12890
+ return false;
12891
+ }
12892
+
12893
+ // src/editors/NumericEditor.tsx
12894
+ import React32, { useState as useState23, useEffect as useEffect16 } from "react";
12895
+ function NumericEditor(props) {
12896
+ const {
12897
+ value,
12898
+ onChange,
12899
+ onCommit,
12900
+ onCancel,
12901
+ autoFocus = true,
12902
+ min,
12903
+ max,
12904
+ step = 1,
12905
+ decimals = 0,
12906
+ prefix = "",
12907
+ suffix = "",
12908
+ allowNegative = true,
12909
+ showSteppers = true,
12910
+ thousandsSeparator = ",",
12911
+ decimalSeparator = "."
12912
+ } = props;
12913
+ const [inputValue, setInputValue] = useState23("");
12914
+ const [isFocused, setIsFocused] = useState23(true);
12915
+ const inputRef = useEditorAutoFocus(autoFocus, true);
12916
+ useEffect16(() => {
12917
+ if (value !== null && value !== void 0) {
12918
+ if (isFocused) {
12919
+ queueMicrotask(() => setInputValue(String(value)));
12920
+ } else {
12921
+ queueMicrotask(() => setInputValue(formatNumber(value, decimals, thousandsSeparator, decimalSeparator)));
12922
+ }
12923
+ } else {
12924
+ queueMicrotask(() => setInputValue(""));
12925
+ }
12926
+ }, [value, isFocused, decimals, thousandsSeparator, decimalSeparator]);
12927
+ const clampValue = (val) => {
12928
+ let clamped = val;
12929
+ if (min !== void 0 && clamped < min) clamped = min;
12930
+ if (max !== void 0 && clamped > max) clamped = max;
12931
+ return clamped;
12932
+ };
12933
+ const handleInputChange = (e) => {
12934
+ const newValue = e.target.value;
12935
+ setInputValue(newValue);
12936
+ if (newValue === "" || newValue === "-") {
12937
+ onChange(null);
12938
+ return;
12939
+ }
12940
+ let cleaned = newValue;
12941
+ if (prefix) cleaned = cleaned.replace(prefix, "");
12942
+ if (suffix) cleaned = cleaned.replace(suffix, "");
12943
+ const parsed = parseFormattedNumber(cleaned, thousandsSeparator, decimalSeparator);
12944
+ if (parsed !== null && !isNaN(parsed)) {
12945
+ if (!allowNegative && parsed < 0) {
12946
+ return;
12947
+ }
12948
+ const clamped = clampValue(parsed);
12949
+ onChange(clamped);
12950
+ }
12951
+ };
12952
+ const handleIncrement = () => {
12953
+ const currentValue = value ?? 0;
12954
+ const newValue = clampValue(currentValue + step);
12955
+ onChange(newValue);
12956
+ setInputValue(String(newValue));
12957
+ };
12958
+ const handleDecrement = () => {
12959
+ const currentValue = value ?? 0;
12960
+ const newValue = clampValue(currentValue - step);
12961
+ onChange(newValue);
12962
+ setInputValue(String(newValue));
12963
+ };
12964
+ const handleFocus = () => {
12965
+ setIsFocused(true);
12966
+ if (value !== null && value !== void 0) {
12967
+ setInputValue(String(value));
12968
+ }
12969
+ };
12970
+ const handleBlur = () => {
12971
+ setIsFocused(false);
12972
+ if (value !== null && value !== void 0) {
12973
+ const formatted = formatNumber(value, decimals, thousandsSeparator, decimalSeparator);
12974
+ setInputValue(formatted);
12975
+ }
12976
+ setTimeout(() => onCommit(), 100);
12977
+ };
12978
+ const handleKeyDown = (e) => {
12979
+ if (e.key === "ArrowUp") {
12980
+ e.preventDefault();
12981
+ handleIncrement();
12982
+ } else if (e.key === "ArrowDown") {
12983
+ e.preventDefault();
12984
+ handleDecrement();
12985
+ }
12986
+ };
12987
+ const { handleKeyDown: handleEditorKeyDown } = useEditorKeyboardNavigation(
12988
+ {
12989
+ onEnter: onCommit,
12990
+ onEscape: onCancel
12991
+ },
12992
+ {
12993
+ commitOnTab: true,
12994
+ commitOnBlur: false,
12995
+ preventDefault: false
12996
+ // Let handleKeyDown handle arrow keys
12997
+ }
12998
+ );
12999
+ const handleCombinedKeyDown = (e) => {
13000
+ handleKeyDown(e);
13001
+ handleEditorKeyDown(e);
13002
+ };
13003
+ const displayValue = isFocused ? inputValue : inputValue ? `${prefix}${inputValue}${suffix}` : "";
13004
+ return /* @__PURE__ */ React32.createElement("div", { className: "editor-container editor-numeric-container" }, /* @__PURE__ */ React32.createElement("div", { className: "editor-input-wrapper" }, prefix && !isFocused && /* @__PURE__ */ React32.createElement("span", { className: "editor-numeric-prefix" }, prefix), /* @__PURE__ */ React32.createElement(
13005
+ "input",
13006
+ {
13007
+ ref: inputRef,
13008
+ type: "text",
13009
+ inputMode: "decimal",
13010
+ className: "editor-input editor-numeric-input",
13011
+ value: displayValue,
13012
+ onChange: handleInputChange,
13013
+ onFocus: handleFocus,
13014
+ onBlur: handleBlur,
13015
+ onKeyDown: handleCombinedKeyDown,
13016
+ "aria-label": "Numeric input",
13017
+ "aria-valuemin": min,
13018
+ "aria-valuemax": max,
13019
+ "aria-valuenow": value ?? void 0,
13020
+ autoComplete: "off"
13021
+ }
13022
+ ), suffix && !isFocused && /* @__PURE__ */ React32.createElement("span", { className: "editor-numeric-suffix" }, suffix), showSteppers && /* @__PURE__ */ React32.createElement("div", { className: "editor-numeric-steppers" }, /* @__PURE__ */ React32.createElement(
13023
+ "button",
13024
+ {
13025
+ type: "button",
13026
+ className: "editor-numeric-stepper editor-numeric-increment",
13027
+ onClick: handleIncrement,
13028
+ onMouseDown: (e) => e.preventDefault(),
13029
+ disabled: max !== void 0 && (value ?? 0) >= max,
13030
+ "aria-label": "Increment",
13031
+ tabIndex: -1
13032
+ },
13033
+ "\u25B2"
13034
+ ), /* @__PURE__ */ React32.createElement(
13035
+ "button",
13036
+ {
13037
+ type: "button",
13038
+ className: "editor-numeric-stepper editor-numeric-decrement",
13039
+ onClick: handleDecrement,
13040
+ onMouseDown: (e) => e.preventDefault(),
13041
+ disabled: min !== void 0 && (value ?? 0) <= min,
13042
+ "aria-label": "Decrement",
13043
+ tabIndex: -1
13044
+ },
13045
+ "\u25BC"
13046
+ ))), (min !== void 0 || max !== void 0) && /* @__PURE__ */ React32.createElement("div", { className: "editor-numeric-range" }, min !== void 0 && /* @__PURE__ */ React32.createElement("span", null, "Min: ", min), max !== void 0 && /* @__PURE__ */ React32.createElement("span", null, "Max: ", max)));
13047
+ }
13048
+ NumericEditor.displayName = "NumericEditor";
13049
+
13050
+ // src/editors/MultiSelectEditor.tsx
13051
+ import React33, { useState as useState24, useRef as useRef18, useMemo as useMemo11 } from "react";
13052
+ function MultiSelectEditor(props) {
13053
+ const {
13054
+ value = [],
13055
+ onChange,
13056
+ onCommit,
13057
+ onCancel,
13058
+ autoFocus = true,
13059
+ options,
13060
+ placeholder = "Select...",
13061
+ maxTagCount = 3,
13062
+ filterable = true,
13063
+ maxDropdownHeight = 300,
13064
+ allowEmpty = true
13065
+ } = props;
13066
+ const [isOpen] = useState24(true);
13067
+ const [searchQuery, setSearchQuery] = useState24("");
13068
+ const [focusedIndex, setFocusedIndex] = useState24(-1);
13069
+ const containerRef = useRef18(null);
13070
+ const inputRef = useEditorAutoFocus(autoFocus);
13071
+ const dropdownRef = useRef18(null);
13072
+ const tagContainerRef = useRef18(null);
13073
+ const selectedValues = useMemo11(
13074
+ () => Array.isArray(value) ? value : [],
13075
+ [value]
13076
+ );
13077
+ const filteredOptions = useMemo11(
13078
+ () => filterable ? filterOptions(options, searchQuery) : options,
13079
+ [options, searchQuery, filterable]
13080
+ );
13081
+ const selectedOptions = useMemo11(
13082
+ () => options.filter((opt) => selectedValues.includes(opt.value)),
13083
+ [options, selectedValues]
13084
+ );
13085
+ usePopupPosition(containerRef, dropdownRef, isOpen, "auto");
13086
+ useEditorClickOutside(
13087
+ containerRef,
13088
+ () => {
13089
+ if (isOpen) {
13090
+ onCommit();
13091
+ }
13092
+ },
13093
+ true
13094
+ );
13095
+ const handleToggleOption = (option) => {
13096
+ if (option.disabled) return;
13097
+ const isSelected = selectedValues.includes(option.value);
13098
+ let newValues;
13099
+ if (isSelected) {
13100
+ if (!allowEmpty && selectedValues.length === 1) {
13101
+ return;
13102
+ }
13103
+ newValues = selectedValues.filter((v) => v !== option.value);
13104
+ } else {
13105
+ newValues = [...selectedValues, option.value];
13106
+ }
13107
+ onChange(newValues);
13108
+ };
13109
+ const handleRemoveTag = (optionValue, e) => {
13110
+ e.stopPropagation();
13111
+ if (!allowEmpty && selectedValues.length === 1) {
13112
+ return;
13113
+ }
13114
+ const newValues = selectedValues.filter((v) => v !== optionValue);
13115
+ onChange(newValues);
13116
+ };
13117
+ const handleKeyDown = (e) => {
13118
+ if (e.key === "Backspace" && searchQuery === "" && selectedValues.length > 0) {
13119
+ e.preventDefault();
13120
+ if (allowEmpty || selectedValues.length > 1) {
13121
+ const newValues = selectedValues.slice(0, -1);
13122
+ onChange(newValues);
13123
+ }
13124
+ }
13125
+ };
13126
+ const handleArrowDown = () => {
13127
+ setFocusedIndex((prev) => {
13128
+ var _a;
13129
+ let next = prev + 1;
13130
+ while (next < filteredOptions.length && ((_a = filteredOptions[next]) == null ? void 0 : _a.disabled)) {
13131
+ next++;
13132
+ }
13133
+ return next < filteredOptions.length ? next : prev;
13134
+ });
13135
+ };
13136
+ const handleArrowUp = () => {
13137
+ setFocusedIndex((prev) => {
13138
+ var _a;
13139
+ let next = prev - 1;
13140
+ while (next >= 0 && ((_a = filteredOptions[next]) == null ? void 0 : _a.disabled)) {
13141
+ next--;
13142
+ }
13143
+ return next >= 0 ? next : prev;
13144
+ });
13145
+ };
13146
+ const handleEnter = () => {
13147
+ if (focusedIndex >= 0 && focusedIndex < filteredOptions.length) {
13148
+ const option = filteredOptions[focusedIndex];
13149
+ if (option && !option.disabled) {
13150
+ handleToggleOption(option);
13151
+ }
13152
+ } else {
13153
+ onCommit();
13154
+ }
13155
+ };
13156
+ const { handleKeyDown: handleEditorKeyDown } = useEditorKeyboardNavigation(
13157
+ {
13158
+ onEnter: handleEnter,
13159
+ onEscape: onCancel,
13160
+ onArrowUp: handleArrowUp,
13161
+ onArrowDown: handleArrowDown
13162
+ },
13163
+ {
13164
+ commitOnTab: true,
13165
+ commitOnBlur: false
13166
+ }
13167
+ );
13168
+ const handleCombinedKeyDown = (e) => {
13169
+ handleKeyDown(e);
13170
+ handleEditorKeyDown(e);
13171
+ };
13172
+ const visibleTags = selectedOptions.slice(0, maxTagCount);
13173
+ const collapsedCount = Math.max(0, selectedOptions.length - maxTagCount);
13174
+ return /* @__PURE__ */ React33.createElement(
13175
+ "div",
13176
+ {
13177
+ ref: containerRef,
13178
+ className: "editor-container editor-multiselect-container",
13179
+ onKeyDown: handleCombinedKeyDown
13180
+ },
13181
+ /* @__PURE__ */ React33.createElement(
13182
+ "div",
13183
+ {
13184
+ ref: tagContainerRef,
13185
+ className: "editor-input-wrapper editor-multiselect-wrapper"
13186
+ },
13187
+ /* @__PURE__ */ React33.createElement("div", { className: "editor-multiselect-tags" }, visibleTags.map((option) => /* @__PURE__ */ React33.createElement("div", { key: option.value, className: "editor-tag" }, option.icon && /* @__PURE__ */ React33.createElement("span", { className: "editor-tag-icon" }, option.icon), /* @__PURE__ */ React33.createElement("span", { className: "editor-tag-label" }, option.label), /* @__PURE__ */ React33.createElement(
13188
+ "button",
13189
+ {
13190
+ type: "button",
13191
+ className: "editor-tag-remove",
13192
+ onClick: (e) => handleRemoveTag(option.value, e),
13193
+ "aria-label": `Remove ${option.label}`,
13194
+ tabIndex: -1
13195
+ },
13196
+ "\xD7"
13197
+ ))), collapsedCount > 0 && /* @__PURE__ */ React33.createElement("div", { className: "editor-tag editor-tag-collapsed" }, "+", collapsedCount)),
13198
+ /* @__PURE__ */ React33.createElement(
13199
+ "input",
13200
+ {
13201
+ ref: inputRef,
13202
+ type: "text",
13203
+ className: "editor-input editor-multiselect-input",
13204
+ value: searchQuery,
13205
+ onChange: (e) => {
13206
+ setSearchQuery(e.target.value);
13207
+ setFocusedIndex(-1);
13208
+ },
13209
+ placeholder: selectedValues.length === 0 ? placeholder : "",
13210
+ "aria-label": "Search options",
13211
+ "aria-expanded": isOpen,
13212
+ "aria-autocomplete": "list",
13213
+ "aria-controls": "multiselect-dropdown",
13214
+ autoComplete: "off"
13215
+ }
13216
+ ),
13217
+ /* @__PURE__ */ React33.createElement("span", { className: "editor-dropdown-icon", "aria-hidden": "true" }, "\u25BC")
13218
+ ),
13219
+ isOpen && /* @__PURE__ */ React33.createElement(
13220
+ "div",
13221
+ {
13222
+ ref: dropdownRef,
13223
+ id: "multiselect-dropdown",
13224
+ className: "editor-dropdown",
13225
+ role: "listbox",
13226
+ "aria-multiselectable": "true",
13227
+ style: { maxHeight: maxDropdownHeight }
13228
+ },
13229
+ filteredOptions.length === 0 ? /* @__PURE__ */ React33.createElement("div", { className: "editor-dropdown-empty" }, "No options found") : filteredOptions.map((option, index) => {
13230
+ const isSelected = selectedValues.includes(option.value);
13231
+ return /* @__PURE__ */ React33.createElement(
13232
+ "div",
13233
+ {
13234
+ key: option.value,
13235
+ className: `editor-dropdown-option editor-multiselect-option ${isSelected ? "selected" : ""} ${option.disabled ? "disabled" : ""} ${index === focusedIndex ? "focused" : ""}`,
13236
+ role: "option",
13237
+ "aria-selected": isSelected,
13238
+ "aria-disabled": option.disabled,
13239
+ onClick: () => handleToggleOption(option),
13240
+ onMouseEnter: () => !option.disabled && setFocusedIndex(index)
13241
+ },
13242
+ /* @__PURE__ */ React33.createElement(
13243
+ "input",
13244
+ {
13245
+ type: "checkbox",
13246
+ className: "editor-multiselect-checkbox",
13247
+ checked: isSelected,
13248
+ onChange: () => {
13249
+ },
13250
+ disabled: option.disabled,
13251
+ tabIndex: -1,
13252
+ "aria-hidden": "true"
13253
+ }
13254
+ ),
13255
+ /* @__PURE__ */ React33.createElement("div", { className: "editor-option-content" }, option.icon && /* @__PURE__ */ React33.createElement("span", { className: "editor-option-icon" }, option.icon), /* @__PURE__ */ React33.createElement("span", { className: "editor-option-label" }, option.label))
13256
+ );
13257
+ })
13258
+ )
13259
+ );
13260
+ }
13261
+ MultiSelectEditor.displayName = "MultiSelectEditor";
13262
+
13263
+ // src/editors/MarkdownEditor.tsx
13264
+ import React34, { useState as useState25, useRef as useRef19, useMemo as useMemo12 } from "react";
13265
+ function MarkdownEditor(props) {
13266
+ const {
13267
+ value = "",
13268
+ onChange,
13269
+ onCommit,
13270
+ onCancel,
13271
+ autoFocus = true,
13272
+ maxLength,
13273
+ showPreview = true,
13274
+ minHeight = 150,
13275
+ maxHeight = 400,
13276
+ rows = 6
13277
+ } = props;
13278
+ const [internalValue, setInternalValue] = useState25(value || "");
13279
+ const [showPreviewPanel, setShowPreviewPanel] = useState25(showPreview);
13280
+ const containerRef = useRef19(null);
13281
+ const textareaRef = useEditorAutoFocus(autoFocus, true);
13282
+ useEditorClickOutside(
13283
+ containerRef,
13284
+ () => {
13285
+ onCommit();
13286
+ },
13287
+ true
13288
+ );
13289
+ const handleChange = (e) => {
13290
+ const newValue = e.target.value;
13291
+ if (maxLength && newValue.length > maxLength) {
13292
+ return;
13293
+ }
13294
+ setInternalValue(newValue);
13295
+ onChange(newValue);
13296
+ };
13297
+ const handleKeyDown = (e) => {
13298
+ if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
13299
+ e.preventDefault();
13300
+ onCommit();
13301
+ return;
13302
+ }
13303
+ if (e.key === "Escape") {
13304
+ e.preventDefault();
13305
+ onCancel();
13306
+ return;
13307
+ }
13308
+ if (e.key === "Tab") {
13309
+ e.preventDefault();
13310
+ const start = e.currentTarget.selectionStart;
13311
+ const end = e.currentTarget.selectionEnd;
13312
+ const newValue = internalValue.substring(0, start) + " " + internalValue.substring(end);
13313
+ setInternalValue(newValue);
13314
+ onChange(newValue);
13315
+ setTimeout(() => {
13316
+ if (textareaRef.current) {
13317
+ textareaRef.current.selectionStart = start + 1;
13318
+ textareaRef.current.selectionEnd = start + 1;
13319
+ }
13320
+ }, 0);
13321
+ return;
13322
+ }
13323
+ if (e.ctrlKey || e.metaKey) {
13324
+ let insertion = "";
13325
+ let cursorOffset = 0;
13326
+ switch (e.key) {
13327
+ case "b":
13328
+ insertion = "****";
13329
+ cursorOffset = 2;
13330
+ break;
13331
+ case "i":
13332
+ insertion = "__";
13333
+ cursorOffset = 1;
13334
+ break;
13335
+ case "k":
13336
+ insertion = "[](url)";
13337
+ cursorOffset = 1;
13338
+ break;
13339
+ default:
13340
+ return;
13341
+ }
13342
+ if (insertion) {
13343
+ e.preventDefault();
13344
+ const start = e.currentTarget.selectionStart;
13345
+ const end = e.currentTarget.selectionEnd;
13346
+ const selectedText = internalValue.substring(start, end);
13347
+ let newValue;
13348
+ let newCursorPos;
13349
+ if (selectedText) {
13350
+ const before = insertion.substring(0, cursorOffset);
13351
+ const after = insertion.substring(cursorOffset);
13352
+ newValue = internalValue.substring(0, start) + before + selectedText + after + internalValue.substring(end);
13353
+ newCursorPos = start + before.length + selectedText.length;
13354
+ } else {
13355
+ newValue = internalValue.substring(0, start) + insertion + internalValue.substring(end);
13356
+ newCursorPos = start + cursorOffset;
13357
+ }
13358
+ setInternalValue(newValue);
13359
+ onChange(newValue);
13360
+ setTimeout(() => {
13361
+ if (textareaRef.current) {
13362
+ textareaRef.current.selectionStart = newCursorPos;
13363
+ textareaRef.current.selectionEnd = newCursorPos;
13364
+ textareaRef.current.focus();
13365
+ }
13366
+ }, 0);
13367
+ }
13368
+ }
13369
+ };
13370
+ const charCount = internalValue.length;
13371
+ const charCountClass = maxLength && charCount > maxLength * 0.9 ? "warning" : "";
13372
+ const previewHtml = useMemo12(() => {
13373
+ return renderMarkdownPreview(internalValue);
13374
+ }, [internalValue]);
13375
+ return /* @__PURE__ */ React34.createElement(
13376
+ "div",
13377
+ {
13378
+ ref: containerRef,
13379
+ className: "editor-container editor-markdown-container",
13380
+ style: { minHeight }
13381
+ },
13382
+ /* @__PURE__ */ React34.createElement("div", { className: "editor-markdown-toolbar" }, /* @__PURE__ */ React34.createElement("div", { className: "editor-markdown-toolbar-group" }, /* @__PURE__ */ React34.createElement(
13383
+ "button",
13384
+ {
13385
+ type: "button",
13386
+ className: "editor-toolbar-btn",
13387
+ onClick: () => {
13388
+ if (textareaRef.current) {
13389
+ const start = textareaRef.current.selectionStart;
13390
+ const end = textareaRef.current.selectionEnd;
13391
+ const selectedText = internalValue.substring(start, end);
13392
+ const newValue = internalValue.substring(0, start) + `**${selectedText || "bold"}**` + internalValue.substring(end);
13393
+ setInternalValue(newValue);
13394
+ onChange(newValue);
13395
+ textareaRef.current.focus();
13396
+ }
13397
+ },
13398
+ title: "Bold (Ctrl+B)",
13399
+ "aria-label": "Bold"
13400
+ },
13401
+ /* @__PURE__ */ React34.createElement("strong", null, "B")
13402
+ ), /* @__PURE__ */ React34.createElement(
13403
+ "button",
13404
+ {
13405
+ type: "button",
13406
+ className: "editor-toolbar-btn",
13407
+ onClick: () => {
13408
+ if (textareaRef.current) {
13409
+ const start = textareaRef.current.selectionStart;
13410
+ const end = textareaRef.current.selectionEnd;
13411
+ const selectedText = internalValue.substring(start, end);
13412
+ const newValue = internalValue.substring(0, start) + `_${selectedText || "italic"}_` + internalValue.substring(end);
13413
+ setInternalValue(newValue);
13414
+ onChange(newValue);
13415
+ textareaRef.current.focus();
13416
+ }
13417
+ },
13418
+ title: "Italic (Ctrl+I)",
13419
+ "aria-label": "Italic"
13420
+ },
13421
+ /* @__PURE__ */ React34.createElement("em", null, "I")
13422
+ ), /* @__PURE__ */ React34.createElement(
13423
+ "button",
13424
+ {
13425
+ type: "button",
13426
+ className: "editor-toolbar-btn",
13427
+ onClick: () => {
13428
+ if (textareaRef.current) {
13429
+ const start = textareaRef.current.selectionStart;
13430
+ const end = textareaRef.current.selectionEnd;
13431
+ const selectedText = internalValue.substring(start, end);
13432
+ const newValue = internalValue.substring(0, start) + `[${selectedText || "link text"}](url)` + internalValue.substring(end);
13433
+ setInternalValue(newValue);
13434
+ onChange(newValue);
13435
+ textareaRef.current.focus();
13436
+ }
13437
+ },
13438
+ title: "Link (Ctrl+K)",
13439
+ "aria-label": "Link"
13440
+ },
13441
+ "\u{1F517}"
13442
+ )), /* @__PURE__ */ React34.createElement("div", { className: "editor-markdown-toolbar-group" }, /* @__PURE__ */ React34.createElement(
13443
+ "button",
13444
+ {
13445
+ type: "button",
13446
+ className: `editor-toolbar-btn ${showPreviewPanel ? "active" : ""}`,
13447
+ onClick: () => setShowPreviewPanel(!showPreviewPanel),
13448
+ title: "Toggle preview",
13449
+ "aria-label": "Toggle preview",
13450
+ "aria-pressed": showPreviewPanel
13451
+ },
13452
+ "\u{1F441}"
13453
+ ))),
13454
+ /* @__PURE__ */ React34.createElement("div", { className: `editor-markdown-content ${showPreviewPanel ? "split" : ""}` }, /* @__PURE__ */ React34.createElement("div", { className: "editor-markdown-editor" }, /* @__PURE__ */ React34.createElement(
13455
+ "textarea",
13456
+ {
13457
+ ref: textareaRef,
13458
+ className: "editor-textarea editor-markdown-textarea",
13459
+ value: internalValue,
13460
+ onChange: handleChange,
13461
+ onKeyDown: handleKeyDown,
13462
+ rows,
13463
+ maxLength,
13464
+ placeholder: "Enter markdown text...",
13465
+ "aria-label": "Markdown editor",
13466
+ style: { maxHeight }
13467
+ }
13468
+ ), /* @__PURE__ */ React34.createElement("div", { className: `editor-markdown-char-count ${charCountClass}` }, charCount, maxLength && ` / ${maxLength}`)), showPreviewPanel && /* @__PURE__ */ React34.createElement("div", { className: "editor-markdown-preview" }, /* @__PURE__ */ React34.createElement("div", { className: "editor-markdown-preview-label" }, "Preview"), /* @__PURE__ */ React34.createElement(
13469
+ "div",
13470
+ {
13471
+ className: "editor-markdown-preview-content",
13472
+ dangerouslySetInnerHTML: { __html: previewHtml }
13473
+ }
13474
+ ))),
13475
+ /* @__PURE__ */ React34.createElement("div", { className: "editor-markdown-footer" }, /* @__PURE__ */ React34.createElement("div", { className: "editor-markdown-hint" }, /* @__PURE__ */ React34.createElement("kbd", null, "Ctrl+Enter"), " to save \u2022 ", /* @__PURE__ */ React34.createElement("kbd", null, "Esc"), " to cancel"), /* @__PURE__ */ React34.createElement("div", { className: "editor-markdown-actions" }, /* @__PURE__ */ React34.createElement(
13476
+ "button",
13477
+ {
13478
+ type: "button",
13479
+ className: "editor-btn editor-btn-secondary",
13480
+ onClick: onCancel
13481
+ },
13482
+ "Cancel"
13483
+ ), /* @__PURE__ */ React34.createElement(
13484
+ "button",
13485
+ {
13486
+ type: "button",
13487
+ className: "editor-btn editor-btn-primary",
13488
+ onClick: onCommit
13489
+ },
13490
+ "Save"
13491
+ )))
13492
+ );
13493
+ }
13494
+ MarkdownEditor.displayName = "MarkdownEditor";
13495
+ function renderMarkdownPreview(markdown) {
13496
+ if (!markdown) return '<p class="editor-markdown-empty">Nothing to preview</p>';
13497
+ let html = markdown;
13498
+ html = html.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
13499
+ html = html.replace(/^### (.*$)/gim, "<h3>$1</h3>");
13500
+ html = html.replace(/^## (.*$)/gim, "<h2>$1</h2>");
13501
+ html = html.replace(/^# (.*$)/gim, "<h1>$1</h1>");
13502
+ html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
13503
+ html = html.replace(/__(.+?)__/g, "<strong>$1</strong>");
13504
+ html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
13505
+ html = html.replace(/_(.+?)_/g, "<em>$1</em>");
13506
+ html = html.replace(/`(.+?)`/g, "<code>$1</code>");
13507
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
13508
+ html = html.replace(/^\* (.+)$/gim, "<li>$1</li>");
13509
+ html = html.replace(/^- (.+)$/gim, "<li>$1</li>");
13510
+ html = html.replace(/(<li>.*<\/li>)/s, "<ul>$1</ul>");
13511
+ html = html.replace(/\n\n/g, "</p><p>");
13512
+ html = html.replace(/\n/g, "<br>");
13513
+ if (!html.startsWith("<h") && !html.startsWith("<ul") && !html.startsWith("<p")) {
13514
+ html = "<p>" + html + "</p>";
13515
+ }
13516
+ return html;
13517
+ }
12235
13518
  export {
12236
13519
  AdvancedFilterBuilder,
12237
13520
  BadgeCell,
@@ -12240,6 +13523,7 @@ export {
12240
13523
  ColumnFilters,
12241
13524
  CurrencyCell,
12242
13525
  DataGrid,
13526
+ DateEditor,
12243
13527
  DensityToggle,
12244
13528
  ExportMenu,
12245
13529
  FacetedSearch,
@@ -12252,12 +13536,16 @@ export {
12252
13536
  LayoutPersistenceManager,
12253
13537
  LayoutPresetsManager,
12254
13538
  LocalStorageAdapter,
13539
+ MarkdownEditor,
12255
13540
  MarketDataEngine,
12256
13541
  MarketDataGrid,
13542
+ MultiSelectEditor,
13543
+ NumericEditor,
12257
13544
  PivotToolbar,
12258
13545
  PriorityIndicator,
12259
13546
  ProgressBar,
12260
13547
  Rating,
13548
+ RichSelectEditor,
12261
13549
  ServerAdapter,
12262
13550
  ServerSideDataSource,
12263
13551
  StatusChip,
@@ -12277,14 +13565,17 @@ export {
12277
13565
  createMockWebSocket,
12278
13566
  createPreset,
12279
13567
  darkTheme,
13568
+ debounce2 as debounce,
12280
13569
  densityConfigs,
12281
13570
  downloadCSV,
12282
13571
  expandAllNodes,
12283
13572
  exportPivotToCSV,
12284
13573
  exportToCSV,
12285
13574
  exportToXLSX,
13575
+ filterOptions,
12286
13576
  filterTree,
12287
13577
  flattenTree,
13578
+ formatNumber,
12288
13579
  generateDensityCSS,
12289
13580
  generateFilename,
12290
13581
  generatePresetId,
@@ -12302,10 +13593,15 @@ export {
12302
13593
  isTreeNode,
12303
13594
  loadDensityMode,
12304
13595
  materialTheme,
13596
+ parseFormattedNumber,
12305
13597
  quartzTheme,
12306
13598
  saveDensityMode,
12307
13599
  themes,
12308
13600
  toggleNodeExpansion,
12309
13601
  useDensityMode,
12310
- useMarketData
13602
+ useEditorAutoFocus,
13603
+ useEditorClickOutside,
13604
+ useEditorKeyboardNavigation,
13605
+ useMarketData,
13606
+ usePopupPosition
12311
13607
  };