@trackunit/react-components 2.11.3 → 2.11.7
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/index.cjs.js +152 -11
- package/index.esm.js +152 -12
- package/package.json +6 -6
- package/src/components/Menu/MenuContent/MenuContent.d.ts +14 -1
- package/src/components/Menu/MenuContent/MenuContentContext.d.ts +11 -0
- package/src/components/Menu/MenuContent/compactFloatingListRefs.d.ts +4 -3
- package/src/components/Menu/MenuContent/useMenuContentState.d.ts +2 -1
- package/src/components/Menu/MenuContent/useMenuSearchField.d.ts +61 -0
- package/src/index.d.ts +1 -0
- package/migrations/entry.js.map +0 -1
- package/migrations/utils/jsx-utils.js.map +0 -1
- package/migrations/v2-0-0/breadcrumb-remove-deprecated-props.js.map +0 -1
- package/migrations/v2-0-0/cardheader-onclickclose-to-actions.js.map +0 -1
- package/migrations/v2-0-0/iconbutton-add-required-name.js.map +0 -1
- package/migrations/v2-0-0/kpi-tooltiplabel-to-wrapper.js.map +0 -1
- package/migrations/v2-0-0/notice-tooltiplabel-to-wrapper.js.map +0 -1
- package/migrations/v2-0-0/pagination-add-required-titles.js.map +0 -1
- package/migrations/v2-0-0/tag-add-removetaglabel.js.map +0 -1
- package/migrations/v2-0-0/togglegroup-remove-item-style.js.map +0 -1
- package/migrations/v3-0-0/menulist-rename-to-menucontent.js.map +0 -1
package/index.cjs.js
CHANGED
|
@@ -7364,15 +7364,16 @@ const compactFloatingListRefs = (elementsRef, labelsRef) => {
|
|
|
7364
7364
|
labels.length = lastLive + 1;
|
|
7365
7365
|
};
|
|
7366
7366
|
/**
|
|
7367
|
-
* Index of the first registered
|
|
7367
|
+
* Index of the first registered entry that is not `aria-disabled`. Used as the initial tab stop
|
|
7368
7368
|
* when `activeIndex` is still null.
|
|
7369
7369
|
*
|
|
7370
7370
|
* @param elementsRef The FloatingList elements array
|
|
7371
|
-
* @
|
|
7371
|
+
* @param skipIndex Registered index to ignore, used to look past a search field to the first real item
|
|
7372
|
+
* @returns {number} The first enabled index, or `0` when the list has no enabled entry
|
|
7372
7373
|
*/
|
|
7373
|
-
const firstEnabledListIndex = (elementsRef) => {
|
|
7374
|
-
const index = elementsRef.current.findIndex(element => {
|
|
7375
|
-
if (!element) {
|
|
7374
|
+
const firstEnabledListIndex = (elementsRef, skipIndex) => {
|
|
7375
|
+
const index = elementsRef.current.findIndex((element, elementIndex) => {
|
|
7376
|
+
if (!element || elementIndex === skipIndex) {
|
|
7376
7377
|
return false;
|
|
7377
7378
|
}
|
|
7378
7379
|
return element.getAttribute("aria-disabled") !== "true";
|
|
@@ -7385,7 +7386,7 @@ const UNMANAGED_SELECTION = [];
|
|
|
7385
7386
|
* Owns MenuContent's list registration, selection, and typeable-child key handling so the
|
|
7386
7387
|
* component file stays the shell + JSX.
|
|
7387
7388
|
*/
|
|
7388
|
-
const useMenuContentState = ({ children, isMulti, selectedItems: controlledSelectedItems, onSelectionChange, ref, }) => {
|
|
7389
|
+
const useMenuContentState = ({ children, isMulti, autoFocus, selectedItems: controlledSelectedItems, onSelectionChange, ref, }) => {
|
|
7389
7390
|
const selectionManaged = controlledSelectedItems !== undefined || onSelectionChange !== undefined;
|
|
7390
7391
|
const [internalSelectedItems, setInternalSelectedItems] = react.useState(controlledSelectedItems ?? []);
|
|
7391
7392
|
const selectedItems = selectionManaged ? (controlledSelectedItems ?? internalSelectedItems) : UNMANAGED_SELECTION;
|
|
@@ -7393,6 +7394,10 @@ const useMenuContentState = ({ children, isMulti, selectedItems: controlledSelec
|
|
|
7393
7394
|
const [firstEnabledIndex, setFirstEnabledIndex] = react.useState(0);
|
|
7394
7395
|
const listRef = react.useRef([]);
|
|
7395
7396
|
const labelsRef = react.useRef([]);
|
|
7397
|
+
const searchFieldIndexRef = react.useRef(null);
|
|
7398
|
+
const registerSearchField = react.useCallback((index) => {
|
|
7399
|
+
searchFieldIndexRef.current = index;
|
|
7400
|
+
}, []);
|
|
7396
7401
|
const ambientPopover = useOptionalPopoverContext();
|
|
7397
7402
|
// Falls back to a local, always-open floating context when there is no ambient `Popover` (e.g. `MenuContent`
|
|
7398
7403
|
// rendered standalone inside a `Collapse`) -- `useFloating` must still be called unconditionally per the
|
|
@@ -7416,6 +7421,10 @@ const useMenuContentState = ({ children, isMulti, selectedItems: controlledSelec
|
|
|
7416
7421
|
onNavigate: setActiveIndex,
|
|
7417
7422
|
loop: true,
|
|
7418
7423
|
nested: isNested,
|
|
7424
|
+
// Floating UI's own entry focus, which waits for the list to finish registering before it
|
|
7425
|
+
// picks the first enabled index. Off by default: most menus open from a trigger the user
|
|
7426
|
+
// should stay on.
|
|
7427
|
+
focusItemOnOpen: autoFocus,
|
|
7419
7428
|
});
|
|
7420
7429
|
const typeahead = react$1.useTypeahead(context, {
|
|
7421
7430
|
listRef: labelsRef,
|
|
@@ -7426,6 +7435,10 @@ const useMenuContentState = ({ children, isMulti, selectedItems: controlledSelec
|
|
|
7426
7435
|
const { getFloatingProps, getItemProps } = react$1.useInteractions([listNavigation, typeahead]);
|
|
7427
7436
|
const syncRegisteredList = react.useCallback(() => {
|
|
7428
7437
|
compactFloatingListRefs(listRef, labelsRef);
|
|
7438
|
+
// Deliberately *not* skipping the search field. `firstEnabledIndex` is the menu's single
|
|
7439
|
+
// roving tab stop, and a registered field should hold it: that is what keeps every row's
|
|
7440
|
+
// `tabIndex` at -1 while the user is typing, so Tab leaves for the menu's chrome instead of
|
|
7441
|
+
// stopping on a row. Only Home (below) looks past the field, because Home addresses items.
|
|
7429
7442
|
const nextFirstEnabled = firstEnabledListIndex(listRef);
|
|
7430
7443
|
setFirstEnabledIndex(current => (current === nextFirstEnabled ? current : nextFirstEnabled));
|
|
7431
7444
|
setActiveIndex(current => {
|
|
@@ -7471,24 +7484,62 @@ const useMenuContentState = ({ children, isMulti, selectedItems: controlledSelec
|
|
|
7471
7484
|
selectedItems,
|
|
7472
7485
|
handleItemClick,
|
|
7473
7486
|
syncRegisteredList,
|
|
7474
|
-
|
|
7487
|
+
setActiveIndex,
|
|
7488
|
+
registerSearchField,
|
|
7489
|
+
}), [
|
|
7490
|
+
activeIndex,
|
|
7491
|
+
firstEnabledIndex,
|
|
7492
|
+
getItemProps,
|
|
7493
|
+
isMulti,
|
|
7494
|
+
selectedItems,
|
|
7495
|
+
handleItemClick,
|
|
7496
|
+
syncRegisteredList,
|
|
7497
|
+
registerSearchField,
|
|
7498
|
+
]);
|
|
7475
7499
|
// Printable characters and Home/End/Left/Right stay native in nested inputs (e.g. FilterBar
|
|
7476
7500
|
// Search). ArrowDown is left alone so a typeable child can enter the registered list.
|
|
7477
7501
|
const handleKeyDownCapture = react.useCallback((event) => {
|
|
7478
7502
|
if (!utils.isTypeableElement(event.target)) {
|
|
7503
|
+
// Home/End address the *items*. A registered search field is a navigation stop but not an
|
|
7504
|
+
// item, and it renders above them, so Floating UI's Home -- the list's minimum index -- would
|
|
7505
|
+
// land in the input. Redirect to the first item instead, by moving `activeIndex` rather than
|
|
7506
|
+
// calling `focus()`, so list navigation stays the only thing that moves focus. End needs no
|
|
7507
|
+
// counterpart: the field is never the maximum index.
|
|
7508
|
+
//
|
|
7509
|
+
// The containment check is not redundant: a `MenuItem` submenu renders its own `MenuContent`
|
|
7510
|
+
// into a portal, and React still routes that subtree's synthetic events through the React
|
|
7511
|
+
// tree, so this handler sees a submenu's Home first and would claim it before the submenu
|
|
7512
|
+
// ever ran its own.
|
|
7513
|
+
if (event.key === "Home" &&
|
|
7514
|
+
searchFieldIndexRef.current !== null &&
|
|
7515
|
+
event.target instanceof Node &&
|
|
7516
|
+
event.currentTarget.contains(event.target)) {
|
|
7517
|
+
event.preventDefault();
|
|
7518
|
+
event.stopPropagation();
|
|
7519
|
+
const firstItemIndex = firstEnabledListIndex(listRef, searchFieldIndexRef.current);
|
|
7520
|
+
// `firstEnabledListIndex` falls back to 0 when nothing qualifies, which is the field's own
|
|
7521
|
+
// index once a query has filtered every row away. Leave the highlight alone rather than
|
|
7522
|
+
// pulling it into the input.
|
|
7523
|
+
if (firstItemIndex !== searchFieldIndexRef.current) {
|
|
7524
|
+
setActiveIndex(firstItemIndex);
|
|
7525
|
+
}
|
|
7526
|
+
}
|
|
7479
7527
|
return;
|
|
7480
7528
|
}
|
|
7481
7529
|
if (event.key === "ArrowDown") {
|
|
7482
7530
|
return;
|
|
7483
7531
|
}
|
|
7484
|
-
// ArrowUp is
|
|
7485
|
-
//
|
|
7532
|
+
// ArrowUp is swallowed only for an *unregistered* typeable child, where a wrap has no sensible
|
|
7533
|
+
// destination: the input is not in the list, so list navigation would move the menu's roving
|
|
7534
|
+
// tab stop to the last item behind the user's back. A field registered via `useMenuSearchField`
|
|
7535
|
+
// *is* in the list, at the index above the first row, so its ArrowUp is an ordinary wrap to the
|
|
7536
|
+
// last item -- symmetric with the ArrowDown wrap that already lands back in the field.
|
|
7486
7537
|
if (event.key.length === 1 ||
|
|
7487
7538
|
event.key === "Home" ||
|
|
7488
7539
|
event.key === "End" ||
|
|
7489
7540
|
event.key === "ArrowLeft" ||
|
|
7490
7541
|
event.key === "ArrowRight" ||
|
|
7491
|
-
event.key === "ArrowUp") {
|
|
7542
|
+
(event.key === "ArrowUp" && searchFieldIndexRef.current === null)) {
|
|
7492
7543
|
event.stopPropagation();
|
|
7493
7544
|
}
|
|
7494
7545
|
}, []);
|
|
@@ -7568,10 +7619,11 @@ const useMenuContentState = ({ children, isMulti, selectedItems: controlledSelec
|
|
|
7568
7619
|
* @param {MenuContentProps} props - The props for the MenuContent component
|
|
7569
7620
|
* @returns {ReactElement} MenuContent component
|
|
7570
7621
|
*/
|
|
7571
|
-
const MenuContent = ({ "data-testid": dataTestId, className, listClassName, children, isMulti = false, selectedItems, onSelectionChange, style, ref, "aria-label": ariaLabel, ...args }) => {
|
|
7622
|
+
const MenuContent = ({ "data-testid": dataTestId, className, listClassName, children, isMulti = false, autoFocus = false, selectedItems, onSelectionChange, style, ref, "aria-label": ariaLabel, ...args }) => {
|
|
7572
7623
|
const { floatingRef, getFloatingProps, handleKeyDownCapture, listRef, labelsRef, menuContext } = useMenuContentState({
|
|
7573
7624
|
children,
|
|
7574
7625
|
isMulti,
|
|
7626
|
+
autoFocus,
|
|
7575
7627
|
selectedItems,
|
|
7576
7628
|
onSelectionChange,
|
|
7577
7629
|
ref,
|
|
@@ -7579,6 +7631,94 @@ const MenuContent = ({ "data-testid": dataTestId, className, listClassName, chil
|
|
|
7579
7631
|
return (jsxRuntime.jsx("div", { "aria-label": ariaLabel, className: cvaMenu({ className, limitWidth: true }), "data-testid": dataTestId ? `${dataTestId}-menu-list` : "menu-list", onKeyDownCapture: handleKeyDownCapture, ref: floatingRef, role: "menu", style: style, tabIndex: -1, ...getFloatingProps({ onClick: args.onClick }), children: jsxRuntime.jsx("div", { className: cvaMenuList({ className: listClassName }), children: jsxRuntime.jsx(MenuContentContext.Provider, { value: menuContext, children: jsxRuntime.jsx(react$1.FloatingList, { elementsRef: listRef, labelsRef: labelsRef, children: children }) }) }) }));
|
|
7580
7632
|
};
|
|
7581
7633
|
|
|
7634
|
+
/**
|
|
7635
|
+
* Joins a search input to the nearest `MenuContent` as the stop *above* the first item, so Floating
|
|
7636
|
+
* UI's list navigation moves focus in and out of the list on its own: ArrowDown from the field enters
|
|
7637
|
+
* the list, and ArrowUp from the first item comes back to the field. The field is a full member of
|
|
7638
|
+
* the loop, so ArrowUp from it wraps to the last item and ArrowDown from the last item wraps back.
|
|
7639
|
+
*
|
|
7640
|
+
* Focus then has a single owner. Moving between an unregistered field and the list means calling
|
|
7641
|
+
* `.focus()` from a key handler, which races the `requestAnimationFrame` Floating UI asserts item
|
|
7642
|
+
* focus from: the field wins or loses depending on how far apart the two key presses land. That is
|
|
7643
|
+
* a flake rather than a behaviour, and it cannot be fixed from the outside (ADR-0005).
|
|
7644
|
+
*
|
|
7645
|
+
* Registering also keeps the rows off the sequential Tab path while the field holds focus: the menu's
|
|
7646
|
+
* single roving tab stop is the field itself, so Tab continues to the menu's chrome (reset buttons,
|
|
7647
|
+
* footers) instead of stopping on a row.
|
|
7648
|
+
*
|
|
7649
|
+
* Call it from a component rendered *inside* `MenuContent`: registration goes through the
|
|
7650
|
+
* `FloatingList` context that `MenuContent` opens around its children, so a caller that also renders
|
|
7651
|
+
* the `MenuContent` sits outside that context and registers nothing.
|
|
7652
|
+
*
|
|
7653
|
+
* Product code should render `Search` from `@trackunit/react-form-components` here, as
|
|
7654
|
+
* `FiltersMenuSearchField` does -- the native `<input>` below (and in this package's story and specs)
|
|
7655
|
+
* is only there because `@trackunit/react-components` cannot depend on `@trackunit/react-form-components`
|
|
7656
|
+
* without a dependency cycle.
|
|
7657
|
+
*
|
|
7658
|
+
* @returns {UseMenuSearchFieldResult} A getter for the search input's props
|
|
7659
|
+
* @example
|
|
7660
|
+
* ```tsx
|
|
7661
|
+
* const MenuSearch = () => {
|
|
7662
|
+
* const { getSearchFieldProps } = useMenuSearchField();
|
|
7663
|
+
* return <input aria-label="Search" type="text" {...getSearchFieldProps()} />;
|
|
7664
|
+
* };
|
|
7665
|
+
*
|
|
7666
|
+
* const MenuWithSearch = () => (
|
|
7667
|
+
* <MenuContent autoFocus>
|
|
7668
|
+
* <MenuSearch />
|
|
7669
|
+
* <MenuItem id="alpha" label="Alpha" />
|
|
7670
|
+
* </MenuContent>
|
|
7671
|
+
* );
|
|
7672
|
+
* ```
|
|
7673
|
+
*/
|
|
7674
|
+
const useMenuSearchField = () => {
|
|
7675
|
+
const menu = useOptionalMenuContentContext();
|
|
7676
|
+
// `label: null` keeps the field out of typeahead: it is a navigation stop, not a match target.
|
|
7677
|
+
const { ref: listItemRef, index } = react$1.useListItem({ label: null });
|
|
7678
|
+
const syncRegisteredList = menu?.syncRegisteredList;
|
|
7679
|
+
const registerSearchField = menu?.registerSearchField;
|
|
7680
|
+
const setActiveIndex = menu?.setActiveIndex;
|
|
7681
|
+
react.useLayoutEffect(() => {
|
|
7682
|
+
if (index < 0 || registerSearchField === undefined) {
|
|
7683
|
+
return;
|
|
7684
|
+
}
|
|
7685
|
+
registerSearchField(index);
|
|
7686
|
+
syncRegisteredList?.();
|
|
7687
|
+
return () => {
|
|
7688
|
+
registerSearchField(null);
|
|
7689
|
+
};
|
|
7690
|
+
}, [index, registerSearchField, syncRegisteredList]);
|
|
7691
|
+
// A consumer's ref only arrives when the getter is called, so it is held here and forwarded
|
|
7692
|
+
// through one callback whose identity never changes. Floating UI's `useMergeRefs` memoizes on the
|
|
7693
|
+
// refs themselves, which matters: `useListItem`'s ref changes identity when it learns its index,
|
|
7694
|
+
// and only re-running it writes the input into `elementsRef` at that index.
|
|
7695
|
+
const userRef = react.useRef(undefined);
|
|
7696
|
+
const forwardUserRef = react.useCallback(node => {
|
|
7697
|
+
const target = userRef.current;
|
|
7698
|
+
if (typeof target === "function") {
|
|
7699
|
+
target(node);
|
|
7700
|
+
return;
|
|
7701
|
+
}
|
|
7702
|
+
if (target !== null && target !== undefined) {
|
|
7703
|
+
target.current = node;
|
|
7704
|
+
}
|
|
7705
|
+
}, []);
|
|
7706
|
+
const ref = react$1.useMergeRefs([listItemRef, forwardUserRef]);
|
|
7707
|
+
const getSearchFieldProps = react.useCallback((userProps) => {
|
|
7708
|
+
userRef.current = userProps?.ref;
|
|
7709
|
+
return {
|
|
7710
|
+
ref,
|
|
7711
|
+
onFocus: (event) => {
|
|
7712
|
+
if (index >= 0) {
|
|
7713
|
+
setActiveIndex?.(index);
|
|
7714
|
+
}
|
|
7715
|
+
userProps?.onFocus?.(event);
|
|
7716
|
+
},
|
|
7717
|
+
};
|
|
7718
|
+
}, [index, ref, setActiveIndex]);
|
|
7719
|
+
return react.useMemo(() => ({ getSearchFieldProps }), [getSearchFieldProps]);
|
|
7720
|
+
};
|
|
7721
|
+
|
|
7582
7722
|
/**
|
|
7583
7723
|
* MenuDivider renders a horizontal line to visually separate groups of items within a MenuContent.
|
|
7584
7724
|
*
|
|
@@ -14922,6 +15062,7 @@ exports.useListItemHeight = useListItemHeight;
|
|
|
14922
15062
|
exports.useLocalStorage = useLocalStorage;
|
|
14923
15063
|
exports.useLocalStorageReducer = useLocalStorageReducer;
|
|
14924
15064
|
exports.useMeasure = useMeasure;
|
|
15065
|
+
exports.useMenuSearchField = useMenuSearchField;
|
|
14925
15066
|
exports.useMenuTree = useMenuTree;
|
|
14926
15067
|
exports.useMergeRefs = useMergeRefs;
|
|
14927
15068
|
exports.useModifierKey = useModifierKey;
|
package/index.esm.js
CHANGED
|
@@ -7363,15 +7363,16 @@ const compactFloatingListRefs = (elementsRef, labelsRef) => {
|
|
|
7363
7363
|
labels.length = lastLive + 1;
|
|
7364
7364
|
};
|
|
7365
7365
|
/**
|
|
7366
|
-
* Index of the first registered
|
|
7366
|
+
* Index of the first registered entry that is not `aria-disabled`. Used as the initial tab stop
|
|
7367
7367
|
* when `activeIndex` is still null.
|
|
7368
7368
|
*
|
|
7369
7369
|
* @param elementsRef The FloatingList elements array
|
|
7370
|
-
* @
|
|
7370
|
+
* @param skipIndex Registered index to ignore, used to look past a search field to the first real item
|
|
7371
|
+
* @returns {number} The first enabled index, or `0` when the list has no enabled entry
|
|
7371
7372
|
*/
|
|
7372
|
-
const firstEnabledListIndex = (elementsRef) => {
|
|
7373
|
-
const index = elementsRef.current.findIndex(element => {
|
|
7374
|
-
if (!element) {
|
|
7373
|
+
const firstEnabledListIndex = (elementsRef, skipIndex) => {
|
|
7374
|
+
const index = elementsRef.current.findIndex((element, elementIndex) => {
|
|
7375
|
+
if (!element || elementIndex === skipIndex) {
|
|
7375
7376
|
return false;
|
|
7376
7377
|
}
|
|
7377
7378
|
return element.getAttribute("aria-disabled") !== "true";
|
|
@@ -7384,7 +7385,7 @@ const UNMANAGED_SELECTION = [];
|
|
|
7384
7385
|
* Owns MenuContent's list registration, selection, and typeable-child key handling so the
|
|
7385
7386
|
* component file stays the shell + JSX.
|
|
7386
7387
|
*/
|
|
7387
|
-
const useMenuContentState = ({ children, isMulti, selectedItems: controlledSelectedItems, onSelectionChange, ref, }) => {
|
|
7388
|
+
const useMenuContentState = ({ children, isMulti, autoFocus, selectedItems: controlledSelectedItems, onSelectionChange, ref, }) => {
|
|
7388
7389
|
const selectionManaged = controlledSelectedItems !== undefined || onSelectionChange !== undefined;
|
|
7389
7390
|
const [internalSelectedItems, setInternalSelectedItems] = useState(controlledSelectedItems ?? []);
|
|
7390
7391
|
const selectedItems = selectionManaged ? (controlledSelectedItems ?? internalSelectedItems) : UNMANAGED_SELECTION;
|
|
@@ -7392,6 +7393,10 @@ const useMenuContentState = ({ children, isMulti, selectedItems: controlledSelec
|
|
|
7392
7393
|
const [firstEnabledIndex, setFirstEnabledIndex] = useState(0);
|
|
7393
7394
|
const listRef = useRef([]);
|
|
7394
7395
|
const labelsRef = useRef([]);
|
|
7396
|
+
const searchFieldIndexRef = useRef(null);
|
|
7397
|
+
const registerSearchField = useCallback((index) => {
|
|
7398
|
+
searchFieldIndexRef.current = index;
|
|
7399
|
+
}, []);
|
|
7395
7400
|
const ambientPopover = useOptionalPopoverContext();
|
|
7396
7401
|
// Falls back to a local, always-open floating context when there is no ambient `Popover` (e.g. `MenuContent`
|
|
7397
7402
|
// rendered standalone inside a `Collapse`) -- `useFloating` must still be called unconditionally per the
|
|
@@ -7415,6 +7420,10 @@ const useMenuContentState = ({ children, isMulti, selectedItems: controlledSelec
|
|
|
7415
7420
|
onNavigate: setActiveIndex,
|
|
7416
7421
|
loop: true,
|
|
7417
7422
|
nested: isNested,
|
|
7423
|
+
// Floating UI's own entry focus, which waits for the list to finish registering before it
|
|
7424
|
+
// picks the first enabled index. Off by default: most menus open from a trigger the user
|
|
7425
|
+
// should stay on.
|
|
7426
|
+
focusItemOnOpen: autoFocus,
|
|
7418
7427
|
});
|
|
7419
7428
|
const typeahead = useTypeahead(context, {
|
|
7420
7429
|
listRef: labelsRef,
|
|
@@ -7425,6 +7434,10 @@ const useMenuContentState = ({ children, isMulti, selectedItems: controlledSelec
|
|
|
7425
7434
|
const { getFloatingProps, getItemProps } = useInteractions([listNavigation, typeahead]);
|
|
7426
7435
|
const syncRegisteredList = useCallback(() => {
|
|
7427
7436
|
compactFloatingListRefs(listRef, labelsRef);
|
|
7437
|
+
// Deliberately *not* skipping the search field. `firstEnabledIndex` is the menu's single
|
|
7438
|
+
// roving tab stop, and a registered field should hold it: that is what keeps every row's
|
|
7439
|
+
// `tabIndex` at -1 while the user is typing, so Tab leaves for the menu's chrome instead of
|
|
7440
|
+
// stopping on a row. Only Home (below) looks past the field, because Home addresses items.
|
|
7428
7441
|
const nextFirstEnabled = firstEnabledListIndex(listRef);
|
|
7429
7442
|
setFirstEnabledIndex(current => (current === nextFirstEnabled ? current : nextFirstEnabled));
|
|
7430
7443
|
setActiveIndex(current => {
|
|
@@ -7470,24 +7483,62 @@ const useMenuContentState = ({ children, isMulti, selectedItems: controlledSelec
|
|
|
7470
7483
|
selectedItems,
|
|
7471
7484
|
handleItemClick,
|
|
7472
7485
|
syncRegisteredList,
|
|
7473
|
-
|
|
7486
|
+
setActiveIndex,
|
|
7487
|
+
registerSearchField,
|
|
7488
|
+
}), [
|
|
7489
|
+
activeIndex,
|
|
7490
|
+
firstEnabledIndex,
|
|
7491
|
+
getItemProps,
|
|
7492
|
+
isMulti,
|
|
7493
|
+
selectedItems,
|
|
7494
|
+
handleItemClick,
|
|
7495
|
+
syncRegisteredList,
|
|
7496
|
+
registerSearchField,
|
|
7497
|
+
]);
|
|
7474
7498
|
// Printable characters and Home/End/Left/Right stay native in nested inputs (e.g. FilterBar
|
|
7475
7499
|
// Search). ArrowDown is left alone so a typeable child can enter the registered list.
|
|
7476
7500
|
const handleKeyDownCapture = useCallback((event) => {
|
|
7477
7501
|
if (!isTypeableElement(event.target)) {
|
|
7502
|
+
// Home/End address the *items*. A registered search field is a navigation stop but not an
|
|
7503
|
+
// item, and it renders above them, so Floating UI's Home -- the list's minimum index -- would
|
|
7504
|
+
// land in the input. Redirect to the first item instead, by moving `activeIndex` rather than
|
|
7505
|
+
// calling `focus()`, so list navigation stays the only thing that moves focus. End needs no
|
|
7506
|
+
// counterpart: the field is never the maximum index.
|
|
7507
|
+
//
|
|
7508
|
+
// The containment check is not redundant: a `MenuItem` submenu renders its own `MenuContent`
|
|
7509
|
+
// into a portal, and React still routes that subtree's synthetic events through the React
|
|
7510
|
+
// tree, so this handler sees a submenu's Home first and would claim it before the submenu
|
|
7511
|
+
// ever ran its own.
|
|
7512
|
+
if (event.key === "Home" &&
|
|
7513
|
+
searchFieldIndexRef.current !== null &&
|
|
7514
|
+
event.target instanceof Node &&
|
|
7515
|
+
event.currentTarget.contains(event.target)) {
|
|
7516
|
+
event.preventDefault();
|
|
7517
|
+
event.stopPropagation();
|
|
7518
|
+
const firstItemIndex = firstEnabledListIndex(listRef, searchFieldIndexRef.current);
|
|
7519
|
+
// `firstEnabledListIndex` falls back to 0 when nothing qualifies, which is the field's own
|
|
7520
|
+
// index once a query has filtered every row away. Leave the highlight alone rather than
|
|
7521
|
+
// pulling it into the input.
|
|
7522
|
+
if (firstItemIndex !== searchFieldIndexRef.current) {
|
|
7523
|
+
setActiveIndex(firstItemIndex);
|
|
7524
|
+
}
|
|
7525
|
+
}
|
|
7478
7526
|
return;
|
|
7479
7527
|
}
|
|
7480
7528
|
if (event.key === "ArrowDown") {
|
|
7481
7529
|
return;
|
|
7482
7530
|
}
|
|
7483
|
-
// ArrowUp is
|
|
7484
|
-
//
|
|
7531
|
+
// ArrowUp is swallowed only for an *unregistered* typeable child, where a wrap has no sensible
|
|
7532
|
+
// destination: the input is not in the list, so list navigation would move the menu's roving
|
|
7533
|
+
// tab stop to the last item behind the user's back. A field registered via `useMenuSearchField`
|
|
7534
|
+
// *is* in the list, at the index above the first row, so its ArrowUp is an ordinary wrap to the
|
|
7535
|
+
// last item -- symmetric with the ArrowDown wrap that already lands back in the field.
|
|
7485
7536
|
if (event.key.length === 1 ||
|
|
7486
7537
|
event.key === "Home" ||
|
|
7487
7538
|
event.key === "End" ||
|
|
7488
7539
|
event.key === "ArrowLeft" ||
|
|
7489
7540
|
event.key === "ArrowRight" ||
|
|
7490
|
-
event.key === "ArrowUp") {
|
|
7541
|
+
(event.key === "ArrowUp" && searchFieldIndexRef.current === null)) {
|
|
7491
7542
|
event.stopPropagation();
|
|
7492
7543
|
}
|
|
7493
7544
|
}, []);
|
|
@@ -7567,10 +7618,11 @@ const useMenuContentState = ({ children, isMulti, selectedItems: controlledSelec
|
|
|
7567
7618
|
* @param {MenuContentProps} props - The props for the MenuContent component
|
|
7568
7619
|
* @returns {ReactElement} MenuContent component
|
|
7569
7620
|
*/
|
|
7570
|
-
const MenuContent = ({ "data-testid": dataTestId, className, listClassName, children, isMulti = false, selectedItems, onSelectionChange, style, ref, "aria-label": ariaLabel, ...args }) => {
|
|
7621
|
+
const MenuContent = ({ "data-testid": dataTestId, className, listClassName, children, isMulti = false, autoFocus = false, selectedItems, onSelectionChange, style, ref, "aria-label": ariaLabel, ...args }) => {
|
|
7571
7622
|
const { floatingRef, getFloatingProps, handleKeyDownCapture, listRef, labelsRef, menuContext } = useMenuContentState({
|
|
7572
7623
|
children,
|
|
7573
7624
|
isMulti,
|
|
7625
|
+
autoFocus,
|
|
7574
7626
|
selectedItems,
|
|
7575
7627
|
onSelectionChange,
|
|
7576
7628
|
ref,
|
|
@@ -7578,6 +7630,94 @@ const MenuContent = ({ "data-testid": dataTestId, className, listClassName, chil
|
|
|
7578
7630
|
return (jsx("div", { "aria-label": ariaLabel, className: cvaMenu({ className, limitWidth: true }), "data-testid": dataTestId ? `${dataTestId}-menu-list` : "menu-list", onKeyDownCapture: handleKeyDownCapture, ref: floatingRef, role: "menu", style: style, tabIndex: -1, ...getFloatingProps({ onClick: args.onClick }), children: jsx("div", { className: cvaMenuList({ className: listClassName }), children: jsx(MenuContentContext.Provider, { value: menuContext, children: jsx(FloatingList, { elementsRef: listRef, labelsRef: labelsRef, children: children }) }) }) }));
|
|
7579
7631
|
};
|
|
7580
7632
|
|
|
7633
|
+
/**
|
|
7634
|
+
* Joins a search input to the nearest `MenuContent` as the stop *above* the first item, so Floating
|
|
7635
|
+
* UI's list navigation moves focus in and out of the list on its own: ArrowDown from the field enters
|
|
7636
|
+
* the list, and ArrowUp from the first item comes back to the field. The field is a full member of
|
|
7637
|
+
* the loop, so ArrowUp from it wraps to the last item and ArrowDown from the last item wraps back.
|
|
7638
|
+
*
|
|
7639
|
+
* Focus then has a single owner. Moving between an unregistered field and the list means calling
|
|
7640
|
+
* `.focus()` from a key handler, which races the `requestAnimationFrame` Floating UI asserts item
|
|
7641
|
+
* focus from: the field wins or loses depending on how far apart the two key presses land. That is
|
|
7642
|
+
* a flake rather than a behaviour, and it cannot be fixed from the outside (ADR-0005).
|
|
7643
|
+
*
|
|
7644
|
+
* Registering also keeps the rows off the sequential Tab path while the field holds focus: the menu's
|
|
7645
|
+
* single roving tab stop is the field itself, so Tab continues to the menu's chrome (reset buttons,
|
|
7646
|
+
* footers) instead of stopping on a row.
|
|
7647
|
+
*
|
|
7648
|
+
* Call it from a component rendered *inside* `MenuContent`: registration goes through the
|
|
7649
|
+
* `FloatingList` context that `MenuContent` opens around its children, so a caller that also renders
|
|
7650
|
+
* the `MenuContent` sits outside that context and registers nothing.
|
|
7651
|
+
*
|
|
7652
|
+
* Product code should render `Search` from `@trackunit/react-form-components` here, as
|
|
7653
|
+
* `FiltersMenuSearchField` does -- the native `<input>` below (and in this package's story and specs)
|
|
7654
|
+
* is only there because `@trackunit/react-components` cannot depend on `@trackunit/react-form-components`
|
|
7655
|
+
* without a dependency cycle.
|
|
7656
|
+
*
|
|
7657
|
+
* @returns {UseMenuSearchFieldResult} A getter for the search input's props
|
|
7658
|
+
* @example
|
|
7659
|
+
* ```tsx
|
|
7660
|
+
* const MenuSearch = () => {
|
|
7661
|
+
* const { getSearchFieldProps } = useMenuSearchField();
|
|
7662
|
+
* return <input aria-label="Search" type="text" {...getSearchFieldProps()} />;
|
|
7663
|
+
* };
|
|
7664
|
+
*
|
|
7665
|
+
* const MenuWithSearch = () => (
|
|
7666
|
+
* <MenuContent autoFocus>
|
|
7667
|
+
* <MenuSearch />
|
|
7668
|
+
* <MenuItem id="alpha" label="Alpha" />
|
|
7669
|
+
* </MenuContent>
|
|
7670
|
+
* );
|
|
7671
|
+
* ```
|
|
7672
|
+
*/
|
|
7673
|
+
const useMenuSearchField = () => {
|
|
7674
|
+
const menu = useOptionalMenuContentContext();
|
|
7675
|
+
// `label: null` keeps the field out of typeahead: it is a navigation stop, not a match target.
|
|
7676
|
+
const { ref: listItemRef, index } = useListItem({ label: null });
|
|
7677
|
+
const syncRegisteredList = menu?.syncRegisteredList;
|
|
7678
|
+
const registerSearchField = menu?.registerSearchField;
|
|
7679
|
+
const setActiveIndex = menu?.setActiveIndex;
|
|
7680
|
+
useLayoutEffect(() => {
|
|
7681
|
+
if (index < 0 || registerSearchField === undefined) {
|
|
7682
|
+
return;
|
|
7683
|
+
}
|
|
7684
|
+
registerSearchField(index);
|
|
7685
|
+
syncRegisteredList?.();
|
|
7686
|
+
return () => {
|
|
7687
|
+
registerSearchField(null);
|
|
7688
|
+
};
|
|
7689
|
+
}, [index, registerSearchField, syncRegisteredList]);
|
|
7690
|
+
// A consumer's ref only arrives when the getter is called, so it is held here and forwarded
|
|
7691
|
+
// through one callback whose identity never changes. Floating UI's `useMergeRefs` memoizes on the
|
|
7692
|
+
// refs themselves, which matters: `useListItem`'s ref changes identity when it learns its index,
|
|
7693
|
+
// and only re-running it writes the input into `elementsRef` at that index.
|
|
7694
|
+
const userRef = useRef(undefined);
|
|
7695
|
+
const forwardUserRef = useCallback(node => {
|
|
7696
|
+
const target = userRef.current;
|
|
7697
|
+
if (typeof target === "function") {
|
|
7698
|
+
target(node);
|
|
7699
|
+
return;
|
|
7700
|
+
}
|
|
7701
|
+
if (target !== null && target !== undefined) {
|
|
7702
|
+
target.current = node;
|
|
7703
|
+
}
|
|
7704
|
+
}, []);
|
|
7705
|
+
const ref = useMergeRefs$1([listItemRef, forwardUserRef]);
|
|
7706
|
+
const getSearchFieldProps = useCallback((userProps) => {
|
|
7707
|
+
userRef.current = userProps?.ref;
|
|
7708
|
+
return {
|
|
7709
|
+
ref,
|
|
7710
|
+
onFocus: (event) => {
|
|
7711
|
+
if (index >= 0) {
|
|
7712
|
+
setActiveIndex?.(index);
|
|
7713
|
+
}
|
|
7714
|
+
userProps?.onFocus?.(event);
|
|
7715
|
+
},
|
|
7716
|
+
};
|
|
7717
|
+
}, [index, ref, setActiveIndex]);
|
|
7718
|
+
return useMemo(() => ({ getSearchFieldProps }), [getSearchFieldProps]);
|
|
7719
|
+
};
|
|
7720
|
+
|
|
7581
7721
|
/**
|
|
7582
7722
|
* MenuDivider renders a horizontal line to visually separate groups of items within a MenuContent.
|
|
7583
7723
|
*
|
|
@@ -14756,4 +14896,4 @@ const useWindowActivity = ({ onFocus, onBlur, skip = false } = { onBlur: undefin
|
|
|
14756
14896
|
*/
|
|
14757
14897
|
setupLibraryTranslations();
|
|
14758
14898
|
|
|
14759
|
-
export { Alert, Badge, Breadcrumb, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyButton, CopyableText, DEFAULT_SKELETON_PREFERENCE_CARD_PROPS, DetailsList, EmptyState, EmptyValue, ExternalLink, GridAreas, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, KPICardSkeleton, KPISkeleton, LabeledValue, LabeledValueList, List, ListItem, MAX_HASH_LENGTH, MAX_URL_LENGTH, MenuContent, MenuDivider, MenuItem, MenuTree, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, PreferenceCard, PreferenceCardSkeleton, Prompt, ROLE_CARD, SHEET_TRANSITION_DURATION, SHEET_TRANSITION_DURATION_MS, SHEET_TRANSITION_EASING, SectionHeader, SegmentedValueBar, Sheet, Sidebar, SidebarContentLayout, SkeletonBlock, SkeletonLabel, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, TrendIndicator, TrendIndicators, ValueBar, ZStack, createGrid, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaContentContainer, cvaContentWrapper, cvaDescriptionCard, cvaIconBackground, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInputContainer, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenu, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaMenuList, cvaMenuListDivider, cvaMenuListItem, cvaMenuListMultiSelect, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaPreferenceCard, cvaTitleCard, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, preferenceCardGrid, useBidirectionalScroll, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useCopyToClipboard, useCursorUrlSync, useCustomEncoding, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGridAreas, useHashParamSync, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useKeyboardShortcut, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, useMenuTree, useMergeRefs, useModifierKey, useOptionalPopoverContext, useOverflowBorder, useOverflowItems, usePersistedState, usePopoverContext, usePrevious, usePrompt, useRandomCSSLengths, useRelayPagination, useResize, useScrollBlock, useScrollDetection, useSearchParamSync, useSelfUpdatingRef, useSessionStorage, useSessionStorageReducer, useSheet, useSheetSnap, useStorageKey, useTextSearch, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
|
|
14899
|
+
export { Alert, Badge, Breadcrumb, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyButton, CopyableText, DEFAULT_SKELETON_PREFERENCE_CARD_PROPS, DetailsList, EmptyState, EmptyValue, ExternalLink, GridAreas, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, KPICardSkeleton, KPISkeleton, LabeledValue, LabeledValueList, List, ListItem, MAX_HASH_LENGTH, MAX_URL_LENGTH, MenuContent, MenuDivider, MenuItem, MenuTree, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, PreferenceCard, PreferenceCardSkeleton, Prompt, ROLE_CARD, SHEET_TRANSITION_DURATION, SHEET_TRANSITION_DURATION_MS, SHEET_TRANSITION_EASING, SectionHeader, SegmentedValueBar, Sheet, Sidebar, SidebarContentLayout, SkeletonBlock, SkeletonLabel, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, TrendIndicator, TrendIndicators, ValueBar, ZStack, createGrid, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaContentContainer, cvaContentWrapper, cvaDescriptionCard, cvaIconBackground, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInputContainer, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenu, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaMenuList, cvaMenuListDivider, cvaMenuListItem, cvaMenuListMultiSelect, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaPreferenceCard, cvaTitleCard, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, preferenceCardGrid, useBidirectionalScroll, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useCopyToClipboard, useCursorUrlSync, useCustomEncoding, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGridAreas, useHashParamSync, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useKeyboardShortcut, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, useMenuSearchField, useMenuTree, useMergeRefs, useModifierKey, useOptionalPopoverContext, useOverflowBorder, useOverflowItems, usePersistedState, usePopoverContext, usePrevious, usePrompt, useRandomCSSLengths, useRelayPagination, useResize, useScrollBlock, useScrollDetection, useSearchParamSync, useSelfUpdatingRef, useSessionStorage, useSessionStorageReducer, useSheet, useSheetSnap, useStorageKey, useTextSearch, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-components",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.7",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"migrations": "./migrations.json",
|
|
@@ -14,16 +14,16 @@
|
|
|
14
14
|
"@floating-ui/react": "^0.26.25",
|
|
15
15
|
"string-ts": "^2.0.0",
|
|
16
16
|
"tailwind-merge": "^2.0.0",
|
|
17
|
-
"@trackunit/ui-design-tokens": "1.15.
|
|
18
|
-
"@trackunit/css-class-variance-utilities": "1.14.
|
|
19
|
-
"@trackunit/shared-utils": "1.16.
|
|
20
|
-
"@trackunit/ui-icons": "1.14.
|
|
17
|
+
"@trackunit/ui-design-tokens": "1.15.18",
|
|
18
|
+
"@trackunit/css-class-variance-utilities": "1.14.28",
|
|
19
|
+
"@trackunit/shared-utils": "1.16.31",
|
|
20
|
+
"@trackunit/ui-icons": "1.14.27",
|
|
21
21
|
"es-toolkit": "^1.39.10",
|
|
22
22
|
"@tanstack/react-virtual": "^3.14.3",
|
|
23
23
|
"dequal": "^2.0.3",
|
|
24
24
|
"fflate": "^0.8.2",
|
|
25
25
|
"zod": "^3.25.76",
|
|
26
|
-
"@trackunit/i18n-library-translation": "2.4.
|
|
26
|
+
"@trackunit/i18n-library-translation": "2.4.39"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"react": "^19.0.0",
|
|
@@ -29,6 +29,19 @@ export interface MenuContentProps extends CommonProps, Styleable, Refable<HTMLDi
|
|
|
29
29
|
* Callback triggered when selected items change.
|
|
30
30
|
*/
|
|
31
31
|
onSelectionChange?: (selected: Array<string>) => void;
|
|
32
|
+
/**
|
|
33
|
+
* Move focus into the menu as soon as it opens, rather than leaving it on whatever opened it.
|
|
34
|
+
*
|
|
35
|
+
* The menu decides where "into the menu" is: a search field registered with `useMenuSearchField`
|
|
36
|
+
* if there is one, otherwise the first enabled item. Chrome that merely happens to come first in
|
|
37
|
+
* the DOM -- a reset button, a header link -- is never the entry point.
|
|
38
|
+
*
|
|
39
|
+
* Use it for menus the keyboard should land inside immediately (a filter panel the user opened to
|
|
40
|
+
* type in). Leave it off for action menus opened from a trigger the user should stay on.
|
|
41
|
+
*
|
|
42
|
+
* @default false
|
|
43
|
+
*/
|
|
44
|
+
autoFocus?: boolean;
|
|
32
45
|
/**
|
|
33
46
|
* Accessible name for the `role="menu"` node. Prefer this (or `aria-labelledby` via the trigger)
|
|
34
47
|
* when the menu is not named by surrounding chrome.
|
|
@@ -101,4 +114,4 @@ export interface MenuContentProps extends CommonProps, Styleable, Refable<HTMLDi
|
|
|
101
114
|
* @param {MenuContentProps} props - The props for the MenuContent component
|
|
102
115
|
* @returns {ReactElement} MenuContent component
|
|
103
116
|
*/
|
|
104
|
-
export declare const MenuContent: ({ "data-testid": dataTestId, className, listClassName, children, isMulti, selectedItems, onSelectionChange, style, ref, "aria-label": ariaLabel, ...args }: MenuContentProps) => ReactElement;
|
|
117
|
+
export declare const MenuContent: ({ "data-testid": dataTestId, className, listClassName, children, isMulti, autoFocus, selectedItems, onSelectionChange, style, ref, "aria-label": ariaLabel, ...args }: MenuContentProps) => ReactElement;
|
|
@@ -11,6 +11,17 @@ export interface MenuContentContextValue {
|
|
|
11
11
|
selectedItems: Array<string>;
|
|
12
12
|
handleItemClick: (id: string) => void;
|
|
13
13
|
syncRegisteredList: () => void;
|
|
14
|
+
/**
|
|
15
|
+
* Moves the menu's highlight to a registered index. Rows get this through `getItemProps`;
|
|
16
|
+
* a registered search field (`useMenuSearchField`) needs it directly, because it must not
|
|
17
|
+
* take Floating UI's hover props -- those pull focus out of the input on pointer leave.
|
|
18
|
+
*/
|
|
19
|
+
setActiveIndex: (index: number | null) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Reports the registered index of the menu's search field, or `null` when it unregisters. The
|
|
22
|
+
* field is a navigation stop but not an item, which is the difference Home/End have to respect.
|
|
23
|
+
*/
|
|
24
|
+
registerSearchField: (index: number | null) => void;
|
|
14
25
|
}
|
|
15
26
|
declare const MenuContentContext: import("react").Context<MenuContentContextValue | null>;
|
|
16
27
|
/**
|
|
@@ -9,10 +9,11 @@ import { MutableRefObject } from "react";
|
|
|
9
9
|
*/
|
|
10
10
|
export declare const compactFloatingListRefs: (elementsRef: MutableRefObject<Array<HTMLElement | null>>, labelsRef: MutableRefObject<Array<string | null>>) => void;
|
|
11
11
|
/**
|
|
12
|
-
* Index of the first registered
|
|
12
|
+
* Index of the first registered entry that is not `aria-disabled`. Used as the initial tab stop
|
|
13
13
|
* when `activeIndex` is still null.
|
|
14
14
|
*
|
|
15
15
|
* @param elementsRef The FloatingList elements array
|
|
16
|
-
* @
|
|
16
|
+
* @param skipIndex Registered index to ignore, used to look past a search field to the first real item
|
|
17
|
+
* @returns {number} The first enabled index, or `0` when the list has no enabled entry
|
|
17
18
|
*/
|
|
18
|
-
export declare const firstEnabledListIndex: (elementsRef: MutableRefObject<Array<HTMLElement | null
|
|
19
|
+
export declare const firstEnabledListIndex: (elementsRef: MutableRefObject<Array<HTMLElement | null>>, skipIndex?: number | null) => number;
|
|
@@ -4,6 +4,7 @@ import { MenuContentContextValue } from "./MenuContentContext";
|
|
|
4
4
|
export interface UseMenuContentStateProps {
|
|
5
5
|
children: ReactNode;
|
|
6
6
|
isMulti: boolean;
|
|
7
|
+
autoFocus: boolean;
|
|
7
8
|
selectedItems?: Array<string>;
|
|
8
9
|
onSelectionChange?: (selected: Array<string>) => void;
|
|
9
10
|
ref?: Ref<HTMLDivElement>;
|
|
@@ -20,4 +21,4 @@ export interface UseMenuContentStateResult {
|
|
|
20
21
|
* Owns MenuContent's list registration, selection, and typeable-child key handling so the
|
|
21
22
|
* component file stays the shell + JSX.
|
|
22
23
|
*/
|
|
23
|
-
export declare const useMenuContentState: ({ children, isMulti, selectedItems: controlledSelectedItems, onSelectionChange, ref, }: UseMenuContentStateProps) => UseMenuContentStateResult;
|
|
24
|
+
export declare const useMenuContentState: ({ children, isMulti, autoFocus, selectedItems: controlledSelectedItems, onSelectionChange, ref, }: UseMenuContentStateProps) => UseMenuContentStateResult;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { FocusEventHandler, Ref, RefCallback } from "react";
|
|
2
|
+
export interface MenuSearchFieldUserProps {
|
|
3
|
+
/** The consumer's own ref, kept alongside the one that registers the input. */
|
|
4
|
+
ref?: Ref<HTMLElement>;
|
|
5
|
+
/** The consumer's own focus handler, called after the menu has moved its highlight. */
|
|
6
|
+
onFocus?: FocusEventHandler<HTMLElement>;
|
|
7
|
+
}
|
|
8
|
+
export interface MenuSearchFieldProps {
|
|
9
|
+
/** Puts the input in its place in the menu's keyboard order. */
|
|
10
|
+
ref: RefCallback<HTMLElement> | null;
|
|
11
|
+
/** Moves the menu's highlight off the items when the input is clicked or tabbed into. */
|
|
12
|
+
onFocus: FocusEventHandler<HTMLElement>;
|
|
13
|
+
}
|
|
14
|
+
export interface UseMenuSearchFieldResult {
|
|
15
|
+
/**
|
|
16
|
+
* Props for the search input. Pass your own `ref` / `onFocus` in and they are merged rather
|
|
17
|
+
* than replaced, so spread order cannot silently take the menu's registration away.
|
|
18
|
+
*/
|
|
19
|
+
getSearchFieldProps: (userProps?: MenuSearchFieldUserProps) => MenuSearchFieldProps;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Joins a search input to the nearest `MenuContent` as the stop *above* the first item, so Floating
|
|
23
|
+
* UI's list navigation moves focus in and out of the list on its own: ArrowDown from the field enters
|
|
24
|
+
* the list, and ArrowUp from the first item comes back to the field. The field is a full member of
|
|
25
|
+
* the loop, so ArrowUp from it wraps to the last item and ArrowDown from the last item wraps back.
|
|
26
|
+
*
|
|
27
|
+
* Focus then has a single owner. Moving between an unregistered field and the list means calling
|
|
28
|
+
* `.focus()` from a key handler, which races the `requestAnimationFrame` Floating UI asserts item
|
|
29
|
+
* focus from: the field wins or loses depending on how far apart the two key presses land. That is
|
|
30
|
+
* a flake rather than a behaviour, and it cannot be fixed from the outside (ADR-0005).
|
|
31
|
+
*
|
|
32
|
+
* Registering also keeps the rows off the sequential Tab path while the field holds focus: the menu's
|
|
33
|
+
* single roving tab stop is the field itself, so Tab continues to the menu's chrome (reset buttons,
|
|
34
|
+
* footers) instead of stopping on a row.
|
|
35
|
+
*
|
|
36
|
+
* Call it from a component rendered *inside* `MenuContent`: registration goes through the
|
|
37
|
+
* `FloatingList` context that `MenuContent` opens around its children, so a caller that also renders
|
|
38
|
+
* the `MenuContent` sits outside that context and registers nothing.
|
|
39
|
+
*
|
|
40
|
+
* Product code should render `Search` from `@trackunit/react-form-components` here, as
|
|
41
|
+
* `FiltersMenuSearchField` does -- the native `<input>` below (and in this package's story and specs)
|
|
42
|
+
* is only there because `@trackunit/react-components` cannot depend on `@trackunit/react-form-components`
|
|
43
|
+
* without a dependency cycle.
|
|
44
|
+
*
|
|
45
|
+
* @returns {UseMenuSearchFieldResult} A getter for the search input's props
|
|
46
|
+
* @example
|
|
47
|
+
* ```tsx
|
|
48
|
+
* const MenuSearch = () => {
|
|
49
|
+
* const { getSearchFieldProps } = useMenuSearchField();
|
|
50
|
+
* return <input aria-label="Search" type="text" {...getSearchFieldProps()} />;
|
|
51
|
+
* };
|
|
52
|
+
*
|
|
53
|
+
* const MenuWithSearch = () => (
|
|
54
|
+
* <MenuContent autoFocus>
|
|
55
|
+
* <MenuSearch />
|
|
56
|
+
* <MenuItem id="alpha" label="Alpha" />
|
|
57
|
+
* </MenuContent>
|
|
58
|
+
* );
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare const useMenuSearchField: () => UseMenuSearchFieldResult;
|
package/src/index.d.ts
CHANGED
|
@@ -55,6 +55,7 @@ export * from "./components/ListItem/ListItem";
|
|
|
55
55
|
export * from "./components/ListItem/useListItemHeight";
|
|
56
56
|
export * from "./components/Menu/MenuContent/MenuContent";
|
|
57
57
|
export * from "./components/Menu/MenuContent/MenuContent.variants";
|
|
58
|
+
export * from "./components/Menu/MenuContent/useMenuSearchField";
|
|
58
59
|
export * from "./components/Menu/MenuDivider/MenuDivider";
|
|
59
60
|
export * from "./components/Menu/MenuItem/MenuItem";
|
|
60
61
|
export * from "./components/Menu/MenuItem/MenuItem.variants";
|
package/migrations/entry.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../../../../../libs/react/components/migrations/entry.ts"],"names":[],"mappings":"","sourcesContent":["// Migration entry point for @nx/js:tsc build target.\n// Migrations are registered in ../migrations.json and resolved by NX at runtime.\nexport {};\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"jsx-utils.js","sourceRoot":"","sources":["../../../../../../libs/react/components/migrations/utils/jsx-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { logger, type Tree, visitNotIgnoredFiles } from \"@nx/devkit\";\nimport * as ts from \"typescript\";\n\nconst TSX_EXTENSIONS = [\".tsx\", \".jsx\"] as const;\nconst TS_EXTENSIONS = [\".ts\", \".tsx\", \".jsx\"] as const;\n\nconst isTsxFile = (filePath: string): boolean => TSX_EXTENSIONS.some(ext => filePath.endsWith(ext));\nconst isTsFile = (filePath: string): boolean => TS_EXTENSIONS.some(ext => filePath.endsWith(ext));\n\nconst isUnderNodeModules = (filePath: string): boolean =>\n filePath.includes(\"/node_modules/\") || filePath.startsWith(\"node_modules/\");\n\nconst isUnderDist = (filePath: string): boolean =>\n filePath.includes(\"/dist/\") || filePath.startsWith(\"dist/\") || filePath.includes(\"/.nx/\");\n\n/**\n * Visits every `.tsx`/`.jsx` file under the workspace root and invokes the\n * callback with the file path and its current contents. Files that don't\n * include the marker substring are skipped, which makes large workspaces\n * cheap to scan.\n */\nexport const visitTsxFiles = (\n tree: Tree,\n marker: string,\n callback: (filePath: string, content: string) => string | null | undefined\n): number => {\n let touched = 0;\n visitNotIgnoredFiles(tree, \"/\", filePath => {\n if (!isTsxFile(filePath)) return;\n if (isUnderNodeModules(filePath) || isUnderDist(filePath)) return;\n const content = tree.read(filePath, \"utf-8\");\n if (content === null) return;\n if (!content.includes(marker)) return;\n const updated = callback(filePath, content);\n if (updated !== null && updated !== undefined && updated !== content) {\n tree.write(filePath, updated);\n touched += 1;\n }\n });\n return touched;\n};\n\n/**\n * Same as {@link visitTsxFiles} but also includes plain `.ts` files. Use this\n * when the codemod also rewrites non-JSX files such as helpers or hooks.\n */\nexport const visitTsFiles = (\n tree: Tree,\n marker: string,\n callback: (filePath: string, content: string) => string | null | undefined\n): number => {\n let touched = 0;\n visitNotIgnoredFiles(tree, \"/\", filePath => {\n if (!isTsFile(filePath)) return;\n if (isUnderNodeModules(filePath) || isUnderDist(filePath)) return;\n const content = tree.read(filePath, \"utf-8\");\n if (content === null) return;\n if (!content.includes(marker)) return;\n const updated = callback(filePath, content);\n if (updated !== null && updated !== undefined && updated !== content) {\n tree.write(filePath, updated);\n touched += 1;\n }\n });\n return touched;\n};\n\n/**\n * Mapping of imported component aliases to their original names for a given\n * package. For `import { IconButton as Btn } from \"@trackunit/react-components\"`\n * this returns `{ Btn: \"IconButton\" }`.\n *\n * Returns `null` when the file imports nothing from the package, which lets\n * callers short-circuit before parsing JSX.\n */\nexport const getImportedAliases = (sourceFile: ts.SourceFile, packageName: string): Record<string, string> | null => {\n const result: Record<string, string> = {};\n let found = false;\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const moduleSpecifier = stmt.moduleSpecifier;\n if (!ts.isStringLiteral(moduleSpecifier)) continue;\n if (moduleSpecifier.text !== packageName) continue;\n\n const namedBindings = stmt.importClause?.namedBindings;\n if (namedBindings === undefined || !ts.isNamedImports(namedBindings)) continue;\n\n for (const element of namedBindings.elements) {\n const localName = element.name.text;\n const importedName = element.propertyName?.text ?? localName;\n result[localName] = importedName;\n found = true;\n }\n }\n\n return found ? result : null;\n};\n\n/**\n * Returns the local alias used in this file for `originalName` when imported\n * from `packageName`, or `null` if the component is not imported.\n */\nexport const getLocalAliasFor = (\n sourceFile: ts.SourceFile,\n packageName: string,\n originalName: string\n): string | null => {\n const aliases = getImportedAliases(sourceFile, packageName);\n if (aliases === null) return null;\n for (const [local, original] of Object.entries(aliases)) {\n if (original === originalName) return local;\n }\n return null;\n};\n\nexport type JsxElementMatch = {\n /** The opening JSX element (the one carrying the attributes). */\n openingElement: ts.JsxOpeningElement | ts.JsxSelfClosingElement;\n /** The full element including children, or the self-closing element. */\n element: ts.JsxElement | ts.JsxSelfClosingElement;\n};\n\n/**\n * Finds every JSX element whose tag name resolves to one of `tagNames`\n * (typically the local aliases returned by {@link getImportedAliases}).\n *\n * The result intentionally includes both opening and full elements so callers\n * can manipulate attributes (via `openingElement`) or wrap/replace the whole\n * element (via `element`).\n */\nexport const findJsxElements = (\n sourceFile: ts.SourceFile,\n tagNames: ReadonlyArray<string>\n): ReadonlyArray<JsxElementMatch> => {\n const matches: Array<JsxElementMatch> = [];\n const tagSet = new Set(tagNames);\n\n const visit = (node: ts.Node): void => {\n if (ts.isJsxSelfClosingElement(node)) {\n if (ts.isIdentifier(node.tagName) && tagSet.has(node.tagName.text)) {\n matches.push({ openingElement: node, element: node });\n }\n } else if (ts.isJsxElement(node)) {\n const opening = node.openingElement;\n if (ts.isIdentifier(opening.tagName) && tagSet.has(opening.tagName.text)) {\n matches.push({ openingElement: opening, element: node });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n return matches;\n};\n\n/**\n * Looks up a named JSX attribute on the opening element. Returns `null` when\n * the attribute is missing or part of a spread attribute (which we cannot\n * statically inspect).\n */\nexport const findJsxAttribute = (\n openingElement: ts.JsxOpeningElement | ts.JsxSelfClosingElement,\n attributeName: string\n): ts.JsxAttribute | null => {\n for (const attr of openingElement.attributes.properties) {\n if (!ts.isJsxAttribute(attr)) continue;\n if (!ts.isIdentifier(attr.name)) continue;\n if (attr.name.text === attributeName) return attr;\n }\n return null;\n};\n\n/**\n * `true` when the element forwards a spread expression such as `{...rest}`,\n * which means we can't be sure which attributes are actually set.\n */\nexport const hasSpreadAttribute = (openingElement: ts.JsxOpeningElement | ts.JsxSelfClosingElement): boolean => {\n return openingElement.attributes.properties.some(prop => ts.isJsxSpreadAttribute(prop));\n};\n\n/**\n * Parses the file as TSX so JSX is recognised. We do not need a full program\n * here; tsquery's `ast` helper produces a script-kind source file which loses\n * JSX context, so we go through `createSourceFile` directly.\n */\nexport const parseTsx = (content: string, fileName = \"file.tsx\"): ts.SourceFile =>\n ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);\n\n/**\n * Helper for codemods to log a one-line summary at the end of a run. Migration\n * runners aggregate logs per migration, so this keeps output focused.\n */\nexport const logSummary = (migrationName: string, touched: number, manualReviewNeeded?: number): void => {\n if (touched === 0) {\n logger.info(` ${migrationName}: no files changed`);\n return;\n }\n const reviewSuffix =\n manualReviewNeeded !== undefined && manualReviewNeeded > 0\n ? ` (${manualReviewNeeded} location(s) need manual review)`\n : \"\";\n logger.info(` ${migrationName}: updated ${touched} file(s)${reviewSuffix}`);\n};\n\n/**\n * Inserts an attribute string immediately after the tag name (so the new\n * attribute appears first). Returns the resulting source content.\n *\n * The added attribute is rendered verbatim, so callers are responsible for\n * including the leading space (e.g. ` title=\"Close\"`).\n */\nexport const insertAttributeIntoOpeningTag = (\n content: string,\n openingElement: ts.JsxOpeningElement | ts.JsxSelfClosingElement,\n attributeText: string\n): string => {\n const tagName = openingElement.tagName;\n const insertPos = tagName.getEnd();\n return `${content.slice(0, insertPos)} ${attributeText}${content.slice(insertPos)}`;\n};\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"breadcrumb-remove-deprecated-props.js","sourceRoot":"","sources":["../../../../../../libs/react/components/migrations/v2-0-0/breadcrumb-remove-deprecated-props.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Tree, logger } from \"@nx/devkit\";\nimport {\n findJsxAttribute,\n findJsxElements,\n getImportedAliases,\n logSummary,\n parseTsx,\n visitTsxFiles,\n} from \"../utils/jsx-utils\";\n\nconst PACKAGE_NAME = \"@trackunit/react-components\";\nconst COMPONENT_NAMES = [\"Breadcrumb\", \"BreadcrumbContainer\", \"BreadcrumbForMediumScreen\"] as const;\nconst PROPS_TO_REMOVE = [\"backButtonTitle\", \"expandCollapsedItemsTitle\"] as const;\n\nconst stripDeprecatedProps = (filePath: string, content: string): string | null => {\n const sourceFile = parseTsx(content, filePath);\n\n const aliases = getImportedAliases(sourceFile, PACKAGE_NAME);\n if (aliases === null) return null;\n\n const localTagNames: Array<string> = [];\n for (const [local, original] of Object.entries(aliases)) {\n if (COMPONENT_NAMES.some(name => name === original)) {\n localTagNames.push(local);\n }\n }\n if (localTagNames.length === 0) return null;\n\n const matches = findJsxElements(sourceFile, localTagNames);\n if (matches.length === 0) return null;\n\n // Collect every (start, end) range we want to delete. We process them in\n // reverse so earlier offsets stay valid while we splice.\n const ranges: Array<{ start: number; end: number }> = [];\n for (const { openingElement } of matches) {\n for (const propName of PROPS_TO_REMOVE) {\n const attr = findJsxAttribute(openingElement, propName);\n if (attr === null) continue;\n\n // Drop trailing whitespace + the attribute itself so we don't leave a\n // dangling space inside the opening tag. e.g. `<X foo=\"a\" bar=\"b\" />`\n // becomes `<X foo=\"a\" />` and `<X bar=\"b\" />` becomes `<X />`.\n let start = attr.getFullStart();\n const end = attr.getEnd();\n // getFullStart includes leading whitespace which is what we want.\n // Guard against negative ranges if multiple props collapse later.\n if (start < 0) start = attr.getStart();\n ranges.push({ start, end });\n }\n }\n\n if (ranges.length === 0) return null;\n\n ranges.sort((a, b) => b.start - a.start);\n\n let updated = content;\n for (const { start, end } of ranges) {\n updated = updated.slice(0, start) + updated.slice(end);\n }\n\n return updated;\n};\n\n/**\n * Removes the no-longer-supported `backButtonTitle` and\n * `expandCollapsedItemsTitle` props from `<Breadcrumb>`,\n * `<BreadcrumbContainer>`, and `<BreadcrumbForMediumScreen>`. Their accessible\n * names are now sourced from the `react-components` library translations.\n */\nexport const breadcrumbRemoveDeprecatedProps = (tree: Tree): void => {\n const touched = visitTsxFiles(tree, \"Breadcrumb\", stripDeprecatedProps);\n logSummary(\"breadcrumb-remove-deprecated-props\", touched);\n logger.info(\n \" Ensure the host application registers the @trackunit/react-components library translations so the back/expand buttons render localized labels.\"\n );\n};\n\nexport default breadcrumbRemoveDeprecatedProps;\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cardheader-onclickclose-to-actions.js","sourceRoot":"","sources":["../../../../../../libs/react/components/migrations/v2-0-0/cardheader-onclickclose-to-actions.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Tree } from \"@nx/devkit\";\nimport * as ts from \"typescript\";\nimport {\n findJsxAttribute,\n findJsxElements,\n getImportedAliases,\n logSummary,\n parseTsx,\n visitTsxFiles,\n} from \"../utils/jsx-utils\";\n\nconst PACKAGE_NAME = \"@trackunit/react-components\";\nconst COMPONENT_NAME = \"CardHeader\";\n\nconst CLOSE_BUTTON_NAMES = [\"IconButton\", \"Icon\"] as const;\n\nconst renderCloseButton = (handlerExpr: string, iconAlias: string, iconButtonAlias: string): string =>\n `<${iconButtonAlias} icon={<${iconAlias} name=\"XMark\" size=\"small\" />} onClick={${handlerExpr}} title=\"Close\" variant=\"ghost-neutral\" />`;\n\nconst ensureNamedImport = (content: string, packageName: string, namesToAdd: ReadonlyArray<string>): string => {\n const sourceFile = parseTsx(content, \"source.tsx\");\n const aliases = getImportedAliases(sourceFile, packageName) ?? {};\n\n const missing = namesToAdd.filter(name => !Object.values(aliases).includes(name));\n if (missing.length === 0) return content;\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const moduleSpecifier = stmt.moduleSpecifier;\n if (!ts.isStringLiteral(moduleSpecifier) || moduleSpecifier.text !== packageName) continue;\n\n const namedBindings = stmt.importClause?.namedBindings;\n if (namedBindings === undefined || !ts.isNamedImports(namedBindings)) continue;\n\n const elementsText = namedBindings.elements.map(el => el.getText()).join(\", \");\n const merged = [elementsText, ...missing].join(\", \");\n const before = content.slice(0, namedBindings.getStart());\n const after = content.slice(namedBindings.getEnd());\n return `${before}{ ${merged} }${after}`;\n }\n\n // No existing import from the package; add a fresh line near the top.\n const importLine = `import { ${missing.join(\", \")} } from \"${packageName}\";\\n`;\n return importLine + content;\n};\n\nconst transformCardHeaderUsage = (filePath: string, content: string): string | null => {\n const sourceFile = parseTsx(content, filePath);\n\n const aliases = getImportedAliases(sourceFile, PACKAGE_NAME);\n if (aliases === null) return null;\n\n const cardHeaderAlias = Object.entries(aliases).find(([, name]) => name === COMPONENT_NAME)?.[0];\n if (cardHeaderAlias === undefined) return null;\n\n const matches = findJsxElements(sourceFile, [cardHeaderAlias]);\n if (matches.length === 0) return null;\n\n type Edit = { start: number; end: number; replacement: string };\n const edits: Array<Edit> = [];\n\n for (const { openingElement } of matches) {\n const onClickCloseAttr = findJsxAttribute(openingElement, \"onClickClose\");\n if (onClickCloseAttr === null) continue;\n\n const initializer = onClickCloseAttr.initializer;\n if (initializer === undefined) continue;\n\n let handlerExpr: string;\n if (ts.isJsxExpression(initializer) && initializer.expression !== undefined) {\n handlerExpr = initializer.expression.getText();\n } else if (ts.isStringLiteral(initializer)) {\n handlerExpr = `\"${initializer.text}\"`;\n } else {\n continue;\n }\n\n const actionsAttr = findJsxAttribute(openingElement, \"actions\");\n if (actionsAttr !== null) {\n // Don't try to merge with an existing actions prop; flag and skip.\n continue;\n }\n\n const replacement = `actions={${renderCloseButton(handlerExpr, \"Icon\", \"IconButton\")}}`;\n edits.push({\n start: onClickCloseAttr.getStart(),\n end: onClickCloseAttr.getEnd(),\n replacement,\n });\n }\n\n if (edits.length === 0) return null;\n\n edits.sort((a, b) => b.start - a.start);\n\n let updated = content;\n for (const { start, end, replacement } of edits) {\n updated = updated.slice(0, start) + replacement + updated.slice(end);\n }\n\n updated = ensureNamedImport(updated, PACKAGE_NAME, [...CLOSE_BUTTON_NAMES]);\n\n return updated;\n};\n\n/**\n * Replaces the removed `onClickClose` prop on `<CardHeader>` with the\n * equivalent action rendered through the `actions` slot using `IconButton` +\n * the `XMark` icon.\n *\n * Cards that already declared `actions` are skipped to avoid clobbering\n * existing markup.\n */\nexport const cardHeaderOnClickCloseToActions = (tree: Tree): void => {\n const touched = visitTsxFiles(tree, \"CardHeader\", transformCardHeaderUsage);\n logSummary(\"cardheader-onclickclose-to-actions\", touched);\n};\n\nexport default cardHeaderOnClickCloseToActions;\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"iconbutton-add-required-name.js","sourceRoot":"","sources":["../../../../../../libs/react/components/migrations/v2-0-0/iconbutton-add-required-name.ts"],"names":[],"mappings":";;;AAAA,uCAA+C;AAC/C,kDAQ4B;AAE5B,MAAM,YAAY,GAAG,6BAA6B,CAAC;AACnD,MAAM,cAAc,GAAG,YAAY,CAAC;AAEpC,MAAM,iBAAiB,GAAG,0CAA0C,CAAC;AAErE,MAAM,wBAAwB,GAAG,CAAC,QAAgB,EAAE,OAAe,EAAiB,EAAE;IACpF,MAAM,UAAU,GAAG,IAAA,oBAAQ,EAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAE/C,MAAM,OAAO,GAAG,IAAA,8BAAkB,EAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAC7D,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAElC,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,KAAK,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5F,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAE1C,MAAM,OAAO,GAAG,IAAA,2BAAe,EAAC,UAAU,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAC1D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAGtC,MAAM,KAAK,GAAgB,EAAE,CAAC;IAE9B,KAAK,MAAM,EAAE,cAAc,EAAE,IAAI,OAAO,EAAE,CAAC;QACzC,IAAI,IAAA,8BAAkB,EAAC,cAAc,CAAC;YAAE,SAAS;QAEjD,MAAM,QAAQ,GAAG,IAAA,4BAAgB,EAAC,cAAc,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QACpE,MAAM,YAAY,GAAG,IAAA,4BAAgB,EAAC,cAAc,EAAE,WAAW,CAAC,KAAK,IAAI,CAAC;QAC5E,IAAI,QAAQ,IAAI,YAAY;YAAE,SAAS;QAEvC,KAAK,CAAC,IAAI,CAAC;YACT,MAAM,EAAE,cAAc,CAAC,OAAO,CAAC,MAAM,EAAE;YACvC,IAAI,EAAE,WAAW,iBAAiB,GAAG;SACtC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAExE,IAAI,OAAO,GAAG,OAAO,CAAC;IACtB,KAAK,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,EAAE,CAAC;QACrC,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AAEF;;;;;;;GAOG;AACI,MAAM,yBAAyB,GAAG,CAAC,IAAU,EAAQ,EAAE;IAC5D,MAAM,OAAO,GAAG,IAAA,yBAAa,EAAC,IAAI,EAAE,YAAY,EAAE,wBAAwB,CAAC,CAAC;IAC5E,IAAA,sBAAU,EAAC,8BAA8B,EAAE,OAAO,CAAC,CAAC;IACpD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,eAAM,CAAC,IAAI,CACT,8BAA8B,iBAAiB,6EAA6E,CAC7H,CAAC;IACJ,CAAC;AACH,CAAC,CAAC;AARW,QAAA,yBAAyB,6BAQpC;AAEF,kBAAe,iCAAyB,CAAC","sourcesContent":["import { type Tree, logger } from \"@nx/devkit\";\nimport {\n findJsxAttribute,\n findJsxElements,\n getImportedAliases,\n hasSpreadAttribute,\n logSummary,\n parseTsx,\n visitTsxFiles,\n} from \"../utils/jsx-utils\";\n\nconst PACKAGE_NAME = \"@trackunit/react-components\";\nconst COMPONENT_NAME = \"IconButton\";\n\nconst PLACEHOLDER_TITLE = \"TODO Replace with localized button title\";\n\nconst transformIconButtonUsage = (filePath: string, content: string): string | null => {\n const sourceFile = parseTsx(content, filePath);\n\n const aliases = getImportedAliases(sourceFile, PACKAGE_NAME);\n if (aliases === null) return null;\n\n const localAlias = Object.entries(aliases).find(([, name]) => name === COMPONENT_NAME)?.[0];\n if (localAlias === undefined) return null;\n\n const matches = findJsxElements(sourceFile, [localAlias]);\n if (matches.length === 0) return null;\n\n type Edit = { offset: number; text: string };\n const edits: Array<Edit> = [];\n\n for (const { openingElement } of matches) {\n if (hasSpreadAttribute(openingElement)) continue;\n\n const hasTitle = findJsxAttribute(openingElement, \"title\") !== null;\n const hasAriaLabel = findJsxAttribute(openingElement, \"ariaLabel\") !== null;\n if (hasTitle || hasAriaLabel) continue;\n\n edits.push({\n offset: openingElement.tagName.getEnd(),\n text: ` title=\"${PLACEHOLDER_TITLE}\"`,\n });\n }\n\n if (edits.length === 0) return null;\n\n edits.sort((a, b) => (a.offset === b.offset ? 0 : b.offset - a.offset));\n\n let updated = content;\n for (const { offset, text } of edits) {\n updated = updated.slice(0, offset) + text + updated.slice(offset);\n }\n return updated;\n};\n\n/**\n * Adds a placeholder `title` to `<IconButton>` usages that don't already\n * provide either `title` or `ariaLabel`. The new accessible name requirement\n * means every icon button must carry one of these two props.\n *\n * Usages with a spread attribute (e.g. `<IconButton {...props} />`) are\n * skipped because we cannot tell whether `title`/`ariaLabel` is forwarded.\n */\nexport const iconButtonAddRequiredName = (tree: Tree): void => {\n const touched = visitTsxFiles(tree, \"IconButton\", transformIconButtonUsage);\n logSummary(\"iconbutton-add-required-name\", touched);\n if (touched > 0) {\n logger.info(\n ` Replace the placeholder \"${PLACEHOLDER_TITLE}\" titles with localized strings (or switch to ariaLabel where appropriate).`\n );\n }\n};\n\nexport default iconButtonAddRequiredName;\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"kpi-tooltiplabel-to-wrapper.js","sourceRoot":"","sources":["../../../../../../libs/react/components/migrations/v2-0-0/kpi-tooltiplabel-to-wrapper.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Tree } from \"@nx/devkit\";\nimport * as ts from \"typescript\";\nimport {\n findJsxAttribute,\n findJsxElements,\n getImportedAliases,\n logSummary,\n parseTsx,\n visitTsxFiles,\n} from \"../utils/jsx-utils\";\n\nconst PACKAGE_NAME = \"@trackunit/react-components\";\nconst COMPONENT_NAME = \"KPI\";\n\nconst ensureTooltipImport = (content: string): string => {\n const sourceFile = parseTsx(content, \"source.tsx\");\n const aliases = getImportedAliases(sourceFile, PACKAGE_NAME);\n if (aliases !== null && Object.values(aliases).includes(\"Tooltip\")) return content;\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const moduleSpecifier = stmt.moduleSpecifier;\n if (!ts.isStringLiteral(moduleSpecifier) || moduleSpecifier.text !== PACKAGE_NAME) continue;\n\n const namedBindings = stmt.importClause?.namedBindings;\n if (namedBindings === undefined || !ts.isNamedImports(namedBindings)) continue;\n\n const elementsText = namedBindings.elements.map(el => el.getText()).join(\", \");\n const merged = `${elementsText}, Tooltip`;\n const before = content.slice(0, namedBindings.getStart());\n const after = content.slice(namedBindings.getEnd());\n return `${before}{ ${merged} }${after}`;\n }\n\n return `import { Tooltip } from \"${PACKAGE_NAME}\";\\n${content}`;\n};\n\nconst renderTooltipExpression = (label: string): string => label;\n\nconst transformKpiUsage = (filePath: string, content: string): string | null => {\n const sourceFile = parseTsx(content, filePath);\n\n const aliases = getImportedAliases(sourceFile, PACKAGE_NAME);\n if (aliases === null) return null;\n\n const localAlias = Object.entries(aliases).find(([, name]) => name === COMPONENT_NAME)?.[0];\n if (localAlias === undefined) return null;\n\n const matches = findJsxElements(sourceFile, [localAlias]);\n if (matches.length === 0) return null;\n\n type Edit = {\n elementStart: number;\n elementEnd: number;\n attrStart: number;\n attrEnd: number;\n label: string;\n elementText: string;\n };\n const edits: Array<Edit> = [];\n\n for (const { openingElement, element } of matches) {\n const tooltipLabelAttr = findJsxAttribute(openingElement, \"tooltipLabel\");\n if (tooltipLabelAttr === null) continue;\n\n const initializer = tooltipLabelAttr.initializer;\n if (initializer === undefined) continue;\n\n let label: string;\n if (ts.isStringLiteral(initializer)) {\n label = `\"${initializer.text}\"`;\n } else if (ts.isJsxExpression(initializer) && initializer.expression !== undefined) {\n label = `{${initializer.expression.getText()}}`;\n } else {\n continue;\n }\n\n edits.push({\n elementStart: element.getStart(),\n elementEnd: element.getEnd(),\n attrStart: tooltipLabelAttr.getFullStart(),\n attrEnd: tooltipLabelAttr.getEnd(),\n label,\n elementText: content.slice(element.getStart(), element.getEnd()),\n });\n }\n\n if (edits.length === 0) return null;\n\n edits.sort((a, b) => b.elementStart - a.elementStart);\n\n let updated = content;\n for (const edit of edits) {\n // Remove the tooltipLabel attribute first (it's inside the element).\n const elementWithoutAttr =\n edit.elementText.slice(0, edit.attrStart - edit.elementStart) +\n edit.elementText.slice(edit.attrEnd - edit.elementStart);\n const wrapped = `<Tooltip label=${renderTooltipExpression(edit.label)} placement=\"bottom\">${elementWithoutAttr}</Tooltip>`;\n updated = updated.slice(0, edit.elementStart) + wrapped + updated.slice(edit.elementEnd);\n }\n\n updated = ensureTooltipImport(updated);\n\n return updated;\n};\n\n/**\n * Replaces the removed `tooltipLabel` prop on `<KPI>` with an explicit\n * `<Tooltip>` wrapper. The `KPICard` variant is intentionally not touched\n * because it still owns its own `tooltipLabel` prop.\n */\nexport const kpiTooltipLabelToWrapper = (tree: Tree): void => {\n const touched = visitTsxFiles(tree, \"tooltipLabel\", transformKpiUsage);\n logSummary(\"kpi-tooltiplabel-to-wrapper\", touched);\n};\n\nexport default kpiTooltipLabelToWrapper;\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"notice-tooltiplabel-to-wrapper.js","sourceRoot":"","sources":["../../../../../../libs/react/components/migrations/v2-0-0/notice-tooltiplabel-to-wrapper.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Tree } from \"@nx/devkit\";\nimport * as ts from \"typescript\";\nimport {\n findJsxAttribute,\n findJsxElements,\n getImportedAliases,\n logSummary,\n parseTsx,\n visitTsxFiles,\n} from \"../utils/jsx-utils\";\n\nconst PACKAGE_NAME = \"@trackunit/react-components\";\nconst COMPONENT_NAME = \"Notice\";\n\nconst ensureTooltipImport = (content: string): string => {\n const sourceFile = parseTsx(content, \"source.tsx\");\n const aliases = getImportedAliases(sourceFile, PACKAGE_NAME);\n if (aliases !== null && Object.values(aliases).includes(\"Tooltip\")) return content;\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const moduleSpecifier = stmt.moduleSpecifier;\n if (!ts.isStringLiteral(moduleSpecifier) || moduleSpecifier.text !== PACKAGE_NAME) continue;\n\n const namedBindings = stmt.importClause?.namedBindings;\n if (namedBindings === undefined || !ts.isNamedImports(namedBindings)) continue;\n\n const elementsText = namedBindings.elements.map(el => el.getText()).join(\", \");\n const merged = `${elementsText}, Tooltip`;\n const before = content.slice(0, namedBindings.getStart());\n const after = content.slice(namedBindings.getEnd());\n return `${before}{ ${merged} }${after}`;\n }\n\n return `import { Tooltip } from \"${PACKAGE_NAME}\";\\n${content}`;\n};\n\nconst transformNoticeUsage = (filePath: string, content: string): string | null => {\n const sourceFile = parseTsx(content, filePath);\n\n const aliases = getImportedAliases(sourceFile, PACKAGE_NAME);\n if (aliases === null) return null;\n\n const localAlias = Object.entries(aliases).find(([, name]) => name === COMPONENT_NAME)?.[0];\n if (localAlias === undefined) return null;\n\n const matches = findJsxElements(sourceFile, [localAlias]);\n if (matches.length === 0) return null;\n\n type Edit = {\n elementStart: number;\n elementEnd: number;\n elementText: string;\n label: string | null;\n attrEdits: Array<{ start: number; end: number }>;\n };\n const edits: Array<Edit> = [];\n\n for (const { openingElement, element } of matches) {\n const tooltipLabelAttr = findJsxAttribute(openingElement, \"tooltipLabel\");\n const withTooltipAttr = findJsxAttribute(openingElement, \"withTooltip\");\n if (tooltipLabelAttr === null && withTooltipAttr === null) continue;\n\n let label: string | null = null;\n if (tooltipLabelAttr !== null) {\n const initializer = tooltipLabelAttr.initializer;\n if (initializer !== undefined) {\n if (ts.isStringLiteral(initializer)) {\n label = `\"${initializer.text}\"`;\n } else if (ts.isJsxExpression(initializer) && initializer.expression !== undefined) {\n label = `{${initializer.expression.getText()}}`;\n }\n }\n }\n\n const attrEdits: Array<{ start: number; end: number }> = [];\n for (const attr of [tooltipLabelAttr, withTooltipAttr]) {\n if (attr === null) continue;\n attrEdits.push({ start: attr.getFullStart(), end: attr.getEnd() });\n }\n\n edits.push({\n elementStart: element.getStart(),\n elementEnd: element.getEnd(),\n elementText: content.slice(element.getStart(), element.getEnd()),\n label,\n attrEdits,\n });\n }\n\n if (edits.length === 0) return null;\n\n edits.sort((a, b) => b.elementStart - a.elementStart);\n\n let updated = content;\n let needsTooltipImport = false;\n\n for (const edit of edits) {\n // Remove the tooltipLabel/withTooltip attributes from the element first.\n let elementWithoutAttrs = edit.elementText;\n const sortedAttrEdits = [...edit.attrEdits].sort((a, b) => b.start - a.start);\n for (const { start, end } of sortedAttrEdits) {\n elementWithoutAttrs =\n elementWithoutAttrs.slice(0, start - edit.elementStart) + elementWithoutAttrs.slice(end - edit.elementStart);\n }\n\n if (edit.label !== null) {\n const wrapped = `<Tooltip label=${edit.label} placement=\"bottom\">${elementWithoutAttrs}</Tooltip>`;\n updated = updated.slice(0, edit.elementStart) + wrapped + updated.slice(edit.elementEnd);\n needsTooltipImport = true;\n } else {\n // No label to forward (e.g. only `withTooltip`); just strip the props.\n updated = updated.slice(0, edit.elementStart) + elementWithoutAttrs + updated.slice(edit.elementEnd);\n }\n }\n\n if (needsTooltipImport) {\n updated = ensureTooltipImport(updated);\n }\n\n return updated;\n};\n\n/**\n * Removes the deprecated `withTooltip`/`tooltipLabel` props from `<Notice>`\n * usages. When a label is present the element is wrapped in `<Tooltip>` to\n * preserve hover behaviour; when only `withTooltip` was set the props are\n * dropped because they had no remaining effect.\n */\nexport const noticeTooltipLabelToWrapper = (tree: Tree): void => {\n const touched = visitTsxFiles(tree, \"Notice\", transformNoticeUsage);\n logSummary(\"notice-tooltiplabel-to-wrapper\", touched);\n};\n\nexport default noticeTooltipLabelToWrapper;\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pagination-add-required-titles.js","sourceRoot":"","sources":["../../../../../../libs/react/components/migrations/v2-0-0/pagination-add-required-titles.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Tree, logger } from \"@nx/devkit\";\nimport {\n findJsxAttribute,\n findJsxElements,\n getImportedAliases,\n hasSpreadAttribute,\n logSummary,\n parseTsx,\n visitTsxFiles,\n} from \"../utils/jsx-utils\";\n\nconst PACKAGE_NAME = \"@trackunit/react-components\";\nconst COMPONENT_NAME = \"Pagination\";\n\nconst PLACEHOLDERS: Record<string, string> = {\n previousPageTitle: \"Previous page\",\n nextPageTitle: \"Next page\",\n};\n\nconst transformPaginationUsage = (filePath: string, content: string): string | null => {\n const sourceFile = parseTsx(content, filePath);\n\n const aliases = getImportedAliases(sourceFile, PACKAGE_NAME);\n if (aliases === null) return null;\n\n const localAlias = Object.entries(aliases).find(([, name]) => name === COMPONENT_NAME)?.[0];\n if (localAlias === undefined) return null;\n\n const matches = findJsxElements(sourceFile, [localAlias]);\n if (matches.length === 0) return null;\n\n type Edit = { offset: number; text: string };\n const edits: Array<Edit> = [];\n\n for (const { openingElement } of matches) {\n if (hasSpreadAttribute(openingElement)) continue;\n\n for (const [propName, placeholder] of Object.entries(PLACEHOLDERS)) {\n if (findJsxAttribute(openingElement, propName) !== null) continue;\n edits.push({\n offset: openingElement.tagName.getEnd(),\n text: ` ${propName}=\"${placeholder}\"`,\n });\n }\n }\n\n if (edits.length === 0) return null;\n\n // Apply in reverse so earlier offsets stay valid; keep order at the same\n // offset so attributes appear in declaration order in the source.\n edits.sort((a, b) => (a.offset === b.offset ? 0 : b.offset - a.offset));\n\n let updated = content;\n for (const { offset, text } of edits) {\n updated = updated.slice(0, offset) + text + updated.slice(offset);\n }\n return updated;\n};\n\n/**\n * Adds the now-required `previousPageTitle` and `nextPageTitle` props to\n * `<Pagination>` usages that don't already provide them. The values default\n * to the English strings \"Previous page\"/\"Next page\"; consumers should\n * replace them with their own localized strings.\n *\n * Usages with a spread attribute (e.g. `<Pagination {...props} />`) are\n * skipped because we cannot tell whether the props are already provided.\n */\nexport const paginationAddRequiredTitles = (tree: Tree): void => {\n const touched = visitTsxFiles(tree, \"Pagination\", transformPaginationUsage);\n logSummary(\"pagination-add-required-titles\", touched);\n if (touched > 0) {\n logger.info(` Replace the placeholder \"Previous page\"/\"Next page\" strings with your own localized values.`);\n }\n};\n\nexport default paginationAddRequiredTitles;\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tag-add-removetaglabel.js","sourceRoot":"","sources":["../../../../../../libs/react/components/migrations/v2-0-0/tag-add-removetaglabel.ts"],"names":[],"mappings":";;;AAAA,uCAA+C;AAC/C,kDAQ4B;AAE5B,MAAM,YAAY,GAAG,6BAA6B,CAAC;AACnD,MAAM,cAAc,GAAG,KAAK,CAAC;AAE7B,MAAM,4BAA4B,GAAG,YAAY,CAAC;AAElD,MAAM,iBAAiB,GAAG,CAAC,QAAgB,EAAE,OAAe,EAAiB,EAAE;IAC7E,MAAM,UAAU,GAAG,IAAA,oBAAQ,EAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAE/C,MAAM,OAAO,GAAG,IAAA,8BAAkB,EAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAC7D,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAElC,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,KAAK,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5F,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAE1C,MAAM,OAAO,GAAG,IAAA,2BAAe,EAAC,UAAU,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAC1D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAGtC,MAAM,KAAK,GAAgB,EAAE,CAAC;IAE9B,KAAK,MAAM,EAAE,cAAc,EAAE,IAAI,OAAO,EAAE,CAAC;QACzC,IAAI,IAAA,8BAAkB,EAAC,cAAc,CAAC;YAAE,SAAS;QAEjD,0EAA0E;QAC1E,wBAAwB;QACxB,IAAI,IAAA,4BAAgB,EAAC,cAAc,EAAE,cAAc,CAAC,KAAK,IAAI;YAAE,SAAS;QACxE,IAAI,IAAA,4BAAgB,EAAC,cAAc,EAAE,gBAAgB,CAAC,KAAK,IAAI;YAAE,SAAS;QAE1E,KAAK,CAAC,IAAI,CAAC;YACT,MAAM,EAAE,cAAc,CAAC,OAAO,CAAC,MAAM,EAAE;YACvC,IAAI,EAAE,oBAAoB,4BAA4B,GAAG;SAC1D,CAAC,CAAC;IACL,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAExE,IAAI,OAAO,GAAG,OAAO,CAAC;IACtB,KAAK,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,EAAE,CAAC;QACrC,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AAEF;;;;;GAKG;AACI,MAAM,oBAAoB,GAAG,CAAC,IAAU,EAAQ,EAAE;IACvD,MAAM,OAAO,GAAG,IAAA,yBAAa,EAAC,IAAI,EAAE,KAAK,EAAE,iBAAiB,CAAC,CAAC;IAC9D,IAAA,sBAAU,EAAC,wBAAwB,EAAE,OAAO,CAAC,CAAC;IAC9C,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,eAAM,CAAC,IAAI,CAAC,8EAA8E,CAAC,CAAC;IAC9F,CAAC;AACH,CAAC,CAAC;AANW,QAAA,oBAAoB,wBAM/B;AAEF,kBAAe,4BAAoB,CAAC","sourcesContent":["import { type Tree, logger } from \"@nx/devkit\";\nimport {\n findJsxAttribute,\n findJsxElements,\n getImportedAliases,\n hasSpreadAttribute,\n logSummary,\n parseTsx,\n visitTsxFiles,\n} from \"../utils/jsx-utils\";\n\nconst PACKAGE_NAME = \"@trackunit/react-components\";\nconst COMPONENT_NAME = \"Tag\";\n\nconst REMOVE_TAG_LABEL_PLACEHOLDER = \"Remove tag\";\n\nconst transformTagUsage = (filePath: string, content: string): string | null => {\n const sourceFile = parseTsx(content, filePath);\n\n const aliases = getImportedAliases(sourceFile, PACKAGE_NAME);\n if (aliases === null) return null;\n\n const localAlias = Object.entries(aliases).find(([, name]) => name === COMPONENT_NAME)?.[0];\n if (localAlias === undefined) return null;\n\n const matches = findJsxElements(sourceFile, [localAlias]);\n if (matches.length === 0) return null;\n\n type Edit = { offset: number; text: string };\n const edits: Array<Edit> = [];\n\n for (const { openingElement } of matches) {\n if (hasSpreadAttribute(openingElement)) continue;\n\n // Only act when the tag is dismissible (has onClickClose) and missing the\n // newly required label.\n if (findJsxAttribute(openingElement, \"onClickClose\") === null) continue;\n if (findJsxAttribute(openingElement, \"removeTagLabel\") !== null) continue;\n\n edits.push({\n offset: openingElement.tagName.getEnd(),\n text: ` removeTagLabel=\"${REMOVE_TAG_LABEL_PLACEHOLDER}\"`,\n });\n }\n\n if (edits.length === 0) return null;\n\n edits.sort((a, b) => (a.offset === b.offset ? 0 : b.offset - a.offset));\n\n let updated = content;\n for (const { offset, text } of edits) {\n updated = updated.slice(0, offset) + text + updated.slice(offset);\n }\n return updated;\n};\n\n/**\n * Adds the now-required `removeTagLabel` prop to dismissible `<Tag>` usages\n * (those with `onClickClose`) when it is missing. Default value is the English\n * string \"Remove tag\"; consumers should replace it with their own localized\n * label.\n */\nexport const tagAddRemoveTagLabel = (tree: Tree): void => {\n const touched = visitTsxFiles(tree, \"Tag\", transformTagUsage);\n logSummary(\"tag-add-removetaglabel\", touched);\n if (touched > 0) {\n logger.info(` Replace the placeholder \"Remove tag\" string with your own localized value.`);\n }\n};\n\nexport default tagAddRemoveTagLabel;\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"togglegroup-remove-item-style.js","sourceRoot":"","sources":["../../../../../../libs/react/components/migrations/v2-0-0/togglegroup-remove-item-style.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Tree, logger } from \"@nx/devkit\";\nimport * as ts from \"typescript\";\nimport {\n findJsxAttribute,\n findJsxElements,\n getImportedAliases,\n logSummary,\n parseTsx,\n visitTsxFiles,\n} from \"../utils/jsx-utils\";\n\nconst PACKAGE_NAME = \"@trackunit/react-components\";\nconst COMPONENT_NAME = \"ToggleGroup\";\n\ntype Removal = { start: number; end: number };\n\nconst collectStyleRemovalsFromArray = (arrayLiteral: ts.ArrayLiteralExpression): ReadonlyArray<Removal> => {\n const removals: Array<Removal> = [];\n for (const element of arrayLiteral.elements) {\n if (!ts.isObjectLiteralExpression(element)) continue;\n for (const prop of element.properties) {\n if (\n (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) &&\n ts.isIdentifier(prop.name) &&\n prop.name.text === \"style\"\n ) {\n removals.push({ start: prop.getFullStart(), end: prop.getEnd() });\n }\n }\n }\n return removals;\n};\n\nconst transformToggleGroupUsage = (\n filePath: string,\n content: string\n): { content: string | null; skippedNonInline: number } => {\n const sourceFile = parseTsx(content, filePath);\n\n const aliases = getImportedAliases(sourceFile, PACKAGE_NAME);\n if (aliases === null) return { content: null, skippedNonInline: 0 };\n\n const localAlias = Object.entries(aliases).find(([, name]) => name === COMPONENT_NAME)?.[0];\n if (localAlias === undefined) return { content: null, skippedNonInline: 0 };\n\n const matches = findJsxElements(sourceFile, [localAlias]);\n if (matches.length === 0) return { content: null, skippedNonInline: 0 };\n\n const removals: Array<Removal> = [];\n let skippedNonInline = 0;\n\n for (const { openingElement } of matches) {\n const listAttr = findJsxAttribute(openingElement, \"list\");\n if (listAttr === null) continue;\n\n const initializer = listAttr.initializer;\n if (initializer === undefined || !ts.isJsxExpression(initializer)) continue;\n const expr = initializer.expression;\n if (expr === undefined) continue;\n\n if (!ts.isArrayLiteralExpression(expr)) {\n // The list is referenced through a variable / function call; we can't\n // safely strip `style` without potentially editing unrelated objects.\n skippedNonInline += 1;\n continue;\n }\n\n removals.push(...collectStyleRemovalsFromArray(expr));\n }\n\n if (removals.length === 0) return { content: null, skippedNonInline };\n\n // Remove the property and its trailing comma if present so we don't leave\n // dangling `, ,` sequences.\n const sorted = [...removals].sort((a, b) => b.start - a.start);\n let updated = content;\n for (let { start, end } of sorted) {\n while (start > 0 && (updated[start - 1] === \" \" || updated[start - 1] === \"\\t\")) {\n start -= 1;\n }\n if (updated[end] === \",\") {\n end += 1;\n }\n updated = updated.slice(0, start) + updated.slice(end);\n }\n\n return { content: updated, skippedNonInline };\n};\n\n/**\n * Removes the `style` property from items in `<ToggleGroup list={[...]}>`\n * because `BasicToggleGroupListProps` no longer extends `Styleable`.\n *\n * Only inline array literals are processed; lists pulled in via a variable or\n * function call are left untouched and reported in the migration summary so\n * the developer can review them.\n */\nexport const toggleGroupRemoveItemStyle = (tree: Tree): void => {\n let touched = 0;\n let totalSkipped = 0;\n visitTsxFiles(tree, COMPONENT_NAME, (filePath, content) => {\n const { content: updated, skippedNonInline } = transformToggleGroupUsage(filePath, content);\n totalSkipped += skippedNonInline;\n if (updated !== null && updated !== content) {\n touched += 1;\n return updated;\n }\n return null;\n });\n logSummary(\"togglegroup-remove-item-style\", touched, totalSkipped);\n if (totalSkipped > 0) {\n logger.info(\n ` ${totalSkipped} ToggleGroup usage(s) referenced their list through a variable/function. Remove any 'style' properties from those item objects manually.`\n );\n }\n};\n\nexport default toggleGroupRemoveItemStyle;\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"menulist-rename-to-menucontent.js","sourceRoot":"","sources":["../../../../../../libs/react/components/migrations/v3-0-0/menulist-rename-to-menucontent.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,+CAAiC;AACjC,kDAA6F;AAE7F,MAAM,YAAY,GAAG,6BAA6B,CAAC;AAEnD,MAAM,OAAO,GAAkE;IAC7E,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,aAAa,EAAE;IACvC,EAAE,IAAI,EAAE,eAAe,EAAE,EAAE,EAAE,kBAAkB,EAAE;CAClD,CAAC;AAIF;;;;;;GAMG;AACH,MAAM,sBAAsB,GAAG,CAAC,UAAyB,EAAE,IAAY,EAAE,WAAmB,EAAe,EAAE;IAC3G,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAG,CAAC,IAAa,EAAQ,EAAE;QACpC,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YAChD,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;QAC1F,CAAC;QACD,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC/B,CAAC,CAAC;IACF,KAAK,CAAC,UAAU,CAAC,CAAC;IAClB,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,8BAA8B,GAAG,CACrC,UAAyB,EACzB,WAAmB,EACnB,YAAoB,EACpB,WAAmB,EACN,EAAE;IACf,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,UAAU,EAAE,CAAC;QACzC,IAAI,CAAC,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC;YAAE,SAAS;QAC5C,MAAM,eAAe,GAAG,IAAI,CAAC,eAAe,CAAC;QAC7C,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC,eAAe,CAAC,IAAI,eAAe,CAAC,IAAI,KAAK,WAAW;YAAE,SAAS;QAE3F,MAAM,aAAa,GAAG,IAAI,CAAC,YAAY,EAAE,aAAa,CAAC;QACvD,IAAI,aAAa,KAAK,SAAS,IAAI,CAAC,EAAE,CAAC,cAAc,CAAC,aAAa,CAAC;YAAE,SAAS;QAE/E,KAAK,MAAM,OAAO,IAAI,aAAa,CAAC,QAAQ,EAAE,CAAC;YAC7C,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS;gBAAE,SAAS;YACjD,IAAI,OAAO,CAAC,YAAY,CAAC,IAAI,KAAK,YAAY;gBAAE,SAAS;YACzD,OAAO;gBACL,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC;gBAChD,GAAG,EAAE,OAAO,CAAC,YAAY,CAAC,MAAM,EAAE;gBAClC,IAAI,EAAE,WAAW;aAClB,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,MAAM,sBAAsB,GAAG,CAAC,QAAgB,EAAE,OAAe,EAAiB,EAAE;IAClF,MAAM,UAAU,GAAG,IAAA,oBAAQ,EAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAE/C,MAAM,OAAO,GAAG,IAAA,8BAAkB,EAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAC7D,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAElC,MAAM,KAAK,GAAgB,EAAE,CAAC;IAE9B,KAAK,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,OAAO,EAAE,CAAC;QACnC,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,QAAQ,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC1F,IAAI,UAAU,KAAK,SAAS;YAAE,SAAS;QAEvC,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC,GAAG,sBAAsB,CAAC,UAAU,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;QAC9D,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,GAAG,8BAA8B,CAAC,UAAU,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;YAChF,IAAI,IAAI,KAAK,IAAI;gBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAExC,IAAI,OAAO,GAAG,OAAO,CAAC;IACtB,KAAK,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,KAAK,EAAE,CAAC;QACzC,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AAEF;;;;;;;;;;GAUG;AACI,MAAM,2BAA2B,GAAG,CAAC,IAAU,EAAQ,EAAE;IAC9D,MAAM,OAAO,GAAG,IAAA,yBAAa,EAAC,IAAI,EAAE,UAAU,EAAE,sBAAsB,CAAC,CAAC;IACxE,IAAA,sBAAU,EAAC,gCAAgC,EAAE,OAAO,CAAC,CAAC;AACxD,CAAC,CAAC;AAHW,QAAA,2BAA2B,+BAGtC;AAEF,kBAAe,mCAA2B,CAAC","sourcesContent":["import { type Tree } from \"@nx/devkit\";\nimport * as ts from \"typescript\";\nimport { getImportedAliases, logSummary, parseTsx, visitTsxFiles } from \"../utils/jsx-utils\";\n\nconst PACKAGE_NAME = \"@trackunit/react-components\";\n\nconst RENAMES: ReadonlyArray<{ readonly from: string; readonly to: string }> = [\n { from: \"MenuList\", to: \"MenuContent\" },\n { from: \"MenuListProps\", to: \"MenuContentProps\" },\n];\n\ntype Edit = { start: number; end: number; text: string };\n\n/**\n * Every `Identifier` node in the file matching `name`. Safe to use as a\n * blanket rename once we've confirmed (via `getImportedAliases`) that `name`\n * is genuinely imported from the target package with no local alias — in\n * that case the import specifier, JSX tag references, and type references\n * all share the same identifier text, so one pass covers every site.\n */\nconst collectIdentifierEdits = (sourceFile: ts.SourceFile, name: string, replacement: string): Array<Edit> => {\n const edits: Array<Edit> = [];\n const visit = (node: ts.Node): void => {\n if (ts.isIdentifier(node) && node.text === name) {\n edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), text: replacement });\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return edits;\n};\n\n/**\n * Locates the `propertyName` of an aliased named import specifier\n * (`{ MenuList as Foo }`) for `importedName`. Only that part needs\n * rewriting — the local binding `Foo` (and therefore every usage site in\n * the file) stays exactly as the author wrote it.\n */\nconst findAliasedImportSpecifierEdit = (\n sourceFile: ts.SourceFile,\n packageName: string,\n importedName: string,\n replacement: string\n): Edit | null => {\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const moduleSpecifier = stmt.moduleSpecifier;\n if (!ts.isStringLiteral(moduleSpecifier) || moduleSpecifier.text !== packageName) continue;\n\n const namedBindings = stmt.importClause?.namedBindings;\n if (namedBindings === undefined || !ts.isNamedImports(namedBindings)) continue;\n\n for (const element of namedBindings.elements) {\n if (element.propertyName === undefined) continue;\n if (element.propertyName.text !== importedName) continue;\n return {\n start: element.propertyName.getStart(sourceFile),\n end: element.propertyName.getEnd(),\n text: replacement,\n };\n }\n }\n return null;\n};\n\nconst transformMenuListUsage = (filePath: string, content: string): string | null => {\n const sourceFile = parseTsx(content, filePath);\n\n const aliases = getImportedAliases(sourceFile, PACKAGE_NAME);\n if (aliases === null) return null;\n\n const edits: Array<Edit> = [];\n\n for (const { from, to } of RENAMES) {\n const localAlias = Object.entries(aliases).find(([, original]) => original === from)?.[0];\n if (localAlias === undefined) continue;\n\n if (localAlias === from) {\n edits.push(...collectIdentifierEdits(sourceFile, from, to));\n } else {\n const edit = findAliasedImportSpecifierEdit(sourceFile, PACKAGE_NAME, from, to);\n if (edit !== null) edits.push(edit);\n }\n }\n\n if (edits.length === 0) return null;\n\n edits.sort((a, b) => b.start - a.start);\n\n let updated = content;\n for (const { start, end, text } of edits) {\n updated = updated.slice(0, start) + text + updated.slice(end);\n }\n return updated;\n};\n\n/**\n * Renames `MenuList`/`MenuListProps` to `MenuContent`/`MenuContentProps`\n * everywhere they're imported from `@trackunit/react-components` — the\n * import specifier, every JSX tag usage, and every type reference (e.g.\n * `Omit<MenuListProps, \"children\">`).\n *\n * Aliased imports (`{ MenuList as Foo }`) only need the imported name\n * rewritten; the local alias and its usage sites are left untouched.\n * Unrelated same-prefix exports like `cvaMenuList`/`cvaMenuListItem` are\n * never matched since identifier comparisons are exact, not substring.\n */\nexport const menuListRenameToMenuContent = (tree: Tree): void => {\n const touched = visitTsxFiles(tree, \"MenuList\", transformMenuListUsage);\n logSummary(\"menulist-rename-to-menucontent\", touched);\n};\n\nexport default menuListRenameToMenuContent;\n"]}
|