@box/metadata-taxonomy-picker 3.4.3 → 3.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/taxonomy-ancestor-breadcrumb.js +1 -1
- package/dist/chunks/taxonomy-items-list.js +1 -1
- package/dist/esm/lib/constants.js +1 -1
- package/dist/esm/lib/hooks/use-taxonomy-picker-controller.js +1 -1
- package/dist/styles/taxonomy-ancestor-breadcrumb.css +1 -1
- package/dist/types/lib/components/taxonomy-ancestor-breadcrumb/taxonomy-ancestor-breadcrumb.d.ts +7 -5
- package/dist/types/lib/constants.d.ts +4 -6
- package/dist/types/lib/stories/tests/rendering-performance.interaction-tests.stories.d.ts +48 -0
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
import"../esm/lib/constants.js";import{Fragment,memo,
|
|
1
|
+
import"../esm/lib/constants.js";import{Fragment,memo,useLayoutEffect,useRef,useState}from"react";import{Text,Tooltip}from"@box/blueprint-web";import{Ellipsis}from"@box/blueprint-web-assets/icons/Fill";import{Fragment as Fragment$1,jsx,jsxs}from"react/jsx-runtime";import '../styles/taxonomy-ancestor-breadcrumb.css';var taxonomy_ancestor_breadcrumb_module_default={root:`_root_1nr4f_2`,segment:`_segment_1nr4f_15`,trail:`_trail_1nr4f_31`,ellipsisIcon:`_ellipsisIcon_1nr4f_40`,probe:`_probe_1nr4f_49`},NO_OVERFLOW={isTrailOverflowing:!1,hasClippedSegment:!1},readLineHeight=el=>{let computed=window.getComputedStyle(el),parsed=parseFloat(computed.lineHeight);return Number.isFinite(parsed)&&parsed>0?parsed:(parseFloat(computed.fontSize)||16)*1.2},measureProbe=probe=>{let maxHeight=readLineHeight(probe)*2+2,segments=Array.from(probe.querySelectorAll(`.${taxonomy_ancestor_breadcrumb_module_default.segment}`));return{isTrailOverflowing:probe.scrollHeight>maxHeight,hasClippedSegment:segments.some(segment=>segment.scrollWidth-segment.clientWidth>2)}},Segment=({ancestor,bold})=>jsx(Text,{as:`span`,className:taxonomy_ancestor_breadcrumb_module_default.segment,color:`textOnLightSecondary`,variant:bold?`captionBold`:`caption`,children:ancestor.displayName}),Separator=()=>jsx(Text,{"aria-hidden":`true`,as:`span`,color:`textOnLightSecondary`,variant:`caption`,children:` / `}),Segments=({items})=>jsx(Fragment$1,{children:items.map((ancestor,index)=>jsxs(Fragment,{children:[index>0&&jsx(Separator,{}),jsx(Segment,{ancestor,bold:index===items.length-1})]},ancestor.id))}),TaxonomyAncestorBreadcrumb=memo(function({ancestors}){let probeRef=useRef(null),[overflow,setOverflow]=useState(NO_OVERFLOW);useLayoutEffect(()=>{let probe=probeRef.current;if(!probe)return;let observer=new ResizeObserver(()=>{let next=measureProbe(probe);setOverflow(prev=>prev.isTrailOverflowing===next.isTrailOverflowing&&prev.hasClippedSegment===next.hasClippedSegment?prev:next)});return observer.observe(probe),()=>{observer.disconnect()}},[ancestors]);let lastIndex=ancestors.length-1,showTruncated=ancestors.length>=3&&overflow.isTrailOverflowing,isTextHidden=overflow.isTrailOverflowing||overflow.hasClippedSegment;return jsx(Tooltip,{content:ancestors.map(ancestor=>ancestor.displayName).join(` / `),open:isTextHidden?void 0:!1,children:jsxs(Text,{as:`span`,className:taxonomy_ancestor_breadcrumb_module_default.root,"data-testid":`taxonomy-ancestor-breadcrumb`,role:isTextHidden?`group`:void 0,tabIndex:isTextHidden?0:void 0,children:[jsx(Text,{as:`span`,className:taxonomy_ancestor_breadcrumb_module_default.trail,color:`textOnLightSecondary`,variant:`caption`,children:showTruncated?jsxs(Fragment$1,{children:[jsx(Segment,{ancestor:ancestors[0]}),jsx(Separator,{}),jsx(Ellipsis,{"aria-hidden":!0,className:taxonomy_ancestor_breadcrumb_module_default.ellipsisIcon,color:`currentColor`}),jsx(Separator,{}),jsx(Segment,{ancestor:ancestors[lastIndex],bold:!0})]}):jsx(Segments,{items:ancestors})}),jsx(Text,{"aria-hidden":`true`,as:`span`,className:taxonomy_ancestor_breadcrumb_module_default.probe,ref:probeRef,variant:`caption`,children:jsx(Segments,{items:ancestors})})]})})});export{TaxonomyAncestorBreadcrumb as t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import"../esm/lib/constants.js";import{t as EmptyListState}from"./empty-state.js";import{t as ErrorState}from"./error-state.js";import{t as LoadingState}from"./loading-state.js";import{t as TaxonomyMenuItem}from"./taxonomy-menu-item.js";import{t as LoadMoreErrorRow}from"./load-more-error-row.js";import{t as LoadMoreLoadingRow}from"./load-more-loading-row.js";import{useTaxonomyItemsListFocus}from"../esm/lib/components/taxonomy-items-list/use-taxonomy-items-list-focus.js";import{forwardRef,memo,useCallback,useEffect,useImperativeHandle,useLayoutEffect,useRef}from"react";import{Radio}from"@box/blueprint-web";import{jsx,jsxs}from"react/jsx-runtime";import{Composite,CompositeProvider}from"@ariakit/react";import{useVirtualizer}from"@tanstack/react-virtual";import '../styles/taxonomy-items-list.css';var taxonomy_items_list_module_default={scrollContainer:`_scrollContainer_lky3p_1`,virtualContent:`_virtualContent_lky3p_11`,virtualRow:`_virtualRow_lky3p_23`},RadioGroupWrapper=memo(function({children,selectedValue,onValueChange}){let lastKeyRef=useRef(null),handleKeyDown=useCallback(e=>{lastKeyRef.current=e.key},[]),handleValueChange=useCallback(value=>{let isArrowNavigation=lastKeyRef.current===`ArrowUp`||lastKeyRef.current===`ArrowDown`;lastKeyRef.current=null,onValueChange(value,!isArrowNavigation)},[onValueChange]);return jsx(Radio.Group,{name:`taxonomy-picker`,onKeyDown:handleKeyDown,onValueChange:handleValueChange,value:selectedValue,children})}),measureElement=element=>element.getBoundingClientRect().height,LOAD_MORE_VIRTUAL_KEY=`__load-more-skeleton__`,TaxonomyItemsList=forwardRef(function({items,multiSelect,selectedValue,estimatedRowHeight=36,hasVariableRowHeight=!1,isSearchMode=!1,isFetching,error,loadMoreError=null,listboxId,onFocusExit,onRetry,hasMore=!1,isFetchingMore=!1,onLoadMore},ref){let scrollRef=useRef(null),loadingRef=useRef(null),virtualCount=items.length+ +!!isFetchingMore,estimateSize=useCallback(index=>index>=items.length?112:estimatedRowHeight,[estimatedRowHeight,items.length]),getItemKey=useCallback(index=>index>=items.length?LOAD_MORE_VIRTUAL_KEY:items[index].id,[items]),handleRadioGroupChange=useCallback((nextValue,closeAfterSelect)=>{let target=items.find(item=>item.id===nextValue);target?.selectable&&target.onSelect(nextValue,closeAfterSelect)},[items]),virtualizer=useVirtualizer({count:virtualCount,getScrollElement:()=>scrollRef.current,estimateSize,gap:1,overscan:
|
|
1
|
+
import"../esm/lib/constants.js";import{t as EmptyListState}from"./empty-state.js";import{t as ErrorState}from"./error-state.js";import{t as LoadingState}from"./loading-state.js";import{t as TaxonomyMenuItem}from"./taxonomy-menu-item.js";import{t as LoadMoreErrorRow}from"./load-more-error-row.js";import{t as LoadMoreLoadingRow}from"./load-more-loading-row.js";import{useTaxonomyItemsListFocus}from"../esm/lib/components/taxonomy-items-list/use-taxonomy-items-list-focus.js";import{forwardRef,memo,useCallback,useEffect,useImperativeHandle,useLayoutEffect,useRef}from"react";import{Radio}from"@box/blueprint-web";import{jsx,jsxs}from"react/jsx-runtime";import{Composite,CompositeProvider}from"@ariakit/react";import{useVirtualizer}from"@tanstack/react-virtual";import '../styles/taxonomy-items-list.css';var taxonomy_items_list_module_default={scrollContainer:`_scrollContainer_lky3p_1`,virtualContent:`_virtualContent_lky3p_11`,virtualRow:`_virtualRow_lky3p_23`},RadioGroupWrapper=memo(function({children,selectedValue,onValueChange}){let lastKeyRef=useRef(null),handleKeyDown=useCallback(e=>{lastKeyRef.current=e.key},[]),handleValueChange=useCallback(value=>{let isArrowNavigation=lastKeyRef.current===`ArrowUp`||lastKeyRef.current===`ArrowDown`;lastKeyRef.current=null,onValueChange(value,!isArrowNavigation)},[onValueChange]);return jsx(Radio.Group,{name:`taxonomy-picker`,onKeyDown:handleKeyDown,onValueChange:handleValueChange,value:selectedValue,children})}),measureElement=element=>element.getBoundingClientRect().height,LOAD_MORE_VIRTUAL_KEY=`__load-more-skeleton__`,TaxonomyItemsList=forwardRef(function({items,multiSelect,selectedValue,estimatedRowHeight=36,hasVariableRowHeight=!1,isSearchMode=!1,isFetching,error,loadMoreError=null,listboxId,onFocusExit,onRetry,hasMore=!1,isFetchingMore=!1,onLoadMore},ref){let scrollRef=useRef(null),loadingRef=useRef(null),virtualCount=items.length+ +!!isFetchingMore,estimateSize=useCallback(index=>index>=items.length?112:estimatedRowHeight,[estimatedRowHeight,items.length]),getItemKey=useCallback(index=>index>=items.length?LOAD_MORE_VIRTUAL_KEY:items[index].id,[items]),handleRadioGroupChange=useCallback((nextValue,closeAfterSelect)=>{let target=items.find(item=>item.id===nextValue);target?.selectable&&target.onSelect(nextValue,closeAfterSelect)},[items]),virtualizer=useVirtualizer({count:virtualCount,getScrollElement:()=>scrollRef.current,estimateSize,gap:1,overscan:8,getItemKey,measureElement});useLayoutEffect(()=>{virtualizer.measure()},[hasVariableRowHeight,estimatedRowHeight]);let showSkeleton=isFetching&&items.length===0,showError=!isFetching&&error!==null,showEmpty=!isFetching&&error===null&&items.length===0,{autoFocusFirst,focusItem,holdFocusDuringLoad,cancelPendingFocus}=useTaxonomyItemsListFocus({scrollRef,loadingRef,items,showSkeleton,isFetching,virtualizer});useImperativeHandle(ref,()=>({getScrollTop:()=>scrollRef.current?.scrollTop??0,scrollTo:top=>{scrollRef.current&&(scrollRef.current.scrollTop=top)},focusItem,holdFocusDuringLoad,cancelPendingFocus}),[focusItem,holdFocusDuringLoad,cancelPendingFocus]);let previousItemsLengthRef=useRef(items.length),preCommitScrollTopRef=useRef(null);items.length!==previousItemsLengthRef.current&&(items.length>previousItemsLengthRef.current&&scrollRef.current&&(preCommitScrollTopRef.current=scrollRef.current.scrollTop),previousItemsLengthRef.current=items.length),useLayoutEffect(()=>{let targetScrollTop=preCommitScrollTopRef.current;if(preCommitScrollTopRef.current=null,targetScrollTop===null)return;let scrollContainer=scrollRef.current;scrollContainer&&scrollContainer.scrollTop!==targetScrollTop&&(scrollContainer.scrollTop=targetScrollTop)});let onLoadMoreRef=useRef(onLoadMore);onLoadMoreRef.current=onLoadMore;let canLoadMoreRef=useRef(!1);if(canLoadMoreRef.current=hasMore&&!isFetchingMore&&loadMoreError===null&&!!onLoadMore,useEffect(()=>{let scrollContainer=scrollRef.current;if(!scrollContainer)return;let rafId=0,maybeLoadMore=()=>{canLoadMoreRef.current&&scrollContainer.scrollHeight-scrollContainer.scrollTop-scrollContainer.clientHeight<=200&&onLoadMoreRef.current?.()},handleScroll=()=>{cancelAnimationFrame(rafId),rafId=requestAnimationFrame(maybeLoadMore)};return scrollContainer.addEventListener(`scroll`,handleScroll,{passive:!0}),rafId=requestAnimationFrame(maybeLoadMore),()=>{scrollContainer.removeEventListener(`scroll`,handleScroll),cancelAnimationFrame(rafId)}},[items.length]),showSkeleton)return jsx(LoadingState,{ref:loadingRef});if(showError)return jsx(ErrorState,{onRetry});if(showEmpty)return jsx(EmptyListState,{isSearchMode});let compositeRows=jsx(CompositeProvider,{children:jsx(Composite,{children:jsx(`div`,{className:taxonomy_items_list_module_default.virtualContent,style:{height:virtualizer.getTotalSize()},children:virtualizer.getVirtualItems().map(virtualRow=>{if(virtualRow.index>=items.length)return jsx(`div`,{ref:virtualizer.measureElement,className:taxonomy_items_list_module_default.virtualRow,"data-index":virtualRow.index,style:{transform:`translateY(${virtualRow.start}px)`},children:jsx(LoadMoreLoadingRow,{})},virtualRow.key);let itemProps=items[virtualRow.index];return jsx(`div`,{ref:hasVariableRowHeight?virtualizer.measureElement:void 0,className:taxonomy_items_list_module_default.virtualRow,"data-index":virtualRow.index,style:{transform:`translateY(${virtualRow.start}px)`},children:jsx(TaxonomyMenuItem,{...itemProps,autoFocus:autoFocusFirst&&virtualRow.index===0,onFocusExit:virtualRow.index===0?onFocusExit:void 0,isSearchMode})},virtualRow.key)})})})});return jsxs(`div`,{ref:scrollRef,"aria-multiselectable":multiSelect||void 0,className:taxonomy_items_list_module_default.scrollContainer,id:listboxId,role:`listbox`,children:[multiSelect?compositeRows:jsx(RadioGroupWrapper,{onValueChange:handleRadioGroupChange,selectedValue,children:compositeRows}),loadMoreError!==null&&!isFetchingMore&&jsx(LoadMoreErrorRow,{onRetry})]})});export{TaxonomyItemsList as t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var ESTIMATED_BROWSE_ROW_HEIGHT=36,ESTIMATED_SEARCH_ROW_HEIGHT=44,OVERSCAN_COUNT=
|
|
1
|
+
var ESTIMATED_BROWSE_ROW_HEIGHT=36,ESTIMATED_SEARCH_ROW_HEIGHT=44,OVERSCAN_COUNT=8,SKELETON_ROW_COUNT=9,SKELETON_ROW_HEIGHT=16,LOAD_MORE_GHOST_ROW_COUNT=3,LOAD_MORE_GHOST_ROW_HEIGHT=16,LOAD_MORE_GHOST_BLOCK_HEIGHT=112,LOAD_MORE_ROOT_MARGIN_PX=200,LOAD_MORE_ERROR_MIN_VISIBLE_MS=400,BREADCRUMB_SEPARATOR=` / `,BREADCRUMB_MAX_LINES=2,BREADCRUMB_OVERFLOW_THRESHOLD_PX=2,DEFAULT_PAGE_LIMIT=100,CACHE_TTL_MS=300*1e3,ROOT_CACHE_KEY=`__root__`,BREADCRUMB_ROOT_ID=`__breadcrumb_root__`,SEARCH_DEBOUNCE_MS=500;export{BREADCRUMB_MAX_LINES,BREADCRUMB_OVERFLOW_THRESHOLD_PX,BREADCRUMB_ROOT_ID,BREADCRUMB_SEPARATOR,CACHE_TTL_MS,DEFAULT_PAGE_LIMIT,ESTIMATED_BROWSE_ROW_HEIGHT,ESTIMATED_SEARCH_ROW_HEIGHT,LOAD_MORE_ERROR_MIN_VISIBLE_MS,LOAD_MORE_GHOST_BLOCK_HEIGHT,LOAD_MORE_GHOST_ROW_COUNT,LOAD_MORE_GHOST_ROW_HEIGHT,LOAD_MORE_ROOT_MARGIN_PX,OVERSCAN_COUNT,ROOT_CACHE_KEY,SEARCH_DEBOUNCE_MS,SKELETON_ROW_COUNT,SKELETON_ROW_HEIGHT};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import"../constants.js";import{FailedOp,PickerActionType,initialState,pickerReducer}from"../state/reducer.js";import{messages}from"./messages.js";import{useCallback,useEffect,useReducer,useRef,useState}from"react";import{useIntl}from"react-intl";var EMPTY_LEVELS=[];function cacheKeyFor(parentId){return parentId??`__root__`}function useTaxonomyPickerController(itemsService,levels=EMPTY_LEVELS,eventService){let[state,dispatch]=useReducer(pickerReducer,initialState),[isOpen,setIsOpen]=useState(!1),stateRef=useRef(state);stateRef.current=state;let serviceRef=useRef(itemsService);serviceRef.current=itemsService;let eventServiceRef=useRef(eventService);eventServiceRef.current=eventService;let levelsRef=useRef(levels);levelsRef.current=levels;let searchScopeOverrideRef=useRef(void 0),{formatMessage}=useIntl(),formatMessageRef=useRef(formatMessage);formatMessageRef.current=formatMessage;let browseAbortRef=useRef(null),searchAbortRef=useRef(null),searchDebounceRef=useRef(null),hasLoadedRef=useRef(!1),cacheRef=useRef(new Map),writeCache=useCallback((parentId,items,nextMarker)=>{cacheRef.current.set(cacheKeyFor(parentId),{items,nextMarker,fetchedAt:Date.now()})},[]),readCache=useCallback(parentId=>{let key=cacheKeyFor(parentId),entry=cacheRef.current.get(key);return entry?Date.now()-entry.fetchedAt>3e5?(cacheRef.current.delete(key),null):entry:null},[]),fetchLevel=useCallback(async parentId=>{browseAbortRef.current?.abort();let controller=new AbortController;browseAbortRef.current=controller;try{let response=await serviceRef.current.getNodes(parentId,{limit:100,signal:controller.signal});if(controller.signal.aborted)return;hasLoadedRef.current=!0,dispatch({type:PickerActionType.FetchSuccess,entries:response.entries,nextMarker:response.next_marker,forParentId:parentId})}catch(error){if(controller.signal.aborted||error instanceof DOMException&&error.name===`AbortError`)return;dispatch({type:PickerActionType.FetchError,error:error instanceof Error?error.message:formatMessageRef.current(messages.initialLoadErrorFallback),forParentId:parentId})}},[]),fetchNodes=useCallback(parentId=>{dispatch({type:PickerActionType.FetchStart}),fetchLevel(parentId)},[fetchLevel]),performSearch=useCallback(async(query,explicitLevelFilter)=>{searchAbortRef.current?.abort();let controller=new AbortController;searchAbortRef.current=controller,dispatch({type:PickerActionType.SearchFetchStart,query});try{let stateLevelFilter=explicitLevelFilter===void 0?stateRef.current.levelFilter:explicitLevelFilter,override=searchScopeOverrideRef.current,parentId=override?override.parentId:stateRef.current.currentParentId,levelFilter=override?override.levelFilter:stateLevelFilter,response=await serviceRef.current.searchNodes(query,{limit:100,signal:controller.signal,parentId,levelFilter:levelFilter??void 0});if(controller.signal.aborted)return;dispatch({type:PickerActionType.SearchFetchSuccess,results:response.entries,nextMarker:response.next_marker,forQuery:query}),eventServiceRef.current?.onSearch?.(query)}catch(error){if(controller.signal.aborted||error instanceof DOMException&&error.name===`AbortError`)return;dispatch({type:PickerActionType.SearchFetchError,error:error instanceof Error?error.message:formatMessageRef.current(messages.searchErrorFallback)})}},[]),handleInputValueChange=useCallback(inputValue=>{if(dispatch({type:PickerActionType.SearchInputChange,value:inputValue,availableLevels:levelsRef.current}),searchDebounceRef.current!==null&&(clearTimeout(searchDebounceRef.current),searchDebounceRef.current=null),!inputValue){searchAbortRef.current?.abort(),dispatch({type:PickerActionType.SearchClear});return}searchDebounceRef.current=setTimeout(()=>{performSearch(inputValue)},500)},[performSearch]),handleOpenChange=useCallback(open=>{setIsOpen(open),eventServiceRef.current?.onOpenChange?.(open),open&&!hasLoadedRef.current&&fetchNodes(null),open||(searchDebounceRef.current!==null&&(clearTimeout(searchDebounceRef.current),searchDebounceRef.current=null),browseAbortRef.current?.abort(),searchAbortRef.current?.abort(),cacheRef.current.clear(),hasLoadedRef.current=!1,dispatch({type:PickerActionType.Reset}))},[fetchNodes]);useEffect(()=>{let browseAbort=browseAbortRef,searchAbort=searchAbortRef;return()=>{browseAbort.current?.abort(),searchAbort.current?.abort(),browseAbortRef.current?.abort(),searchAbortRef.current?.abort(),searchDebounceRef.current!==null&&clearTimeout(searchDebounceRef.current)}},[]);let setSearchScopeOverride=useCallback(next=>{let prev=searchScopeOverrideRef.current;if(prev?.parentId===next?.parentId&&prev?.levelFilter===next?.levelFilter||(searchScopeOverrideRef.current=next,!hasLoadedRef.current))return;let{searchInputValue}=stateRef.current;searchInputValue!==``&&(searchDebounceRef.current!==null&&(clearTimeout(searchDebounceRef.current),searchDebounceRef.current=null),performSearch(searchInputValue))},[performSearch]),drillDown=useCallback(target=>{searchDebounceRef.current!==null&&(clearTimeout(searchDebounceRef.current),searchDebounceRef.current=null),searchAbortRef.current?.abort();let previous=stateRef.current;writeCache(previous.currentParentId,previous.items,previous.itemsNextMarker);let newLevelStack=target.ancestors?[...target.ancestors.map(a=>({id:a.id,displayName:a.displayName})),{id:target.id,displayName:target.displayName}]:[...previous.levelStack,{id:target.id,displayName:target.displayName}];dispatch({type:PickerActionType.DrillDown,targetParentId:target.id,levelStack:newLevelStack}),fetchLevel(target.id),eventServiceRef.current?.onNavigate?.(target.id,newLevelStack)},[fetchLevel,writeCache]),navigateBack=useCallback(targetParentId=>{searchDebounceRef.current!==null&&(clearTimeout(searchDebounceRef.current),searchDebounceRef.current=null),browseAbortRef.current?.abort(),searchAbortRef.current?.abort();let previous=stateRef.current;writeCache(previous.currentParentId,previous.items,previous.itemsNextMarker);let targetIndex=targetParentId===null?-1:previous.levelStack.findIndex(e=>e.id===targetParentId),newLevelStack=targetIndex===-1?[]:previous.levelStack.slice(0,targetIndex+1),cached=readCache(targetParentId);cached?dispatch({type:PickerActionType.NavigateBack,targetParentId,levelStack:newLevelStack,cached:{items:cached.items,nextMarker:cached.nextMarker}}):(dispatch({type:PickerActionType.NavigateBackRefetch,targetParentId,levelStack:newLevelStack}),fetchLevel(targetParentId)),eventServiceRef.current?.onNavigate?.(targetParentId,newLevelStack)},[fetchLevel,readCache,writeCache]),loadMore=useCallback(async()=>{let{isFetchingMore,itemsNextMarker:marker,currentParentId:parentId,items:previousItems}=stateRef.current;if(isFetchingMore||marker===void 0)return;browseAbortRef.current?.abort();let controller=new AbortController;browseAbortRef.current=controller;let startedAt=Date.now();dispatch({type:PickerActionType.LoadMoreStart});try{let response=await serviceRef.current.getNodes(parentId,{limit:100,marker,signal:controller.signal});if(controller.signal.aborted||stateRef.current.currentParentId!==parentId)return;dispatch({type:PickerActionType.LoadMoreSuccess,entries:response.entries,nextMarker:response.next_marker}),writeCache(parentId,[...previousItems,...response.entries],response.next_marker)}catch(error){if(controller.signal.aborted||error instanceof DOMException&&error.name===`AbortError`||stateRef.current.currentParentId!==parentId)return;let remaining=400-(Date.now()-startedAt);if(remaining>0&&(await new Promise(resolve=>setTimeout(resolve,remaining)),controller.signal.aborted||stateRef.current.currentParentId!==parentId))return;dispatch({type:PickerActionType.LoadMoreError,error:error instanceof Error?error.message:formatMessageRef.current(messages.loadMoreErrorFallback)})}},[writeCache]),searchLoadMore=useCallback(async()=>{let{isFetchingSearchMore,searchNextMarker:marker,searchQuery:query,currentParentId:stateParentId,levelFilter:stateLevelFilter}=stateRef.current;if(isFetchingSearchMore||marker===void 0||query===``)return;let override=searchScopeOverrideRef.current,parentId=override?override.parentId:stateParentId,levelFilterParam=(override?override.levelFilter:stateLevelFilter)??void 0;searchAbortRef.current?.abort();let controller=new AbortController;searchAbortRef.current=controller;let startedAt=Date.now();dispatch({type:PickerActionType.SearchLoadMoreStart});try{let response=await serviceRef.current.searchNodes(query,{limit:100,marker,parentId,levelFilter:levelFilterParam,signal:controller.signal});if(controller.signal.aborted)return;dispatch({type:PickerActionType.SearchLoadMoreSuccess,entries:response.entries,nextMarker:response.next_marker,forQuery:query})}catch(error){if(controller.signal.aborted||error instanceof DOMException&&error.name===`AbortError`)return;let remaining=400-(Date.now()-startedAt);if(remaining>0&&(await new Promise(resolve=>setTimeout(resolve,remaining)),controller.signal.aborted))return;dispatch({type:PickerActionType.SearchLoadMoreError,error:error instanceof Error?error.message:formatMessageRef.current(messages.loadMoreErrorFallback),forQuery:query})}},[]),retry=useCallback(()=>{switch(stateRef.current.lastFailedOp){case FailedOp.Browse:fetchNodes(stateRef.current.currentParentId);break;case FailedOp.BrowseMore:loadMore();break;case FailedOp.Search:{let{searchQuery:query}=stateRef.current;query!==``&&performSearch(query);break}case FailedOp.SearchMore:searchLoadMore();break;default:break}},[fetchNodes,loadMore,performSearch,searchLoadMore]);return{state,isOpen,handleOpenChange,handleInputValueChange,actions:{drillDown,navigateBack,loadMore,searchLoadMore,setLevelFilter:useCallback(level=>{dispatch({type:PickerActionType.SetLevelFilter,level}),searchDebounceRef.current!==null&&(clearTimeout(searchDebounceRef.current),searchDebounceRef.current=null);let query=stateRef.current.searchInputValue;query&&performSearch(query,level)},[performSearch]),retry,setSearchScopeOverride}}}export{useTaxonomyPickerController};
|
|
1
|
+
import"../constants.js";import{FailedOp,PickerActionType,initialState,pickerReducer}from"../state/reducer.js";import{messages}from"./messages.js";import{useCallback,useEffect,useReducer,useRef,useState}from"react";import{useIntl}from"react-intl";var EMPTY_LEVELS=[];function cacheKeyFor(parentId){return parentId??`__root__`}function releaseController(ref,controller){ref.current===controller&&(ref.current=null)}function useTaxonomyPickerController(itemsService,levels=EMPTY_LEVELS,eventService){let[state,dispatch]=useReducer(pickerReducer,initialState),[isOpen,setIsOpen]=useState(!1),stateRef=useRef(state);stateRef.current=state;let serviceRef=useRef(itemsService);serviceRef.current=itemsService;let eventServiceRef=useRef(eventService);eventServiceRef.current=eventService;let levelsRef=useRef(levels);levelsRef.current=levels;let searchScopeOverrideRef=useRef(void 0),{formatMessage}=useIntl(),formatMessageRef=useRef(formatMessage);formatMessageRef.current=formatMessage;let browseAbortRef=useRef(null),searchAbortRef=useRef(null),searchDebounceRef=useRef(null),hasLoadedRef=useRef(!1),cacheRef=useRef(new Map),writeCache=useCallback((parentId,items,nextMarker)=>{cacheRef.current.set(cacheKeyFor(parentId),{items,nextMarker,fetchedAt:Date.now()})},[]),readCache=useCallback(parentId=>{let key=cacheKeyFor(parentId),entry=cacheRef.current.get(key);return entry?Date.now()-entry.fetchedAt>3e5?(cacheRef.current.delete(key),null):entry:null},[]),fetchLevel=useCallback(async parentId=>{browseAbortRef.current?.abort();let controller=new AbortController;browseAbortRef.current=controller;try{let response=await serviceRef.current.getNodes(parentId,{limit:100,signal:controller.signal});if(controller.signal.aborted)return;hasLoadedRef.current=!0,dispatch({type:PickerActionType.FetchSuccess,entries:response.entries,nextMarker:response.next_marker,forParentId:parentId})}catch(error){if(controller.signal.aborted||error instanceof DOMException&&error.name===`AbortError`)return;dispatch({type:PickerActionType.FetchError,error:error instanceof Error?error.message:formatMessageRef.current(messages.initialLoadErrorFallback),forParentId:parentId})}finally{releaseController(browseAbortRef,controller)}},[]),fetchNodes=useCallback(parentId=>{dispatch({type:PickerActionType.FetchStart}),fetchLevel(parentId)},[fetchLevel]),performSearch=useCallback(async(query,explicitLevelFilter)=>{searchAbortRef.current?.abort();let controller=new AbortController;searchAbortRef.current=controller,dispatch({type:PickerActionType.SearchFetchStart,query});try{let stateLevelFilter=explicitLevelFilter===void 0?stateRef.current.levelFilter:explicitLevelFilter,override=searchScopeOverrideRef.current,parentId=override?override.parentId:stateRef.current.currentParentId,levelFilter=override?override.levelFilter:stateLevelFilter,response=await serviceRef.current.searchNodes(query,{limit:100,signal:controller.signal,parentId,levelFilter:levelFilter??void 0});if(controller.signal.aborted)return;dispatch({type:PickerActionType.SearchFetchSuccess,results:response.entries,nextMarker:response.next_marker,forQuery:query}),eventServiceRef.current?.onSearch?.(query)}catch(error){if(controller.signal.aborted||error instanceof DOMException&&error.name===`AbortError`)return;dispatch({type:PickerActionType.SearchFetchError,error:error instanceof Error?error.message:formatMessageRef.current(messages.searchErrorFallback)})}finally{releaseController(searchAbortRef,controller)}},[]),handleInputValueChange=useCallback(inputValue=>{if(dispatch({type:PickerActionType.SearchInputChange,value:inputValue,availableLevels:levelsRef.current}),searchDebounceRef.current!==null&&(clearTimeout(searchDebounceRef.current),searchDebounceRef.current=null),!inputValue){searchAbortRef.current?.abort(),dispatch({type:PickerActionType.SearchClear});return}searchDebounceRef.current=setTimeout(()=>{performSearch(inputValue)},500)},[performSearch]),handleOpenChange=useCallback(open=>{setIsOpen(open),eventServiceRef.current?.onOpenChange?.(open),open&&!hasLoadedRef.current&&fetchNodes(null),open||(searchDebounceRef.current!==null&&(clearTimeout(searchDebounceRef.current),searchDebounceRef.current=null),browseAbortRef.current?.abort(),searchAbortRef.current?.abort(),cacheRef.current.clear(),hasLoadedRef.current=!1,dispatch({type:PickerActionType.Reset}))},[fetchNodes]);useEffect(()=>{let browseAbort=browseAbortRef,searchAbort=searchAbortRef;return()=>{browseAbort.current?.abort(),searchAbort.current?.abort(),browseAbortRef.current?.abort(),searchAbortRef.current?.abort(),searchDebounceRef.current!==null&&clearTimeout(searchDebounceRef.current)}},[]);let setSearchScopeOverride=useCallback(next=>{let prev=searchScopeOverrideRef.current;if(prev?.parentId===next?.parentId&&prev?.levelFilter===next?.levelFilter||(searchScopeOverrideRef.current=next,!hasLoadedRef.current))return;let{searchInputValue}=stateRef.current;searchInputValue!==``&&(searchDebounceRef.current!==null&&(clearTimeout(searchDebounceRef.current),searchDebounceRef.current=null),performSearch(searchInputValue))},[performSearch]),drillDown=useCallback(target=>{searchDebounceRef.current!==null&&(clearTimeout(searchDebounceRef.current),searchDebounceRef.current=null),searchAbortRef.current?.abort();let previous=stateRef.current;writeCache(previous.currentParentId,previous.items,previous.itemsNextMarker);let newLevelStack=target.ancestors?[...target.ancestors.map(a=>({id:a.id,displayName:a.displayName})),{id:target.id,displayName:target.displayName}]:[...previous.levelStack,{id:target.id,displayName:target.displayName}];dispatch({type:PickerActionType.DrillDown,targetParentId:target.id,levelStack:newLevelStack}),fetchLevel(target.id),eventServiceRef.current?.onNavigate?.(target.id,newLevelStack)},[fetchLevel,writeCache]),navigateBack=useCallback(targetParentId=>{searchDebounceRef.current!==null&&(clearTimeout(searchDebounceRef.current),searchDebounceRef.current=null),browseAbortRef.current?.abort(),searchAbortRef.current?.abort();let previous=stateRef.current;writeCache(previous.currentParentId,previous.items,previous.itemsNextMarker);let targetIndex=targetParentId===null?-1:previous.levelStack.findIndex(e=>e.id===targetParentId),newLevelStack=targetIndex===-1?[]:previous.levelStack.slice(0,targetIndex+1),cached=readCache(targetParentId);cached?dispatch({type:PickerActionType.NavigateBack,targetParentId,levelStack:newLevelStack,cached:{items:cached.items,nextMarker:cached.nextMarker}}):(dispatch({type:PickerActionType.NavigateBackRefetch,targetParentId,levelStack:newLevelStack}),fetchLevel(targetParentId)),eventServiceRef.current?.onNavigate?.(targetParentId,newLevelStack)},[fetchLevel,readCache,writeCache]),loadMore=useCallback(async()=>{let{isFetchingMore,itemsNextMarker:marker,currentParentId:parentId,items:previousItems}=stateRef.current;if(isFetchingMore||marker===void 0)return;browseAbortRef.current?.abort();let controller=new AbortController;browseAbortRef.current=controller;let startedAt=Date.now();dispatch({type:PickerActionType.LoadMoreStart});try{let response=await serviceRef.current.getNodes(parentId,{limit:100,marker,signal:controller.signal});if(controller.signal.aborted||stateRef.current.currentParentId!==parentId)return;dispatch({type:PickerActionType.LoadMoreSuccess,entries:response.entries,nextMarker:response.next_marker}),writeCache(parentId,[...previousItems,...response.entries],response.next_marker)}catch(error){if(controller.signal.aborted||error instanceof DOMException&&error.name===`AbortError`||stateRef.current.currentParentId!==parentId)return;let remaining=400-(Date.now()-startedAt);if(remaining>0&&(await new Promise(resolve=>setTimeout(resolve,remaining)),controller.signal.aborted||stateRef.current.currentParentId!==parentId))return;dispatch({type:PickerActionType.LoadMoreError,error:error instanceof Error?error.message:formatMessageRef.current(messages.loadMoreErrorFallback)})}finally{releaseController(browseAbortRef,controller)}},[writeCache]),searchLoadMore=useCallback(async()=>{let{isFetchingSearchMore,searchNextMarker:marker,searchQuery:query,currentParentId:stateParentId,levelFilter:stateLevelFilter}=stateRef.current;if(isFetchingSearchMore||marker===void 0||query===``)return;let override=searchScopeOverrideRef.current,parentId=override?override.parentId:stateParentId,levelFilterParam=(override?override.levelFilter:stateLevelFilter)??void 0;searchAbortRef.current?.abort();let controller=new AbortController;searchAbortRef.current=controller;let startedAt=Date.now();dispatch({type:PickerActionType.SearchLoadMoreStart});try{let response=await serviceRef.current.searchNodes(query,{limit:100,marker,parentId,levelFilter:levelFilterParam,signal:controller.signal});if(controller.signal.aborted)return;dispatch({type:PickerActionType.SearchLoadMoreSuccess,entries:response.entries,nextMarker:response.next_marker,forQuery:query})}catch(error){if(controller.signal.aborted||error instanceof DOMException&&error.name===`AbortError`)return;let remaining=400-(Date.now()-startedAt);if(remaining>0&&(await new Promise(resolve=>setTimeout(resolve,remaining)),controller.signal.aborted))return;dispatch({type:PickerActionType.SearchLoadMoreError,error:error instanceof Error?error.message:formatMessageRef.current(messages.loadMoreErrorFallback),forQuery:query})}finally{releaseController(searchAbortRef,controller)}},[]),retry=useCallback(()=>{switch(stateRef.current.lastFailedOp){case FailedOp.Browse:fetchNodes(stateRef.current.currentParentId);break;case FailedOp.BrowseMore:loadMore();break;case FailedOp.Search:{let{searchQuery:query}=stateRef.current;query!==``&&performSearch(query);break}case FailedOp.SearchMore:searchLoadMore();break;default:break}},[fetchNodes,loadMore,performSearch,searchLoadMore]);return{state,isOpen,handleOpenChange,handleInputValueChange,actions:{drillDown,navigateBack,loadMore,searchLoadMore,setLevelFilter:useCallback(level=>{dispatch({type:PickerActionType.SetLevelFilter,level}),searchDebounceRef.current!==null&&(clearTimeout(searchDebounceRef.current),searchDebounceRef.current=null);let query=stateRef.current.searchInputValue;query&&performSearch(query,level)},[performSearch]),retry,setSearchScopeOverride}}}export{useTaxonomyPickerController};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.
|
|
1
|
+
._root_1nr4f_2{border-radius:var(--bp-radius-02,.25rem);width:100%;max-width:100%;display:block;position:relative}._root_1nr4f_2:focus-visible{outline:2px solid var(--bp-color-border-focus,currentColor);outline-offset:2px}._segment_1nr4f_15{text-overflow:ellipsis;vertical-align:bottom;white-space:nowrap;max-width:7.5rem;display:inline-block;overflow:hidden}._trail_1nr4f_31{width:100%;max-width:100%;max-height:1.75rem;max-height:calc(2lh + .125rem);display:block;overflow:hidden}._ellipsisIcon_1nr4f_40{vertical-align:-.15em;width:1em;height:1em;color:var(--text-color-on-light-secondary);display:inline-block}._probe_1nr4f_49{visibility:hidden;pointer-events:none;overflow-wrap:anywhere;width:100%;position:absolute;top:0;left:0}
|
package/dist/types/lib/components/taxonomy-ancestor-breadcrumb/taxonomy-ancestor-breadcrumb.d.ts
CHANGED
|
@@ -5,12 +5,14 @@ export interface TaxonomyAncestorBreadcrumbProps {
|
|
|
5
5
|
/**
|
|
6
6
|
* Compact, non-interactive breadcrumb tailored for the taxonomy picker popover.
|
|
7
7
|
*
|
|
8
|
-
* - Each segment is capped at 120px and ellipsizes individually
|
|
8
|
+
* - Each segment is capped at 120px and ellipsizes individually via CSS.
|
|
9
9
|
* - The trail wraps to at most 2 lines. When the full path naturally exceeds
|
|
10
10
|
* 2 lines and there are 3+ ancestors, it collapses to `first / icon / last`.
|
|
11
|
-
* - The
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* - The tooltip with the full path is enabled (and the trail becomes focusable)
|
|
12
|
+
* only when some text is actually hidden. `Tooltip` is always mounted so
|
|
13
|
+
* toggling it never remounts the trail.
|
|
14
|
+
*
|
|
15
|
+
* Overflow is derived from an always-mounted, hidden probe that renders the full
|
|
16
|
+
* path, so the collapse decision does not depend on what the visible trail shows.
|
|
15
17
|
*/
|
|
16
18
|
export declare const TaxonomyAncestorBreadcrumb: import('react').NamedExoticComponent<TaxonomyAncestorBreadcrumbProps>;
|
|
@@ -7,11 +7,11 @@ export declare const ESTIMATED_BROWSE_ROW_HEIGHT = 36;
|
|
|
7
7
|
export declare const ESTIMATED_SEARCH_ROW_HEIGHT = 44;
|
|
8
8
|
/**
|
|
9
9
|
* How many off-screen rows the virtualizer renders above and below the
|
|
10
|
-
* viewport.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* viewport. Every row mounted on a list swap is paid for before first paint
|
|
11
|
+
* (search rows each carry a breadcrumb with a ResizeObserver), so keep this
|
|
12
|
+
* close to one viewport of rows.
|
|
13
13
|
*/
|
|
14
|
-
export declare const OVERSCAN_COUNT =
|
|
14
|
+
export declare const OVERSCAN_COUNT = 8;
|
|
15
15
|
/** Skeleton rows shown during initial load. Sized to fill the stable 21rem popover area. */
|
|
16
16
|
export declare const SKELETON_ROW_COUNT = 9;
|
|
17
17
|
/** Height (px) of each skeleton row Ghost placeholder. */
|
|
@@ -35,8 +35,6 @@ export declare const BREADCRUMB_MAX_LINES = 2;
|
|
|
35
35
|
* Browsers can report ~1px diffs even when content visually fits.
|
|
36
36
|
*/
|
|
37
37
|
export declare const BREADCRUMB_OVERFLOW_THRESHOLD_PX = 2;
|
|
38
|
-
/** Unicode ellipsis character used when trimming overflowed breadcrumb segments. */
|
|
39
|
-
export declare const BREADCRUMB_ELLIPSIS_CHAR = "\u2026";
|
|
40
38
|
/** Default page size for taxonomy node fetch requests. */
|
|
41
39
|
export declare const DEFAULT_PAGE_LIMIT = 100;
|
|
42
40
|
/** Stale-after threshold for cached levels; older entries are evicted on read. */
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { MetadataTaxonomyPickerProps, TaxonomyItemsService, TaxonomyPickerMode, TaxonomyValue } from '../../types';
|
|
3
|
+
type ControlledPickerProps = Omit<MetadataTaxonomyPickerProps, 'value' | 'onValueChange'>;
|
|
4
|
+
declare function ControlledPicker(props: Readonly<ControlledPickerProps>): import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
declare const _default: {
|
|
6
|
+
beforeEach(): Promise<void>;
|
|
7
|
+
parameters: {
|
|
8
|
+
docs: {
|
|
9
|
+
page: () => import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
};
|
|
11
|
+
controls: {
|
|
12
|
+
exclude: RegExp;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
title: string;
|
|
16
|
+
component: import('react').ForwardRefExoticComponent<MetadataTaxonomyPickerProps & import('react').RefAttributes<HTMLInputElement>>;
|
|
17
|
+
render: (args: MetadataTaxonomyPickerProps & import('react').RefAttributes<HTMLInputElement>) => import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
args: {
|
|
19
|
+
itemsService: TaxonomyItemsService;
|
|
20
|
+
mode: TaxonomyPickerMode.MultiLevel;
|
|
21
|
+
multiSelect: false;
|
|
22
|
+
placeholder: string;
|
|
23
|
+
};
|
|
24
|
+
decorators: ((Story: import('storybook/internal/csf').PartialStoryFn<import('@storybook/react').ReactRenderer, {
|
|
25
|
+
mode: TaxonomyPickerMode;
|
|
26
|
+
multiSelect: boolean;
|
|
27
|
+
value: TaxonomyValue[];
|
|
28
|
+
onValueChange: (values: TaxonomyValue[]) => void;
|
|
29
|
+
itemsService: TaxonomyItemsService;
|
|
30
|
+
eventService?: import('../../types').TaxonomyPickerEventService | undefined;
|
|
31
|
+
levels?: import('../../types').TaxonomyLevel[] | undefined;
|
|
32
|
+
placeholder?: string | undefined;
|
|
33
|
+
disabled?: boolean | undefined;
|
|
34
|
+
label?: import('react').ReactNode;
|
|
35
|
+
containerClassName?: string | undefined;
|
|
36
|
+
portalElement?: (HTMLElement | (() => HTMLElement | null) | null) | undefined;
|
|
37
|
+
ref?: import('react').LegacyRef<HTMLInputElement> | undefined;
|
|
38
|
+
key?: import('react').Key | null | undefined;
|
|
39
|
+
}>) => import("react/jsx-runtime").JSX.Element)[];
|
|
40
|
+
};
|
|
41
|
+
export default _default;
|
|
42
|
+
type Story = StoryObj<typeof ControlledPicker>;
|
|
43
|
+
/**
|
|
44
|
+
* A full page of results with breadcrumbs replaces the skeleton promptly after the
|
|
45
|
+
* response arrives: only the visible window is mounted, and breadcrumb measuring
|
|
46
|
+
* does not feed back into ResizeObserver.
|
|
47
|
+
*/
|
|
48
|
+
export declare const ManySearchResultsRenderPromptly: Story;
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@box/metadata-taxonomy-picker",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.5",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"@ariakit/react": "^0.4.21",
|
|
7
|
-
"@box/blueprint-web": "^17.19.
|
|
8
|
-
"@box/blueprint-web-assets": "^5.11.
|
|
7
|
+
"@box/blueprint-web": "^17.19.1",
|
|
8
|
+
"@box/blueprint-web-assets": "^5.11.4",
|
|
9
9
|
"@tanstack/react-virtual": "^3.10.8",
|
|
10
10
|
"react": "^18.0.0",
|
|
11
11
|
"react-dom": "^18.0.0",
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@ariakit/react": "^0.4.21",
|
|
16
|
-
"@box/blueprint-web": "^17.19.
|
|
17
|
-
"@box/blueprint-web-assets": "^5.11.
|
|
18
|
-
"@box/eslint-plugin-blueprint": "2.2.
|
|
19
|
-
"@box/storybook-utils": "1.3.
|
|
16
|
+
"@box/blueprint-web": "^17.19.1",
|
|
17
|
+
"@box/blueprint-web-assets": "^5.11.4",
|
|
18
|
+
"@box/eslint-plugin-blueprint": "2.2.26",
|
|
19
|
+
"@box/storybook-utils": "1.3.4",
|
|
20
20
|
"@tanstack/react-virtual": "^3.10.8",
|
|
21
21
|
"react-intl": "^6.4.2"
|
|
22
22
|
},
|