@optilogic/core 1.2.1 → 1.2.3

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.cjs CHANGED
@@ -388,17 +388,26 @@ var SelectItem = React20__namespace.forwardRef(({ className, children, ...props
388
388
  {
389
389
  ref,
390
390
  className: cn(
391
- "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
391
+ "relative flex w-full cursor-default select-none items-start rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
392
392
  className
393
393
  ),
394
394
  ...props,
395
395
  children: [
396
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.ItemIndicator, { children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }) }) }),
396
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center mt-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.ItemIndicator, { children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }) }) }),
397
397
  /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.ItemText, { children })
398
398
  ]
399
399
  }
400
400
  ));
401
401
  SelectItem.displayName = SelectPrimitive__namespace.Item.displayName;
402
+ var SelectItemDescription = React20__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
403
+ "span",
404
+ {
405
+ ref,
406
+ className: cn("text-xs text-muted-foreground", className),
407
+ ...props
408
+ }
409
+ ));
410
+ SelectItemDescription.displayName = "SelectItemDescription";
402
411
  var SelectSeparator = React20__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
403
412
  SelectPrimitive__namespace.Separator,
404
413
  {
@@ -409,41 +418,85 @@ var SelectSeparator = React20__namespace.forwardRef(({ className, ...props }, re
409
418
  ));
410
419
  SelectSeparator.displayName = SelectPrimitive__namespace.Separator.displayName;
411
420
  var Tabs = TabsPrimitive__namespace.Root;
412
- var TabsList = React20__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
421
+ var tabsListVariants = classVarianceAuthority.cva(
422
+ "inline-flex h-10 items-center justify-start bg-transparent",
423
+ {
424
+ variants: {
425
+ variant: {
426
+ default: "border-b border-border",
427
+ pill: "gap-1 rounded-lg bg-muted p-1",
428
+ unstyled: ""
429
+ }
430
+ },
431
+ defaultVariants: {
432
+ variant: "default"
433
+ }
434
+ }
435
+ );
436
+ var tabsTriggerVariants = classVarianceAuthority.cva(
437
+ [
438
+ "inline-flex items-center justify-center whitespace-nowrap",
439
+ "px-4 py-2.5 text-sm font-medium",
440
+ "transition-colors",
441
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
442
+ "disabled:pointer-events-none disabled:opacity-50"
443
+ ],
444
+ {
445
+ variants: {
446
+ variant: {
447
+ default: [
448
+ "-mb-px",
449
+ "border-transparent text-muted-foreground",
450
+ "hover:text-foreground hover:border-muted-foreground/50",
451
+ "data-[state=active]:border-foreground data-[state=active]:text-foreground"
452
+ ],
453
+ pill: [
454
+ "rounded-md",
455
+ "text-muted-foreground",
456
+ "hover:text-foreground",
457
+ "data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm"
458
+ ],
459
+ unstyled: [
460
+ "text-muted-foreground",
461
+ "hover:text-foreground",
462
+ "data-[state=active]:text-foreground"
463
+ ]
464
+ },
465
+ indicatorSize: {
466
+ sm: "border-b",
467
+ default: "border-b-2",
468
+ lg: "border-b-[3px]"
469
+ }
470
+ },
471
+ compoundVariants: [
472
+ { variant: "pill", indicatorSize: "sm", className: "border-b-0" },
473
+ { variant: "pill", indicatorSize: "default", className: "border-b-0" },
474
+ { variant: "pill", indicatorSize: "lg", className: "border-b-0" },
475
+ { variant: "unstyled", indicatorSize: "sm", className: "border-b-0" },
476
+ { variant: "unstyled", indicatorSize: "default", className: "border-b-0" },
477
+ { variant: "unstyled", indicatorSize: "lg", className: "border-b-0" }
478
+ ],
479
+ defaultVariants: {
480
+ variant: "default",
481
+ indicatorSize: "default"
482
+ }
483
+ }
484
+ );
485
+ var TabsList = React20__namespace.forwardRef(({ className, variant, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
413
486
  TabsPrimitive__namespace.List,
414
487
  {
415
488
  ref,
416
- className: cn(
417
- "inline-flex h-10 items-center justify-start",
418
- "border-b border-border",
419
- "bg-transparent",
420
- className
421
- ),
489
+ className: cn(tabsListVariants({ variant }), className),
422
490
  ...props
423
491
  }
424
492
  ));
425
493
  TabsList.displayName = TabsPrimitive__namespace.List.displayName;
426
- var TabsTrigger = React20__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
494
+ var TabsTrigger = React20__namespace.forwardRef(({ className, variant, indicatorSize, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
427
495
  TabsPrimitive__namespace.Trigger,
428
496
  {
429
497
  ref,
430
498
  className: cn(
431
- // Base styles
432
- "inline-flex items-center justify-center whitespace-nowrap",
433
- "px-4 py-2.5 text-sm font-medium",
434
- "transition-colors",
435
- // Border-bottom indicator style
436
- "border-b-2 -mb-px",
437
- // Default state
438
- "border-transparent text-muted-foreground",
439
- // Hover state
440
- "hover:text-foreground hover:border-muted-foreground/50",
441
- // Active/selected state
442
- "data-[state=active]:border-foreground data-[state=active]:text-foreground",
443
- // Focus styles
444
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
445
- // Disabled styles
446
- "disabled:pointer-events-none disabled:opacity-50",
499
+ tabsTriggerVariants({ variant, indicatorSize }),
447
500
  className
448
501
  ),
449
502
  ...props
@@ -456,7 +509,6 @@ var TabsContent = React20__namespace.forwardRef(({ className, ...props }, ref) =
456
509
  ref,
457
510
  className: cn(
458
511
  "mt-2",
459
- // Focus styles
460
512
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
461
513
  className
462
514
  ),
@@ -3419,6 +3471,7 @@ function HeaderCell({
3419
3471
  sorting,
3420
3472
  filter,
3421
3473
  isResizable,
3474
+ fillWidth,
3422
3475
  onSort,
3423
3476
  onFilterChange,
3424
3477
  onResizeMouseDown,
@@ -3462,7 +3515,8 @@ function HeaderCell({
3462
3515
  "div",
3463
3516
  {
3464
3517
  className: cn(
3465
- "relative flex-shrink-0 border-r border-border last:border-r-0",
3518
+ "relative border-r border-border last:border-r-0",
3519
+ !fillWidth && "flex-shrink-0",
3466
3520
  "bg-muted select-none",
3467
3521
  isResizing && "bg-accent/20"
3468
3522
  ),
@@ -4402,11 +4456,15 @@ function useColumnResizeManager(options) {
4402
4456
  resizableColumns,
4403
4457
  onColumnResize,
4404
4458
  onColumnResizeStart,
4405
- onColumnResizeEnd
4459
+ onColumnResizeEnd,
4460
+ fillWidth,
4461
+ containerWidth,
4462
+ effectiveColumnWidths
4406
4463
  } = options;
4407
4464
  const [resizingColumn, setResizingColumn] = React20.useState(null);
4408
4465
  const startXRef = React20.useRef(0);
4409
4466
  const startWidthRef = React20.useRef(0);
4467
+ const startEffectiveWidthsRef = React20.useRef({});
4410
4468
  const getColumn = React20.useCallback(
4411
4469
  (columnKey) => {
4412
4470
  return columns.find((c) => c.key === columnKey);
@@ -4430,9 +4488,20 @@ function useColumnResizeManager(options) {
4430
4488
  resizingColumn,
4431
4489
  startWidthRef.current + deltaX
4432
4490
  );
4491
+ if (fillWidth && containerWidth) {
4492
+ const otherCols = columns.filter((c) => c.key !== resizingColumn);
4493
+ const startEffective = startEffectiveWidthsRef.current;
4494
+ const otherTotal = otherCols.reduce((s, c) => s + (startEffective[c.key] || 0), 0);
4495
+ const remaining = containerWidth - newWidth;
4496
+ for (const col of otherCols) {
4497
+ const proportion = (startEffective[col.key] || 0) / otherTotal;
4498
+ const adjusted = clampWidth(col.key, Math.floor(remaining * proportion));
4499
+ onColumnResize(col.key, adjusted);
4500
+ }
4501
+ }
4433
4502
  onColumnResize(resizingColumn, newWidth);
4434
4503
  },
4435
- [resizingColumn, clampWidth, onColumnResize]
4504
+ [resizingColumn, clampWidth, onColumnResize, fillWidth, containerWidth, columns]
4436
4505
  );
4437
4506
  const handleMouseUp = React20.useCallback(() => {
4438
4507
  if (!resizingColumn) return;
@@ -4466,6 +4535,9 @@ function useColumnResizeManager(options) {
4466
4535
  document.body.style.userSelect = "none";
4467
4536
  document.body.style.cursor = "col-resize";
4468
4537
  onColumnResizeStart?.(columnKey);
4538
+ if (fillWidth && effectiveColumnWidths) {
4539
+ startEffectiveWidthsRef.current = { ...effectiveColumnWidths };
4540
+ }
4469
4541
  };
4470
4542
  const handleDoubleClick = (event) => {
4471
4543
  if (!isResizable) return;
@@ -4491,7 +4563,9 @@ function useColumnResizeManager(options) {
4491
4563
  resizingColumn,
4492
4564
  onColumnResize,
4493
4565
  onColumnResizeStart,
4494
- onColumnResizeEnd
4566
+ onColumnResizeEnd,
4567
+ fillWidth,
4568
+ effectiveColumnWidths
4495
4569
  ]
4496
4570
  );
4497
4571
  return {
@@ -4716,6 +4790,7 @@ function DataGrid({
4716
4790
  tooltipMinLength = 30,
4717
4791
  enableKeyboardNavigation = true,
4718
4792
  showColumnBorders = true,
4793
+ fillWidth,
4719
4794
  enableInternalSorting = true,
4720
4795
  enableInternalFiltering = true,
4721
4796
  // Controlled sorting
@@ -4770,6 +4845,16 @@ function DataGrid({
4770
4845
  const headerRef = React20__namespace.useRef(null);
4771
4846
  const [headerHeight, setHeaderHeight] = React20__namespace.useState(40);
4772
4847
  const [hoveredCell, setHoveredCell] = React20__namespace.useState(null);
4848
+ const [containerWidth, setContainerWidth] = React20__namespace.useState(0);
4849
+ React20__namespace.useLayoutEffect(() => {
4850
+ if (!fillWidth || !parentRef.current) return;
4851
+ const observer = new ResizeObserver((entries) => {
4852
+ const w = entries[0]?.contentRect.width;
4853
+ if (w && w > 0) setContainerWidth(w);
4854
+ });
4855
+ observer.observe(parentRef.current);
4856
+ return () => observer.disconnect();
4857
+ }, [fillWidth]);
4773
4858
  const visibleColumns = React20__namespace.useMemo(
4774
4859
  () => columns.filter((col) => !col.hidden),
4775
4860
  [columns]
@@ -4823,14 +4908,6 @@ function DataGrid({
4823
4908
  visibleColumns
4824
4909
  ]);
4825
4910
  processedDataRef.current = processedData;
4826
- const { resizingColumn, getResizeProps } = useColumnResizeManager({
4827
- columns: visibleColumns,
4828
- columnWidths: state.columnWidths,
4829
- resizableColumns,
4830
- onColumnResize: actions.setColumnWidth,
4831
- onColumnResizeStart,
4832
- onColumnResizeEnd
4833
- });
4834
4911
  const shouldVirtualize = virtualized ?? processedData.length > 100;
4835
4912
  React20__namespace.useLayoutEffect(() => {
4836
4913
  if (headerRef.current && shouldVirtualize) {
@@ -4844,12 +4921,38 @@ function DataGrid({
4844
4921
  overscan: DEFAULT_OVERSCAN,
4845
4922
  enabled: shouldVirtualize
4846
4923
  });
4847
- const tableWidth = React20__namespace.useMemo(() => {
4848
- return visibleColumns.reduce((acc, col) => {
4849
- const width = state.columnWidths[col.key] || col.width || estimateColumnWidth(col);
4850
- return acc + width;
4851
- }, 0);
4852
- }, [visibleColumns, state.columnWidths]);
4924
+ const { tableWidth, effectiveColumnWidths } = React20__namespace.useMemo(() => {
4925
+ const rawWidths = {};
4926
+ let rawTotal = 0;
4927
+ for (const col of visibleColumns) {
4928
+ const w = state.columnWidths[col.key] || col.width || estimateColumnWidth(col);
4929
+ rawWidths[col.key] = w;
4930
+ rawTotal += w;
4931
+ }
4932
+ if (!fillWidth || !containerWidth || rawTotal >= containerWidth) {
4933
+ return { tableWidth: rawTotal, effectiveColumnWidths: rawWidths };
4934
+ }
4935
+ const scale = containerWidth / rawTotal;
4936
+ const scaled = {};
4937
+ for (const col of visibleColumns) {
4938
+ let w = Math.floor(rawWidths[col.key] * scale);
4939
+ if (col.minWidth) w = Math.max(w, col.minWidth);
4940
+ if (col.maxWidth) w = Math.min(w, col.maxWidth);
4941
+ scaled[col.key] = w;
4942
+ }
4943
+ return { tableWidth: containerWidth, effectiveColumnWidths: scaled };
4944
+ }, [visibleColumns, state.columnWidths, fillWidth, containerWidth]);
4945
+ const { resizingColumn, getResizeProps } = useColumnResizeManager({
4946
+ columns: visibleColumns,
4947
+ columnWidths: state.columnWidths,
4948
+ resizableColumns,
4949
+ onColumnResize: actions.setColumnWidth,
4950
+ onColumnResizeStart,
4951
+ onColumnResizeEnd,
4952
+ fillWidth,
4953
+ containerWidth,
4954
+ effectiveColumnWidths
4955
+ });
4853
4956
  const visibleRowCount = React20__namespace.useMemo(() => {
4854
4957
  if (!parentRef.current) return 10;
4855
4958
  return Math.floor(parentRef.current.clientHeight / DEFAULT_ROW_HEIGHT);
@@ -5081,7 +5184,7 @@ function DataGrid({
5081
5184
  },
5082
5185
  role: "row",
5083
5186
  children: visibleColumns.map((column, colIndex) => {
5084
- const width = state.columnWidths[column.key] || column.width || estimateColumnWidth(column);
5187
+ const width = effectiveColumnWidths[column.key];
5085
5188
  const resizeProps = getResizeProps(column.key);
5086
5189
  return /* @__PURE__ */ jsxRuntime.jsx(
5087
5190
  HeaderCell,
@@ -5092,6 +5195,7 @@ function DataGrid({
5092
5195
  sorting: getColumnSort(column.key),
5093
5196
  filter: getColumnFilter(column.key),
5094
5197
  isResizable: resizableColumns && column.resizable !== false,
5198
+ fillWidth,
5095
5199
  onSort: () => handleSort(column.key),
5096
5200
  onFilterChange: (filter) => handleFilterChange(column.key, filter),
5097
5201
  onResizeMouseDown: resizeProps.handleMouseDown,
@@ -5140,7 +5244,7 @@ function DataGrid({
5140
5244
  "aria-rowindex": virtualRow.index + 1,
5141
5245
  "aria-selected": isSelected,
5142
5246
  children: visibleColumns.map((column, colIndex) => {
5143
- const width = state.columnWidths[column.key] || column.width || estimateColumnWidth(column);
5247
+ const width = effectiveColumnWidths[column.key];
5144
5248
  const isEditingThisCell = state.editingCell?.rowIndex === virtualRow.index && state.editingCell?.columnKey === column.key;
5145
5249
  const isFocused = state.focusedCell?.rowIndex === virtualRow.index && state.focusedCell?.columnKey === column.key;
5146
5250
  const cellContent = renderCell(
@@ -5155,7 +5259,8 @@ function DataGrid({
5155
5259
  "div",
5156
5260
  {
5157
5261
  className: cn(
5158
- "flex-shrink-0 px-3 py-2 text-sm overflow-hidden",
5262
+ "px-3 py-2 text-sm overflow-hidden",
5263
+ !fillWidth && "flex-shrink-0",
5159
5264
  showColumnBorders && "border-r border-border last:border-r-0",
5160
5265
  isFocused && !isEditingThisCell && "ring-2 ring-inset ring-primary",
5161
5266
  isEditingThisCell && "ring-2 ring-inset ring-primary bg-background",
@@ -5267,7 +5372,7 @@ function DataGrid({
5267
5372
  }
5268
5373
  )
5269
5374
  ] }) }),
5270
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-auto min-h-0", children: [
5375
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: parentRef, className: "flex-1 overflow-auto min-h-0", children: [
5271
5376
  /* @__PURE__ */ jsxRuntime.jsx(
5272
5377
  "div",
5273
5378
  {
@@ -5278,7 +5383,7 @@ function DataGrid({
5278
5383
  style: { width: tableWidth ? `${tableWidth}px` : "100%" },
5279
5384
  role: "row",
5280
5385
  children: visibleColumns.map((column, colIndex) => {
5281
- const width = state.columnWidths[column.key] || column.width || estimateColumnWidth(column);
5386
+ const width = effectiveColumnWidths[column.key];
5282
5387
  const resizeProps = getResizeProps(column.key);
5283
5388
  return /* @__PURE__ */ jsxRuntime.jsx(
5284
5389
  HeaderCell,
@@ -5289,6 +5394,7 @@ function DataGrid({
5289
5394
  sorting: getColumnSort(column.key),
5290
5395
  filter: getColumnFilter(column.key),
5291
5396
  isResizable: resizableColumns && column.resizable !== false,
5397
+ fillWidth,
5292
5398
  onSort: () => handleSort(column.key),
5293
5399
  onFilterChange: (filter) => handleFilterChange(column.key, filter),
5294
5400
  onResizeMouseDown: resizeProps.handleMouseDown,
@@ -5318,14 +5424,15 @@ function DataGrid({
5318
5424
  onDoubleClick: () => onRowDoubleClick?.(row, rowIndex),
5319
5425
  role: "row",
5320
5426
  children: visibleColumns.map((column) => {
5321
- const width = state.columnWidths[column.key] || column.width || estimateColumnWidth(column);
5427
+ const width = effectiveColumnWidths[column.key];
5322
5428
  const isEditingThisCell = state.editingCell?.rowIndex === rowIndex && state.editingCell?.columnKey === column.key;
5323
5429
  const isFocused = state.focusedCell?.rowIndex === rowIndex && state.focusedCell?.columnKey === column.key;
5324
5430
  return /* @__PURE__ */ jsxRuntime.jsx(
5325
5431
  "div",
5326
5432
  {
5327
5433
  className: cn(
5328
- "flex-shrink-0 px-3 py-2 text-sm overflow-hidden",
5434
+ "px-3 py-2 text-sm overflow-hidden",
5435
+ !fillWidth && "flex-shrink-0",
5329
5436
  showColumnBorders && "border-r border-border last:border-r-0",
5330
5437
  isFocused && !isEditingThisCell && "ring-2 ring-inset ring-primary",
5331
5438
  isEditingThisCell && "ring-2 ring-inset ring-primary bg-background",
@@ -5650,6 +5757,504 @@ function Autocomplete({
5650
5757
  )
5651
5758
  ] });
5652
5759
  }
5760
+ function MultiSelect({
5761
+ options,
5762
+ value,
5763
+ onChange,
5764
+ placeholder = "Select items...",
5765
+ searchPlaceholder = "Search...",
5766
+ emptyText = "No options found.",
5767
+ disabled = false,
5768
+ className,
5769
+ clearable = true,
5770
+ maxDisplayItems = 3,
5771
+ showSelectAll = false,
5772
+ selectAllLabel = "Select all"
5773
+ }) {
5774
+ const [open, setOpen] = React20__namespace.useState(false);
5775
+ const [search, setSearch] = React20__namespace.useState("");
5776
+ const inputRef = React20__namespace.useRef(null);
5777
+ const safeOptions = options ?? [];
5778
+ const safeValue = value ?? [];
5779
+ const selectedSet = React20__namespace.useMemo(() => new Set(safeValue), [safeValue]);
5780
+ const filteredOptions = React20__namespace.useMemo(() => {
5781
+ if (!search.trim()) return safeOptions;
5782
+ const searchLower = search.toLowerCase();
5783
+ return safeOptions.filter(
5784
+ (opt) => opt.label.toLowerCase().includes(searchLower) || opt.description?.toLowerCase().includes(searchLower)
5785
+ );
5786
+ }, [safeOptions, search]);
5787
+ const groupedOptions = React20__namespace.useMemo(() => {
5788
+ const groups = {};
5789
+ const ungrouped = [];
5790
+ filteredOptions.forEach((opt) => {
5791
+ if (opt.group) {
5792
+ if (!groups[opt.group]) groups[opt.group] = [];
5793
+ groups[opt.group].push(opt);
5794
+ } else {
5795
+ ungrouped.push(opt);
5796
+ }
5797
+ });
5798
+ return { groups, ungrouped };
5799
+ }, [filteredOptions]);
5800
+ const hasGroups = Object.keys(groupedOptions.groups).length > 0;
5801
+ const selectableFiltered = React20__namespace.useMemo(
5802
+ () => filteredOptions.filter((opt) => !opt.disabled),
5803
+ [filteredOptions]
5804
+ );
5805
+ const allFilteredSelected = React20__namespace.useMemo(
5806
+ () => selectableFiltered.length > 0 && selectableFiltered.every((opt) => selectedSet.has(opt.value)),
5807
+ [selectableFiltered, selectedSet]
5808
+ );
5809
+ const someFilteredSelected = React20__namespace.useMemo(
5810
+ () => !allFilteredSelected && selectableFiltered.some((opt) => selectedSet.has(opt.value)),
5811
+ [selectableFiltered, selectedSet, allFilteredSelected]
5812
+ );
5813
+ const handleToggle = React20__namespace.useCallback(
5814
+ (optionValue) => {
5815
+ const next = selectedSet.has(optionValue) ? safeValue.filter((v) => v !== optionValue) : [...safeValue, optionValue];
5816
+ onChange?.(next);
5817
+ },
5818
+ [onChange, safeValue, selectedSet]
5819
+ );
5820
+ const handleRemove = React20__namespace.useCallback(
5821
+ (optionValue, e) => {
5822
+ e.stopPropagation();
5823
+ onChange?.(safeValue.filter((v) => v !== optionValue));
5824
+ },
5825
+ [onChange, safeValue]
5826
+ );
5827
+ const handleClearAll = React20__namespace.useCallback(
5828
+ (e) => {
5829
+ e.stopPropagation();
5830
+ onChange?.([]);
5831
+ },
5832
+ [onChange]
5833
+ );
5834
+ const handleSelectAll = React20__namespace.useCallback(() => {
5835
+ if (allFilteredSelected) {
5836
+ const filteredValues = new Set(selectableFiltered.map((o) => o.value));
5837
+ onChange?.(safeValue.filter((v) => !filteredValues.has(v)));
5838
+ } else {
5839
+ const existing = new Set(safeValue);
5840
+ const next = [...safeValue];
5841
+ for (const opt of selectableFiltered) {
5842
+ if (!existing.has(opt.value)) {
5843
+ next.push(opt.value);
5844
+ }
5845
+ }
5846
+ onChange?.(next);
5847
+ }
5848
+ }, [allFilteredSelected, selectableFiltered, safeValue, onChange]);
5849
+ React20__namespace.useEffect(() => {
5850
+ if (open) {
5851
+ const timeout = setTimeout(() => inputRef.current?.focus(), 0);
5852
+ return () => clearTimeout(timeout);
5853
+ } else {
5854
+ setSearch("");
5855
+ }
5856
+ }, [open]);
5857
+ const handleKeyDown = React20__namespace.useCallback((e) => {
5858
+ if (e.key === "Escape") setOpen(false);
5859
+ }, []);
5860
+ const selectedLabels = React20__namespace.useMemo(
5861
+ () => safeValue.map((v) => safeOptions.find((o) => o.value === v)?.label ?? v).slice(0, maxDisplayItems),
5862
+ [safeValue, safeOptions, maxDisplayItems]
5863
+ );
5864
+ const overflow = safeValue.length - maxDisplayItems;
5865
+ const isSearching = search.trim().length > 0;
5866
+ const renderOption = (option) => /* @__PURE__ */ jsxRuntime.jsxs(
5867
+ "button",
5868
+ {
5869
+ type: "button",
5870
+ disabled: option.disabled,
5871
+ onClick: () => handleToggle(option.value),
5872
+ className: cn(
5873
+ "relative flex w-full cursor-pointer select-none items-start gap-2 rounded-sm px-2 py-1.5 text-sm outline-none",
5874
+ "hover:bg-accent hover:text-accent-foreground",
5875
+ option.disabled && "pointer-events-none opacity-50"
5876
+ ),
5877
+ children: [
5878
+ /* @__PURE__ */ jsxRuntime.jsx(
5879
+ Checkbox,
5880
+ {
5881
+ checked: selectedSet.has(option.value),
5882
+ tabIndex: -1,
5883
+ className: "mt-0.5 pointer-events-none"
5884
+ }
5885
+ ),
5886
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
5887
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate", children: option.label }),
5888
+ option.description && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground truncate", children: option.description })
5889
+ ] })
5890
+ ]
5891
+ },
5892
+ option.value
5893
+ );
5894
+ return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: setOpen, children: [
5895
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, disabled, children: /* @__PURE__ */ jsxRuntime.jsxs(
5896
+ "button",
5897
+ {
5898
+ type: "button",
5899
+ role: "combobox",
5900
+ "aria-expanded": open,
5901
+ "aria-haspopup": "listbox",
5902
+ disabled,
5903
+ className: cn(
5904
+ "flex min-h-9 w-full items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-1.5 text-sm shadow-sm ring-offset-background",
5905
+ "hover:border-input-hover",
5906
+ "focus:outline-none focus:ring-1 focus:ring-ring",
5907
+ "disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:border-input",
5908
+ className
5909
+ ),
5910
+ children: [
5911
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-1 flex-wrap gap-1 items-center min-w-0", children: safeValue.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: placeholder }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5912
+ selectedLabels.map((label, i) => /* @__PURE__ */ jsxRuntime.jsxs(
5913
+ "span",
5914
+ {
5915
+ className: "inline-flex items-center gap-0.5 rounded-sm bg-accent px-1.5 py-0.5 text-xs font-medium text-accent-foreground",
5916
+ children: [
5917
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate max-w-[100px]", children: label }),
5918
+ /* @__PURE__ */ jsxRuntime.jsx(
5919
+ "span",
5920
+ {
5921
+ role: "button",
5922
+ tabIndex: -1,
5923
+ onClick: (e) => handleRemove(safeValue[i], e),
5924
+ className: "rounded-sm hover:bg-foreground/10 p-0.5",
5925
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3 w-3" })
5926
+ }
5927
+ )
5928
+ ]
5929
+ },
5930
+ safeValue[i]
5931
+ )),
5932
+ overflow > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
5933
+ "+",
5934
+ overflow
5935
+ ] })
5936
+ ] }) }),
5937
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 flex-shrink-0", children: [
5938
+ clearable && safeValue.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
5939
+ "span",
5940
+ {
5941
+ role: "button",
5942
+ tabIndex: -1,
5943
+ onClick: handleClearAll,
5944
+ className: "rounded-sm hover:bg-muted p-0.5",
5945
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3.5 w-3.5 text-muted-foreground" })
5946
+ }
5947
+ ),
5948
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4 opacity-50" })
5949
+ ] })
5950
+ ]
5951
+ }
5952
+ ) }),
5953
+ /* @__PURE__ */ jsxRuntime.jsxs(
5954
+ PopoverContent,
5955
+ {
5956
+ className: "w-[--radix-popover-trigger-width] p-0",
5957
+ align: "start",
5958
+ sideOffset: 4,
5959
+ onKeyDown: handleKeyDown,
5960
+ children: [
5961
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center border-b border-border px-3", children: [
5962
+ /* @__PURE__ */ jsxRuntime.jsx(
5963
+ "input",
5964
+ {
5965
+ ref: inputRef,
5966
+ type: "text",
5967
+ value: search,
5968
+ onChange: (e) => setSearch(e.target.value),
5969
+ placeholder: searchPlaceholder,
5970
+ className: "flex h-9 w-full bg-transparent py-2 text-sm outline-none placeholder:text-muted-foreground"
5971
+ }
5972
+ ),
5973
+ search && /* @__PURE__ */ jsxRuntime.jsx(
5974
+ "button",
5975
+ {
5976
+ type: "button",
5977
+ onClick: () => setSearch(""),
5978
+ className: "p-1 hover:bg-muted rounded-sm",
5979
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3.5 w-3.5 text-muted-foreground" })
5980
+ }
5981
+ )
5982
+ ] }),
5983
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-h-[300px] overflow-y-auto p-1", children: [
5984
+ showSelectAll && selectableFiltered.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5985
+ /* @__PURE__ */ jsxRuntime.jsxs(
5986
+ "button",
5987
+ {
5988
+ type: "button",
5989
+ onClick: handleSelectAll,
5990
+ className: cn(
5991
+ "relative flex w-full cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none",
5992
+ "hover:bg-accent hover:text-accent-foreground"
5993
+ ),
5994
+ children: [
5995
+ /* @__PURE__ */ jsxRuntime.jsx(
5996
+ Checkbox,
5997
+ {
5998
+ checked: allFilteredSelected ? true : someFilteredSelected ? "indeterminate" : false,
5999
+ tabIndex: -1,
6000
+ className: "pointer-events-none"
6001
+ }
6002
+ ),
6003
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: isSearching ? `${selectAllLabel} (filtered)` : selectAllLabel })
6004
+ ]
6005
+ }
6006
+ ),
6007
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "-mx-1 my-1 h-px bg-border" })
6008
+ ] }),
6009
+ filteredOptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-6 text-center text-sm text-muted-foreground", children: emptyText }) : hasGroups ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6010
+ groupedOptions.ungrouped.map(renderOption),
6011
+ Object.entries(groupedOptions.groups).map(([group, opts]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
6012
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-1.5 text-xs font-semibold text-muted-foreground", children: group }),
6013
+ opts.map(renderOption)
6014
+ ] }, group))
6015
+ ] }) : filteredOptions.map(renderOption)
6016
+ ] })
6017
+ ]
6018
+ }
6019
+ )
6020
+ ] });
6021
+ }
6022
+ MultiSelect.displayName = "MultiSelect";
6023
+ function Combobox({
6024
+ options,
6025
+ value,
6026
+ onChange,
6027
+ onInputChange,
6028
+ placeholder = "Type or select...",
6029
+ emptyText = "No options found.",
6030
+ disabled = false,
6031
+ className,
6032
+ clearable = false,
6033
+ allowCustomValue = true
6034
+ }) {
6035
+ const [open, setOpen] = React20__namespace.useState(false);
6036
+ const [inputValue, setInputValue] = React20__namespace.useState("");
6037
+ const inputRef = React20__namespace.useRef(null);
6038
+ const wrapperRef = React20__namespace.useRef(null);
6039
+ const selectedOption = React20__namespace.useMemo(
6040
+ () => options.find((opt) => opt.value === value),
6041
+ [options, value]
6042
+ );
6043
+ React20__namespace.useEffect(() => {
6044
+ if (!open) {
6045
+ setInputValue(selectedOption?.label ?? value ?? "");
6046
+ }
6047
+ }, [value, selectedOption, open]);
6048
+ const filteredOptions = React20__namespace.useMemo(() => {
6049
+ if (!inputValue.trim()) return options;
6050
+ const searchLower = inputValue.toLowerCase();
6051
+ return options.filter(
6052
+ (opt) => opt.label.toLowerCase().includes(searchLower) || opt.description?.toLowerCase().includes(searchLower)
6053
+ );
6054
+ }, [options, inputValue]);
6055
+ const groupedOptions = React20__namespace.useMemo(() => {
6056
+ const groups = {};
6057
+ const ungrouped = [];
6058
+ filteredOptions.forEach((opt) => {
6059
+ if (opt.group) {
6060
+ if (!groups[opt.group]) groups[opt.group] = [];
6061
+ groups[opt.group].push(opt);
6062
+ } else {
6063
+ ungrouped.push(opt);
6064
+ }
6065
+ });
6066
+ return { groups, ungrouped };
6067
+ }, [filteredOptions]);
6068
+ const hasGroups = Object.keys(groupedOptions.groups).length > 0;
6069
+ const handleInputChange = React20__namespace.useCallback(
6070
+ (e) => {
6071
+ const newValue = e.target.value;
6072
+ setInputValue(newValue);
6073
+ onInputChange?.(newValue);
6074
+ if (!open) setOpen(true);
6075
+ },
6076
+ [onInputChange, open]
6077
+ );
6078
+ const handleSelect = React20__namespace.useCallback(
6079
+ (optionValue) => {
6080
+ const option = options.find((o) => o.value === optionValue);
6081
+ onChange?.(optionValue);
6082
+ setInputValue(option?.label ?? optionValue);
6083
+ setOpen(false);
6084
+ },
6085
+ [onChange, options]
6086
+ );
6087
+ const handleClear = React20__namespace.useCallback(
6088
+ (e) => {
6089
+ e.stopPropagation();
6090
+ e.preventDefault();
6091
+ onChange?.(void 0);
6092
+ setInputValue("");
6093
+ inputRef.current?.focus();
6094
+ },
6095
+ [onChange]
6096
+ );
6097
+ const handleFocus = React20__namespace.useCallback(() => {
6098
+ setOpen(true);
6099
+ inputRef.current?.select();
6100
+ }, []);
6101
+ const handleBlur = React20__namespace.useCallback(() => {
6102
+ setTimeout(() => {
6103
+ if (!wrapperRef.current?.contains(document.activeElement)) {
6104
+ setOpen(false);
6105
+ }
6106
+ if (allowCustomValue && inputValue.trim()) {
6107
+ const matchingOption = options.find(
6108
+ (opt) => opt.label.toLowerCase() === inputValue.toLowerCase()
6109
+ );
6110
+ if (matchingOption) {
6111
+ onChange?.(matchingOption.value);
6112
+ setInputValue(matchingOption.label);
6113
+ } else {
6114
+ onChange?.(inputValue.trim());
6115
+ }
6116
+ } else if (!allowCustomValue) {
6117
+ setInputValue(selectedOption?.label ?? "");
6118
+ }
6119
+ }, 200);
6120
+ }, [allowCustomValue, inputValue, options, onChange, selectedOption]);
6121
+ const handleKeyDown = React20__namespace.useCallback(
6122
+ (e) => {
6123
+ if (e.key === "Escape") {
6124
+ setOpen(false);
6125
+ setInputValue(selectedOption?.label ?? value ?? "");
6126
+ inputRef.current?.blur();
6127
+ } else if (e.key === "Enter" && open) {
6128
+ e.preventDefault();
6129
+ if (filteredOptions.length === 1) {
6130
+ handleSelect(filteredOptions[0].value);
6131
+ } else if (allowCustomValue && inputValue.trim()) {
6132
+ const exactMatch = filteredOptions.find(
6133
+ (opt) => opt.label.toLowerCase() === inputValue.toLowerCase()
6134
+ );
6135
+ if (exactMatch) {
6136
+ handleSelect(exactMatch.value);
6137
+ } else {
6138
+ onChange?.(inputValue.trim());
6139
+ setOpen(false);
6140
+ }
6141
+ }
6142
+ }
6143
+ },
6144
+ [
6145
+ open,
6146
+ filteredOptions,
6147
+ handleSelect,
6148
+ allowCustomValue,
6149
+ inputValue,
6150
+ onChange,
6151
+ selectedOption,
6152
+ value
6153
+ ]
6154
+ );
6155
+ const renderOption = (option) => /* @__PURE__ */ jsxRuntime.jsxs(
6156
+ "button",
6157
+ {
6158
+ type: "button",
6159
+ disabled: option.disabled,
6160
+ onMouseDown: (e) => e.preventDefault(),
6161
+ onClick: () => handleSelect(option.value),
6162
+ className: cn(
6163
+ "relative flex w-full cursor-pointer select-none items-start gap-2 rounded-sm px-2 py-1.5 text-sm outline-none",
6164
+ "hover:bg-accent hover:text-accent-foreground",
6165
+ "focus:bg-accent focus:text-accent-foreground",
6166
+ option.disabled && "pointer-events-none opacity-50",
6167
+ value === option.value && "bg-accent/50"
6168
+ ),
6169
+ children: [
6170
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-4 w-4 items-center justify-center flex-shrink-0 mt-0.5", children: value === option.value && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }) }),
6171
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
6172
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate", children: option.label }),
6173
+ option.description && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground truncate", children: option.description })
6174
+ ] })
6175
+ ]
6176
+ },
6177
+ option.value
6178
+ );
6179
+ return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: setOpen, children: [
6180
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
6181
+ "div",
6182
+ {
6183
+ ref: wrapperRef,
6184
+ className: cn(
6185
+ "flex h-9 w-full items-center gap-2 rounded-md border border-input bg-transparent px-3 text-sm shadow-sm ring-offset-background",
6186
+ "hover:border-input-hover",
6187
+ "focus-within:outline-none focus-within:ring-1 focus-within:ring-ring",
6188
+ disabled && "cursor-not-allowed opacity-50 hover:border-input",
6189
+ className
6190
+ ),
6191
+ children: [
6192
+ /* @__PURE__ */ jsxRuntime.jsx(
6193
+ "input",
6194
+ {
6195
+ ref: inputRef,
6196
+ type: "text",
6197
+ value: inputValue,
6198
+ onChange: handleInputChange,
6199
+ onFocus: handleFocus,
6200
+ onBlur: handleBlur,
6201
+ onKeyDown: handleKeyDown,
6202
+ placeholder,
6203
+ disabled,
6204
+ className: "flex-1 min-w-0 bg-transparent py-2 outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed",
6205
+ role: "combobox",
6206
+ "aria-expanded": open,
6207
+ "aria-haspopup": "listbox",
6208
+ "aria-autocomplete": "list"
6209
+ }
6210
+ ),
6211
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 flex-shrink-0", children: [
6212
+ clearable && value && /* @__PURE__ */ jsxRuntime.jsx(
6213
+ "span",
6214
+ {
6215
+ role: "button",
6216
+ tabIndex: -1,
6217
+ onMouseDown: (e) => e.preventDefault(),
6218
+ onClick: handleClear,
6219
+ className: "rounded-sm hover:bg-muted p-0.5",
6220
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3.5 w-3.5 text-muted-foreground" })
6221
+ }
6222
+ ),
6223
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4 opacity-50" })
6224
+ ] })
6225
+ ]
6226
+ }
6227
+ ) }),
6228
+ /* @__PURE__ */ jsxRuntime.jsx(
6229
+ PopoverContent,
6230
+ {
6231
+ className: "p-0",
6232
+ style: { width: wrapperRef.current?.offsetWidth },
6233
+ align: "start",
6234
+ sideOffset: 4,
6235
+ onOpenAutoFocus: (e) => e.preventDefault(),
6236
+ onFocusOutside: (e) => {
6237
+ if (wrapperRef.current?.contains(e.target)) {
6238
+ e.preventDefault();
6239
+ }
6240
+ },
6241
+ onInteractOutside: (e) => {
6242
+ if (wrapperRef.current?.contains(e.target)) {
6243
+ e.preventDefault();
6244
+ }
6245
+ },
6246
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-[300px] overflow-y-auto p-1", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-6 text-center text-sm text-muted-foreground", children: emptyText }) : hasGroups ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6247
+ groupedOptions.ungrouped.map(renderOption),
6248
+ Object.entries(groupedOptions.groups).map(([group, opts]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
6249
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-1.5 text-xs font-semibold text-muted-foreground", children: group }),
6250
+ opts.map(renderOption)
6251
+ ] }, group))
6252
+ ] }) : filteredOptions.map(renderOption) })
6253
+ }
6254
+ )
6255
+ ] });
6256
+ }
6257
+ Combobox.displayName = "Combobox";
5653
6258
  var iconButtonVariants = classVarianceAuthority.cva(
5654
6259
  // Base styles
5655
6260
  [
@@ -7499,6 +8104,8 @@ var PERMISSIONS_POLICY = [
7499
8104
  "geolocation=()",
7500
8105
  "payment=()",
7501
8106
  "usb=()",
8107
+ "clipboard-read=()",
8108
+ "clipboard-write=()",
7502
8109
  "display-capture=()",
7503
8110
  "fullscreen=()",
7504
8111
  "autoplay=()",
@@ -7961,6 +8568,7 @@ exports.CellEditor = CellEditor;
7961
8568
  exports.Checkbox = Checkbox;
7962
8569
  exports.Chip = Chip;
7963
8570
  exports.CodeRenderer = CodeRenderer;
8571
+ exports.Combobox = Combobox;
7964
8572
  exports.ConfirmationModal = ConfirmationModal;
7965
8573
  exports.ContextMenu = ContextMenu;
7966
8574
  exports.CopyButton = CopyButton;
@@ -8003,6 +8611,7 @@ exports.MODERN_LIGHT_THEME = MODERN_LIGHT_THEME;
8003
8611
  exports.MarkdownRenderer = MarkdownRenderer;
8004
8612
  exports.Modal = Modal;
8005
8613
  exports.ModalButton = ModalButton;
8614
+ exports.MultiSelect = MultiSelect;
8006
8615
  exports.OPTILOGIC_DARK_THEME = OPTILOGIC_DARK_THEME;
8007
8616
  exports.OPTILOGIC_LEGACY_THEME = OPTILOGIC_LEGACY_THEME;
8008
8617
  exports.OptilogicLogo = OptilogicLogo;
@@ -8020,6 +8629,7 @@ exports.Select = Select;
8020
8629
  exports.SelectContent = SelectContent;
8021
8630
  exports.SelectGroup = SelectGroup;
8022
8631
  exports.SelectItem = SelectItem;
8632
+ exports.SelectItemDescription = SelectItemDescription;
8023
8633
  exports.SelectLabel = SelectLabel;
8024
8634
  exports.SelectScrollDownButton = SelectScrollDownButton;
8025
8635
  exports.SelectScrollUpButton = SelectScrollUpButton;
@@ -8087,6 +8697,8 @@ exports.labelVariants = labelVariants;
8087
8697
  exports.loadingSpinnerVariants = loadingSpinnerVariants;
8088
8698
  exports.mergeRenderers = mergeRenderers;
8089
8699
  exports.resolveRenderer = resolveRenderer;
8700
+ exports.tabsListVariants = tabsListVariants;
8701
+ exports.tabsTriggerVariants = tabsTriggerVariants;
8090
8702
  exports.themeToHsl = themeToHsl;
8091
8703
  exports.useColumnResize = useColumnResize;
8092
8704
  exports.useColumnResizeManager = useColumnResizeManager;