myoperator-mcp 0.2.376 → 0.2.378
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +153 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6861,6 +6861,18 @@ export interface MultiSelectProps extends VariantProps<
|
|
|
6861
6861
|
searchable?: boolean;
|
|
6862
6862
|
/** Search placeholder text */
|
|
6863
6863
|
searchPlaceholder?: string;
|
|
6864
|
+
/**
|
|
6865
|
+
* Controlled search value. Pair with \`onSearchQueryChange\` when filtering
|
|
6866
|
+
* happens server-side (e.g. alongside \`onScrollEnd\` pagination, where each
|
|
6867
|
+
* page only has a slice of the full result set \u2014 client-side filtering
|
|
6868
|
+
* would search just that slice and show false "No results found" states).
|
|
6869
|
+
* When provided, the component stops managing its own search state and
|
|
6870
|
+
* stops filtering \`options\` itself; the caller is expected to pass already
|
|
6871
|
+
* filtered \`options\` for the current \`searchQuery\`.
|
|
6872
|
+
*/
|
|
6873
|
+
searchQuery?: string;
|
|
6874
|
+
/** Fires on every search input change. Required to pair with \`searchQuery\`. */
|
|
6875
|
+
onSearchQueryChange?: (query: string) => void;
|
|
6864
6876
|
/**
|
|
6865
6877
|
* When set, the trigger shows a single compact summary (e.g. "3 lines
|
|
6866
6878
|
* selected") instead of one chip per selection; hovering it reveals the full
|
|
@@ -6953,6 +6965,8 @@ const MultiSelect = React.forwardRef(
|
|
|
6953
6965
|
options,
|
|
6954
6966
|
searchable,
|
|
6955
6967
|
searchPlaceholder = "Search...",
|
|
6968
|
+
searchQuery: searchQueryProp,
|
|
6969
|
+
onSearchQueryChange,
|
|
6956
6970
|
selectAllLabel,
|
|
6957
6971
|
summaryLabel,
|
|
6958
6972
|
maxSelections,
|
|
@@ -6978,8 +6992,20 @@ const MultiSelect = React.forwardRef(
|
|
|
6978
6992
|
React.useState<string[]>(defaultValue);
|
|
6979
6993
|
// Dropdown open state
|
|
6980
6994
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
6981
|
-
// Search query
|
|
6982
|
-
|
|
6995
|
+
// Search query \u2014 controlled when the caller passes \`searchQuery\` (server-side
|
|
6996
|
+
// filtering), uncontrolled otherwise.
|
|
6997
|
+
const [internalSearchQuery, setInternalSearchQuery] = React.useState("");
|
|
6998
|
+
const isSearchControlled = searchQueryProp !== undefined;
|
|
6999
|
+
const searchQuery = isSearchControlled ? searchQueryProp : internalSearchQuery;
|
|
7000
|
+
const updateSearchQuery = React.useCallback(
|
|
7001
|
+
(next: string) => {
|
|
7002
|
+
if (!isSearchControlled) {
|
|
7003
|
+
setInternalSearchQuery(next);
|
|
7004
|
+
}
|
|
7005
|
+
onSearchQueryChange?.(next);
|
|
7006
|
+
},
|
|
7007
|
+
[isSearchControlled, onSearchQueryChange]
|
|
7008
|
+
);
|
|
6983
7009
|
|
|
6984
7010
|
// \`detailed\` rows are a single-line design, so they truncate unless the
|
|
6985
7011
|
// caller opts out; \`simple\` rows wrap the full label unless asked not to.
|
|
@@ -7257,8 +7283,11 @@ const MultiSelect = React.forwardRef(
|
|
|
7257
7283
|
// Determine aria-describedby
|
|
7258
7284
|
const ariaDescribedBy = error ? errorId : helperText ? helperId : undefined;
|
|
7259
7285
|
|
|
7260
|
-
// Filter options by search query
|
|
7286
|
+
// Filter options by search query. Skipped when \`searchQuery\` is
|
|
7287
|
+
// controlled \u2014 the caller owns filtering then (typically server-side, so
|
|
7288
|
+
// \`options\` is already the filtered slice for the current query).
|
|
7261
7289
|
const filteredOptions = React.useMemo(() => {
|
|
7290
|
+
if (isSearchControlled) return flatOptions;
|
|
7262
7291
|
if (!searchable || !searchQuery.trim()) return flatOptions;
|
|
7263
7292
|
const q = searchQuery.toLowerCase();
|
|
7264
7293
|
return flatOptions.filter((option) => {
|
|
@@ -7269,7 +7298,7 @@ const MultiSelect = React.forwardRef(
|
|
|
7269
7298
|
(option.group?.toLowerCase().includes(q) ?? false)
|
|
7270
7299
|
);
|
|
7271
7300
|
});
|
|
7272
|
-
}, [flatOptions, searchable, searchQuery]);
|
|
7301
|
+
}, [flatOptions, searchable, searchQuery, isSearchControlled]);
|
|
7273
7302
|
|
|
7274
7303
|
type DisplayItem =
|
|
7275
7304
|
| { type: "option"; option: MultiSelectOption }
|
|
@@ -7387,7 +7416,7 @@ const MultiSelect = React.forwardRef(
|
|
|
7387
7416
|
return;
|
|
7388
7417
|
}
|
|
7389
7418
|
setIsOpen(false);
|
|
7390
|
-
|
|
7419
|
+
updateSearchQuery("");
|
|
7391
7420
|
};
|
|
7392
7421
|
|
|
7393
7422
|
const timeoutId = window.setTimeout(() => {
|
|
@@ -7398,13 +7427,13 @@ const MultiSelect = React.forwardRef(
|
|
|
7398
7427
|
window.clearTimeout(timeoutId);
|
|
7399
7428
|
document.removeEventListener("mousedown", handleClickOutside);
|
|
7400
7429
|
};
|
|
7401
|
-
}, [isOpen, refs.floating]);
|
|
7430
|
+
}, [isOpen, refs.floating, updateSearchQuery]);
|
|
7402
7431
|
|
|
7403
7432
|
// Handle keyboard navigation
|
|
7404
7433
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
7405
7434
|
if (e.key === "Escape" && closeOnEscape) {
|
|
7406
7435
|
setIsOpen(false);
|
|
7407
|
-
|
|
7436
|
+
updateSearchQuery("");
|
|
7408
7437
|
} else if (e.key === "Enter" || e.key === " ") {
|
|
7409
7438
|
if (!isOpen) {
|
|
7410
7439
|
e.preventDefault();
|
|
@@ -7607,7 +7636,7 @@ const MultiSelect = React.forwardRef(
|
|
|
7607
7636
|
type="text"
|
|
7608
7637
|
placeholder={searchPlaceholder}
|
|
7609
7638
|
value={searchQuery}
|
|
7610
|
-
onChange={(e) =>
|
|
7639
|
+
onChange={(e) => updateSearchQuery(e.target.value)}
|
|
7611
7640
|
className="w-full h-[42px] px-3 text-base text-semantic-text-primary border border-solid border-semantic-border-input rounded bg-semantic-bg-primary placeholder:text-semantic-text-placeholder focus:outline-none focus:border-semantic-border-input-focus/50"
|
|
7612
7641
|
onClick={(e) => e.stopPropagation()}
|
|
7613
7642
|
/>
|
|
@@ -8489,6 +8518,49 @@ function PaginationEllipsis({
|
|
|
8489
8518
|
}
|
|
8490
8519
|
PaginationEllipsis.displayName = "PaginationEllipsis";
|
|
8491
8520
|
|
|
8521
|
+
export interface PaginationInfoProps extends React.ComponentProps<"p"> {
|
|
8522
|
+
/** Current page (1-based) */
|
|
8523
|
+
currentPage: number;
|
|
8524
|
+
/** Number of items shown per page */
|
|
8525
|
+
pageSize: number;
|
|
8526
|
+
/** Total number of items across all pages */
|
|
8527
|
+
totalItems: number;
|
|
8528
|
+
/** Leading label before the range (default: "Showing") */
|
|
8529
|
+
label?: string;
|
|
8530
|
+
/** Additional CSS classes */
|
|
8531
|
+
className?: string;
|
|
8532
|
+
}
|
|
8533
|
+
|
|
8534
|
+
function PaginationInfo({
|
|
8535
|
+
currentPage,
|
|
8536
|
+
pageSize,
|
|
8537
|
+
totalItems,
|
|
8538
|
+
label = "Showing",
|
|
8539
|
+
className,
|
|
8540
|
+
...props
|
|
8541
|
+
}: PaginationInfoProps) {
|
|
8542
|
+
const startItem = totalItems === 0 ? 0 : (currentPage - 1) * pageSize + 1;
|
|
8543
|
+
const endItem = Math.min(currentPage * pageSize, totalItems);
|
|
8544
|
+
|
|
8545
|
+
return (
|
|
8546
|
+
<p
|
|
8547
|
+
data-slot="pagination-info"
|
|
8548
|
+
aria-live="polite"
|
|
8549
|
+
className={cn(
|
|
8550
|
+
"m-0 text-sm text-semantic-text-muted whitespace-nowrap",
|
|
8551
|
+
className
|
|
8552
|
+
)}
|
|
8553
|
+
{...props}
|
|
8554
|
+
>
|
|
8555
|
+
{label}{" "}
|
|
8556
|
+
<span className="font-medium text-semantic-text-primary">
|
|
8557
|
+
{startItem}\u2013{endItem} of {totalItems}
|
|
8558
|
+
</span>
|
|
8559
|
+
</p>
|
|
8560
|
+
);
|
|
8561
|
+
}
|
|
8562
|
+
PaginationInfo.displayName = "PaginationInfo";
|
|
8563
|
+
|
|
8492
8564
|
export interface PaginationWidgetProps {
|
|
8493
8565
|
/** Current page (1-based) */
|
|
8494
8566
|
currentPage: number;
|
|
@@ -8498,7 +8570,13 @@ export interface PaginationWidgetProps {
|
|
|
8498
8570
|
onPageChange: (page: number) => void;
|
|
8499
8571
|
/** Number of pages shown on each side of current page (default: 1) */
|
|
8500
8572
|
siblingCount?: number;
|
|
8501
|
-
/**
|
|
8573
|
+
/** Total number of items \u2014 required to render the "Showing X-Y of Z" summary */
|
|
8574
|
+
totalItems?: number;
|
|
8575
|
+
/** Items per page \u2014 required to render the "Showing X-Y of Z" summary */
|
|
8576
|
+
pageSize?: number;
|
|
8577
|
+
/** Horizontal placement of the page controls (default: "center", or "end" when the summary is shown) */
|
|
8578
|
+
align?: "start" | "center" | "end";
|
|
8579
|
+
/** Additional CSS classes for the outer wrapper */
|
|
8502
8580
|
className?: string;
|
|
8503
8581
|
}
|
|
8504
8582
|
|
|
@@ -8540,17 +8618,35 @@ function usePaginationRange(
|
|
|
8540
8618
|
return pages;
|
|
8541
8619
|
}
|
|
8542
8620
|
|
|
8621
|
+
const paginationAlignClasses = {
|
|
8622
|
+
start: "justify-start",
|
|
8623
|
+
center: "justify-center",
|
|
8624
|
+
end: "justify-end",
|
|
8625
|
+
} as const;
|
|
8626
|
+
|
|
8543
8627
|
function PaginationWidget({
|
|
8544
8628
|
currentPage,
|
|
8545
8629
|
totalPages,
|
|
8546
8630
|
onPageChange,
|
|
8547
8631
|
siblingCount = 1,
|
|
8632
|
+
totalItems,
|
|
8633
|
+
pageSize,
|
|
8634
|
+
align,
|
|
8548
8635
|
className,
|
|
8549
8636
|
}: PaginationWidgetProps) {
|
|
8550
8637
|
const pages = usePaginationRange(currentPage, totalPages, siblingCount);
|
|
8638
|
+
const showInfo = totalItems !== undefined && pageSize !== undefined;
|
|
8639
|
+
const resolvedAlign = align ?? (showInfo ? "end" : "center");
|
|
8551
8640
|
|
|
8552
|
-
|
|
8553
|
-
<Pagination
|
|
8641
|
+
const controls = (
|
|
8642
|
+
<Pagination
|
|
8643
|
+
className={cn(
|
|
8644
|
+
"mx-0 w-auto",
|
|
8645
|
+
paginationAlignClasses[resolvedAlign],
|
|
8646
|
+
!showInfo && "w-full",
|
|
8647
|
+
!showInfo && className
|
|
8648
|
+
)}
|
|
8649
|
+
>
|
|
8554
8650
|
<PaginationContent>
|
|
8555
8651
|
<PaginationItem>
|
|
8556
8652
|
<PaginationPrevious
|
|
@@ -8595,6 +8691,25 @@ function PaginationWidget({
|
|
|
8595
8691
|
</PaginationContent>
|
|
8596
8692
|
</Pagination>
|
|
8597
8693
|
);
|
|
8694
|
+
|
|
8695
|
+
if (!showInfo) return controls;
|
|
8696
|
+
|
|
8697
|
+
return (
|
|
8698
|
+
<div
|
|
8699
|
+
data-slot="pagination-widget"
|
|
8700
|
+
className={cn(
|
|
8701
|
+
"flex w-full flex-col items-start gap-3 sm:flex-row sm:items-center sm:justify-between",
|
|
8702
|
+
className
|
|
8703
|
+
)}
|
|
8704
|
+
>
|
|
8705
|
+
<PaginationInfo
|
|
8706
|
+
currentPage={currentPage}
|
|
8707
|
+
pageSize={pageSize}
|
|
8708
|
+
totalItems={totalItems}
|
|
8709
|
+
/>
|
|
8710
|
+
{controls}
|
|
8711
|
+
</div>
|
|
8712
|
+
);
|
|
8598
8713
|
}
|
|
8599
8714
|
PaginationWidget.displayName = "PaginationWidget";
|
|
8600
8715
|
|
|
@@ -8606,6 +8721,7 @@ export {
|
|
|
8606
8721
|
PaginationPrevious,
|
|
8607
8722
|
PaginationNext,
|
|
8608
8723
|
PaginationEllipsis,
|
|
8724
|
+
PaginationInfo,
|
|
8609
8725
|
PaginationWidget,
|
|
8610
8726
|
};
|
|
8611
8727
|
`,
|
|
@@ -9580,11 +9696,32 @@ const SearchFilter = React.forwardRef<HTMLDivElement, SearchFilterProps>(
|
|
|
9580
9696
|
[disabled]
|
|
9581
9697
|
);
|
|
9582
9698
|
|
|
9699
|
+
/**
|
|
9700
|
+
* The pending focus frame has to be cancellable. Opening the menu schedules
|
|
9701
|
+
* a frame that refocuses the input, and the input's \`onFocus\` reopens the
|
|
9702
|
+
* menu. If that frame lands AFTER an option has been picked, it reopens the
|
|
9703
|
+
* dropdown that \`selectOption\` just closed \u2014 so selecting an option looks
|
|
9704
|
+
* like it does nothing. Whether the frame lands before or after the click is
|
|
9705
|
+
* pure timing, which is why the bug comes and goes.
|
|
9706
|
+
*/
|
|
9707
|
+
const focusFrameRef = React.useRef<number | null>(null);
|
|
9708
|
+
|
|
9709
|
+
const cancelPendingFocus = React.useCallback(() => {
|
|
9710
|
+
if (focusFrameRef.current === null) return;
|
|
9711
|
+
window.cancelAnimationFrame(focusFrameRef.current);
|
|
9712
|
+
focusFrameRef.current = null;
|
|
9713
|
+
}, []);
|
|
9714
|
+
|
|
9583
9715
|
const focusSearchInput = React.useCallback(() => {
|
|
9584
|
-
|
|
9716
|
+
cancelPendingFocus();
|
|
9717
|
+
focusFrameRef.current = window.requestAnimationFrame(() => {
|
|
9718
|
+
focusFrameRef.current = null;
|
|
9585
9719
|
searchInputRef.current?.focus();
|
|
9586
9720
|
});
|
|
9587
|
-
}, []);
|
|
9721
|
+
}, [cancelPendingFocus]);
|
|
9722
|
+
|
|
9723
|
+
// Never let a queued frame fire into an unmounted component.
|
|
9724
|
+
React.useEffect(() => cancelPendingFocus, [cancelPendingFocus]);
|
|
9588
9725
|
|
|
9589
9726
|
const setRootRef = React.useCallback(
|
|
9590
9727
|
(node: HTMLDivElement | null) => {
|
|
@@ -9663,9 +9800,12 @@ const SearchFilter = React.forwardRef<HTMLDivElement, SearchFilterProps>(
|
|
|
9663
9800
|
onValueChange?.(option.value);
|
|
9664
9801
|
onOptionSelect?.(option);
|
|
9665
9802
|
onSearchChange?.(option.label);
|
|
9803
|
+
// Drop any queued refocus first, or it reopens what we are closing.
|
|
9804
|
+
cancelPendingFocus();
|
|
9666
9805
|
setOpen(false);
|
|
9667
9806
|
},
|
|
9668
9807
|
[
|
|
9808
|
+
cancelPendingFocus,
|
|
9669
9809
|
disabled,
|
|
9670
9810
|
onSearchChange,
|
|
9671
9811
|
onOptionSelect,
|
package/package.json
CHANGED