@vielzeug/focus 26.10.1 → 26.10.2

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/focus.cjs CHANGED
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@vielzeug/keymap");var FocusError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},FocusConfigError=class extends FocusError{},t={first:[`Home`],last:[`End`],next:[`ArrowRight`],nextRow:[`ArrowDown`],prev:[`ArrowLeft`],prevRow:[`ArrowUp`]},n={...t,next:[`ArrowLeft`],prev:[`ArrowRight`]},r=[`first`,`last`,`next`,`nextRow`,`prev`,`prevRow`],resolve=(e,t)=>typeof e==`function`?e():e??t,createGridNavigation=i=>{let a=new Map;for(let e of r)for(let t of i.keys?.[e]??[]){let n=a.get(t);if(n&&n!==e)throw new FocusConfigError(`Key "${t}" is assigned to both ${n} and ${e}`);a.set(t,e)}let o=-1,resolveColumns=()=>{let e=resolve(i.columns,1);if(!Number.isInteger(e)||e<1)throw new FocusConfigError(`columns must resolve to a positive integer, got: ${e}`);return e},activeIndex=e=>{let t=i.getActiveIndex?.();return t!==void 0&&(o=t>=0&&t<e.length?t:-1),o},commitIndex=(e,t,n,r)=>(o=t,Object.freeze({action:n,event:r,index:t,item:e[t]})),navigateWithItems=(e,t,n)=>{if(!e.length)return o=-1,null;if(t===`first`)return commitIndex(e,0,t,n);if(t===`last`)return commitIndex(e,e.length-1,t,n);let r=resolveColumns(),a=t===`next`?1:t===`prev`?-1:t===`nextRow`?r:-r,s=activeIndex(e),c=s<0?a>0?0:e.length-1:s+a;if(i.loop)return commitIndex(e,(c%e.length+e.length)%e.length,t,n);let l=Math.min(e.length-1,Math.max(0,c));return l===s?null:commitIndex(e,l,t,n)},resolveKeyAction=a=>{let o=resolve(i.direction,`ltr`)===`rtl`?n:t;for(let t of r)if((i.keys?.[t]??o[t]).some(t=>(0,e.matchKey)(a,t)))return t},handleKeydown=e=>{if(e.defaultPrevented||e.isComposing||resolve(i.disabled,!1))return null;let t=resolveKeyAction(e);if(!t)return null;let n=navigateWithItems(i.getItems(),t,e);return e.preventDefault(),Object.freeze({change:n,handled:!0})},navigate=e=>navigateWithItems(i.getItems(),e),set=e=>{let t=i.getItems();if(!Number.isInteger(e)||e<0||!t.length){o=-1;return}o=Math.min(e,t.length-1)},reset=()=>{o=-1},getIndex=()=>activeIndex(i.getItems()),getActiveItem=()=>{let e=i.getItems(),t=activeIndex(e);return t>=0?e[t]:void 0};return{getActiveItem,getIndex,handleKeydown,navigate,reset,set}},i={both:{first:[`Home`],last:[`End`],next:[`ArrowDown`,`ArrowRight`],prev:[`ArrowUp`,`ArrowLeft`]},horizontal:{first:[`Home`],last:[`End`],next:[`ArrowRight`],prev:[`ArrowLeft`]},vertical:{first:[`Home`],last:[`End`],next:[`ArrowDown`],prev:[`ArrowUp`]}},a={both:{first:[`Home`],last:[`End`],next:[`ArrowDown`,`ArrowLeft`],prev:[`ArrowUp`,`ArrowRight`]},horizontal:{first:[`Home`],last:[`End`],next:[`ArrowLeft`],prev:[`ArrowRight`]},vertical:i.vertical},o=500,findForward=(e,t,n)=>{for(let r=t;r<e.length;r++)if(n(e[r],r))return r;return-1},findBackward=(e,t,n)=>{for(let r=t;r>=0;r--)if(n(e[r],r))return r;return-1},resolveTypeaheadDelay=e=>{if(e===void 0)return o;if(!Number.isFinite(e)||e<=0)throw new FocusConfigError(`Typeahead delay must be a positive finite number`);return e},createListNavigation=t=>{let n=resolveTypeaheadDelay(t.typeahead?.delayMs),r=new Map;for(let e of[`next`,`prev`,`first`,`last`])for(let n of t.keys?.[e]??[]){let t=r.get(n);if(t&&t!==e)throw new FocusConfigError(`Key "${n}" is assigned to both ${t} and ${e}`);r.set(n,e)}let o=-1,s=``,c=0,isItemDisabled=(e,n)=>t.isItemDisabled?.(e,n)??!1,isUsableIndex=(e,t)=>t>=0&&t<e.length&&!isItemDisabled(e[t],t),normalizeIndex=e=>(isUsableIndex(e,o)||(o=-1),o),resetTypeahead=()=>{s=``,c=0},commitIndex=(e,t,n,r)=>isUsableIndex(e,t)?(o=t,Object.freeze({action:n,event:r,index:t,item:e[t]})):(o=-1,null),findEnabledIndex=(e,t,n)=>n===`forward`?findForward(e,t,(e,t)=>!isItemDisabled(e,t)):findBackward(e,t,(e,t)=>!isItemDisabled(e,t)),move=(e,n,r)=>{let i=normalizeIndex(e);if(!e.length)return o=-1,null;let a=i<0?n===`forward`?0:e.length-1:n===`forward`?i+1:i-1,s=findEnabledIndex(e,a,n),c=n===`forward`?`next`:`prev`;if(s>=0)return commitIndex(e,s,c,r);if(t.loop){let t=n===`forward`?0:e.length-1,i=findEnabledIndex(e,t,n);if(i>=0)return commitIndex(e,i,c,r)}return null},navigateWithItems=(e,t,n)=>(resetTypeahead(),normalizeIndex(e),e.length?t===`first`?commitIndex(e,findEnabledIndex(e,0,`forward`),t,n):t===`last`?commitIndex(e,findEnabledIndex(e,e.length-1,`backward`),t,n):move(e,t===`next`?`forward`:`backward`,n):(o=-1,null)),findTypeaheadMatch=(e,n,r)=>{let i=normalizeIndex(e),a=i<0?0:r?i:i+1;for(let r=0;r<e.length;r++){let i=(a+r)%e.length,o=e[i];if(!isItemDisabled(o,i)&&t.typeahead?.getLabel(o,i).toLocaleLowerCase().startsWith(n))return i}return-1},handleTypeahead=(e,r)=>{if(!t.typeahead||e.key.length!==1||e.ctrlKey||e.altKey||e.metaKey)return null;let i=performance.now(),a=e.key.toLocaleLowerCase();i-c>=n&&(s=``),c=i;let o=s+a;s=o.length>1&&[...o].every(e=>e===a)?a:o;let l=findTypeaheadMatch(r,s,s.length>1);return l<0&&s.length>1&&(s=a,l=findTypeaheadMatch(r,s,!1)),l<0?null:commitIndex(r,l,`typeahead`,e)},set=e=>{let n=t.getItems();if(normalizeIndex(n),resetTypeahead(),!Number.isInteger(e)||e<0||!n.length)return o=-1,o;let r=Math.min(e,n.length-1);return isUsableIndex(n,r)?(o=r,o):(o=-1,o)},navigate=e=>navigateWithItems(t.getItems(),e),getActiveItem=()=>{let e=t.getItems(),n=normalizeIndex(e);return n>=0?e[n]:void 0},reset=()=>{o=-1,resetTypeahead()},getIndex=()=>normalizeIndex(t.getItems()),resolve=(e,t)=>typeof e==`function`?e():e??t,resolveKeyAction=n=>{let r=t.keys,o=(resolve(t.direction,`ltr`)===`rtl`?a:i)[resolve(t.orientation,`vertical`)];for(let t of[`next`,`prev`,`first`,`last`])if((r?.[t]??o[t]).some(t=>(0,e.matchKey)(n,t)))return t},handleKeydown=e=>{if(e.defaultPrevented||e.isComposing||resolve(t.disabled,!1))return null;let n=t.getItems();normalizeIndex(n);let r=resolveKeyAction(e);if(r){let t=navigateWithItems(n,r,e);return e.preventDefault(),Object.freeze({change:t,handled:!0})}let i=handleTypeahead(e,n);return i?(t.typeahead?.preventDefault&&e.preventDefault(),Object.freeze({change:i,handled:!0})):null};return{getActiveItem,getIndex,handleKeydown,navigate,reset,set}},resolveTarget=e=>typeof e==`function`?e():e,getDeepActiveElement=e=>{let t=e.activeElement;for(;t instanceof HTMLElement&&t.shadowRoot?.activeElement;)t=t.shadowRoot.activeElement;return t},isConnected=e=>e.isConnected,isDisabled=e=>e instanceof HTMLElement&&`disabled`in e&&!!e.disabled,isInert=e=>e.closest(`[inert]`)?!0:e instanceof HTMLElement&&e.inert,canRestoreTo=e=>isConnected(e)&&!isDisabled(e)&&!isInert(e),focusAndVerify=(e,t)=>{try{return e.focus({preventScroll:t}),getDeepActiveElement(e.ownerDocument)===e}catch{return!1}},restoreFocus=(e,t={})=>{let n;try{n=resolveTarget(e)}catch{n=null}if(n&&canRestoreTo(n)&&focusAndVerify(n,t.preventScroll))return!0;if(!t.fallback)return!1;let r;try{r=resolveTarget(t.fallback)}catch{return!1}return!r||!canRestoreTo(r)?!1:focusAndVerify(r,t.preventScroll)},rescueFocus=(e,t={})=>{let n=getDeepActiveElement(document);return n&&n!==document.body?!1:restoreFocus(e,t)},captureFocus=(e={})=>{let t=getDeepActiveElement(document),n=!e.signal?.aborted;n||(t=null);let cancel=()=>{n=!1,t=null};return e.signal?.aborted||e.signal?.addEventListener(`abort`,cancel,{once:!0}),()=>{if(!n)return!1;n=!1,e.signal?.removeEventListener(`abort`,cancel);let r=t;return t=null,r?restoreFocus(r,{fallback:e.fallback,preventScroll:e.preventScroll}):e.fallback?restoreFocus(e.fallback,{preventScroll:e.preventScroll}):!1}};exports.FocusConfigError=FocusConfigError,exports.FocusError=FocusError,exports.captureFocus=captureFocus,exports.createGridNavigation=createGridNavigation,exports.createListNavigation=createListNavigation,exports.rescueFocus=rescueFocus,exports.restoreFocus=restoreFocus;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@vielzeug/keymap");var FocusError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},FocusConfigError=class extends FocusError{},t={first:[`Home`],last:[`End`],next:[`ArrowRight`],nextRow:[`ArrowDown`],prev:[`ArrowLeft`],prevRow:[`ArrowUp`]},n={...t,next:[`ArrowLeft`],prev:[`ArrowRight`]},r=[`first`,`last`,`next`,`nextRow`,`prev`,`prevRow`],resolve$1=(e,t)=>typeof e==`function`?e():e??t,createGridNavigation=i=>{let a=new Map;for(let e of r)for(let t of i.keys?.[e]??[]){let n=a.get(t);if(n&&n!==e)throw new FocusConfigError(`Key "${t}" is assigned to both ${n} and ${e}`);a.set(t,e)}let o=-1,resolveColumns=()=>{let e=resolve$1(i.columns,1);if(!Number.isInteger(e)||e<1)throw new FocusConfigError(`columns must resolve to a positive integer, got: ${e}`);return e},activeIndex=e=>{let t=i.getActiveIndex?.();return t!==void 0&&(o=t>=0&&t<e.length?t:-1),o},commitIndex=(e,t,n,r)=>(o=t,Object.freeze({action:n,event:r,index:t,item:e[t]})),navigateWithItems=(e,t,n)=>{if(!e.length)return o=-1,null;if(t===`first`)return commitIndex(e,0,t,n);if(t===`last`)return commitIndex(e,e.length-1,t,n);let r=resolveColumns(),a=t===`next`?1:t===`prev`?-1:t===`nextRow`?r:-r,s=activeIndex(e),c=s<0?a>0?0:e.length-1:s+a;if(i.loop)return commitIndex(e,(c%e.length+e.length)%e.length,t,n);let l=Math.min(e.length-1,Math.max(0,c));return l===s?null:commitIndex(e,l,t,n)},resolveKeyAction=a=>{let o=resolve$1(i.direction,`ltr`)===`rtl`?n:t;for(let t of r)if((i.keys?.[t]??o[t]).some(t=>(0,e.matchKey)(a,t)))return t},handleKeydown=e=>{if(e.defaultPrevented||e.isComposing||resolve$1(i.disabled,!1))return null;let t=resolveKeyAction(e);if(!t)return null;let n=navigateWithItems(i.getItems(),t,e);return e.preventDefault(),Object.freeze({change:n,handled:!0})},navigate=e=>navigateWithItems(i.getItems(),e),set=e=>{let t=i.getItems();if(!Number.isInteger(e)||e<0||!t.length){o=-1;return}o=Math.min(e,t.length-1)},reset=()=>{o=-1},getIndex=()=>activeIndex(i.getItems()),getActiveItem=()=>{let e=i.getItems(),t=activeIndex(e);return t>=0?e[t]:void 0};return{getActiveItem,getIndex,handleKeydown,navigate,reset,set}},i={both:{first:[`Home`],last:[`End`],next:[`ArrowDown`,`ArrowRight`],prev:[`ArrowUp`,`ArrowLeft`]},horizontal:{first:[`Home`],last:[`End`],next:[`ArrowRight`],prev:[`ArrowLeft`]},vertical:{first:[`Home`],last:[`End`],next:[`ArrowDown`],prev:[`ArrowUp`]}},a={both:{first:[`Home`],last:[`End`],next:[`ArrowDown`,`ArrowLeft`],prev:[`ArrowUp`,`ArrowRight`]},horizontal:{first:[`Home`],last:[`End`],next:[`ArrowLeft`],prev:[`ArrowRight`]},vertical:i.vertical},o=500,findForward=(e,t,n)=>{for(let r=t;r<e.length;r++)if(n(e[r],r))return r;return-1},findBackward=(e,t,n)=>{for(let r=t;r>=0;r--)if(n(e[r],r))return r;return-1},resolveTypeaheadDelay=e=>{if(e===void 0)return o;if(!Number.isFinite(e)||e<=0)throw new FocusConfigError(`Typeahead delay must be a positive finite number`);return e},createListNavigation=t=>{let n=resolveTypeaheadDelay(t.typeahead?.delayMs),r=new Map;for(let e of[`next`,`prev`,`first`,`last`])for(let n of t.keys?.[e]??[]){let t=r.get(n);if(t&&t!==e)throw new FocusConfigError(`Key "${n}" is assigned to both ${t} and ${e}`);r.set(n,e)}let o=-1,s=``,c=0,isItemDisabled=(e,n)=>t.isItemDisabled?.(e,n)??!1,isUsableIndex=(e,t)=>t>=0&&t<e.length&&!isItemDisabled(e[t],t),normalizeIndex=e=>(isUsableIndex(e,o)||(o=-1),o),resetTypeahead=()=>{s=``,c=0},commitIndex=(e,t,n,r)=>isUsableIndex(e,t)?(o=t,Object.freeze({action:n,event:r,index:t,item:e[t]})):(o=-1,null),findEnabledIndex=(e,t,n)=>n===`forward`?findForward(e,t,(e,t)=>!isItemDisabled(e,t)):findBackward(e,t,(e,t)=>!isItemDisabled(e,t)),move=(e,n,r)=>{let i=normalizeIndex(e);if(!e.length)return o=-1,null;let a=i<0?n===`forward`?0:e.length-1:n===`forward`?i+1:i-1,s=findEnabledIndex(e,a,n),c=n===`forward`?`next`:`prev`;if(s>=0)return commitIndex(e,s,c,r);if(t.loop){let t=n===`forward`?0:e.length-1,i=findEnabledIndex(e,t,n);if(i>=0)return commitIndex(e,i,c,r)}return null},navigateWithItems=(e,t,n)=>(resetTypeahead(),normalizeIndex(e),e.length?t===`first`?commitIndex(e,findEnabledIndex(e,0,`forward`),t,n):t===`last`?commitIndex(e,findEnabledIndex(e,e.length-1,`backward`),t,n):move(e,t===`next`?`forward`:`backward`,n):(o=-1,null)),findTypeaheadMatch=(e,n,r)=>{let i=normalizeIndex(e),a=i<0?0:r?i:i+1;for(let r=0;r<e.length;r++){let i=(a+r)%e.length,o=e[i];if(!isItemDisabled(o,i)&&t.typeahead?.getLabel(o,i).toLocaleLowerCase().startsWith(n))return i}return-1},handleTypeahead=(e,r)=>{if(!t.typeahead||e.key.length!==1||e.ctrlKey||e.altKey||e.metaKey)return null;let i=performance.now(),a=e.key.toLocaleLowerCase();i-c>=n&&(s=``),c=i;let o=s+a;s=o.length>1&&[...o].every(e=>e===a)?a:o;let l=findTypeaheadMatch(r,s,s.length>1);return l<0&&s.length>1&&(s=a,l=findTypeaheadMatch(r,s,!1)),l<0?null:commitIndex(r,l,`typeahead`,e)},set=e=>{let n=t.getItems();if(normalizeIndex(n),resetTypeahead(),!Number.isInteger(e)||e<0||!n.length)return o=-1,o;let r=Math.min(e,n.length-1);return isUsableIndex(n,r)?(o=r,o):(o=-1,o)},navigate=e=>navigateWithItems(t.getItems(),e),getActiveItem=()=>{let e=t.getItems(),n=normalizeIndex(e);return n>=0?e[n]:void 0},reset=()=>{o=-1,resetTypeahead()},getIndex=()=>normalizeIndex(t.getItems()),resolve=(e,t)=>typeof e==`function`?e():e??t,resolveKeyAction=n=>{let r=t.keys,o=(resolve(t.direction,`ltr`)===`rtl`?a:i)[resolve(t.orientation,`vertical`)];for(let t of[`next`,`prev`,`first`,`last`])if((r?.[t]??o[t]).some(t=>(0,e.matchKey)(n,t)))return t},handleKeydown=e=>{if(e.defaultPrevented||e.isComposing||resolve(t.disabled,!1))return null;let n=t.getItems();normalizeIndex(n);let r=resolveKeyAction(e);if(r){let t=navigateWithItems(n,r,e);return e.preventDefault(),Object.freeze({change:t,handled:!0})}let i=handleTypeahead(e,n);return i?(t.typeahead?.preventDefault&&e.preventDefault(),Object.freeze({change:i,handled:!0})):null};return{getActiveItem,getIndex,handleKeydown,navigate,reset,set}},resolve=(e,t)=>typeof e==`function`?e():e??t,createQuickLookGrid=e=>{let activeIndex=()=>e.getItems().indexOf(document.activeElement),measuredColumns=()=>{let t=e.getItems(),n=t.indexOf(document.activeElement);if(n<0)return 1;let r=t[n]?.getBoundingClientRect().top??0;return Math.max(1,t.filter(e=>Math.abs(e.getBoundingClientRect().top-r)<1).length)},t=createGridNavigation({columns:e.columns??measuredColumns,getActiveIndex:e.getActiveIndex??activeIndex,getItems:e.getItems,loop:e.loop}),resolveItem=t=>{let n=e.getItems(),r=t.target instanceof HTMLElement?t.target:null,i=r===null?-1:n.findIndex(e=>e===r||e.contains(r));if(i>=0)return{index:i,item:n[i]};let a=(e.getActiveIndex??activeIndex)();return{index:a,item:a>=0?n[a]:void 0}},handleKeydown=n=>{if(n.defaultPrevented||n.isComposing||resolve(e.disabled,!1)||n.ctrlKey||n.metaKey||n.altKey)return;if(n.key===(e.inspectKey??` `)){let{index:t,item:r}=resolveItem(n);if(!r)return;n.preventDefault(),n.stopPropagation(),e.onInspect(r,t);return}let r=t.handleKeydown(n);r?.change&&r.change.item.focus()};return{handleKeydown}},resolveTarget=e=>typeof e==`function`?e():e,getDeepActiveElement=e=>{let t=e.activeElement;for(;t instanceof HTMLElement&&t.shadowRoot?.activeElement;)t=t.shadowRoot.activeElement;return t},isConnected=e=>e.isConnected,isDisabled=e=>e instanceof HTMLElement&&`disabled`in e&&!!e.disabled,isInert=e=>e.closest(`[inert]`)?!0:e instanceof HTMLElement&&e.inert,canRestoreTo=e=>isConnected(e)&&!isDisabled(e)&&!isInert(e),focusAndVerify=(e,t)=>{try{return e.focus({preventScroll:t}),getDeepActiveElement(e.ownerDocument)===e}catch{return!1}},restoreFocus=(e,t={})=>{let n;try{n=resolveTarget(e)}catch{n=null}if(n&&canRestoreTo(n)&&focusAndVerify(n,t.preventScroll))return!0;if(!t.fallback)return!1;let r;try{r=resolveTarget(t.fallback)}catch{return!1}return!r||!canRestoreTo(r)?!1:focusAndVerify(r,t.preventScroll)},rescueFocus=(e,t={})=>{let n=getDeepActiveElement(document);return n&&n!==document.body?!1:restoreFocus(e,t)},captureFocus=(e={})=>{let t=getDeepActiveElement(document),n=!e.signal?.aborted;n||(t=null);let cancel=()=>{n=!1,t=null};return e.signal?.aborted||e.signal?.addEventListener(`abort`,cancel,{once:!0}),()=>{if(!n)return!1;n=!1,e.signal?.removeEventListener(`abort`,cancel);let r=t;return t=null,r?restoreFocus(r,{fallback:e.fallback,preventScroll:e.preventScroll}):e.fallback?restoreFocus(e.fallback,{preventScroll:e.preventScroll}):!1}};exports.FocusConfigError=FocusConfigError,exports.FocusError=FocusError,exports.captureFocus=captureFocus,exports.createGridNavigation=createGridNavigation,exports.createListNavigation=createListNavigation,exports.createQuickLookGrid=createQuickLookGrid,exports.rescueFocus=rescueFocus,exports.restoreFocus=restoreFocus;
2
2
  //# sourceMappingURL=focus.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"focus.cjs","names":[],"sources":["../src/errors.ts","../src/grid-navigation.ts","../src/list-navigation.ts","../src/restore-focus.ts"],"sourcesContent":["/** Base class for all focus errors. Use `instanceof FocusError` to catch any focus-originated error. */\nexport class FocusError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when navigation options are invalid, such as a non-positive typeahead delay or a key assigned to two actions. */\nexport class FocusConfigError extends FocusError {}\n","import { matchKey } from '@vielzeug/keymap';\n\nimport { FocusConfigError } from './errors.js';\nimport type { MaybeGetter } from './list-navigation.js';\n\nexport type GridNavigationAction = 'first' | 'last' | 'next' | 'nextRow' | 'prev' | 'prevRow';\n\nexport type GridNavigationChange<T> = {\n readonly action: GridNavigationAction;\n readonly event?: KeyboardEvent;\n readonly index: number;\n readonly item: T;\n};\n\nexport type GridKeyResult<T> = {\n readonly change: GridNavigationChange<T> | null;\n readonly handled: true;\n};\n\n/**\n * Columns per row: a fixed count, or a getter resolved on every navigation. Responsive grids\n * read a media query or computed style; measured grids read the rendered row length.\n */\nexport type GridColumns = number | (() => number);\n\nexport type GridNavigationOptions<T> = {\n /** Columns per row, resolved on every navigation. */\n columns: GridColumns;\n direction?: MaybeGetter<'ltr' | 'rtl'>;\n disabled?: MaybeGetter<boolean>;\n /**\n * Reads the active index from the outside (usually the index of `document.activeElement`\n * among the items), so focus moved by pointer or Tab stays in sync. When omitted, the\n * navigation tracks the index internally through `set()`.\n */\n getActiveIndex?: () => number;\n getItems: () => readonly T[];\n keys?: Partial<Record<GridNavigationAction, readonly string[]>>;\n /** Wrap moves around the grid's edges by flat index. Default clamps at the edges. */\n loop?: boolean;\n};\n\nexport type GridNavigation<T> = {\n getActiveItem(): T | undefined;\n getIndex(): number;\n handleKeydown(event: KeyboardEvent): GridKeyResult<T> | null;\n navigate(action: GridNavigationAction): GridNavigationChange<T> | null;\n reset(): void;\n set(index: number): void;\n};\n\nconst DEFAULT_KEYS: Record<GridNavigationAction, readonly string[]> = {\n first: ['Home'],\n last: ['End'],\n next: ['ArrowRight'],\n nextRow: ['ArrowDown'],\n prev: ['ArrowLeft'],\n prevRow: ['ArrowUp'],\n};\n\nconst DEFAULT_KEYS_RTL: typeof DEFAULT_KEYS = {\n ...DEFAULT_KEYS,\n next: ['ArrowLeft'],\n prev: ['ArrowRight'],\n};\n\nconst GRID_ACTIONS = ['first', 'last', 'next', 'nextRow', 'prev', 'prevRow'] as const;\n\nconst resolve = <V>(value: MaybeGetter<V> | undefined, fallback: V): V =>\n typeof value === 'function' ? (value as () => V)() : (value ?? fallback);\n\n/**\n * Keyboard navigation over a two-dimensional grid of items: horizontal arrows step one\n * item, vertical arrows step one row (the column count), Home/End jump to the ends.\n *\n * Unlike `createListNavigation`, items are not skipped when disabled: skipping in two\n * dimensions would break row alignment. Focus (or otherwise activate) the item reported\n * by the returned change; `handleKeydown` calls `preventDefault()` for every recognized\n * key so unhandled arrows never scroll the page.\n */\nexport const createGridNavigation = <T>(options: GridNavigationOptions<T>): GridNavigation<T> => {\n const owners = new Map<string, GridNavigationAction>();\n\n for (const action of GRID_ACTIONS) {\n for (const key of options.keys?.[action] ?? []) {\n const owner = owners.get(key);\n if (owner && owner !== action)\n throw new FocusConfigError(`Key \"${key}\" is assigned to both ${owner} and ${action}`);\n owners.set(key, action);\n }\n }\n\n let index = -1;\n\n const resolveColumns = (): number => {\n const columns = resolve(options.columns, 1);\n if (!Number.isInteger(columns) || columns < 1)\n throw new FocusConfigError(`columns must resolve to a positive integer, got: ${columns}`);\n return columns;\n };\n\n const activeIndex = (items: readonly T[]): number => {\n const derived = options.getActiveIndex?.();\n if (derived !== undefined) index = derived >= 0 && derived < items.length ? derived : -1;\n return index;\n };\n\n const commitIndex = (\n items: readonly T[],\n nextIndex: number,\n action: GridNavigationAction,\n event?: KeyboardEvent,\n ): GridNavigationChange<T> => {\n index = nextIndex;\n return Object.freeze({ action, event, index: nextIndex, item: items[nextIndex] });\n };\n\n const navigateWithItems = (\n items: readonly T[],\n action: GridNavigationAction,\n event?: KeyboardEvent,\n ): GridNavigationChange<T> | null => {\n if (!items.length) {\n index = -1;\n return null;\n }\n\n if (action === 'first') return commitIndex(items, 0, action, event);\n if (action === 'last') return commitIndex(items, items.length - 1, action, event);\n\n const columns = resolveColumns();\n const offset = action === 'next' ? 1 : action === 'prev' ? -1 : action === 'nextRow' ? columns : -columns;\n const current = activeIndex(items);\n const base = current < 0 ? (offset > 0 ? 0 : items.length - 1) : current + offset;\n\n if (options.loop) {\n return commitIndex(items, ((base % items.length) + items.length) % items.length, action, event);\n }\n\n const clamped = Math.min(items.length - 1, Math.max(0, base));\n if (clamped === current) return null;\n\n return commitIndex(items, clamped, action, event);\n };\n\n const resolveKeyAction = (event: KeyboardEvent): GridNavigationAction | undefined => {\n const keyTable = resolve(options.direction, 'ltr') === 'rtl' ? DEFAULT_KEYS_RTL : DEFAULT_KEYS;\n\n for (const action of GRID_ACTIONS) {\n if ((options.keys?.[action] ?? keyTable[action]).some((pattern) => matchKey(event, pattern))) return action;\n }\n\n return undefined;\n };\n\n const handleKeydown = (event: KeyboardEvent): GridKeyResult<T> | null => {\n if (event.defaultPrevented || event.isComposing || resolve(options.disabled, false)) return null;\n\n const action = resolveKeyAction(event);\n if (!action) return null;\n\n const change = navigateWithItems(options.getItems(), action, event);\n event.preventDefault();\n return Object.freeze({ change, handled: true });\n };\n\n const navigate = (action: GridNavigationAction): GridNavigationChange<T> | null =>\n navigateWithItems(options.getItems(), action);\n\n const set = (nextIndex: number): void => {\n const items = options.getItems();\n\n if (!Number.isInteger(nextIndex) || nextIndex < 0 || !items.length) {\n index = -1;\n return;\n }\n\n index = Math.min(nextIndex, items.length - 1);\n };\n\n const reset = (): void => {\n index = -1;\n };\n\n const getIndex = (): number => activeIndex(options.getItems());\n\n const getActiveItem = (): T | undefined => {\n const items = options.getItems();\n const current = activeIndex(items);\n return current >= 0 ? items[current] : undefined;\n };\n\n return {\n getActiveItem,\n getIndex,\n handleKeydown,\n navigate,\n reset,\n set,\n };\n};\n","import { matchKey } from '@vielzeug/keymap';\n\nimport { FocusConfigError } from './errors.js';\n\nexport type ListNavigationAction = 'first' | 'last' | 'next' | 'prev';\nexport type ListKeyAction = ListNavigationAction | 'typeahead';\n\nexport type ListNavigationChange<T> = {\n readonly action: ListKeyAction;\n readonly event?: KeyboardEvent;\n readonly index: number;\n readonly item: T;\n};\n\nexport type ListKeyResult<T> = {\n readonly change: ListNavigationChange<T> | null;\n readonly handled: true;\n};\n\nexport type ListNavigationTypeaheadOptions<T> = {\n delayMs?: number;\n getLabel: (item: T, index: number) => string;\n preventDefault?: boolean;\n};\n\nexport type MaybeGetter<T> = T | (() => T);\n\nexport type ListNavigationOptions<T> = {\n direction?: MaybeGetter<'ltr' | 'rtl'>;\n disabled?: MaybeGetter<boolean>;\n getItems: () => readonly T[];\n isItemDisabled?: (item: T, index: number) => boolean;\n keys?: Partial<Record<ListNavigationAction, readonly string[]>>;\n loop?: boolean;\n orientation?: MaybeGetter<'both' | 'horizontal' | 'vertical'>;\n typeahead?: ListNavigationTypeaheadOptions<T>;\n};\n\nexport type ListNavigation<T> = {\n getActiveItem(): T | undefined;\n getIndex(): number;\n handleKeydown(event: KeyboardEvent): ListKeyResult<T> | null;\n navigate(action: ListNavigationAction): ListNavigationChange<T> | null;\n reset(): void;\n set(index: number): number;\n};\n\nconst DEFAULT_KEYS: Record<'both' | 'horizontal' | 'vertical', Record<ListNavigationAction, readonly string[]>> = {\n both: { first: ['Home'], last: ['End'], next: ['ArrowDown', 'ArrowRight'], prev: ['ArrowUp', 'ArrowLeft'] },\n horizontal: { first: ['Home'], last: ['End'], next: ['ArrowRight'], prev: ['ArrowLeft'] },\n vertical: { first: ['Home'], last: ['End'], next: ['ArrowDown'], prev: ['ArrowUp'] },\n};\n\nconst DEFAULT_KEYS_RTL: typeof DEFAULT_KEYS = {\n both: { first: ['Home'], last: ['End'], next: ['ArrowDown', 'ArrowLeft'], prev: ['ArrowUp', 'ArrowRight'] },\n horizontal: { first: ['Home'], last: ['End'], next: ['ArrowLeft'], prev: ['ArrowRight'] },\n vertical: DEFAULT_KEYS.vertical,\n};\n\nconst DEFAULT_TYPEAHEAD_DELAY_MS = 500;\n\nconst findForward = <T>(items: readonly T[], start: number, predicate: (item: T, index: number) => boolean): number => {\n for (let index = start; index < items.length; index++) {\n if (predicate(items[index], index)) return index;\n }\n\n return -1;\n};\n\nconst findBackward = <T>(\n items: readonly T[],\n start: number,\n predicate: (item: T, index: number) => boolean,\n): number => {\n for (let index = start; index >= 0; index--) {\n if (predicate(items[index], index)) return index;\n }\n\n return -1;\n};\n\nconst resolveTypeaheadDelay = (delay: number | undefined): number => {\n if (delay === undefined) return DEFAULT_TYPEAHEAD_DELAY_MS;\n if (!Number.isFinite(delay) || delay <= 0)\n throw new FocusConfigError('Typeahead delay must be a positive finite number');\n return delay;\n};\n\nexport const createListNavigation = <T>(options: ListNavigationOptions<T>): ListNavigation<T> => {\n const typeaheadDelay = resolveTypeaheadDelay(options.typeahead?.delayMs);\n const owners = new Map<string, ListNavigationAction>();\n\n for (const action of ['next', 'prev', 'first', 'last'] as const) {\n for (const key of options.keys?.[action] ?? []) {\n const owner = owners.get(key);\n if (owner && owner !== action)\n throw new FocusConfigError(`Key \"${key}\" is assigned to both ${owner} and ${action}`);\n owners.set(key, action);\n }\n }\n\n let index = -1;\n let typeaheadBuffer = '';\n let lastTypeaheadAt = 0;\n\n const isItemDisabled = (item: T, itemIndex: number): boolean => options.isItemDisabled?.(item, itemIndex) ?? false;\n\n const isUsableIndex = (items: readonly T[], itemIndex: number): boolean =>\n itemIndex >= 0 && itemIndex < items.length && !isItemDisabled(items[itemIndex], itemIndex);\n\n const normalizeIndex = (items: readonly T[]): number => {\n if (!isUsableIndex(items, index)) index = -1;\n\n return index;\n };\n\n const resetTypeahead = (): void => {\n typeaheadBuffer = '';\n lastTypeaheadAt = 0;\n };\n\n const commitIndex = (\n items: readonly T[],\n nextIndex: number,\n action: ListKeyAction,\n event?: KeyboardEvent,\n ): ListNavigationChange<T> | null => {\n if (!isUsableIndex(items, nextIndex)) {\n index = -1;\n\n return null;\n }\n\n index = nextIndex;\n\n return Object.freeze({ action, event, index: nextIndex, item: items[nextIndex] });\n };\n\n const findEnabledIndex = (items: readonly T[], start: number, direction: 'forward' | 'backward'): number => {\n if (direction === 'forward')\n return findForward(items, start, (item, itemIndex) => !isItemDisabled(item, itemIndex));\n\n return findBackward(items, start, (item, itemIndex) => !isItemDisabled(item, itemIndex));\n };\n\n const move = (\n items: readonly T[],\n direction: 'forward' | 'backward',\n event?: KeyboardEvent,\n ): ListNavigationChange<T> | null => {\n const current = normalizeIndex(items);\n\n if (!items.length) {\n index = -1;\n\n return null;\n }\n\n const start =\n current < 0\n ? direction === 'forward'\n ? 0\n : items.length - 1\n : direction === 'forward'\n ? current + 1\n : current - 1;\n\n const nextIndex = findEnabledIndex(items, start, direction);\n const action: ListNavigationAction = direction === 'forward' ? 'next' : 'prev';\n\n if (nextIndex >= 0) return commitIndex(items, nextIndex, action, event);\n\n if (options.loop) {\n const wrapStart = direction === 'forward' ? 0 : items.length - 1;\n const wrapped = findEnabledIndex(items, wrapStart, direction);\n\n if (wrapped >= 0) return commitIndex(items, wrapped, action, event);\n }\n\n return null;\n };\n\n const navigateWithItems = (\n items: readonly T[],\n action: ListNavigationAction,\n event?: KeyboardEvent,\n ): ListNavigationChange<T> | null => {\n resetTypeahead();\n normalizeIndex(items);\n\n if (!items.length) {\n index = -1;\n\n return null;\n }\n\n if (action === 'first') {\n return commitIndex(items, findEnabledIndex(items, 0, 'forward'), action, event);\n }\n\n if (action === 'last') {\n return commitIndex(items, findEnabledIndex(items, items.length - 1, 'backward'), action, event);\n }\n\n return move(items, action === 'next' ? 'forward' : 'backward', event);\n };\n\n const findTypeaheadMatch = (items: readonly T[], search: string, includeCurrent: boolean): number => {\n const current = normalizeIndex(items);\n const start = current < 0 ? 0 : includeCurrent ? current : current + 1;\n\n for (let offset = 0; offset < items.length; offset++) {\n const itemIndex = (start + offset) % items.length;\n const item = items[itemIndex];\n\n if (isItemDisabled(item, itemIndex)) continue;\n\n if (options.typeahead?.getLabel(item, itemIndex).toLocaleLowerCase().startsWith(search)) {\n return itemIndex;\n }\n }\n\n return -1;\n };\n\n const handleTypeahead = (event: KeyboardEvent, items: readonly T[]): ListNavigationChange<T> | null => {\n if (!options.typeahead || event.key.length !== 1 || event.ctrlKey || event.altKey || event.metaKey) return null;\n\n const now = performance.now();\n const key = event.key.toLocaleLowerCase();\n\n if (now - lastTypeaheadAt >= typeaheadDelay) typeaheadBuffer = '';\n\n lastTypeaheadAt = now;\n\n const candidate = typeaheadBuffer + key;\n const repeatedCharacter = candidate.length > 1 && [...candidate].every((character) => character === key);\n typeaheadBuffer = repeatedCharacter ? key : candidate;\n\n let nextIndex = findTypeaheadMatch(items, typeaheadBuffer, typeaheadBuffer.length > 1);\n\n if (nextIndex < 0 && typeaheadBuffer.length > 1) {\n typeaheadBuffer = key;\n nextIndex = findTypeaheadMatch(items, typeaheadBuffer, false);\n }\n\n if (nextIndex < 0) return null;\n\n return commitIndex(items, nextIndex, 'typeahead', event);\n };\n\n const set = (nextIndex: number): number => {\n const items = options.getItems();\n normalizeIndex(items);\n resetTypeahead();\n\n if (!Number.isInteger(nextIndex) || nextIndex < 0 || !items.length) {\n index = -1;\n\n return index;\n }\n\n const clamped = Math.min(nextIndex, items.length - 1);\n\n if (!isUsableIndex(items, clamped)) {\n index = -1;\n\n return index;\n }\n\n index = clamped;\n\n return index;\n };\n\n const navigate = (action: ListNavigationAction): ListNavigationChange<T> | null => {\n return navigateWithItems(options.getItems(), action);\n };\n\n const getActiveItem = (): T | undefined => {\n const items = options.getItems();\n const current = normalizeIndex(items);\n\n return current >= 0 ? items[current] : undefined;\n };\n\n const reset = (): void => {\n index = -1;\n resetTypeahead();\n };\n\n const getIndex = (): number => {\n return normalizeIndex(options.getItems());\n };\n\n const resolve = <V>(value: MaybeGetter<V> | undefined, fallback: V): V =>\n typeof value === 'function' ? (value as () => V)() : (value ?? fallback);\n\n const resolveKeyAction = (event: KeyboardEvent): ListNavigationAction | undefined => {\n const keys = options.keys;\n const keyTable = resolve(options.direction, 'ltr') === 'rtl' ? DEFAULT_KEYS_RTL : DEFAULT_KEYS;\n const defaults = keyTable[resolve(options.orientation, 'vertical')];\n\n for (const action of ['next', 'prev', 'first', 'last'] as const) {\n if ((keys?.[action] ?? defaults[action]).some((pattern) => matchKey(event, pattern))) return action;\n }\n\n return undefined;\n };\n\n const handleKeydown = (event: KeyboardEvent): ListKeyResult<T> | null => {\n if (event.defaultPrevented || event.isComposing || resolve(options.disabled, false)) return null;\n\n const items = options.getItems();\n normalizeIndex(items);\n\n const action = resolveKeyAction(event);\n\n if (action) {\n const change = navigateWithItems(items, action, event);\n event.preventDefault();\n return Object.freeze({ change, handled: true });\n }\n\n const change = handleTypeahead(event, items);\n if (!change) return null;\n if (options.typeahead?.preventDefault) event.preventDefault();\n return Object.freeze({ change, handled: true });\n };\n\n return {\n getActiveItem,\n getIndex,\n handleKeydown,\n navigate,\n reset,\n set,\n };\n};\n","export type FocusableElement = HTMLElement | SVGElement;\nexport type FocusTarget = FocusableElement | null | undefined | (() => FocusableElement | null | undefined);\n\nexport type RestoreFocusOptions = {\n fallback?: FocusTarget;\n preventScroll?: boolean;\n};\n\nexport type CaptureFocusOptions = RestoreFocusOptions & {\n signal?: AbortSignal;\n};\n\nexport type FocusRestorer = () => boolean;\n\nconst resolveTarget = (target: FocusTarget): FocusableElement | null | undefined => {\n return typeof target === 'function' ? target() : target;\n};\n\nconst getDeepActiveElement = (rootDocument: Document): Element | null => {\n let active: Element | null = rootDocument.activeElement;\n\n while (active instanceof HTMLElement && active.shadowRoot?.activeElement) {\n active = active.shadowRoot.activeElement;\n }\n\n return active;\n};\n\nconst isConnected = (target: FocusableElement): boolean => {\n // `isConnected` pierces shadow boundaries (shadow-including root is the document);\n // `ownerDocument.contains` does NOT: it would reject elements inside shadow roots.\n return target.isConnected;\n};\n\nconst isDisabled = (target: FocusableElement): boolean => {\n return target instanceof HTMLElement && 'disabled' in target && Boolean((target as HTMLInputElement).disabled);\n};\n\nconst isInert = (target: FocusableElement): boolean => {\n if (target.closest('[inert]')) return true;\n\n return target instanceof HTMLElement && target.inert;\n};\n\nconst canRestoreTo = (target: FocusableElement): boolean => {\n return isConnected(target) && !isDisabled(target) && !isInert(target);\n};\n\nconst focusAndVerify = (target: FocusableElement, preventScroll: boolean | undefined): boolean => {\n try {\n target.focus({ preventScroll });\n return getDeepActiveElement(target.ownerDocument) === target;\n } catch {\n return false;\n }\n};\n\nexport const restoreFocus = (target: FocusTarget, options: RestoreFocusOptions = {}): boolean => {\n let next: FocusableElement | null | undefined;\n try {\n next = resolveTarget(target);\n } catch {\n next = null;\n }\n\n if (next && canRestoreTo(next) && focusAndVerify(next, options.preventScroll)) {\n return true;\n }\n\n if (!options.fallback) return false;\n\n let fallback: FocusableElement | null | undefined;\n try {\n fallback = resolveTarget(options.fallback);\n } catch {\n return false;\n }\n\n if (!fallback || !canRestoreTo(fallback)) return false;\n\n return focusAndVerify(fallback, options.preventScroll);\n};\n\n/**\n * Hands focus to `target` when focus has been lost to the document body: the state left\n * behind when the focused element unmounts mid-swap (browsing a dialog, paging a list),\n * where keydown would never reach a handler and every shortcut dies. Returns `true` when\n * focus was rescued; `false` when focus is already on a real element or the target cannot\n * take focus (the `fallback` option of {@link RestoreFocusOptions} still applies).\n */\nexport const rescueFocus = (target: FocusTarget, options: RestoreFocusOptions = {}): boolean => {\n const active = getDeepActiveElement(document);\n\n if (active && active !== document.body) return false;\n\n return restoreFocus(target, options);\n};\n\nexport const captureFocus = (options: CaptureFocusOptions = {}): FocusRestorer => {\n let captured = getDeepActiveElement(document) as FocusableElement | null;\n let available = !options.signal?.aborted;\n\n if (!available) captured = null;\n\n const cancel = (): void => {\n available = false;\n captured = null;\n };\n\n if (!options.signal?.aborted) {\n options.signal?.addEventListener('abort', cancel, { once: true });\n }\n\n return () => {\n if (!available) return false;\n\n available = false;\n options.signal?.removeEventListener('abort', cancel);\n\n const target = captured;\n captured = null;\n\n if (target) {\n return restoreFocus(target, {\n fallback: options.fallback,\n preventScroll: options.preventScroll,\n });\n }\n\n if (options.fallback) {\n return restoreFocus(options.fallback, {\n preventScroll: options.preventScroll,\n });\n }\n\n return false;\n };\n};\n"],"mappings":"qGACA,IAAa,WAAb,cAAgC,KAAM,CACpC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,iBAAb,cAAsC,UAAW,CAAC,ECyC5C,EAAgE,CACpE,MAAO,CAAC,MAAM,EACd,KAAM,CAAC,KAAK,EACZ,KAAM,CAAC,YAAY,EACnB,QAAS,CAAC,WAAW,EACrB,KAAM,CAAC,WAAW,EAClB,QAAS,CAAC,SAAS,CACrB,EAEM,EAAwC,CAC5C,GAAG,EACH,KAAM,CAAC,WAAW,EAClB,KAAM,CAAC,YAAY,CACrB,EAEM,EAAe,CAAC,QAAS,OAAQ,OAAQ,UAAW,OAAQ,SAAS,EAErE,SAAc,EAAmC,IACrD,OAAO,GAAU,WAAc,EAAkB,EAAK,GAAS,EAWpD,qBAA2B,GAAyD,CAC/F,IAAM,EAAS,IAAI,IAEnB,IAAK,IAAM,KAAU,EACnB,IAAK,IAAM,KAAO,EAAQ,OAAO,IAAW,CAAC,EAAG,CAC9C,IAAM,EAAQ,EAAO,IAAI,CAAG,EAC5B,GAAI,GAAS,IAAU,EACrB,MAAM,IAAI,iBAAiB,QAAQ,EAAI,wBAAwB,EAAM,OAAO,GAAQ,EACtF,EAAO,IAAI,EAAK,CAAM,CACxB,CAGF,IAAI,EAAQ,GAEN,mBAA+B,CACnC,IAAM,EAAU,QAAQ,EAAQ,QAAS,CAAC,EAC1C,GAAI,CAAC,OAAO,UAAU,CAAO,GAAK,EAAU,EAC1C,MAAM,IAAI,iBAAiB,oDAAoD,GAAS,EAC1F,OAAO,CACT,EAEM,YAAe,GAAgC,CACnD,IAAM,EAAU,EAAQ,iBAAiB,EAEzC,OADI,IAAY,IAAA,KAAW,EAAQ,GAAW,GAAK,EAAU,EAAM,OAAS,EAAU,IAC/E,CACT,EAEM,aACJ,EACA,EACA,EACA,KAEA,EAAQ,EACD,OAAO,OAAO,CAAE,SAAQ,QAAO,MAAO,EAAW,KAAM,EAAM,EAAW,CAAC,GAG5E,mBACJ,EACA,EACA,IACmC,CACnC,GAAI,CAAC,EAAM,OAET,MADA,GAAQ,GACD,KAGT,GAAI,IAAW,QAAS,OAAO,YAAY,EAAO,EAAG,EAAQ,CAAK,EAClE,GAAI,IAAW,OAAQ,OAAO,YAAY,EAAO,EAAM,OAAS,EAAG,EAAQ,CAAK,EAEhF,IAAM,EAAU,eAAe,EACzB,EAAS,IAAW,OAAS,EAAI,IAAW,OAAS,GAAK,IAAW,UAAY,EAAU,CAAC,EAC5F,EAAU,YAAY,CAAK,EAC3B,EAAO,EAAU,EAAK,EAAS,EAAI,EAAI,EAAM,OAAS,EAAK,EAAU,EAE3E,GAAI,EAAQ,KACV,OAAO,YAAY,GAAS,EAAO,EAAM,OAAU,EAAM,QAAU,EAAM,OAAQ,EAAQ,CAAK,EAGhG,IAAM,EAAU,KAAK,IAAI,EAAM,OAAS,EAAG,KAAK,IAAI,EAAG,CAAI,CAAC,EAG5D,OAFI,IAAY,EAAgB,KAEzB,YAAY,EAAO,EAAS,EAAQ,CAAK,CAClD,EAEM,iBAAoB,GAA2D,CACnF,IAAM,EAAW,QAAQ,EAAQ,UAAW,KAAK,IAAM,MAAQ,EAAmB,EAElF,IAAK,IAAM,KAAU,EACnB,IAAK,EAAQ,OAAO,IAAW,EAAS,GAAA,CAAS,KAAM,IAAA,EAAY,EAAA,SAAA,CAAS,EAAO,CAAO,CAAC,EAAG,OAAO,CAIzG,EAEM,cAAiB,GAAkD,CACvE,GAAI,EAAM,kBAAoB,EAAM,aAAe,QAAQ,EAAQ,SAAU,EAAK,EAAG,OAAO,KAE5F,IAAM,EAAS,iBAAiB,CAAK,EACrC,GAAI,CAAC,EAAQ,OAAO,KAEpB,IAAM,EAAS,kBAAkB,EAAQ,SAAS,EAAG,EAAQ,CAAK,EAElE,OADA,EAAM,eAAe,EACd,OAAO,OAAO,CAAE,SAAQ,QAAS,EAAK,CAAC,CAChD,EAEM,SAAY,GAChB,kBAAkB,EAAQ,SAAS,EAAG,CAAM,EAExC,IAAO,GAA4B,CACvC,IAAM,EAAQ,EAAQ,SAAS,EAE/B,GAAI,CAAC,OAAO,UAAU,CAAS,GAAK,EAAY,GAAK,CAAC,EAAM,OAAQ,CAClE,EAAQ,GACR,MACF,CAEA,EAAQ,KAAK,IAAI,EAAW,EAAM,OAAS,CAAC,CAC9C,EAEM,UAAoB,CACxB,EAAQ,EACV,EAEM,aAAyB,YAAY,EAAQ,SAAS,CAAC,EAEvD,kBAAqC,CACzC,IAAM,EAAQ,EAAQ,SAAS,EACzB,EAAU,YAAY,CAAK,EACjC,OAAO,GAAW,EAAI,EAAM,GAAW,IAAA,EACzC,EAEA,MAAO,CACL,cACA,SACA,cACA,SACA,MACA,GACF,CACF,ECzJM,EAA4G,CAChH,KAAM,CAAE,MAAO,CAAC,MAAM,EAAG,KAAM,CAAC,KAAK,EAAG,KAAM,CAAC,YAAa,YAAY,EAAG,KAAM,CAAC,UAAW,WAAW,CAAE,EAC1G,WAAY,CAAE,MAAO,CAAC,MAAM,EAAG,KAAM,CAAC,KAAK,EAAG,KAAM,CAAC,YAAY,EAAG,KAAM,CAAC,WAAW,CAAE,EACxF,SAAU,CAAE,MAAO,CAAC,MAAM,EAAG,KAAM,CAAC,KAAK,EAAG,KAAM,CAAC,WAAW,EAAG,KAAM,CAAC,SAAS,CAAE,CACrF,EAEM,EAAwC,CAC5C,KAAM,CAAE,MAAO,CAAC,MAAM,EAAG,KAAM,CAAC,KAAK,EAAG,KAAM,CAAC,YAAa,WAAW,EAAG,KAAM,CAAC,UAAW,YAAY,CAAE,EAC1G,WAAY,CAAE,MAAO,CAAC,MAAM,EAAG,KAAM,CAAC,KAAK,EAAG,KAAM,CAAC,WAAW,EAAG,KAAM,CAAC,YAAY,CAAE,EACxF,SAAU,EAAa,QACzB,EAEM,EAA6B,IAE7B,aAAkB,EAAqB,EAAe,IAA2D,CACrH,IAAK,IAAI,EAAQ,EAAO,EAAQ,EAAM,OAAQ,IAC5C,GAAI,EAAU,EAAM,GAAQ,CAAK,EAAG,OAAO,EAG7C,MAAO,EACT,EAEM,cACJ,EACA,EACA,IACW,CACX,IAAK,IAAI,EAAQ,EAAO,GAAS,EAAG,IAClC,GAAI,EAAU,EAAM,GAAQ,CAAK,EAAG,OAAO,EAG7C,MAAO,EACT,EAEM,sBAAyB,GAAsC,CACnE,GAAI,IAAU,IAAA,GAAW,OAAO,EAChC,GAAI,CAAC,OAAO,SAAS,CAAK,GAAK,GAAS,EACtC,MAAM,IAAI,iBAAiB,kDAAkD,EAC/E,OAAO,CACT,EAEa,qBAA2B,GAAyD,CAC/F,IAAM,EAAiB,sBAAsB,EAAQ,WAAW,OAAO,EACjE,EAAS,IAAI,IAEnB,IAAK,IAAM,IAAU,CAAC,OAAQ,OAAQ,QAAS,MAAM,EACnD,IAAK,IAAM,KAAO,EAAQ,OAAO,IAAW,CAAC,EAAG,CAC9C,IAAM,EAAQ,EAAO,IAAI,CAAG,EAC5B,GAAI,GAAS,IAAU,EACrB,MAAM,IAAI,iBAAiB,QAAQ,EAAI,wBAAwB,EAAM,OAAO,GAAQ,EACtF,EAAO,IAAI,EAAK,CAAM,CACxB,CAGF,IAAI,EAAQ,GACR,EAAkB,GAClB,EAAkB,EAEhB,gBAAkB,EAAS,IAA+B,EAAQ,iBAAiB,EAAM,CAAS,GAAK,GAEvG,eAAiB,EAAqB,IAC1C,GAAa,GAAK,EAAY,EAAM,QAAU,CAAC,eAAe,EAAM,GAAY,CAAS,EAErF,eAAkB,IACjB,cAAc,EAAO,CAAK,IAAG,EAAQ,IAEnC,GAGH,mBAA6B,CACjC,EAAkB,GAClB,EAAkB,CACpB,EAEM,aACJ,EACA,EACA,EACA,IAEK,cAAc,EAAO,CAAS,GAMnC,EAAQ,EAED,OAAO,OAAO,CAAE,SAAQ,QAAO,MAAO,EAAW,KAAM,EAAM,EAAW,CAAC,IAP9E,EAAQ,GAED,MAQL,kBAAoB,EAAqB,EAAe,IACxD,IAAc,UACT,YAAY,EAAO,GAAQ,EAAM,IAAc,CAAC,eAAe,EAAM,CAAS,CAAC,EAEjF,aAAa,EAAO,GAAQ,EAAM,IAAc,CAAC,eAAe,EAAM,CAAS,CAAC,EAGnF,MACJ,EACA,EACA,IACmC,CACnC,IAAM,EAAU,eAAe,CAAK,EAEpC,GAAI,CAAC,EAAM,OAGT,MAFA,GAAQ,GAED,KAGT,IAAM,EACJ,EAAU,EACN,IAAc,UACZ,EACA,EAAM,OAAS,EACjB,IAAc,UACZ,EAAU,EACV,EAAU,EAEZ,EAAY,iBAAiB,EAAO,EAAO,CAAS,EACpD,EAA+B,IAAc,UAAY,OAAS,OAExE,GAAI,GAAa,EAAG,OAAO,YAAY,EAAO,EAAW,EAAQ,CAAK,EAEtE,GAAI,EAAQ,KAAM,CAChB,IAAM,EAAY,IAAc,UAAY,EAAI,EAAM,OAAS,EACzD,EAAU,iBAAiB,EAAO,EAAW,CAAS,EAE5D,GAAI,GAAW,EAAG,OAAO,YAAY,EAAO,EAAS,EAAQ,CAAK,CACpE,CAEA,OAAO,IACT,EAEM,mBACJ,EACA,EACA,KAEA,eAAe,EACf,eAAe,CAAK,EAEf,EAAM,OAMP,IAAW,QACN,YAAY,EAAO,iBAAiB,EAAO,EAAG,SAAS,EAAG,EAAQ,CAAK,EAG5E,IAAW,OACN,YAAY,EAAO,iBAAiB,EAAO,EAAM,OAAS,EAAG,UAAU,EAAG,EAAQ,CAAK,EAGzF,KAAK,EAAO,IAAW,OAAS,UAAY,WAAY,CAAK,GAblE,EAAQ,GAED,OAcL,oBAAsB,EAAqB,EAAgB,IAAoC,CACnG,IAAM,EAAU,eAAe,CAAK,EAC9B,EAAQ,EAAU,EAAI,EAAI,EAAiB,EAAU,EAAU,EAErE,IAAK,IAAI,EAAS,EAAG,EAAS,EAAM,OAAQ,IAAU,CACpD,IAAM,GAAa,EAAQ,GAAU,EAAM,OACrC,EAAO,EAAM,GAEf,mBAAe,EAAM,CAAS,GAE9B,EAAQ,WAAW,SAAS,EAAM,CAAS,CAAC,CAAC,kBAAkB,CAAC,CAAC,WAAW,CAAM,EACpF,OAAO,CAEX,CAEA,MAAO,EACT,EAEM,iBAAmB,EAAsB,IAAwD,CACrG,GAAI,CAAC,EAAQ,WAAa,EAAM,IAAI,SAAW,GAAK,EAAM,SAAW,EAAM,QAAU,EAAM,QAAS,OAAO,KAE3G,IAAM,EAAM,YAAY,IAAI,EACtB,EAAM,EAAM,IAAI,kBAAkB,EAEpC,EAAM,GAAmB,IAAgB,EAAkB,IAE/D,EAAkB,EAElB,IAAM,EAAY,EAAkB,EAEpC,EAD0B,EAAU,OAAS,GAAK,CAAC,GAAG,CAAS,CAAC,CAAC,MAAO,GAAc,IAAc,CAAG,EACjE,EAAM,EAE5C,IAAI,EAAY,mBAAmB,EAAO,EAAiB,EAAgB,OAAS,CAAC,EASrF,OAPI,EAAY,GAAK,EAAgB,OAAS,IAC5C,EAAkB,EAClB,EAAY,mBAAmB,EAAO,EAAiB,EAAK,GAG1D,EAAY,EAAU,KAEnB,YAAY,EAAO,EAAW,YAAa,CAAK,CACzD,EAEM,IAAO,GAA8B,CACzC,IAAM,EAAQ,EAAQ,SAAS,EAI/B,GAHA,eAAe,CAAK,EACpB,eAAe,EAEX,CAAC,OAAO,UAAU,CAAS,GAAK,EAAY,GAAK,CAAC,EAAM,OAG1D,MAFA,GAAQ,GAED,EAGT,IAAM,EAAU,KAAK,IAAI,EAAW,EAAM,OAAS,CAAC,EAUpD,OARK,cAAc,EAAO,CAAO,GAMjC,EAAQ,EAED,IAPL,EAAQ,GAED,EAMX,EAEM,SAAY,GACT,kBAAkB,EAAQ,SAAS,EAAG,CAAM,EAG/C,kBAAqC,CACzC,IAAM,EAAQ,EAAQ,SAAS,EACzB,EAAU,eAAe,CAAK,EAEpC,OAAO,GAAW,EAAI,EAAM,GAAW,IAAA,EACzC,EAEM,UAAoB,CACxB,EAAQ,GACR,eAAe,CACjB,EAEM,aACG,eAAe,EAAQ,SAAS,CAAC,EAGpC,SAAc,EAAmC,IACrD,OAAO,GAAU,WAAc,EAAkB,EAAK,GAAS,EAE3D,iBAAoB,GAA2D,CACnF,IAAM,EAAO,EAAQ,KAEf,GADW,QAAQ,EAAQ,UAAW,KAAK,IAAM,MAAQ,EAAmB,EAAA,CACxD,QAAQ,EAAQ,YAAa,UAAU,GAEjE,IAAK,IAAM,IAAU,CAAC,OAAQ,OAAQ,QAAS,MAAM,EACnD,IAAK,IAAO,IAAW,EAAS,GAAA,CAAS,KAAM,IAAA,EAAY,EAAA,SAAA,CAAS,EAAO,CAAO,CAAC,EAAG,OAAO,CAIjG,EAEM,cAAiB,GAAkD,CACvE,GAAI,EAAM,kBAAoB,EAAM,aAAe,QAAQ,EAAQ,SAAU,EAAK,EAAG,OAAO,KAE5F,IAAM,EAAQ,EAAQ,SAAS,EAC/B,eAAe,CAAK,EAEpB,IAAM,EAAS,iBAAiB,CAAK,EAErC,GAAI,EAAQ,CACV,IAAM,EAAS,kBAAkB,EAAO,EAAQ,CAAK,EAErD,OADA,EAAM,eAAe,EACd,OAAO,OAAO,CAAE,SAAQ,QAAS,EAAK,CAAC,CAChD,CAEA,IAAM,EAAS,gBAAgB,EAAO,CAAK,EAG3C,OAFK,GACD,EAAQ,WAAW,gBAAgB,EAAM,eAAe,EACrD,OAAO,OAAO,CAAE,SAAQ,QAAS,EAAK,CAAC,GAF1B,IAGtB,EAEA,MAAO,CACL,cACA,SACA,cACA,SACA,MACA,GACF,CACF,ECpUM,cAAiB,GACd,OAAO,GAAW,WAAa,EAAO,EAAI,EAG7C,qBAAwB,GAA2C,CACvE,IAAI,EAAyB,EAAa,cAE1C,KAAO,aAAkB,aAAe,EAAO,YAAY,eACzD,EAAS,EAAO,WAAW,cAG7B,OAAO,CACT,EAEM,YAAe,GAGZ,EAAO,YAGV,WAAc,GACX,aAAkB,aAAe,aAAc,GAAU,EAAS,EAA4B,SAGjG,QAAW,GACX,EAAO,QAAQ,SAAS,EAAU,GAE/B,aAAkB,aAAe,EAAO,MAG3C,aAAgB,GACb,YAAY,CAAM,GAAK,CAAC,WAAW,CAAM,GAAK,CAAC,QAAQ,CAAM,EAGhE,gBAAkB,EAA0B,IAAgD,CAChG,GAAI,CAEF,OADA,EAAO,MAAM,CAAE,eAAc,CAAC,EACvB,qBAAqB,EAAO,aAAa,IAAM,CACxD,MAAQ,CACN,MAAO,EACT,CACF,EAEa,cAAgB,EAAqB,EAA+B,CAAC,IAAe,CAC/F,IAAI,EACJ,GAAI,CACF,EAAO,cAAc,CAAM,CAC7B,MAAQ,CACN,EAAO,IACT,CAEA,GAAI,GAAQ,aAAa,CAAI,GAAK,eAAe,EAAM,EAAQ,aAAa,EAC1E,MAAO,GAGT,GAAI,CAAC,EAAQ,SAAU,MAAO,GAE9B,IAAI,EACJ,GAAI,CACF,EAAW,cAAc,EAAQ,QAAQ,CAC3C,MAAQ,CACN,MAAO,EACT,CAIA,MAFI,CAAC,GAAY,CAAC,aAAa,CAAQ,EAAU,GAE1C,eAAe,EAAU,EAAQ,aAAa,CACvD,EASa,aAAe,EAAqB,EAA+B,CAAC,IAAe,CAC9F,IAAM,EAAS,qBAAqB,QAAQ,EAI5C,OAFI,GAAU,IAAW,SAAS,KAAa,GAExC,aAAa,EAAQ,CAAO,CACrC,EAEa,cAAgB,EAA+B,CAAC,IAAqB,CAChF,IAAI,EAAW,qBAAqB,QAAQ,EACxC,EAAY,CAAC,EAAQ,QAAQ,QAE5B,IAAW,EAAW,MAE3B,IAAM,WAAqB,CACzB,EAAY,GACZ,EAAW,IACb,EAMA,OAJK,EAAQ,QAAQ,SACnB,EAAQ,QAAQ,iBAAiB,QAAS,OAAQ,CAAE,KAAM,EAAK,CAAC,MAGrD,CACX,GAAI,CAAC,EAAW,MAAO,GAEvB,EAAY,GACZ,EAAQ,QAAQ,oBAAoB,QAAS,MAAM,EAEnD,IAAM,EAAS,EAgBf,MAfA,GAAW,KAEP,EACK,aAAa,EAAQ,CAC1B,SAAU,EAAQ,SAClB,cAAe,EAAQ,aACzB,CAAC,EAGC,EAAQ,SACH,aAAa,EAAQ,SAAU,CACpC,cAAe,EAAQ,aACzB,CAAC,EAGI,EACT,CACF"}
1
+ {"version":3,"file":"focus.cjs","names":[],"sources":["../src/errors.ts","../src/grid-navigation.ts","../src/list-navigation.ts","../src/quick-look-grid.ts","../src/restore-focus.ts"],"sourcesContent":["/** Base class for all focus errors. Use `instanceof FocusError` to catch any focus-originated error. */\nexport class FocusError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when navigation options are invalid, such as a non-positive typeahead delay or a key assigned to two actions. */\nexport class FocusConfigError extends FocusError {}\n","import { matchKey } from '@vielzeug/keymap';\n\nimport { FocusConfigError } from './errors.js';\nimport type { MaybeGetter } from './list-navigation.js';\n\nexport type GridNavigationAction = 'first' | 'last' | 'next' | 'nextRow' | 'prev' | 'prevRow';\n\nexport type GridNavigationChange<T> = {\n readonly action: GridNavigationAction;\n readonly event?: KeyboardEvent;\n readonly index: number;\n readonly item: T;\n};\n\nexport type GridKeyResult<T> = {\n readonly change: GridNavigationChange<T> | null;\n readonly handled: true;\n};\n\n/**\n * Columns per row: a fixed count, or a getter resolved on every navigation. Responsive grids\n * read a media query or computed style; measured grids read the rendered row length.\n */\nexport type GridColumns = number | (() => number);\n\nexport type GridNavigationOptions<T> = {\n /** Columns per row, resolved on every navigation. */\n columns: GridColumns;\n direction?: MaybeGetter<'ltr' | 'rtl'>;\n disabled?: MaybeGetter<boolean>;\n /**\n * Reads the active index from the outside (usually the index of `document.activeElement`\n * among the items), so focus moved by pointer or Tab stays in sync. When omitted, the\n * navigation tracks the index internally through `set()`.\n */\n getActiveIndex?: () => number;\n getItems: () => readonly T[];\n keys?: Partial<Record<GridNavigationAction, readonly string[]>>;\n /** Wrap moves around the grid's edges by flat index. Default clamps at the edges. */\n loop?: boolean;\n};\n\nexport type GridNavigation<T> = {\n getActiveItem(): T | undefined;\n getIndex(): number;\n handleKeydown(event: KeyboardEvent): GridKeyResult<T> | null;\n navigate(action: GridNavigationAction): GridNavigationChange<T> | null;\n reset(): void;\n set(index: number): void;\n};\n\nconst DEFAULT_KEYS: Record<GridNavigationAction, readonly string[]> = {\n first: ['Home'],\n last: ['End'],\n next: ['ArrowRight'],\n nextRow: ['ArrowDown'],\n prev: ['ArrowLeft'],\n prevRow: ['ArrowUp'],\n};\n\nconst DEFAULT_KEYS_RTL: typeof DEFAULT_KEYS = {\n ...DEFAULT_KEYS,\n next: ['ArrowLeft'],\n prev: ['ArrowRight'],\n};\n\nconst GRID_ACTIONS = ['first', 'last', 'next', 'nextRow', 'prev', 'prevRow'] as const;\n\nconst resolve = <V>(value: MaybeGetter<V> | undefined, fallback: V): V =>\n typeof value === 'function' ? (value as () => V)() : (value ?? fallback);\n\n/**\n * Keyboard navigation over a two-dimensional grid of items: horizontal arrows step one\n * item, vertical arrows step one row (the column count), Home/End jump to the ends.\n *\n * Unlike `createListNavigation`, items are not skipped when disabled: skipping in two\n * dimensions would break row alignment. Focus (or otherwise activate) the item reported\n * by the returned change; `handleKeydown` calls `preventDefault()` for every recognized\n * key so unhandled arrows never scroll the page.\n */\nexport const createGridNavigation = <T>(options: GridNavigationOptions<T>): GridNavigation<T> => {\n const owners = new Map<string, GridNavigationAction>();\n\n for (const action of GRID_ACTIONS) {\n for (const key of options.keys?.[action] ?? []) {\n const owner = owners.get(key);\n if (owner && owner !== action)\n throw new FocusConfigError(`Key \"${key}\" is assigned to both ${owner} and ${action}`);\n owners.set(key, action);\n }\n }\n\n let index = -1;\n\n const resolveColumns = (): number => {\n const columns = resolve(options.columns, 1);\n if (!Number.isInteger(columns) || columns < 1)\n throw new FocusConfigError(`columns must resolve to a positive integer, got: ${columns}`);\n return columns;\n };\n\n const activeIndex = (items: readonly T[]): number => {\n const derived = options.getActiveIndex?.();\n if (derived !== undefined) index = derived >= 0 && derived < items.length ? derived : -1;\n return index;\n };\n\n const commitIndex = (\n items: readonly T[],\n nextIndex: number,\n action: GridNavigationAction,\n event?: KeyboardEvent,\n ): GridNavigationChange<T> => {\n index = nextIndex;\n return Object.freeze({ action, event, index: nextIndex, item: items[nextIndex] });\n };\n\n const navigateWithItems = (\n items: readonly T[],\n action: GridNavigationAction,\n event?: KeyboardEvent,\n ): GridNavigationChange<T> | null => {\n if (!items.length) {\n index = -1;\n return null;\n }\n\n if (action === 'first') return commitIndex(items, 0, action, event);\n if (action === 'last') return commitIndex(items, items.length - 1, action, event);\n\n const columns = resolveColumns();\n const offset = action === 'next' ? 1 : action === 'prev' ? -1 : action === 'nextRow' ? columns : -columns;\n const current = activeIndex(items);\n const base = current < 0 ? (offset > 0 ? 0 : items.length - 1) : current + offset;\n\n if (options.loop) {\n return commitIndex(items, ((base % items.length) + items.length) % items.length, action, event);\n }\n\n const clamped = Math.min(items.length - 1, Math.max(0, base));\n if (clamped === current) return null;\n\n return commitIndex(items, clamped, action, event);\n };\n\n const resolveKeyAction = (event: KeyboardEvent): GridNavigationAction | undefined => {\n const keyTable = resolve(options.direction, 'ltr') === 'rtl' ? DEFAULT_KEYS_RTL : DEFAULT_KEYS;\n\n for (const action of GRID_ACTIONS) {\n if ((options.keys?.[action] ?? keyTable[action]).some((pattern) => matchKey(event, pattern))) return action;\n }\n\n return undefined;\n };\n\n const handleKeydown = (event: KeyboardEvent): GridKeyResult<T> | null => {\n if (event.defaultPrevented || event.isComposing || resolve(options.disabled, false)) return null;\n\n const action = resolveKeyAction(event);\n if (!action) return null;\n\n const change = navigateWithItems(options.getItems(), action, event);\n event.preventDefault();\n return Object.freeze({ change, handled: true });\n };\n\n const navigate = (action: GridNavigationAction): GridNavigationChange<T> | null =>\n navigateWithItems(options.getItems(), action);\n\n const set = (nextIndex: number): void => {\n const items = options.getItems();\n\n if (!Number.isInteger(nextIndex) || nextIndex < 0 || !items.length) {\n index = -1;\n return;\n }\n\n index = Math.min(nextIndex, items.length - 1);\n };\n\n const reset = (): void => {\n index = -1;\n };\n\n const getIndex = (): number => activeIndex(options.getItems());\n\n const getActiveItem = (): T | undefined => {\n const items = options.getItems();\n const current = activeIndex(items);\n return current >= 0 ? items[current] : undefined;\n };\n\n return {\n getActiveItem,\n getIndex,\n handleKeydown,\n navigate,\n reset,\n set,\n };\n};\n","import { matchKey } from '@vielzeug/keymap';\n\nimport { FocusConfigError } from './errors.js';\n\nexport type ListNavigationAction = 'first' | 'last' | 'next' | 'prev';\nexport type ListKeyAction = ListNavigationAction | 'typeahead';\n\nexport type ListNavigationChange<T> = {\n readonly action: ListKeyAction;\n readonly event?: KeyboardEvent;\n readonly index: number;\n readonly item: T;\n};\n\nexport type ListKeyResult<T> = {\n readonly change: ListNavigationChange<T> | null;\n readonly handled: true;\n};\n\nexport type ListNavigationTypeaheadOptions<T> = {\n delayMs?: number;\n getLabel: (item: T, index: number) => string;\n preventDefault?: boolean;\n};\n\nexport type MaybeGetter<T> = T | (() => T);\n\nexport type ListNavigationOptions<T> = {\n direction?: MaybeGetter<'ltr' | 'rtl'>;\n disabled?: MaybeGetter<boolean>;\n getItems: () => readonly T[];\n isItemDisabled?: (item: T, index: number) => boolean;\n keys?: Partial<Record<ListNavigationAction, readonly string[]>>;\n loop?: boolean;\n orientation?: MaybeGetter<'both' | 'horizontal' | 'vertical'>;\n typeahead?: ListNavigationTypeaheadOptions<T>;\n};\n\nexport type ListNavigation<T> = {\n getActiveItem(): T | undefined;\n getIndex(): number;\n handleKeydown(event: KeyboardEvent): ListKeyResult<T> | null;\n navigate(action: ListNavigationAction): ListNavigationChange<T> | null;\n reset(): void;\n set(index: number): number;\n};\n\nconst DEFAULT_KEYS: Record<'both' | 'horizontal' | 'vertical', Record<ListNavigationAction, readonly string[]>> = {\n both: { first: ['Home'], last: ['End'], next: ['ArrowDown', 'ArrowRight'], prev: ['ArrowUp', 'ArrowLeft'] },\n horizontal: { first: ['Home'], last: ['End'], next: ['ArrowRight'], prev: ['ArrowLeft'] },\n vertical: { first: ['Home'], last: ['End'], next: ['ArrowDown'], prev: ['ArrowUp'] },\n};\n\nconst DEFAULT_KEYS_RTL: typeof DEFAULT_KEYS = {\n both: { first: ['Home'], last: ['End'], next: ['ArrowDown', 'ArrowLeft'], prev: ['ArrowUp', 'ArrowRight'] },\n horizontal: { first: ['Home'], last: ['End'], next: ['ArrowLeft'], prev: ['ArrowRight'] },\n vertical: DEFAULT_KEYS.vertical,\n};\n\nconst DEFAULT_TYPEAHEAD_DELAY_MS = 500;\n\nconst findForward = <T>(items: readonly T[], start: number, predicate: (item: T, index: number) => boolean): number => {\n for (let index = start; index < items.length; index++) {\n if (predicate(items[index], index)) return index;\n }\n\n return -1;\n};\n\nconst findBackward = <T>(\n items: readonly T[],\n start: number,\n predicate: (item: T, index: number) => boolean,\n): number => {\n for (let index = start; index >= 0; index--) {\n if (predicate(items[index], index)) return index;\n }\n\n return -1;\n};\n\nconst resolveTypeaheadDelay = (delay: number | undefined): number => {\n if (delay === undefined) return DEFAULT_TYPEAHEAD_DELAY_MS;\n if (!Number.isFinite(delay) || delay <= 0)\n throw new FocusConfigError('Typeahead delay must be a positive finite number');\n return delay;\n};\n\nexport const createListNavigation = <T>(options: ListNavigationOptions<T>): ListNavigation<T> => {\n const typeaheadDelay = resolveTypeaheadDelay(options.typeahead?.delayMs);\n const owners = new Map<string, ListNavigationAction>();\n\n for (const action of ['next', 'prev', 'first', 'last'] as const) {\n for (const key of options.keys?.[action] ?? []) {\n const owner = owners.get(key);\n if (owner && owner !== action)\n throw new FocusConfigError(`Key \"${key}\" is assigned to both ${owner} and ${action}`);\n owners.set(key, action);\n }\n }\n\n let index = -1;\n let typeaheadBuffer = '';\n let lastTypeaheadAt = 0;\n\n const isItemDisabled = (item: T, itemIndex: number): boolean => options.isItemDisabled?.(item, itemIndex) ?? false;\n\n const isUsableIndex = (items: readonly T[], itemIndex: number): boolean =>\n itemIndex >= 0 && itemIndex < items.length && !isItemDisabled(items[itemIndex], itemIndex);\n\n const normalizeIndex = (items: readonly T[]): number => {\n if (!isUsableIndex(items, index)) index = -1;\n\n return index;\n };\n\n const resetTypeahead = (): void => {\n typeaheadBuffer = '';\n lastTypeaheadAt = 0;\n };\n\n const commitIndex = (\n items: readonly T[],\n nextIndex: number,\n action: ListKeyAction,\n event?: KeyboardEvent,\n ): ListNavigationChange<T> | null => {\n if (!isUsableIndex(items, nextIndex)) {\n index = -1;\n\n return null;\n }\n\n index = nextIndex;\n\n return Object.freeze({ action, event, index: nextIndex, item: items[nextIndex] });\n };\n\n const findEnabledIndex = (items: readonly T[], start: number, direction: 'forward' | 'backward'): number => {\n if (direction === 'forward')\n return findForward(items, start, (item, itemIndex) => !isItemDisabled(item, itemIndex));\n\n return findBackward(items, start, (item, itemIndex) => !isItemDisabled(item, itemIndex));\n };\n\n const move = (\n items: readonly T[],\n direction: 'forward' | 'backward',\n event?: KeyboardEvent,\n ): ListNavigationChange<T> | null => {\n const current = normalizeIndex(items);\n\n if (!items.length) {\n index = -1;\n\n return null;\n }\n\n const start =\n current < 0\n ? direction === 'forward'\n ? 0\n : items.length - 1\n : direction === 'forward'\n ? current + 1\n : current - 1;\n\n const nextIndex = findEnabledIndex(items, start, direction);\n const action: ListNavigationAction = direction === 'forward' ? 'next' : 'prev';\n\n if (nextIndex >= 0) return commitIndex(items, nextIndex, action, event);\n\n if (options.loop) {\n const wrapStart = direction === 'forward' ? 0 : items.length - 1;\n const wrapped = findEnabledIndex(items, wrapStart, direction);\n\n if (wrapped >= 0) return commitIndex(items, wrapped, action, event);\n }\n\n return null;\n };\n\n const navigateWithItems = (\n items: readonly T[],\n action: ListNavigationAction,\n event?: KeyboardEvent,\n ): ListNavigationChange<T> | null => {\n resetTypeahead();\n normalizeIndex(items);\n\n if (!items.length) {\n index = -1;\n\n return null;\n }\n\n if (action === 'first') {\n return commitIndex(items, findEnabledIndex(items, 0, 'forward'), action, event);\n }\n\n if (action === 'last') {\n return commitIndex(items, findEnabledIndex(items, items.length - 1, 'backward'), action, event);\n }\n\n return move(items, action === 'next' ? 'forward' : 'backward', event);\n };\n\n const findTypeaheadMatch = (items: readonly T[], search: string, includeCurrent: boolean): number => {\n const current = normalizeIndex(items);\n const start = current < 0 ? 0 : includeCurrent ? current : current + 1;\n\n for (let offset = 0; offset < items.length; offset++) {\n const itemIndex = (start + offset) % items.length;\n const item = items[itemIndex];\n\n if (isItemDisabled(item, itemIndex)) continue;\n\n if (options.typeahead?.getLabel(item, itemIndex).toLocaleLowerCase().startsWith(search)) {\n return itemIndex;\n }\n }\n\n return -1;\n };\n\n const handleTypeahead = (event: KeyboardEvent, items: readonly T[]): ListNavigationChange<T> | null => {\n if (!options.typeahead || event.key.length !== 1 || event.ctrlKey || event.altKey || event.metaKey) return null;\n\n const now = performance.now();\n const key = event.key.toLocaleLowerCase();\n\n if (now - lastTypeaheadAt >= typeaheadDelay) typeaheadBuffer = '';\n\n lastTypeaheadAt = now;\n\n const candidate = typeaheadBuffer + key;\n const repeatedCharacter = candidate.length > 1 && [...candidate].every((character) => character === key);\n typeaheadBuffer = repeatedCharacter ? key : candidate;\n\n let nextIndex = findTypeaheadMatch(items, typeaheadBuffer, typeaheadBuffer.length > 1);\n\n if (nextIndex < 0 && typeaheadBuffer.length > 1) {\n typeaheadBuffer = key;\n nextIndex = findTypeaheadMatch(items, typeaheadBuffer, false);\n }\n\n if (nextIndex < 0) return null;\n\n return commitIndex(items, nextIndex, 'typeahead', event);\n };\n\n const set = (nextIndex: number): number => {\n const items = options.getItems();\n normalizeIndex(items);\n resetTypeahead();\n\n if (!Number.isInteger(nextIndex) || nextIndex < 0 || !items.length) {\n index = -1;\n\n return index;\n }\n\n const clamped = Math.min(nextIndex, items.length - 1);\n\n if (!isUsableIndex(items, clamped)) {\n index = -1;\n\n return index;\n }\n\n index = clamped;\n\n return index;\n };\n\n const navigate = (action: ListNavigationAction): ListNavigationChange<T> | null => {\n return navigateWithItems(options.getItems(), action);\n };\n\n const getActiveItem = (): T | undefined => {\n const items = options.getItems();\n const current = normalizeIndex(items);\n\n return current >= 0 ? items[current] : undefined;\n };\n\n const reset = (): void => {\n index = -1;\n resetTypeahead();\n };\n\n const getIndex = (): number => {\n return normalizeIndex(options.getItems());\n };\n\n const resolve = <V>(value: MaybeGetter<V> | undefined, fallback: V): V =>\n typeof value === 'function' ? (value as () => V)() : (value ?? fallback);\n\n const resolveKeyAction = (event: KeyboardEvent): ListNavigationAction | undefined => {\n const keys = options.keys;\n const keyTable = resolve(options.direction, 'ltr') === 'rtl' ? DEFAULT_KEYS_RTL : DEFAULT_KEYS;\n const defaults = keyTable[resolve(options.orientation, 'vertical')];\n\n for (const action of ['next', 'prev', 'first', 'last'] as const) {\n if ((keys?.[action] ?? defaults[action]).some((pattern) => matchKey(event, pattern))) return action;\n }\n\n return undefined;\n };\n\n const handleKeydown = (event: KeyboardEvent): ListKeyResult<T> | null => {\n if (event.defaultPrevented || event.isComposing || resolve(options.disabled, false)) return null;\n\n const items = options.getItems();\n normalizeIndex(items);\n\n const action = resolveKeyAction(event);\n\n if (action) {\n const change = navigateWithItems(items, action, event);\n event.preventDefault();\n return Object.freeze({ change, handled: true });\n }\n\n const change = handleTypeahead(event, items);\n if (!change) return null;\n if (options.typeahead?.preventDefault) event.preventDefault();\n return Object.freeze({ change, handled: true });\n };\n\n return {\n getActiveItem,\n getIndex,\n handleKeydown,\n navigate,\n reset,\n set,\n };\n};\n","import type { GridColumns } from './grid-navigation.js';\nimport { createGridNavigation } from './grid-navigation.js';\nimport type { MaybeGetter } from './list-navigation.js';\n\nexport type QuickLookGridOptions = {\n /**\n * Columns per row, resolved on every navigation. Defaults to the rendered row length at the\n * active item, which follows the grid's own responsive wrapping without a media query.\n */\n columns?: GridColumns;\n /** Suppresses every key: for grids the consumer has locked. */\n disabled?: MaybeGetter<boolean>;\n /** Reads the grid's items in display order, re-read on every key. */\n getItems: () => readonly HTMLElement[];\n /**\n * Reads the active index among the items when the event target is outside them, for grids\n * whose focus sits on a wrapper around the item. Defaults to the item that is (or contains)\n * `document.activeElement`.\n */\n getActiveIndex?: () => number;\n /** Key that opens the Quick Look. Default `' '` (Space), the platform preview key. */\n inspectKey?: string;\n /** Wrap moves around the grid's edges by flat index. Default clamps at the edges. */\n loop?: boolean;\n /**\n * Opens the Quick Look for one item. The keyboard path has already focused the item; the\n * consumer decides what \"detail\" means (a dialog, a zoom, a preview pane).\n */\n onInspect: (item: HTMLElement, index: number) => void;\n};\n\nexport type QuickLookGrid = {\n /**\n * Route the grid's `keydown` here. Arrow keys and Home/End move focus through the items;\n * `inspectKey` opens the Quick Look for the item under the event (or the active item),\n * cancelling the key so the focused control never activates. Modifier chords pass through.\n */\n handleKeydown(event: KeyboardEvent): void;\n};\n\nconst resolve = <V>(value: MaybeGetter<V> | undefined, fallback: V): V =>\n typeof value === 'function' ? (value as () => V)() : (value ?? fallback);\n\n/**\n * Keyboard browsing over a tile grid with a Quick Look key: the pair every card picker needs,\n * where arrows walk the tiles and Space previews the focused card instead of activating it.\n *\n * Built on `createGridNavigation`: horizontal arrows step one item, vertical arrows step one\n * row, Home/End jump to the ends, and focus follows the reported change. `inspectKey` (Space by\n * default) resolves the item under the event target, calls `preventDefault()` and\n * `stopPropagation()` so the focused button never sees the press, and reports it through\n * `onInspect`. Long-press is the touch counterpart of the same callback; pair this with a\n * pointer recognizer (for example `@vielzeug/gesture`'s long press) at the call site.\n */\nexport const createQuickLookGrid = (options: QuickLookGridOptions): QuickLookGrid => {\n const activeIndex = (): number => options.getItems().indexOf(document.activeElement as HTMLElement);\n\n /** The rendered row length at the active item: items sharing its top edge. */\n const measuredColumns = (): number => {\n const items = options.getItems();\n const active = items.indexOf(document.activeElement as HTMLElement);\n if (active < 0) return 1;\n const top = items[active]?.getBoundingClientRect().top ?? 0;\n return Math.max(1, items.filter((item) => Math.abs(item.getBoundingClientRect().top - top) < 1).length);\n };\n\n const navigation = createGridNavigation<HTMLElement>({\n columns: options.columns ?? measuredColumns,\n getActiveIndex: options.getActiveIndex ?? activeIndex,\n getItems: options.getItems,\n loop: options.loop,\n });\n\n /** The item the key belongs to: the one containing the event target, else the active item. */\n const resolveItem = (event: KeyboardEvent): { index: number; item: HTMLElement | undefined } => {\n const items = options.getItems();\n const target = event.target instanceof HTMLElement ? event.target : null;\n const byTarget = target === null ? -1 : items.findIndex((item) => item === target || item.contains(target));\n if (byTarget >= 0) return { index: byTarget, item: items[byTarget] };\n const index = (options.getActiveIndex ?? activeIndex)();\n return { index, item: index >= 0 ? items[index] : undefined };\n };\n\n const handleKeydown = (event: KeyboardEvent): void => {\n if (event.defaultPrevented || event.isComposing || resolve(options.disabled, false)) return;\n if (event.ctrlKey || event.metaKey || event.altKey) return;\n\n if (event.key === (options.inspectKey ?? ' ')) {\n const { index, item } = resolveItem(event);\n if (!item) return;\n event.preventDefault();\n event.stopPropagation();\n options.onInspect(item, index);\n return;\n }\n\n const result = navigation.handleKeydown(event);\n if (result?.change) result.change.item.focus();\n };\n\n return { handleKeydown };\n};\n","export type FocusableElement = HTMLElement | SVGElement;\nexport type FocusTarget = FocusableElement | null | undefined | (() => FocusableElement | null | undefined);\n\nexport type RestoreFocusOptions = {\n fallback?: FocusTarget;\n preventScroll?: boolean;\n};\n\nexport type CaptureFocusOptions = RestoreFocusOptions & {\n signal?: AbortSignal;\n};\n\nexport type FocusRestorer = () => boolean;\n\nconst resolveTarget = (target: FocusTarget): FocusableElement | null | undefined => {\n return typeof target === 'function' ? target() : target;\n};\n\nconst getDeepActiveElement = (rootDocument: Document): Element | null => {\n let active: Element | null = rootDocument.activeElement;\n\n while (active instanceof HTMLElement && active.shadowRoot?.activeElement) {\n active = active.shadowRoot.activeElement;\n }\n\n return active;\n};\n\nconst isConnected = (target: FocusableElement): boolean => {\n // `isConnected` pierces shadow boundaries (shadow-including root is the document);\n // `ownerDocument.contains` does NOT: it would reject elements inside shadow roots.\n return target.isConnected;\n};\n\nconst isDisabled = (target: FocusableElement): boolean => {\n return target instanceof HTMLElement && 'disabled' in target && Boolean((target as HTMLInputElement).disabled);\n};\n\nconst isInert = (target: FocusableElement): boolean => {\n if (target.closest('[inert]')) return true;\n\n return target instanceof HTMLElement && target.inert;\n};\n\nconst canRestoreTo = (target: FocusableElement): boolean => {\n return isConnected(target) && !isDisabled(target) && !isInert(target);\n};\n\nconst focusAndVerify = (target: FocusableElement, preventScroll: boolean | undefined): boolean => {\n try {\n target.focus({ preventScroll });\n return getDeepActiveElement(target.ownerDocument) === target;\n } catch {\n return false;\n }\n};\n\nexport const restoreFocus = (target: FocusTarget, options: RestoreFocusOptions = {}): boolean => {\n let next: FocusableElement | null | undefined;\n try {\n next = resolveTarget(target);\n } catch {\n next = null;\n }\n\n if (next && canRestoreTo(next) && focusAndVerify(next, options.preventScroll)) {\n return true;\n }\n\n if (!options.fallback) return false;\n\n let fallback: FocusableElement | null | undefined;\n try {\n fallback = resolveTarget(options.fallback);\n } catch {\n return false;\n }\n\n if (!fallback || !canRestoreTo(fallback)) return false;\n\n return focusAndVerify(fallback, options.preventScroll);\n};\n\n/**\n * Hands focus to `target` when focus has been lost to the document body: the state left\n * behind when the focused element unmounts mid-swap (browsing a dialog, paging a list),\n * where keydown would never reach a handler and every shortcut dies. Returns `true` when\n * focus was rescued; `false` when focus is already on a real element or the target cannot\n * take focus (the `fallback` option of {@link RestoreFocusOptions} still applies).\n */\nexport const rescueFocus = (target: FocusTarget, options: RestoreFocusOptions = {}): boolean => {\n const active = getDeepActiveElement(document);\n\n if (active && active !== document.body) return false;\n\n return restoreFocus(target, options);\n};\n\nexport const captureFocus = (options: CaptureFocusOptions = {}): FocusRestorer => {\n let captured = getDeepActiveElement(document) as FocusableElement | null;\n let available = !options.signal?.aborted;\n\n if (!available) captured = null;\n\n const cancel = (): void => {\n available = false;\n captured = null;\n };\n\n if (!options.signal?.aborted) {\n options.signal?.addEventListener('abort', cancel, { once: true });\n }\n\n return () => {\n if (!available) return false;\n\n available = false;\n options.signal?.removeEventListener('abort', cancel);\n\n const target = captured;\n captured = null;\n\n if (target) {\n return restoreFocus(target, {\n fallback: options.fallback,\n preventScroll: options.preventScroll,\n });\n }\n\n if (options.fallback) {\n return restoreFocus(options.fallback, {\n preventScroll: options.preventScroll,\n });\n }\n\n return false;\n };\n};\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- var Focus=(function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var FocusError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},FocusConfigError=class extends FocusError{},n={first:[`Home`],last:[`End`],next:[`ArrowRight`],nextRow:[`ArrowDown`],prev:[`ArrowLeft`],prevRow:[`ArrowUp`]},r={...n,next:[`ArrowLeft`],prev:[`ArrowRight`]},i=[`first`,`last`,`next`,`nextRow`,`prev`,`prevRow`],resolve=(e,t)=>typeof e==`function`?e():e??t,createGridNavigation=e=>{let a=new Map;for(let t of i)for(let n of e.keys?.[t]??[]){let e=a.get(n);if(e&&e!==t)throw new FocusConfigError(`Key "${n}" is assigned to both ${e} and ${t}`);a.set(n,t)}let o=-1,resolveColumns=()=>{let t=resolve(e.columns,1);if(!Number.isInteger(t)||t<1)throw new FocusConfigError(`columns must resolve to a positive integer, got: ${t}`);return t},activeIndex=t=>{let n=e.getActiveIndex?.();return n!==void 0&&(o=n>=0&&n<t.length?n:-1),o},commitIndex=(e,t,n,r)=>(o=t,Object.freeze({action:n,event:r,index:t,item:e[t]})),navigateWithItems=(t,n,r)=>{if(!t.length)return o=-1,null;if(n===`first`)return commitIndex(t,0,n,r);if(n===`last`)return commitIndex(t,t.length-1,n,r);let i=resolveColumns(),a=n===`next`?1:n===`prev`?-1:n===`nextRow`?i:-i,s=activeIndex(t),c=s<0?a>0?0:t.length-1:s+a;if(e.loop)return commitIndex(t,(c%t.length+t.length)%t.length,n,r);let l=Math.min(t.length-1,Math.max(0,c));return l===s?null:commitIndex(t,l,n,r)},resolveKeyAction=a=>{let o=resolve(e.direction,`ltr`)===`rtl`?r:n;for(let n of i)if((e.keys?.[n]??o[n]).some(e=>(0,t.matchKey)(a,e)))return n},handleKeydown=t=>{if(t.defaultPrevented||t.isComposing||resolve(e.disabled,!1))return null;let n=resolveKeyAction(t);if(!n)return null;let r=navigateWithItems(e.getItems(),n,t);return t.preventDefault(),Object.freeze({change:r,handled:!0})},navigate=t=>navigateWithItems(e.getItems(),t),set=t=>{let n=e.getItems();if(!Number.isInteger(t)||t<0||!n.length){o=-1;return}o=Math.min(t,n.length-1)},reset=()=>{o=-1},getIndex=()=>activeIndex(e.getItems()),getActiveItem=()=>{let t=e.getItems(),n=activeIndex(t);return n>=0?t[n]:void 0};return{getActiveItem,getIndex,handleKeydown,navigate,reset,set}},a={both:{first:[`Home`],last:[`End`],next:[`ArrowDown`,`ArrowRight`],prev:[`ArrowUp`,`ArrowLeft`]},horizontal:{first:[`Home`],last:[`End`],next:[`ArrowRight`],prev:[`ArrowLeft`]},vertical:{first:[`Home`],last:[`End`],next:[`ArrowDown`],prev:[`ArrowUp`]}},o={both:{first:[`Home`],last:[`End`],next:[`ArrowDown`,`ArrowLeft`],prev:[`ArrowUp`,`ArrowRight`]},horizontal:{first:[`Home`],last:[`End`],next:[`ArrowLeft`],prev:[`ArrowRight`]},vertical:a.vertical},s=500,findForward=(e,t,n)=>{for(let r=t;r<e.length;r++)if(n(e[r],r))return r;return-1},findBackward=(e,t,n)=>{for(let r=t;r>=0;r--)if(n(e[r],r))return r;return-1},resolveTypeaheadDelay=e=>{if(e===void 0)return s;if(!Number.isFinite(e)||e<=0)throw new FocusConfigError(`Typeahead delay must be a positive finite number`);return e},createListNavigation=e=>{let n=resolveTypeaheadDelay(e.typeahead?.delayMs),r=new Map;for(let t of[`next`,`prev`,`first`,`last`])for(let n of e.keys?.[t]??[]){let e=r.get(n);if(e&&e!==t)throw new FocusConfigError(`Key "${n}" is assigned to both ${e} and ${t}`);r.set(n,t)}let i=-1,s=``,c=0,isItemDisabled=(t,n)=>e.isItemDisabled?.(t,n)??!1,isUsableIndex=(e,t)=>t>=0&&t<e.length&&!isItemDisabled(e[t],t),normalizeIndex=e=>(isUsableIndex(e,i)||(i=-1),i),resetTypeahead=()=>{s=``,c=0},commitIndex=(e,t,n,r)=>isUsableIndex(e,t)?(i=t,Object.freeze({action:n,event:r,index:t,item:e[t]})):(i=-1,null),findEnabledIndex=(e,t,n)=>n===`forward`?findForward(e,t,(e,t)=>!isItemDisabled(e,t)):findBackward(e,t,(e,t)=>!isItemDisabled(e,t)),move=(t,n,r)=>{let a=normalizeIndex(t);if(!t.length)return i=-1,null;let o=a<0?n===`forward`?0:t.length-1:n===`forward`?a+1:a-1,s=findEnabledIndex(t,o,n),c=n===`forward`?`next`:`prev`;if(s>=0)return commitIndex(t,s,c,r);if(e.loop){let e=n===`forward`?0:t.length-1,i=findEnabledIndex(t,e,n);if(i>=0)return commitIndex(t,i,c,r)}return null},navigateWithItems=(e,t,n)=>(resetTypeahead(),normalizeIndex(e),e.length?t===`first`?commitIndex(e,findEnabledIndex(e,0,`forward`),t,n):t===`last`?commitIndex(e,findEnabledIndex(e,e.length-1,`backward`),t,n):move(e,t===`next`?`forward`:`backward`,n):(i=-1,null)),findTypeaheadMatch=(t,n,r)=>{let i=normalizeIndex(t),a=i<0?0:r?i:i+1;for(let r=0;r<t.length;r++){let i=(a+r)%t.length,o=t[i];if(!isItemDisabled(o,i)&&e.typeahead?.getLabel(o,i).toLocaleLowerCase().startsWith(n))return i}return-1},handleTypeahead=(t,r)=>{if(!e.typeahead||t.key.length!==1||t.ctrlKey||t.altKey||t.metaKey)return null;let i=performance.now(),a=t.key.toLocaleLowerCase();i-c>=n&&(s=``),c=i;let o=s+a;s=o.length>1&&[...o].every(e=>e===a)?a:o;let l=findTypeaheadMatch(r,s,s.length>1);return l<0&&s.length>1&&(s=a,l=findTypeaheadMatch(r,s,!1)),l<0?null:commitIndex(r,l,`typeahead`,t)},set=t=>{let n=e.getItems();if(normalizeIndex(n),resetTypeahead(),!Number.isInteger(t)||t<0||!n.length)return i=-1,i;let r=Math.min(t,n.length-1);return isUsableIndex(n,r)?(i=r,i):(i=-1,i)},navigate=t=>navigateWithItems(e.getItems(),t),getActiveItem=()=>{let t=e.getItems(),n=normalizeIndex(t);return n>=0?t[n]:void 0},reset=()=>{i=-1,resetTypeahead()},getIndex=()=>normalizeIndex(e.getItems()),resolve=(e,t)=>typeof e==`function`?e():e??t,resolveKeyAction=n=>{let r=e.keys,i=(resolve(e.direction,`ltr`)===`rtl`?o:a)[resolve(e.orientation,`vertical`)];for(let e of[`next`,`prev`,`first`,`last`])if((r?.[e]??i[e]).some(e=>(0,t.matchKey)(n,e)))return e},handleKeydown=t=>{if(t.defaultPrevented||t.isComposing||resolve(e.disabled,!1))return null;let n=e.getItems();normalizeIndex(n);let r=resolveKeyAction(t);if(r){let e=navigateWithItems(n,r,t);return t.preventDefault(),Object.freeze({change:e,handled:!0})}let i=handleTypeahead(t,n);return i?(e.typeahead?.preventDefault&&t.preventDefault(),Object.freeze({change:i,handled:!0})):null};return{getActiveItem,getIndex,handleKeydown,navigate,reset,set}},resolveTarget=e=>typeof e==`function`?e():e,getDeepActiveElement=e=>{let t=e.activeElement;for(;t instanceof HTMLElement&&t.shadowRoot?.activeElement;)t=t.shadowRoot.activeElement;return t},isConnected=e=>e.isConnected,isDisabled=e=>e instanceof HTMLElement&&`disabled`in e&&!!e.disabled,isInert=e=>e.closest(`[inert]`)?!0:e instanceof HTMLElement&&e.inert,canRestoreTo=e=>isConnected(e)&&!isDisabled(e)&&!isInert(e),focusAndVerify=(e,t)=>{try{return e.focus({preventScroll:t}),getDeepActiveElement(e.ownerDocument)===e}catch{return!1}},restoreFocus=(e,t={})=>{let n;try{n=resolveTarget(e)}catch{n=null}if(n&&canRestoreTo(n)&&focusAndVerify(n,t.preventScroll))return!0;if(!t.fallback)return!1;let r;try{r=resolveTarget(t.fallback)}catch{return!1}return!r||!canRestoreTo(r)?!1:focusAndVerify(r,t.preventScroll)},rescueFocus=(e,t={})=>{let n=getDeepActiveElement(document);return n&&n!==document.body?!1:restoreFocus(e,t)},captureFocus=(e={})=>{let t=getDeepActiveElement(document),n=!e.signal?.aborted;n||(t=null);let cancel=()=>{n=!1,t=null};return e.signal?.aborted||e.signal?.addEventListener(`abort`,cancel,{once:!0}),()=>{if(!n)return!1;n=!1,e.signal?.removeEventListener(`abort`,cancel);let r=t;return t=null,r?restoreFocus(r,{fallback:e.fallback,preventScroll:e.preventScroll}):e.fallback?restoreFocus(e.fallback,{preventScroll:e.preventScroll}):!1}};return e.FocusConfigError=FocusConfigError,e.FocusError=FocusError,e.captureFocus=captureFocus,e.createGridNavigation=createGridNavigation,e.createListNavigation=createListNavigation,e.rescueFocus=rescueFocus,e.restoreFocus=restoreFocus,e})({},Keymap);
1
+ var Focus=(function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var FocusError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},FocusConfigError=class extends FocusError{},n={first:[`Home`],last:[`End`],next:[`ArrowRight`],nextRow:[`ArrowDown`],prev:[`ArrowLeft`],prevRow:[`ArrowUp`]},r={...n,next:[`ArrowLeft`],prev:[`ArrowRight`]},i=[`first`,`last`,`next`,`nextRow`,`prev`,`prevRow`],resolve$1=(e,t)=>typeof e==`function`?e():e??t,createGridNavigation=e=>{let a=new Map;for(let t of i)for(let n of e.keys?.[t]??[]){let e=a.get(n);if(e&&e!==t)throw new FocusConfigError(`Key "${n}" is assigned to both ${e} and ${t}`);a.set(n,t)}let o=-1,resolveColumns=()=>{let t=resolve$1(e.columns,1);if(!Number.isInteger(t)||t<1)throw new FocusConfigError(`columns must resolve to a positive integer, got: ${t}`);return t},activeIndex=t=>{let n=e.getActiveIndex?.();return n!==void 0&&(o=n>=0&&n<t.length?n:-1),o},commitIndex=(e,t,n,r)=>(o=t,Object.freeze({action:n,event:r,index:t,item:e[t]})),navigateWithItems=(t,n,r)=>{if(!t.length)return o=-1,null;if(n===`first`)return commitIndex(t,0,n,r);if(n===`last`)return commitIndex(t,t.length-1,n,r);let i=resolveColumns(),a=n===`next`?1:n===`prev`?-1:n===`nextRow`?i:-i,s=activeIndex(t),c=s<0?a>0?0:t.length-1:s+a;if(e.loop)return commitIndex(t,(c%t.length+t.length)%t.length,n,r);let l=Math.min(t.length-1,Math.max(0,c));return l===s?null:commitIndex(t,l,n,r)},resolveKeyAction=a=>{let o=resolve$1(e.direction,`ltr`)===`rtl`?r:n;for(let n of i)if((e.keys?.[n]??o[n]).some(e=>(0,t.matchKey)(a,e)))return n},handleKeydown=t=>{if(t.defaultPrevented||t.isComposing||resolve$1(e.disabled,!1))return null;let n=resolveKeyAction(t);if(!n)return null;let r=navigateWithItems(e.getItems(),n,t);return t.preventDefault(),Object.freeze({change:r,handled:!0})},navigate=t=>navigateWithItems(e.getItems(),t),set=t=>{let n=e.getItems();if(!Number.isInteger(t)||t<0||!n.length){o=-1;return}o=Math.min(t,n.length-1)},reset=()=>{o=-1},getIndex=()=>activeIndex(e.getItems()),getActiveItem=()=>{let t=e.getItems(),n=activeIndex(t);return n>=0?t[n]:void 0};return{getActiveItem,getIndex,handleKeydown,navigate,reset,set}},a={both:{first:[`Home`],last:[`End`],next:[`ArrowDown`,`ArrowRight`],prev:[`ArrowUp`,`ArrowLeft`]},horizontal:{first:[`Home`],last:[`End`],next:[`ArrowRight`],prev:[`ArrowLeft`]},vertical:{first:[`Home`],last:[`End`],next:[`ArrowDown`],prev:[`ArrowUp`]}},o={both:{first:[`Home`],last:[`End`],next:[`ArrowDown`,`ArrowLeft`],prev:[`ArrowUp`,`ArrowRight`]},horizontal:{first:[`Home`],last:[`End`],next:[`ArrowLeft`],prev:[`ArrowRight`]},vertical:a.vertical},s=500,findForward=(e,t,n)=>{for(let r=t;r<e.length;r++)if(n(e[r],r))return r;return-1},findBackward=(e,t,n)=>{for(let r=t;r>=0;r--)if(n(e[r],r))return r;return-1},resolveTypeaheadDelay=e=>{if(e===void 0)return s;if(!Number.isFinite(e)||e<=0)throw new FocusConfigError(`Typeahead delay must be a positive finite number`);return e},createListNavigation=e=>{let n=resolveTypeaheadDelay(e.typeahead?.delayMs),r=new Map;for(let t of[`next`,`prev`,`first`,`last`])for(let n of e.keys?.[t]??[]){let e=r.get(n);if(e&&e!==t)throw new FocusConfigError(`Key "${n}" is assigned to both ${e} and ${t}`);r.set(n,t)}let i=-1,s=``,c=0,isItemDisabled=(t,n)=>e.isItemDisabled?.(t,n)??!1,isUsableIndex=(e,t)=>t>=0&&t<e.length&&!isItemDisabled(e[t],t),normalizeIndex=e=>(isUsableIndex(e,i)||(i=-1),i),resetTypeahead=()=>{s=``,c=0},commitIndex=(e,t,n,r)=>isUsableIndex(e,t)?(i=t,Object.freeze({action:n,event:r,index:t,item:e[t]})):(i=-1,null),findEnabledIndex=(e,t,n)=>n===`forward`?findForward(e,t,(e,t)=>!isItemDisabled(e,t)):findBackward(e,t,(e,t)=>!isItemDisabled(e,t)),move=(t,n,r)=>{let a=normalizeIndex(t);if(!t.length)return i=-1,null;let o=a<0?n===`forward`?0:t.length-1:n===`forward`?a+1:a-1,s=findEnabledIndex(t,o,n),c=n===`forward`?`next`:`prev`;if(s>=0)return commitIndex(t,s,c,r);if(e.loop){let e=n===`forward`?0:t.length-1,i=findEnabledIndex(t,e,n);if(i>=0)return commitIndex(t,i,c,r)}return null},navigateWithItems=(e,t,n)=>(resetTypeahead(),normalizeIndex(e),e.length?t===`first`?commitIndex(e,findEnabledIndex(e,0,`forward`),t,n):t===`last`?commitIndex(e,findEnabledIndex(e,e.length-1,`backward`),t,n):move(e,t===`next`?`forward`:`backward`,n):(i=-1,null)),findTypeaheadMatch=(t,n,r)=>{let i=normalizeIndex(t),a=i<0?0:r?i:i+1;for(let r=0;r<t.length;r++){let i=(a+r)%t.length,o=t[i];if(!isItemDisabled(o,i)&&e.typeahead?.getLabel(o,i).toLocaleLowerCase().startsWith(n))return i}return-1},handleTypeahead=(t,r)=>{if(!e.typeahead||t.key.length!==1||t.ctrlKey||t.altKey||t.metaKey)return null;let i=performance.now(),a=t.key.toLocaleLowerCase();i-c>=n&&(s=``),c=i;let o=s+a;s=o.length>1&&[...o].every(e=>e===a)?a:o;let l=findTypeaheadMatch(r,s,s.length>1);return l<0&&s.length>1&&(s=a,l=findTypeaheadMatch(r,s,!1)),l<0?null:commitIndex(r,l,`typeahead`,t)},set=t=>{let n=e.getItems();if(normalizeIndex(n),resetTypeahead(),!Number.isInteger(t)||t<0||!n.length)return i=-1,i;let r=Math.min(t,n.length-1);return isUsableIndex(n,r)?(i=r,i):(i=-1,i)},navigate=t=>navigateWithItems(e.getItems(),t),getActiveItem=()=>{let t=e.getItems(),n=normalizeIndex(t);return n>=0?t[n]:void 0},reset=()=>{i=-1,resetTypeahead()},getIndex=()=>normalizeIndex(e.getItems()),resolve=(e,t)=>typeof e==`function`?e():e??t,resolveKeyAction=n=>{let r=e.keys,i=(resolve(e.direction,`ltr`)===`rtl`?o:a)[resolve(e.orientation,`vertical`)];for(let e of[`next`,`prev`,`first`,`last`])if((r?.[e]??i[e]).some(e=>(0,t.matchKey)(n,e)))return e},handleKeydown=t=>{if(t.defaultPrevented||t.isComposing||resolve(e.disabled,!1))return null;let n=e.getItems();normalizeIndex(n);let r=resolveKeyAction(t);if(r){let e=navigateWithItems(n,r,t);return t.preventDefault(),Object.freeze({change:e,handled:!0})}let i=handleTypeahead(t,n);return i?(e.typeahead?.preventDefault&&t.preventDefault(),Object.freeze({change:i,handled:!0})):null};return{getActiveItem,getIndex,handleKeydown,navigate,reset,set}},resolve=(e,t)=>typeof e==`function`?e():e??t,createQuickLookGrid=e=>{let activeIndex=()=>e.getItems().indexOf(document.activeElement),measuredColumns=()=>{let t=e.getItems(),n=t.indexOf(document.activeElement);if(n<0)return 1;let r=t[n]?.getBoundingClientRect().top??0;return Math.max(1,t.filter(e=>Math.abs(e.getBoundingClientRect().top-r)<1).length)},t=createGridNavigation({columns:e.columns??measuredColumns,getActiveIndex:e.getActiveIndex??activeIndex,getItems:e.getItems,loop:e.loop}),resolveItem=t=>{let n=e.getItems(),r=t.target instanceof HTMLElement?t.target:null,i=r===null?-1:n.findIndex(e=>e===r||e.contains(r));if(i>=0)return{index:i,item:n[i]};let a=(e.getActiveIndex??activeIndex)();return{index:a,item:a>=0?n[a]:void 0}},handleKeydown=n=>{if(n.defaultPrevented||n.isComposing||resolve(e.disabled,!1)||n.ctrlKey||n.metaKey||n.altKey)return;if(n.key===(e.inspectKey??` `)){let{index:t,item:r}=resolveItem(n);if(!r)return;n.preventDefault(),n.stopPropagation(),e.onInspect(r,t);return}let r=t.handleKeydown(n);r?.change&&r.change.item.focus()};return{handleKeydown}},resolveTarget=e=>typeof e==`function`?e():e,getDeepActiveElement=e=>{let t=e.activeElement;for(;t instanceof HTMLElement&&t.shadowRoot?.activeElement;)t=t.shadowRoot.activeElement;return t},isConnected=e=>e.isConnected,isDisabled=e=>e instanceof HTMLElement&&`disabled`in e&&!!e.disabled,isInert=e=>e.closest(`[inert]`)?!0:e instanceof HTMLElement&&e.inert,canRestoreTo=e=>isConnected(e)&&!isDisabled(e)&&!isInert(e),focusAndVerify=(e,t)=>{try{return e.focus({preventScroll:t}),getDeepActiveElement(e.ownerDocument)===e}catch{return!1}},restoreFocus=(e,t={})=>{let n;try{n=resolveTarget(e)}catch{n=null}if(n&&canRestoreTo(n)&&focusAndVerify(n,t.preventScroll))return!0;if(!t.fallback)return!1;let r;try{r=resolveTarget(t.fallback)}catch{return!1}return!r||!canRestoreTo(r)?!1:focusAndVerify(r,t.preventScroll)},rescueFocus=(e,t={})=>{let n=getDeepActiveElement(document);return n&&n!==document.body?!1:restoreFocus(e,t)},captureFocus=(e={})=>{let t=getDeepActiveElement(document),n=!e.signal?.aborted;n||(t=null);let cancel=()=>{n=!1,t=null};return e.signal?.aborted||e.signal?.addEventListener(`abort`,cancel,{once:!0}),()=>{if(!n)return!1;n=!1,e.signal?.removeEventListener(`abort`,cancel);let r=t;return t=null,r?restoreFocus(r,{fallback:e.fallback,preventScroll:e.preventScroll}):e.fallback?restoreFocus(e.fallback,{preventScroll:e.preventScroll}):!1}};return e.FocusConfigError=FocusConfigError,e.FocusError=FocusError,e.captureFocus=captureFocus,e.createGridNavigation=createGridNavigation,e.createListNavigation=createListNavigation,e.createQuickLookGrid=createQuickLookGrid,e.rescueFocus=rescueFocus,e.restoreFocus=restoreFocus,e})({},Keymap);
2
2
  //# sourceMappingURL=focus.iife.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"focus.iife.js","names":[],"sources":["../src/errors.ts","../src/grid-navigation.ts","../src/list-navigation.ts","../src/restore-focus.ts"],"sourcesContent":["/** Base class for all focus errors. Use `instanceof FocusError` to catch any focus-originated error. */\nexport class FocusError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when navigation options are invalid, such as a non-positive typeahead delay or a key assigned to two actions. */\nexport class FocusConfigError extends FocusError {}\n","import { matchKey } from '@vielzeug/keymap';\n\nimport { FocusConfigError } from './errors.js';\nimport type { MaybeGetter } from './list-navigation.js';\n\nexport type GridNavigationAction = 'first' | 'last' | 'next' | 'nextRow' | 'prev' | 'prevRow';\n\nexport type GridNavigationChange<T> = {\n readonly action: GridNavigationAction;\n readonly event?: KeyboardEvent;\n readonly index: number;\n readonly item: T;\n};\n\nexport type GridKeyResult<T> = {\n readonly change: GridNavigationChange<T> | null;\n readonly handled: true;\n};\n\n/**\n * Columns per row: a fixed count, or a getter resolved on every navigation. Responsive grids\n * read a media query or computed style; measured grids read the rendered row length.\n */\nexport type GridColumns = number | (() => number);\n\nexport type GridNavigationOptions<T> = {\n /** Columns per row, resolved on every navigation. */\n columns: GridColumns;\n direction?: MaybeGetter<'ltr' | 'rtl'>;\n disabled?: MaybeGetter<boolean>;\n /**\n * Reads the active index from the outside (usually the index of `document.activeElement`\n * among the items), so focus moved by pointer or Tab stays in sync. When omitted, the\n * navigation tracks the index internally through `set()`.\n */\n getActiveIndex?: () => number;\n getItems: () => readonly T[];\n keys?: Partial<Record<GridNavigationAction, readonly string[]>>;\n /** Wrap moves around the grid's edges by flat index. Default clamps at the edges. */\n loop?: boolean;\n};\n\nexport type GridNavigation<T> = {\n getActiveItem(): T | undefined;\n getIndex(): number;\n handleKeydown(event: KeyboardEvent): GridKeyResult<T> | null;\n navigate(action: GridNavigationAction): GridNavigationChange<T> | null;\n reset(): void;\n set(index: number): void;\n};\n\nconst DEFAULT_KEYS: Record<GridNavigationAction, readonly string[]> = {\n first: ['Home'],\n last: ['End'],\n next: ['ArrowRight'],\n nextRow: ['ArrowDown'],\n prev: ['ArrowLeft'],\n prevRow: ['ArrowUp'],\n};\n\nconst DEFAULT_KEYS_RTL: typeof DEFAULT_KEYS = {\n ...DEFAULT_KEYS,\n next: ['ArrowLeft'],\n prev: ['ArrowRight'],\n};\n\nconst GRID_ACTIONS = ['first', 'last', 'next', 'nextRow', 'prev', 'prevRow'] as const;\n\nconst resolve = <V>(value: MaybeGetter<V> | undefined, fallback: V): V =>\n typeof value === 'function' ? (value as () => V)() : (value ?? fallback);\n\n/**\n * Keyboard navigation over a two-dimensional grid of items: horizontal arrows step one\n * item, vertical arrows step one row (the column count), Home/End jump to the ends.\n *\n * Unlike `createListNavigation`, items are not skipped when disabled: skipping in two\n * dimensions would break row alignment. Focus (or otherwise activate) the item reported\n * by the returned change; `handleKeydown` calls `preventDefault()` for every recognized\n * key so unhandled arrows never scroll the page.\n */\nexport const createGridNavigation = <T>(options: GridNavigationOptions<T>): GridNavigation<T> => {\n const owners = new Map<string, GridNavigationAction>();\n\n for (const action of GRID_ACTIONS) {\n for (const key of options.keys?.[action] ?? []) {\n const owner = owners.get(key);\n if (owner && owner !== action)\n throw new FocusConfigError(`Key \"${key}\" is assigned to both ${owner} and ${action}`);\n owners.set(key, action);\n }\n }\n\n let index = -1;\n\n const resolveColumns = (): number => {\n const columns = resolve(options.columns, 1);\n if (!Number.isInteger(columns) || columns < 1)\n throw new FocusConfigError(`columns must resolve to a positive integer, got: ${columns}`);\n return columns;\n };\n\n const activeIndex = (items: readonly T[]): number => {\n const derived = options.getActiveIndex?.();\n if (derived !== undefined) index = derived >= 0 && derived < items.length ? derived : -1;\n return index;\n };\n\n const commitIndex = (\n items: readonly T[],\n nextIndex: number,\n action: GridNavigationAction,\n event?: KeyboardEvent,\n ): GridNavigationChange<T> => {\n index = nextIndex;\n return Object.freeze({ action, event, index: nextIndex, item: items[nextIndex] });\n };\n\n const navigateWithItems = (\n items: readonly T[],\n action: GridNavigationAction,\n event?: KeyboardEvent,\n ): GridNavigationChange<T> | null => {\n if (!items.length) {\n index = -1;\n return null;\n }\n\n if (action === 'first') return commitIndex(items, 0, action, event);\n if (action === 'last') return commitIndex(items, items.length - 1, action, event);\n\n const columns = resolveColumns();\n const offset = action === 'next' ? 1 : action === 'prev' ? -1 : action === 'nextRow' ? columns : -columns;\n const current = activeIndex(items);\n const base = current < 0 ? (offset > 0 ? 0 : items.length - 1) : current + offset;\n\n if (options.loop) {\n return commitIndex(items, ((base % items.length) + items.length) % items.length, action, event);\n }\n\n const clamped = Math.min(items.length - 1, Math.max(0, base));\n if (clamped === current) return null;\n\n return commitIndex(items, clamped, action, event);\n };\n\n const resolveKeyAction = (event: KeyboardEvent): GridNavigationAction | undefined => {\n const keyTable = resolve(options.direction, 'ltr') === 'rtl' ? DEFAULT_KEYS_RTL : DEFAULT_KEYS;\n\n for (const action of GRID_ACTIONS) {\n if ((options.keys?.[action] ?? keyTable[action]).some((pattern) => matchKey(event, pattern))) return action;\n }\n\n return undefined;\n };\n\n const handleKeydown = (event: KeyboardEvent): GridKeyResult<T> | null => {\n if (event.defaultPrevented || event.isComposing || resolve(options.disabled, false)) return null;\n\n const action = resolveKeyAction(event);\n if (!action) return null;\n\n const change = navigateWithItems(options.getItems(), action, event);\n event.preventDefault();\n return Object.freeze({ change, handled: true });\n };\n\n const navigate = (action: GridNavigationAction): GridNavigationChange<T> | null =>\n navigateWithItems(options.getItems(), action);\n\n const set = (nextIndex: number): void => {\n const items = options.getItems();\n\n if (!Number.isInteger(nextIndex) || nextIndex < 0 || !items.length) {\n index = -1;\n return;\n }\n\n index = Math.min(nextIndex, items.length - 1);\n };\n\n const reset = (): void => {\n index = -1;\n };\n\n const getIndex = (): number => activeIndex(options.getItems());\n\n const getActiveItem = (): T | undefined => {\n const items = options.getItems();\n const current = activeIndex(items);\n return current >= 0 ? items[current] : undefined;\n };\n\n return {\n getActiveItem,\n getIndex,\n handleKeydown,\n navigate,\n reset,\n set,\n };\n};\n","import { matchKey } from '@vielzeug/keymap';\n\nimport { FocusConfigError } from './errors.js';\n\nexport type ListNavigationAction = 'first' | 'last' | 'next' | 'prev';\nexport type ListKeyAction = ListNavigationAction | 'typeahead';\n\nexport type ListNavigationChange<T> = {\n readonly action: ListKeyAction;\n readonly event?: KeyboardEvent;\n readonly index: number;\n readonly item: T;\n};\n\nexport type ListKeyResult<T> = {\n readonly change: ListNavigationChange<T> | null;\n readonly handled: true;\n};\n\nexport type ListNavigationTypeaheadOptions<T> = {\n delayMs?: number;\n getLabel: (item: T, index: number) => string;\n preventDefault?: boolean;\n};\n\nexport type MaybeGetter<T> = T | (() => T);\n\nexport type ListNavigationOptions<T> = {\n direction?: MaybeGetter<'ltr' | 'rtl'>;\n disabled?: MaybeGetter<boolean>;\n getItems: () => readonly T[];\n isItemDisabled?: (item: T, index: number) => boolean;\n keys?: Partial<Record<ListNavigationAction, readonly string[]>>;\n loop?: boolean;\n orientation?: MaybeGetter<'both' | 'horizontal' | 'vertical'>;\n typeahead?: ListNavigationTypeaheadOptions<T>;\n};\n\nexport type ListNavigation<T> = {\n getActiveItem(): T | undefined;\n getIndex(): number;\n handleKeydown(event: KeyboardEvent): ListKeyResult<T> | null;\n navigate(action: ListNavigationAction): ListNavigationChange<T> | null;\n reset(): void;\n set(index: number): number;\n};\n\nconst DEFAULT_KEYS: Record<'both' | 'horizontal' | 'vertical', Record<ListNavigationAction, readonly string[]>> = {\n both: { first: ['Home'], last: ['End'], next: ['ArrowDown', 'ArrowRight'], prev: ['ArrowUp', 'ArrowLeft'] },\n horizontal: { first: ['Home'], last: ['End'], next: ['ArrowRight'], prev: ['ArrowLeft'] },\n vertical: { first: ['Home'], last: ['End'], next: ['ArrowDown'], prev: ['ArrowUp'] },\n};\n\nconst DEFAULT_KEYS_RTL: typeof DEFAULT_KEYS = {\n both: { first: ['Home'], last: ['End'], next: ['ArrowDown', 'ArrowLeft'], prev: ['ArrowUp', 'ArrowRight'] },\n horizontal: { first: ['Home'], last: ['End'], next: ['ArrowLeft'], prev: ['ArrowRight'] },\n vertical: DEFAULT_KEYS.vertical,\n};\n\nconst DEFAULT_TYPEAHEAD_DELAY_MS = 500;\n\nconst findForward = <T>(items: readonly T[], start: number, predicate: (item: T, index: number) => boolean): number => {\n for (let index = start; index < items.length; index++) {\n if (predicate(items[index], index)) return index;\n }\n\n return -1;\n};\n\nconst findBackward = <T>(\n items: readonly T[],\n start: number,\n predicate: (item: T, index: number) => boolean,\n): number => {\n for (let index = start; index >= 0; index--) {\n if (predicate(items[index], index)) return index;\n }\n\n return -1;\n};\n\nconst resolveTypeaheadDelay = (delay: number | undefined): number => {\n if (delay === undefined) return DEFAULT_TYPEAHEAD_DELAY_MS;\n if (!Number.isFinite(delay) || delay <= 0)\n throw new FocusConfigError('Typeahead delay must be a positive finite number');\n return delay;\n};\n\nexport const createListNavigation = <T>(options: ListNavigationOptions<T>): ListNavigation<T> => {\n const typeaheadDelay = resolveTypeaheadDelay(options.typeahead?.delayMs);\n const owners = new Map<string, ListNavigationAction>();\n\n for (const action of ['next', 'prev', 'first', 'last'] as const) {\n for (const key of options.keys?.[action] ?? []) {\n const owner = owners.get(key);\n if (owner && owner !== action)\n throw new FocusConfigError(`Key \"${key}\" is assigned to both ${owner} and ${action}`);\n owners.set(key, action);\n }\n }\n\n let index = -1;\n let typeaheadBuffer = '';\n let lastTypeaheadAt = 0;\n\n const isItemDisabled = (item: T, itemIndex: number): boolean => options.isItemDisabled?.(item, itemIndex) ?? false;\n\n const isUsableIndex = (items: readonly T[], itemIndex: number): boolean =>\n itemIndex >= 0 && itemIndex < items.length && !isItemDisabled(items[itemIndex], itemIndex);\n\n const normalizeIndex = (items: readonly T[]): number => {\n if (!isUsableIndex(items, index)) index = -1;\n\n return index;\n };\n\n const resetTypeahead = (): void => {\n typeaheadBuffer = '';\n lastTypeaheadAt = 0;\n };\n\n const commitIndex = (\n items: readonly T[],\n nextIndex: number,\n action: ListKeyAction,\n event?: KeyboardEvent,\n ): ListNavigationChange<T> | null => {\n if (!isUsableIndex(items, nextIndex)) {\n index = -1;\n\n return null;\n }\n\n index = nextIndex;\n\n return Object.freeze({ action, event, index: nextIndex, item: items[nextIndex] });\n };\n\n const findEnabledIndex = (items: readonly T[], start: number, direction: 'forward' | 'backward'): number => {\n if (direction === 'forward')\n return findForward(items, start, (item, itemIndex) => !isItemDisabled(item, itemIndex));\n\n return findBackward(items, start, (item, itemIndex) => !isItemDisabled(item, itemIndex));\n };\n\n const move = (\n items: readonly T[],\n direction: 'forward' | 'backward',\n event?: KeyboardEvent,\n ): ListNavigationChange<T> | null => {\n const current = normalizeIndex(items);\n\n if (!items.length) {\n index = -1;\n\n return null;\n }\n\n const start =\n current < 0\n ? direction === 'forward'\n ? 0\n : items.length - 1\n : direction === 'forward'\n ? current + 1\n : current - 1;\n\n const nextIndex = findEnabledIndex(items, start, direction);\n const action: ListNavigationAction = direction === 'forward' ? 'next' : 'prev';\n\n if (nextIndex >= 0) return commitIndex(items, nextIndex, action, event);\n\n if (options.loop) {\n const wrapStart = direction === 'forward' ? 0 : items.length - 1;\n const wrapped = findEnabledIndex(items, wrapStart, direction);\n\n if (wrapped >= 0) return commitIndex(items, wrapped, action, event);\n }\n\n return null;\n };\n\n const navigateWithItems = (\n items: readonly T[],\n action: ListNavigationAction,\n event?: KeyboardEvent,\n ): ListNavigationChange<T> | null => {\n resetTypeahead();\n normalizeIndex(items);\n\n if (!items.length) {\n index = -1;\n\n return null;\n }\n\n if (action === 'first') {\n return commitIndex(items, findEnabledIndex(items, 0, 'forward'), action, event);\n }\n\n if (action === 'last') {\n return commitIndex(items, findEnabledIndex(items, items.length - 1, 'backward'), action, event);\n }\n\n return move(items, action === 'next' ? 'forward' : 'backward', event);\n };\n\n const findTypeaheadMatch = (items: readonly T[], search: string, includeCurrent: boolean): number => {\n const current = normalizeIndex(items);\n const start = current < 0 ? 0 : includeCurrent ? current : current + 1;\n\n for (let offset = 0; offset < items.length; offset++) {\n const itemIndex = (start + offset) % items.length;\n const item = items[itemIndex];\n\n if (isItemDisabled(item, itemIndex)) continue;\n\n if (options.typeahead?.getLabel(item, itemIndex).toLocaleLowerCase().startsWith(search)) {\n return itemIndex;\n }\n }\n\n return -1;\n };\n\n const handleTypeahead = (event: KeyboardEvent, items: readonly T[]): ListNavigationChange<T> | null => {\n if (!options.typeahead || event.key.length !== 1 || event.ctrlKey || event.altKey || event.metaKey) return null;\n\n const now = performance.now();\n const key = event.key.toLocaleLowerCase();\n\n if (now - lastTypeaheadAt >= typeaheadDelay) typeaheadBuffer = '';\n\n lastTypeaheadAt = now;\n\n const candidate = typeaheadBuffer + key;\n const repeatedCharacter = candidate.length > 1 && [...candidate].every((character) => character === key);\n typeaheadBuffer = repeatedCharacter ? key : candidate;\n\n let nextIndex = findTypeaheadMatch(items, typeaheadBuffer, typeaheadBuffer.length > 1);\n\n if (nextIndex < 0 && typeaheadBuffer.length > 1) {\n typeaheadBuffer = key;\n nextIndex = findTypeaheadMatch(items, typeaheadBuffer, false);\n }\n\n if (nextIndex < 0) return null;\n\n return commitIndex(items, nextIndex, 'typeahead', event);\n };\n\n const set = (nextIndex: number): number => {\n const items = options.getItems();\n normalizeIndex(items);\n resetTypeahead();\n\n if (!Number.isInteger(nextIndex) || nextIndex < 0 || !items.length) {\n index = -1;\n\n return index;\n }\n\n const clamped = Math.min(nextIndex, items.length - 1);\n\n if (!isUsableIndex(items, clamped)) {\n index = -1;\n\n return index;\n }\n\n index = clamped;\n\n return index;\n };\n\n const navigate = (action: ListNavigationAction): ListNavigationChange<T> | null => {\n return navigateWithItems(options.getItems(), action);\n };\n\n const getActiveItem = (): T | undefined => {\n const items = options.getItems();\n const current = normalizeIndex(items);\n\n return current >= 0 ? items[current] : undefined;\n };\n\n const reset = (): void => {\n index = -1;\n resetTypeahead();\n };\n\n const getIndex = (): number => {\n return normalizeIndex(options.getItems());\n };\n\n const resolve = <V>(value: MaybeGetter<V> | undefined, fallback: V): V =>\n typeof value === 'function' ? (value as () => V)() : (value ?? fallback);\n\n const resolveKeyAction = (event: KeyboardEvent): ListNavigationAction | undefined => {\n const keys = options.keys;\n const keyTable = resolve(options.direction, 'ltr') === 'rtl' ? DEFAULT_KEYS_RTL : DEFAULT_KEYS;\n const defaults = keyTable[resolve(options.orientation, 'vertical')];\n\n for (const action of ['next', 'prev', 'first', 'last'] as const) {\n if ((keys?.[action] ?? defaults[action]).some((pattern) => matchKey(event, pattern))) return action;\n }\n\n return undefined;\n };\n\n const handleKeydown = (event: KeyboardEvent): ListKeyResult<T> | null => {\n if (event.defaultPrevented || event.isComposing || resolve(options.disabled, false)) return null;\n\n const items = options.getItems();\n normalizeIndex(items);\n\n const action = resolveKeyAction(event);\n\n if (action) {\n const change = navigateWithItems(items, action, event);\n event.preventDefault();\n return Object.freeze({ change, handled: true });\n }\n\n const change = handleTypeahead(event, items);\n if (!change) return null;\n if (options.typeahead?.preventDefault) event.preventDefault();\n return Object.freeze({ change, handled: true });\n };\n\n return {\n getActiveItem,\n getIndex,\n handleKeydown,\n navigate,\n reset,\n set,\n };\n};\n","export type FocusableElement = HTMLElement | SVGElement;\nexport type FocusTarget = FocusableElement | null | undefined | (() => FocusableElement | null | undefined);\n\nexport type RestoreFocusOptions = {\n fallback?: FocusTarget;\n preventScroll?: boolean;\n};\n\nexport type CaptureFocusOptions = RestoreFocusOptions & {\n signal?: AbortSignal;\n};\n\nexport type FocusRestorer = () => boolean;\n\nconst resolveTarget = (target: FocusTarget): FocusableElement | null | undefined => {\n return typeof target === 'function' ? target() : target;\n};\n\nconst getDeepActiveElement = (rootDocument: Document): Element | null => {\n let active: Element | null = rootDocument.activeElement;\n\n while (active instanceof HTMLElement && active.shadowRoot?.activeElement) {\n active = active.shadowRoot.activeElement;\n }\n\n return active;\n};\n\nconst isConnected = (target: FocusableElement): boolean => {\n // `isConnected` pierces shadow boundaries (shadow-including root is the document);\n // `ownerDocument.contains` does NOT: it would reject elements inside shadow roots.\n return target.isConnected;\n};\n\nconst isDisabled = (target: FocusableElement): boolean => {\n return target instanceof HTMLElement && 'disabled' in target && Boolean((target as HTMLInputElement).disabled);\n};\n\nconst isInert = (target: FocusableElement): boolean => {\n if (target.closest('[inert]')) return true;\n\n return target instanceof HTMLElement && target.inert;\n};\n\nconst canRestoreTo = (target: FocusableElement): boolean => {\n return isConnected(target) && !isDisabled(target) && !isInert(target);\n};\n\nconst focusAndVerify = (target: FocusableElement, preventScroll: boolean | undefined): boolean => {\n try {\n target.focus({ preventScroll });\n return getDeepActiveElement(target.ownerDocument) === target;\n } catch {\n return false;\n }\n};\n\nexport const restoreFocus = (target: FocusTarget, options: RestoreFocusOptions = {}): boolean => {\n let next: FocusableElement | null | undefined;\n try {\n next = resolveTarget(target);\n } catch {\n next = null;\n }\n\n if (next && canRestoreTo(next) && focusAndVerify(next, options.preventScroll)) {\n return true;\n }\n\n if (!options.fallback) return false;\n\n let fallback: FocusableElement | null | undefined;\n try {\n fallback = resolveTarget(options.fallback);\n } catch {\n return false;\n }\n\n if (!fallback || !canRestoreTo(fallback)) return false;\n\n return focusAndVerify(fallback, options.preventScroll);\n};\n\n/**\n * Hands focus to `target` when focus has been lost to the document body: the state left\n * behind when the focused element unmounts mid-swap (browsing a dialog, paging a list),\n * where keydown would never reach a handler and every shortcut dies. Returns `true` when\n * focus was rescued; `false` when focus is already on a real element or the target cannot\n * take focus (the `fallback` option of {@link RestoreFocusOptions} still applies).\n */\nexport const rescueFocus = (target: FocusTarget, options: RestoreFocusOptions = {}): boolean => {\n const active = getDeepActiveElement(document);\n\n if (active && active !== document.body) return false;\n\n return restoreFocus(target, options);\n};\n\nexport const captureFocus = (options: CaptureFocusOptions = {}): FocusRestorer => {\n let captured = getDeepActiveElement(document) as FocusableElement | null;\n let available = !options.signal?.aborted;\n\n if (!available) captured = null;\n\n const cancel = (): void => {\n available = false;\n captured = null;\n };\n\n if (!options.signal?.aborted) {\n options.signal?.addEventListener('abort', cancel, { once: true });\n }\n\n return () => {\n if (!available) return false;\n\n available = false;\n options.signal?.removeEventListener('abort', cancel);\n\n const target = captured;\n captured = null;\n\n if (target) {\n return restoreFocus(target, {\n fallback: options.fallback,\n preventScroll: options.preventScroll,\n });\n }\n\n if (options.fallback) {\n return restoreFocus(options.fallback, {\n preventScroll: options.preventScroll,\n });\n }\n\n return false;\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"focus.iife.js","names":[],"sources":["../src/errors.ts","../src/grid-navigation.ts","../src/list-navigation.ts","../src/quick-look-grid.ts","../src/restore-focus.ts"],"sourcesContent":["/** Base class for all focus errors. Use `instanceof FocusError` to catch any focus-originated error. */\nexport class FocusError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when navigation options are invalid, such as a non-positive typeahead delay or a key assigned to two actions. */\nexport class FocusConfigError extends FocusError {}\n","import { matchKey } from '@vielzeug/keymap';\n\nimport { FocusConfigError } from './errors.js';\nimport type { MaybeGetter } from './list-navigation.js';\n\nexport type GridNavigationAction = 'first' | 'last' | 'next' | 'nextRow' | 'prev' | 'prevRow';\n\nexport type GridNavigationChange<T> = {\n readonly action: GridNavigationAction;\n readonly event?: KeyboardEvent;\n readonly index: number;\n readonly item: T;\n};\n\nexport type GridKeyResult<T> = {\n readonly change: GridNavigationChange<T> | null;\n readonly handled: true;\n};\n\n/**\n * Columns per row: a fixed count, or a getter resolved on every navigation. Responsive grids\n * read a media query or computed style; measured grids read the rendered row length.\n */\nexport type GridColumns = number | (() => number);\n\nexport type GridNavigationOptions<T> = {\n /** Columns per row, resolved on every navigation. */\n columns: GridColumns;\n direction?: MaybeGetter<'ltr' | 'rtl'>;\n disabled?: MaybeGetter<boolean>;\n /**\n * Reads the active index from the outside (usually the index of `document.activeElement`\n * among the items), so focus moved by pointer or Tab stays in sync. When omitted, the\n * navigation tracks the index internally through `set()`.\n */\n getActiveIndex?: () => number;\n getItems: () => readonly T[];\n keys?: Partial<Record<GridNavigationAction, readonly string[]>>;\n /** Wrap moves around the grid's edges by flat index. Default clamps at the edges. */\n loop?: boolean;\n};\n\nexport type GridNavigation<T> = {\n getActiveItem(): T | undefined;\n getIndex(): number;\n handleKeydown(event: KeyboardEvent): GridKeyResult<T> | null;\n navigate(action: GridNavigationAction): GridNavigationChange<T> | null;\n reset(): void;\n set(index: number): void;\n};\n\nconst DEFAULT_KEYS: Record<GridNavigationAction, readonly string[]> = {\n first: ['Home'],\n last: ['End'],\n next: ['ArrowRight'],\n nextRow: ['ArrowDown'],\n prev: ['ArrowLeft'],\n prevRow: ['ArrowUp'],\n};\n\nconst DEFAULT_KEYS_RTL: typeof DEFAULT_KEYS = {\n ...DEFAULT_KEYS,\n next: ['ArrowLeft'],\n prev: ['ArrowRight'],\n};\n\nconst GRID_ACTIONS = ['first', 'last', 'next', 'nextRow', 'prev', 'prevRow'] as const;\n\nconst resolve = <V>(value: MaybeGetter<V> | undefined, fallback: V): V =>\n typeof value === 'function' ? (value as () => V)() : (value ?? fallback);\n\n/**\n * Keyboard navigation over a two-dimensional grid of items: horizontal arrows step one\n * item, vertical arrows step one row (the column count), Home/End jump to the ends.\n *\n * Unlike `createListNavigation`, items are not skipped when disabled: skipping in two\n * dimensions would break row alignment. Focus (or otherwise activate) the item reported\n * by the returned change; `handleKeydown` calls `preventDefault()` for every recognized\n * key so unhandled arrows never scroll the page.\n */\nexport const createGridNavigation = <T>(options: GridNavigationOptions<T>): GridNavigation<T> => {\n const owners = new Map<string, GridNavigationAction>();\n\n for (const action of GRID_ACTIONS) {\n for (const key of options.keys?.[action] ?? []) {\n const owner = owners.get(key);\n if (owner && owner !== action)\n throw new FocusConfigError(`Key \"${key}\" is assigned to both ${owner} and ${action}`);\n owners.set(key, action);\n }\n }\n\n let index = -1;\n\n const resolveColumns = (): number => {\n const columns = resolve(options.columns, 1);\n if (!Number.isInteger(columns) || columns < 1)\n throw new FocusConfigError(`columns must resolve to a positive integer, got: ${columns}`);\n return columns;\n };\n\n const activeIndex = (items: readonly T[]): number => {\n const derived = options.getActiveIndex?.();\n if (derived !== undefined) index = derived >= 0 && derived < items.length ? derived : -1;\n return index;\n };\n\n const commitIndex = (\n items: readonly T[],\n nextIndex: number,\n action: GridNavigationAction,\n event?: KeyboardEvent,\n ): GridNavigationChange<T> => {\n index = nextIndex;\n return Object.freeze({ action, event, index: nextIndex, item: items[nextIndex] });\n };\n\n const navigateWithItems = (\n items: readonly T[],\n action: GridNavigationAction,\n event?: KeyboardEvent,\n ): GridNavigationChange<T> | null => {\n if (!items.length) {\n index = -1;\n return null;\n }\n\n if (action === 'first') return commitIndex(items, 0, action, event);\n if (action === 'last') return commitIndex(items, items.length - 1, action, event);\n\n const columns = resolveColumns();\n const offset = action === 'next' ? 1 : action === 'prev' ? -1 : action === 'nextRow' ? columns : -columns;\n const current = activeIndex(items);\n const base = current < 0 ? (offset > 0 ? 0 : items.length - 1) : current + offset;\n\n if (options.loop) {\n return commitIndex(items, ((base % items.length) + items.length) % items.length, action, event);\n }\n\n const clamped = Math.min(items.length - 1, Math.max(0, base));\n if (clamped === current) return null;\n\n return commitIndex(items, clamped, action, event);\n };\n\n const resolveKeyAction = (event: KeyboardEvent): GridNavigationAction | undefined => {\n const keyTable = resolve(options.direction, 'ltr') === 'rtl' ? DEFAULT_KEYS_RTL : DEFAULT_KEYS;\n\n for (const action of GRID_ACTIONS) {\n if ((options.keys?.[action] ?? keyTable[action]).some((pattern) => matchKey(event, pattern))) return action;\n }\n\n return undefined;\n };\n\n const handleKeydown = (event: KeyboardEvent): GridKeyResult<T> | null => {\n if (event.defaultPrevented || event.isComposing || resolve(options.disabled, false)) return null;\n\n const action = resolveKeyAction(event);\n if (!action) return null;\n\n const change = navigateWithItems(options.getItems(), action, event);\n event.preventDefault();\n return Object.freeze({ change, handled: true });\n };\n\n const navigate = (action: GridNavigationAction): GridNavigationChange<T> | null =>\n navigateWithItems(options.getItems(), action);\n\n const set = (nextIndex: number): void => {\n const items = options.getItems();\n\n if (!Number.isInteger(nextIndex) || nextIndex < 0 || !items.length) {\n index = -1;\n return;\n }\n\n index = Math.min(nextIndex, items.length - 1);\n };\n\n const reset = (): void => {\n index = -1;\n };\n\n const getIndex = (): number => activeIndex(options.getItems());\n\n const getActiveItem = (): T | undefined => {\n const items = options.getItems();\n const current = activeIndex(items);\n return current >= 0 ? items[current] : undefined;\n };\n\n return {\n getActiveItem,\n getIndex,\n handleKeydown,\n navigate,\n reset,\n set,\n };\n};\n","import { matchKey } from '@vielzeug/keymap';\n\nimport { FocusConfigError } from './errors.js';\n\nexport type ListNavigationAction = 'first' | 'last' | 'next' | 'prev';\nexport type ListKeyAction = ListNavigationAction | 'typeahead';\n\nexport type ListNavigationChange<T> = {\n readonly action: ListKeyAction;\n readonly event?: KeyboardEvent;\n readonly index: number;\n readonly item: T;\n};\n\nexport type ListKeyResult<T> = {\n readonly change: ListNavigationChange<T> | null;\n readonly handled: true;\n};\n\nexport type ListNavigationTypeaheadOptions<T> = {\n delayMs?: number;\n getLabel: (item: T, index: number) => string;\n preventDefault?: boolean;\n};\n\nexport type MaybeGetter<T> = T | (() => T);\n\nexport type ListNavigationOptions<T> = {\n direction?: MaybeGetter<'ltr' | 'rtl'>;\n disabled?: MaybeGetter<boolean>;\n getItems: () => readonly T[];\n isItemDisabled?: (item: T, index: number) => boolean;\n keys?: Partial<Record<ListNavigationAction, readonly string[]>>;\n loop?: boolean;\n orientation?: MaybeGetter<'both' | 'horizontal' | 'vertical'>;\n typeahead?: ListNavigationTypeaheadOptions<T>;\n};\n\nexport type ListNavigation<T> = {\n getActiveItem(): T | undefined;\n getIndex(): number;\n handleKeydown(event: KeyboardEvent): ListKeyResult<T> | null;\n navigate(action: ListNavigationAction): ListNavigationChange<T> | null;\n reset(): void;\n set(index: number): number;\n};\n\nconst DEFAULT_KEYS: Record<'both' | 'horizontal' | 'vertical', Record<ListNavigationAction, readonly string[]>> = {\n both: { first: ['Home'], last: ['End'], next: ['ArrowDown', 'ArrowRight'], prev: ['ArrowUp', 'ArrowLeft'] },\n horizontal: { first: ['Home'], last: ['End'], next: ['ArrowRight'], prev: ['ArrowLeft'] },\n vertical: { first: ['Home'], last: ['End'], next: ['ArrowDown'], prev: ['ArrowUp'] },\n};\n\nconst DEFAULT_KEYS_RTL: typeof DEFAULT_KEYS = {\n both: { first: ['Home'], last: ['End'], next: ['ArrowDown', 'ArrowLeft'], prev: ['ArrowUp', 'ArrowRight'] },\n horizontal: { first: ['Home'], last: ['End'], next: ['ArrowLeft'], prev: ['ArrowRight'] },\n vertical: DEFAULT_KEYS.vertical,\n};\n\nconst DEFAULT_TYPEAHEAD_DELAY_MS = 500;\n\nconst findForward = <T>(items: readonly T[], start: number, predicate: (item: T, index: number) => boolean): number => {\n for (let index = start; index < items.length; index++) {\n if (predicate(items[index], index)) return index;\n }\n\n return -1;\n};\n\nconst findBackward = <T>(\n items: readonly T[],\n start: number,\n predicate: (item: T, index: number) => boolean,\n): number => {\n for (let index = start; index >= 0; index--) {\n if (predicate(items[index], index)) return index;\n }\n\n return -1;\n};\n\nconst resolveTypeaheadDelay = (delay: number | undefined): number => {\n if (delay === undefined) return DEFAULT_TYPEAHEAD_DELAY_MS;\n if (!Number.isFinite(delay) || delay <= 0)\n throw new FocusConfigError('Typeahead delay must be a positive finite number');\n return delay;\n};\n\nexport const createListNavigation = <T>(options: ListNavigationOptions<T>): ListNavigation<T> => {\n const typeaheadDelay = resolveTypeaheadDelay(options.typeahead?.delayMs);\n const owners = new Map<string, ListNavigationAction>();\n\n for (const action of ['next', 'prev', 'first', 'last'] as const) {\n for (const key of options.keys?.[action] ?? []) {\n const owner = owners.get(key);\n if (owner && owner !== action)\n throw new FocusConfigError(`Key \"${key}\" is assigned to both ${owner} and ${action}`);\n owners.set(key, action);\n }\n }\n\n let index = -1;\n let typeaheadBuffer = '';\n let lastTypeaheadAt = 0;\n\n const isItemDisabled = (item: T, itemIndex: number): boolean => options.isItemDisabled?.(item, itemIndex) ?? false;\n\n const isUsableIndex = (items: readonly T[], itemIndex: number): boolean =>\n itemIndex >= 0 && itemIndex < items.length && !isItemDisabled(items[itemIndex], itemIndex);\n\n const normalizeIndex = (items: readonly T[]): number => {\n if (!isUsableIndex(items, index)) index = -1;\n\n return index;\n };\n\n const resetTypeahead = (): void => {\n typeaheadBuffer = '';\n lastTypeaheadAt = 0;\n };\n\n const commitIndex = (\n items: readonly T[],\n nextIndex: number,\n action: ListKeyAction,\n event?: KeyboardEvent,\n ): ListNavigationChange<T> | null => {\n if (!isUsableIndex(items, nextIndex)) {\n index = -1;\n\n return null;\n }\n\n index = nextIndex;\n\n return Object.freeze({ action, event, index: nextIndex, item: items[nextIndex] });\n };\n\n const findEnabledIndex = (items: readonly T[], start: number, direction: 'forward' | 'backward'): number => {\n if (direction === 'forward')\n return findForward(items, start, (item, itemIndex) => !isItemDisabled(item, itemIndex));\n\n return findBackward(items, start, (item, itemIndex) => !isItemDisabled(item, itemIndex));\n };\n\n const move = (\n items: readonly T[],\n direction: 'forward' | 'backward',\n event?: KeyboardEvent,\n ): ListNavigationChange<T> | null => {\n const current = normalizeIndex(items);\n\n if (!items.length) {\n index = -1;\n\n return null;\n }\n\n const start =\n current < 0\n ? direction === 'forward'\n ? 0\n : items.length - 1\n : direction === 'forward'\n ? current + 1\n : current - 1;\n\n const nextIndex = findEnabledIndex(items, start, direction);\n const action: ListNavigationAction = direction === 'forward' ? 'next' : 'prev';\n\n if (nextIndex >= 0) return commitIndex(items, nextIndex, action, event);\n\n if (options.loop) {\n const wrapStart = direction === 'forward' ? 0 : items.length - 1;\n const wrapped = findEnabledIndex(items, wrapStart, direction);\n\n if (wrapped >= 0) return commitIndex(items, wrapped, action, event);\n }\n\n return null;\n };\n\n const navigateWithItems = (\n items: readonly T[],\n action: ListNavigationAction,\n event?: KeyboardEvent,\n ): ListNavigationChange<T> | null => {\n resetTypeahead();\n normalizeIndex(items);\n\n if (!items.length) {\n index = -1;\n\n return null;\n }\n\n if (action === 'first') {\n return commitIndex(items, findEnabledIndex(items, 0, 'forward'), action, event);\n }\n\n if (action === 'last') {\n return commitIndex(items, findEnabledIndex(items, items.length - 1, 'backward'), action, event);\n }\n\n return move(items, action === 'next' ? 'forward' : 'backward', event);\n };\n\n const findTypeaheadMatch = (items: readonly T[], search: string, includeCurrent: boolean): number => {\n const current = normalizeIndex(items);\n const start = current < 0 ? 0 : includeCurrent ? current : current + 1;\n\n for (let offset = 0; offset < items.length; offset++) {\n const itemIndex = (start + offset) % items.length;\n const item = items[itemIndex];\n\n if (isItemDisabled(item, itemIndex)) continue;\n\n if (options.typeahead?.getLabel(item, itemIndex).toLocaleLowerCase().startsWith(search)) {\n return itemIndex;\n }\n }\n\n return -1;\n };\n\n const handleTypeahead = (event: KeyboardEvent, items: readonly T[]): ListNavigationChange<T> | null => {\n if (!options.typeahead || event.key.length !== 1 || event.ctrlKey || event.altKey || event.metaKey) return null;\n\n const now = performance.now();\n const key = event.key.toLocaleLowerCase();\n\n if (now - lastTypeaheadAt >= typeaheadDelay) typeaheadBuffer = '';\n\n lastTypeaheadAt = now;\n\n const candidate = typeaheadBuffer + key;\n const repeatedCharacter = candidate.length > 1 && [...candidate].every((character) => character === key);\n typeaheadBuffer = repeatedCharacter ? key : candidate;\n\n let nextIndex = findTypeaheadMatch(items, typeaheadBuffer, typeaheadBuffer.length > 1);\n\n if (nextIndex < 0 && typeaheadBuffer.length > 1) {\n typeaheadBuffer = key;\n nextIndex = findTypeaheadMatch(items, typeaheadBuffer, false);\n }\n\n if (nextIndex < 0) return null;\n\n return commitIndex(items, nextIndex, 'typeahead', event);\n };\n\n const set = (nextIndex: number): number => {\n const items = options.getItems();\n normalizeIndex(items);\n resetTypeahead();\n\n if (!Number.isInteger(nextIndex) || nextIndex < 0 || !items.length) {\n index = -1;\n\n return index;\n }\n\n const clamped = Math.min(nextIndex, items.length - 1);\n\n if (!isUsableIndex(items, clamped)) {\n index = -1;\n\n return index;\n }\n\n index = clamped;\n\n return index;\n };\n\n const navigate = (action: ListNavigationAction): ListNavigationChange<T> | null => {\n return navigateWithItems(options.getItems(), action);\n };\n\n const getActiveItem = (): T | undefined => {\n const items = options.getItems();\n const current = normalizeIndex(items);\n\n return current >= 0 ? items[current] : undefined;\n };\n\n const reset = (): void => {\n index = -1;\n resetTypeahead();\n };\n\n const getIndex = (): number => {\n return normalizeIndex(options.getItems());\n };\n\n const resolve = <V>(value: MaybeGetter<V> | undefined, fallback: V): V =>\n typeof value === 'function' ? (value as () => V)() : (value ?? fallback);\n\n const resolveKeyAction = (event: KeyboardEvent): ListNavigationAction | undefined => {\n const keys = options.keys;\n const keyTable = resolve(options.direction, 'ltr') === 'rtl' ? DEFAULT_KEYS_RTL : DEFAULT_KEYS;\n const defaults = keyTable[resolve(options.orientation, 'vertical')];\n\n for (const action of ['next', 'prev', 'first', 'last'] as const) {\n if ((keys?.[action] ?? defaults[action]).some((pattern) => matchKey(event, pattern))) return action;\n }\n\n return undefined;\n };\n\n const handleKeydown = (event: KeyboardEvent): ListKeyResult<T> | null => {\n if (event.defaultPrevented || event.isComposing || resolve(options.disabled, false)) return null;\n\n const items = options.getItems();\n normalizeIndex(items);\n\n const action = resolveKeyAction(event);\n\n if (action) {\n const change = navigateWithItems(items, action, event);\n event.preventDefault();\n return Object.freeze({ change, handled: true });\n }\n\n const change = handleTypeahead(event, items);\n if (!change) return null;\n if (options.typeahead?.preventDefault) event.preventDefault();\n return Object.freeze({ change, handled: true });\n };\n\n return {\n getActiveItem,\n getIndex,\n handleKeydown,\n navigate,\n reset,\n set,\n };\n};\n","import type { GridColumns } from './grid-navigation.js';\nimport { createGridNavigation } from './grid-navigation.js';\nimport type { MaybeGetter } from './list-navigation.js';\n\nexport type QuickLookGridOptions = {\n /**\n * Columns per row, resolved on every navigation. Defaults to the rendered row length at the\n * active item, which follows the grid's own responsive wrapping without a media query.\n */\n columns?: GridColumns;\n /** Suppresses every key: for grids the consumer has locked. */\n disabled?: MaybeGetter<boolean>;\n /** Reads the grid's items in display order, re-read on every key. */\n getItems: () => readonly HTMLElement[];\n /**\n * Reads the active index among the items when the event target is outside them, for grids\n * whose focus sits on a wrapper around the item. Defaults to the item that is (or contains)\n * `document.activeElement`.\n */\n getActiveIndex?: () => number;\n /** Key that opens the Quick Look. Default `' '` (Space), the platform preview key. */\n inspectKey?: string;\n /** Wrap moves around the grid's edges by flat index. Default clamps at the edges. */\n loop?: boolean;\n /**\n * Opens the Quick Look for one item. The keyboard path has already focused the item; the\n * consumer decides what \"detail\" means (a dialog, a zoom, a preview pane).\n */\n onInspect: (item: HTMLElement, index: number) => void;\n};\n\nexport type QuickLookGrid = {\n /**\n * Route the grid's `keydown` here. Arrow keys and Home/End move focus through the items;\n * `inspectKey` opens the Quick Look for the item under the event (or the active item),\n * cancelling the key so the focused control never activates. Modifier chords pass through.\n */\n handleKeydown(event: KeyboardEvent): void;\n};\n\nconst resolve = <V>(value: MaybeGetter<V> | undefined, fallback: V): V =>\n typeof value === 'function' ? (value as () => V)() : (value ?? fallback);\n\n/**\n * Keyboard browsing over a tile grid with a Quick Look key: the pair every card picker needs,\n * where arrows walk the tiles and Space previews the focused card instead of activating it.\n *\n * Built on `createGridNavigation`: horizontal arrows step one item, vertical arrows step one\n * row, Home/End jump to the ends, and focus follows the reported change. `inspectKey` (Space by\n * default) resolves the item under the event target, calls `preventDefault()` and\n * `stopPropagation()` so the focused button never sees the press, and reports it through\n * `onInspect`. Long-press is the touch counterpart of the same callback; pair this with a\n * pointer recognizer (for example `@vielzeug/gesture`'s long press) at the call site.\n */\nexport const createQuickLookGrid = (options: QuickLookGridOptions): QuickLookGrid => {\n const activeIndex = (): number => options.getItems().indexOf(document.activeElement as HTMLElement);\n\n /** The rendered row length at the active item: items sharing its top edge. */\n const measuredColumns = (): number => {\n const items = options.getItems();\n const active = items.indexOf(document.activeElement as HTMLElement);\n if (active < 0) return 1;\n const top = items[active]?.getBoundingClientRect().top ?? 0;\n return Math.max(1, items.filter((item) => Math.abs(item.getBoundingClientRect().top - top) < 1).length);\n };\n\n const navigation = createGridNavigation<HTMLElement>({\n columns: options.columns ?? measuredColumns,\n getActiveIndex: options.getActiveIndex ?? activeIndex,\n getItems: options.getItems,\n loop: options.loop,\n });\n\n /** The item the key belongs to: the one containing the event target, else the active item. */\n const resolveItem = (event: KeyboardEvent): { index: number; item: HTMLElement | undefined } => {\n const items = options.getItems();\n const target = event.target instanceof HTMLElement ? event.target : null;\n const byTarget = target === null ? -1 : items.findIndex((item) => item === target || item.contains(target));\n if (byTarget >= 0) return { index: byTarget, item: items[byTarget] };\n const index = (options.getActiveIndex ?? activeIndex)();\n return { index, item: index >= 0 ? items[index] : undefined };\n };\n\n const handleKeydown = (event: KeyboardEvent): void => {\n if (event.defaultPrevented || event.isComposing || resolve(options.disabled, false)) return;\n if (event.ctrlKey || event.metaKey || event.altKey) return;\n\n if (event.key === (options.inspectKey ?? ' ')) {\n const { index, item } = resolveItem(event);\n if (!item) return;\n event.preventDefault();\n event.stopPropagation();\n options.onInspect(item, index);\n return;\n }\n\n const result = navigation.handleKeydown(event);\n if (result?.change) result.change.item.focus();\n };\n\n return { handleKeydown };\n};\n","export type FocusableElement = HTMLElement | SVGElement;\nexport type FocusTarget = FocusableElement | null | undefined | (() => FocusableElement | null | undefined);\n\nexport type RestoreFocusOptions = {\n fallback?: FocusTarget;\n preventScroll?: boolean;\n};\n\nexport type CaptureFocusOptions = RestoreFocusOptions & {\n signal?: AbortSignal;\n};\n\nexport type FocusRestorer = () => boolean;\n\nconst resolveTarget = (target: FocusTarget): FocusableElement | null | undefined => {\n return typeof target === 'function' ? target() : target;\n};\n\nconst getDeepActiveElement = (rootDocument: Document): Element | null => {\n let active: Element | null = rootDocument.activeElement;\n\n while (active instanceof HTMLElement && active.shadowRoot?.activeElement) {\n active = active.shadowRoot.activeElement;\n }\n\n return active;\n};\n\nconst isConnected = (target: FocusableElement): boolean => {\n // `isConnected` pierces shadow boundaries (shadow-including root is the document);\n // `ownerDocument.contains` does NOT: it would reject elements inside shadow roots.\n return target.isConnected;\n};\n\nconst isDisabled = (target: FocusableElement): boolean => {\n return target instanceof HTMLElement && 'disabled' in target && Boolean((target as HTMLInputElement).disabled);\n};\n\nconst isInert = (target: FocusableElement): boolean => {\n if (target.closest('[inert]')) return true;\n\n return target instanceof HTMLElement && target.inert;\n};\n\nconst canRestoreTo = (target: FocusableElement): boolean => {\n return isConnected(target) && !isDisabled(target) && !isInert(target);\n};\n\nconst focusAndVerify = (target: FocusableElement, preventScroll: boolean | undefined): boolean => {\n try {\n target.focus({ preventScroll });\n return getDeepActiveElement(target.ownerDocument) === target;\n } catch {\n return false;\n }\n};\n\nexport const restoreFocus = (target: FocusTarget, options: RestoreFocusOptions = {}): boolean => {\n let next: FocusableElement | null | undefined;\n try {\n next = resolveTarget(target);\n } catch {\n next = null;\n }\n\n if (next && canRestoreTo(next) && focusAndVerify(next, options.preventScroll)) {\n return true;\n }\n\n if (!options.fallback) return false;\n\n let fallback: FocusableElement | null | undefined;\n try {\n fallback = resolveTarget(options.fallback);\n } catch {\n return false;\n }\n\n if (!fallback || !canRestoreTo(fallback)) return false;\n\n return focusAndVerify(fallback, options.preventScroll);\n};\n\n/**\n * Hands focus to `target` when focus has been lost to the document body: the state left\n * behind when the focused element unmounts mid-swap (browsing a dialog, paging a list),\n * where keydown would never reach a handler and every shortcut dies. Returns `true` when\n * focus was rescued; `false` when focus is already on a real element or the target cannot\n * take focus (the `fallback` option of {@link RestoreFocusOptions} still applies).\n */\nexport const rescueFocus = (target: FocusTarget, options: RestoreFocusOptions = {}): boolean => {\n const active = getDeepActiveElement(document);\n\n if (active && active !== document.body) return false;\n\n return restoreFocus(target, options);\n};\n\nexport const captureFocus = (options: CaptureFocusOptions = {}): FocusRestorer => {\n let captured = getDeepActiveElement(document) as FocusableElement | null;\n let available = !options.signal?.aborted;\n\n if (!available) captured = null;\n\n const cancel = (): void => {\n available = false;\n captured = null;\n };\n\n if (!options.signal?.aborted) {\n options.signal?.addEventListener('abort', cancel, { once: true });\n }\n\n return () => {\n if (!available) return false;\n\n available = false;\n options.signal?.removeEventListener('abort', cancel);\n\n const target = captured;\n captured = null;\n\n if (target) {\n return restoreFocus(target, {\n fallback: options.fallback,\n preventScroll: options.preventScroll,\n });\n }\n\n if (options.fallback) {\n return restoreFocus(options.fallback, {\n preventScroll: options.preventScroll,\n });\n }\n\n return false;\n };\n};\n"],"mappings":"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"}
package/dist/focus.js CHANGED
@@ -1,2 +1,2 @@
1
- import{matchKey as e}from"@vielzeug/keymap";var FocusError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},FocusConfigError=class extends FocusError{},t={first:[`Home`],last:[`End`],next:[`ArrowRight`],nextRow:[`ArrowDown`],prev:[`ArrowLeft`],prevRow:[`ArrowUp`]},n={...t,next:[`ArrowLeft`],prev:[`ArrowRight`]},r=[`first`,`last`,`next`,`nextRow`,`prev`,`prevRow`],resolve=(e,t)=>typeof e==`function`?e():e??t,createGridNavigation=i=>{let a=new Map;for(let e of r)for(let t of i.keys?.[e]??[]){let n=a.get(t);if(n&&n!==e)throw new FocusConfigError(`Key "${t}" is assigned to both ${n} and ${e}`);a.set(t,e)}let o=-1,resolveColumns=()=>{let e=resolve(i.columns,1);if(!Number.isInteger(e)||e<1)throw new FocusConfigError(`columns must resolve to a positive integer, got: ${e}`);return e},activeIndex=e=>{let t=i.getActiveIndex?.();return t!==void 0&&(o=t>=0&&t<e.length?t:-1),o},commitIndex=(e,t,n,r)=>(o=t,Object.freeze({action:n,event:r,index:t,item:e[t]})),navigateWithItems=(e,t,n)=>{if(!e.length)return o=-1,null;if(t===`first`)return commitIndex(e,0,t,n);if(t===`last`)return commitIndex(e,e.length-1,t,n);let r=resolveColumns(),a=t===`next`?1:t===`prev`?-1:t===`nextRow`?r:-r,s=activeIndex(e),c=s<0?a>0?0:e.length-1:s+a;if(i.loop)return commitIndex(e,(c%e.length+e.length)%e.length,t,n);let l=Math.min(e.length-1,Math.max(0,c));return l===s?null:commitIndex(e,l,t,n)},resolveKeyAction=a=>{let o=resolve(i.direction,`ltr`)===`rtl`?n:t;for(let t of r)if((i.keys?.[t]??o[t]).some(t=>e(a,t)))return t},handleKeydown=e=>{if(e.defaultPrevented||e.isComposing||resolve(i.disabled,!1))return null;let t=resolveKeyAction(e);if(!t)return null;let n=navigateWithItems(i.getItems(),t,e);return e.preventDefault(),Object.freeze({change:n,handled:!0})},navigate=e=>navigateWithItems(i.getItems(),e),set=e=>{let t=i.getItems();if(!Number.isInteger(e)||e<0||!t.length){o=-1;return}o=Math.min(e,t.length-1)},reset=()=>{o=-1},getIndex=()=>activeIndex(i.getItems()),getActiveItem=()=>{let e=i.getItems(),t=activeIndex(e);return t>=0?e[t]:void 0};return{getActiveItem,getIndex,handleKeydown,navigate,reset,set}},i={both:{first:[`Home`],last:[`End`],next:[`ArrowDown`,`ArrowRight`],prev:[`ArrowUp`,`ArrowLeft`]},horizontal:{first:[`Home`],last:[`End`],next:[`ArrowRight`],prev:[`ArrowLeft`]},vertical:{first:[`Home`],last:[`End`],next:[`ArrowDown`],prev:[`ArrowUp`]}},a={both:{first:[`Home`],last:[`End`],next:[`ArrowDown`,`ArrowLeft`],prev:[`ArrowUp`,`ArrowRight`]},horizontal:{first:[`Home`],last:[`End`],next:[`ArrowLeft`],prev:[`ArrowRight`]},vertical:i.vertical},o=500,findForward=(e,t,n)=>{for(let r=t;r<e.length;r++)if(n(e[r],r))return r;return-1},findBackward=(e,t,n)=>{for(let r=t;r>=0;r--)if(n(e[r],r))return r;return-1},resolveTypeaheadDelay=e=>{if(e===void 0)return o;if(!Number.isFinite(e)||e<=0)throw new FocusConfigError(`Typeahead delay must be a positive finite number`);return e},createListNavigation=t=>{let n=resolveTypeaheadDelay(t.typeahead?.delayMs),r=new Map;for(let e of[`next`,`prev`,`first`,`last`])for(let n of t.keys?.[e]??[]){let t=r.get(n);if(t&&t!==e)throw new FocusConfigError(`Key "${n}" is assigned to both ${t} and ${e}`);r.set(n,e)}let o=-1,s=``,c=0,isItemDisabled=(e,n)=>t.isItemDisabled?.(e,n)??!1,isUsableIndex=(e,t)=>t>=0&&t<e.length&&!isItemDisabled(e[t],t),normalizeIndex=e=>(isUsableIndex(e,o)||(o=-1),o),resetTypeahead=()=>{s=``,c=0},commitIndex=(e,t,n,r)=>isUsableIndex(e,t)?(o=t,Object.freeze({action:n,event:r,index:t,item:e[t]})):(o=-1,null),findEnabledIndex=(e,t,n)=>n===`forward`?findForward(e,t,(e,t)=>!isItemDisabled(e,t)):findBackward(e,t,(e,t)=>!isItemDisabled(e,t)),move=(e,n,r)=>{let i=normalizeIndex(e);if(!e.length)return o=-1,null;let a=i<0?n===`forward`?0:e.length-1:n===`forward`?i+1:i-1,s=findEnabledIndex(e,a,n),c=n===`forward`?`next`:`prev`;if(s>=0)return commitIndex(e,s,c,r);if(t.loop){let t=n===`forward`?0:e.length-1,i=findEnabledIndex(e,t,n);if(i>=0)return commitIndex(e,i,c,r)}return null},navigateWithItems=(e,t,n)=>(resetTypeahead(),normalizeIndex(e),e.length?t===`first`?commitIndex(e,findEnabledIndex(e,0,`forward`),t,n):t===`last`?commitIndex(e,findEnabledIndex(e,e.length-1,`backward`),t,n):move(e,t===`next`?`forward`:`backward`,n):(o=-1,null)),findTypeaheadMatch=(e,n,r)=>{let i=normalizeIndex(e),a=i<0?0:r?i:i+1;for(let r=0;r<e.length;r++){let i=(a+r)%e.length,o=e[i];if(!isItemDisabled(o,i)&&t.typeahead?.getLabel(o,i).toLocaleLowerCase().startsWith(n))return i}return-1},handleTypeahead=(e,r)=>{if(!t.typeahead||e.key.length!==1||e.ctrlKey||e.altKey||e.metaKey)return null;let i=performance.now(),a=e.key.toLocaleLowerCase();i-c>=n&&(s=``),c=i;let o=s+a;s=o.length>1&&[...o].every(e=>e===a)?a:o;let l=findTypeaheadMatch(r,s,s.length>1);return l<0&&s.length>1&&(s=a,l=findTypeaheadMatch(r,s,!1)),l<0?null:commitIndex(r,l,`typeahead`,e)},set=e=>{let n=t.getItems();if(normalizeIndex(n),resetTypeahead(),!Number.isInteger(e)||e<0||!n.length)return o=-1,o;let r=Math.min(e,n.length-1);return isUsableIndex(n,r)?(o=r,o):(o=-1,o)},navigate=e=>navigateWithItems(t.getItems(),e),getActiveItem=()=>{let e=t.getItems(),n=normalizeIndex(e);return n>=0?e[n]:void 0},reset=()=>{o=-1,resetTypeahead()},getIndex=()=>normalizeIndex(t.getItems()),resolve=(e,t)=>typeof e==`function`?e():e??t,resolveKeyAction=n=>{let r=t.keys,o=(resolve(t.direction,`ltr`)===`rtl`?a:i)[resolve(t.orientation,`vertical`)];for(let t of[`next`,`prev`,`first`,`last`])if((r?.[t]??o[t]).some(t=>e(n,t)))return t},handleKeydown=e=>{if(e.defaultPrevented||e.isComposing||resolve(t.disabled,!1))return null;let n=t.getItems();normalizeIndex(n);let r=resolveKeyAction(e);if(r){let t=navigateWithItems(n,r,e);return e.preventDefault(),Object.freeze({change:t,handled:!0})}let i=handleTypeahead(e,n);return i?(t.typeahead?.preventDefault&&e.preventDefault(),Object.freeze({change:i,handled:!0})):null};return{getActiveItem,getIndex,handleKeydown,navigate,reset,set}},resolveTarget=e=>typeof e==`function`?e():e,getDeepActiveElement=e=>{let t=e.activeElement;for(;t instanceof HTMLElement&&t.shadowRoot?.activeElement;)t=t.shadowRoot.activeElement;return t},isConnected=e=>e.isConnected,isDisabled=e=>e instanceof HTMLElement&&`disabled`in e&&!!e.disabled,isInert=e=>e.closest(`[inert]`)?!0:e instanceof HTMLElement&&e.inert,canRestoreTo=e=>isConnected(e)&&!isDisabled(e)&&!isInert(e),focusAndVerify=(e,t)=>{try{return e.focus({preventScroll:t}),getDeepActiveElement(e.ownerDocument)===e}catch{return!1}},restoreFocus=(e,t={})=>{let n;try{n=resolveTarget(e)}catch{n=null}if(n&&canRestoreTo(n)&&focusAndVerify(n,t.preventScroll))return!0;if(!t.fallback)return!1;let r;try{r=resolveTarget(t.fallback)}catch{return!1}return!r||!canRestoreTo(r)?!1:focusAndVerify(r,t.preventScroll)},rescueFocus=(e,t={})=>{let n=getDeepActiveElement(document);return n&&n!==document.body?!1:restoreFocus(e,t)},captureFocus=(e={})=>{let t=getDeepActiveElement(document),n=!e.signal?.aborted;n||(t=null);let cancel=()=>{n=!1,t=null};return e.signal?.aborted||e.signal?.addEventListener(`abort`,cancel,{once:!0}),()=>{if(!n)return!1;n=!1,e.signal?.removeEventListener(`abort`,cancel);let r=t;return t=null,r?restoreFocus(r,{fallback:e.fallback,preventScroll:e.preventScroll}):e.fallback?restoreFocus(e.fallback,{preventScroll:e.preventScroll}):!1}};export{FocusConfigError,FocusError,captureFocus,createGridNavigation,createListNavigation,rescueFocus,restoreFocus};
1
+ import{matchKey as e}from"@vielzeug/keymap";var FocusError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},FocusConfigError=class extends FocusError{},t={first:[`Home`],last:[`End`],next:[`ArrowRight`],nextRow:[`ArrowDown`],prev:[`ArrowLeft`],prevRow:[`ArrowUp`]},n={...t,next:[`ArrowLeft`],prev:[`ArrowRight`]},r=[`first`,`last`,`next`,`nextRow`,`prev`,`prevRow`],resolve$1=(e,t)=>typeof e==`function`?e():e??t,createGridNavigation=i=>{let a=new Map;for(let e of r)for(let t of i.keys?.[e]??[]){let n=a.get(t);if(n&&n!==e)throw new FocusConfigError(`Key "${t}" is assigned to both ${n} and ${e}`);a.set(t,e)}let o=-1,resolveColumns=()=>{let e=resolve$1(i.columns,1);if(!Number.isInteger(e)||e<1)throw new FocusConfigError(`columns must resolve to a positive integer, got: ${e}`);return e},activeIndex=e=>{let t=i.getActiveIndex?.();return t!==void 0&&(o=t>=0&&t<e.length?t:-1),o},commitIndex=(e,t,n,r)=>(o=t,Object.freeze({action:n,event:r,index:t,item:e[t]})),navigateWithItems=(e,t,n)=>{if(!e.length)return o=-1,null;if(t===`first`)return commitIndex(e,0,t,n);if(t===`last`)return commitIndex(e,e.length-1,t,n);let r=resolveColumns(),a=t===`next`?1:t===`prev`?-1:t===`nextRow`?r:-r,s=activeIndex(e),c=s<0?a>0?0:e.length-1:s+a;if(i.loop)return commitIndex(e,(c%e.length+e.length)%e.length,t,n);let l=Math.min(e.length-1,Math.max(0,c));return l===s?null:commitIndex(e,l,t,n)},resolveKeyAction=a=>{let o=resolve$1(i.direction,`ltr`)===`rtl`?n:t;for(let t of r)if((i.keys?.[t]??o[t]).some(t=>e(a,t)))return t},handleKeydown=e=>{if(e.defaultPrevented||e.isComposing||resolve$1(i.disabled,!1))return null;let t=resolveKeyAction(e);if(!t)return null;let n=navigateWithItems(i.getItems(),t,e);return e.preventDefault(),Object.freeze({change:n,handled:!0})},navigate=e=>navigateWithItems(i.getItems(),e),set=e=>{let t=i.getItems();if(!Number.isInteger(e)||e<0||!t.length){o=-1;return}o=Math.min(e,t.length-1)},reset=()=>{o=-1},getIndex=()=>activeIndex(i.getItems()),getActiveItem=()=>{let e=i.getItems(),t=activeIndex(e);return t>=0?e[t]:void 0};return{getActiveItem,getIndex,handleKeydown,navigate,reset,set}},i={both:{first:[`Home`],last:[`End`],next:[`ArrowDown`,`ArrowRight`],prev:[`ArrowUp`,`ArrowLeft`]},horizontal:{first:[`Home`],last:[`End`],next:[`ArrowRight`],prev:[`ArrowLeft`]},vertical:{first:[`Home`],last:[`End`],next:[`ArrowDown`],prev:[`ArrowUp`]}},a={both:{first:[`Home`],last:[`End`],next:[`ArrowDown`,`ArrowLeft`],prev:[`ArrowUp`,`ArrowRight`]},horizontal:{first:[`Home`],last:[`End`],next:[`ArrowLeft`],prev:[`ArrowRight`]},vertical:i.vertical},o=500,findForward=(e,t,n)=>{for(let r=t;r<e.length;r++)if(n(e[r],r))return r;return-1},findBackward=(e,t,n)=>{for(let r=t;r>=0;r--)if(n(e[r],r))return r;return-1},resolveTypeaheadDelay=e=>{if(e===void 0)return o;if(!Number.isFinite(e)||e<=0)throw new FocusConfigError(`Typeahead delay must be a positive finite number`);return e},createListNavigation=t=>{let n=resolveTypeaheadDelay(t.typeahead?.delayMs),r=new Map;for(let e of[`next`,`prev`,`first`,`last`])for(let n of t.keys?.[e]??[]){let t=r.get(n);if(t&&t!==e)throw new FocusConfigError(`Key "${n}" is assigned to both ${t} and ${e}`);r.set(n,e)}let o=-1,s=``,c=0,isItemDisabled=(e,n)=>t.isItemDisabled?.(e,n)??!1,isUsableIndex=(e,t)=>t>=0&&t<e.length&&!isItemDisabled(e[t],t),normalizeIndex=e=>(isUsableIndex(e,o)||(o=-1),o),resetTypeahead=()=>{s=``,c=0},commitIndex=(e,t,n,r)=>isUsableIndex(e,t)?(o=t,Object.freeze({action:n,event:r,index:t,item:e[t]})):(o=-1,null),findEnabledIndex=(e,t,n)=>n===`forward`?findForward(e,t,(e,t)=>!isItemDisabled(e,t)):findBackward(e,t,(e,t)=>!isItemDisabled(e,t)),move=(e,n,r)=>{let i=normalizeIndex(e);if(!e.length)return o=-1,null;let a=i<0?n===`forward`?0:e.length-1:n===`forward`?i+1:i-1,s=findEnabledIndex(e,a,n),c=n===`forward`?`next`:`prev`;if(s>=0)return commitIndex(e,s,c,r);if(t.loop){let t=n===`forward`?0:e.length-1,i=findEnabledIndex(e,t,n);if(i>=0)return commitIndex(e,i,c,r)}return null},navigateWithItems=(e,t,n)=>(resetTypeahead(),normalizeIndex(e),e.length?t===`first`?commitIndex(e,findEnabledIndex(e,0,`forward`),t,n):t===`last`?commitIndex(e,findEnabledIndex(e,e.length-1,`backward`),t,n):move(e,t===`next`?`forward`:`backward`,n):(o=-1,null)),findTypeaheadMatch=(e,n,r)=>{let i=normalizeIndex(e),a=i<0?0:r?i:i+1;for(let r=0;r<e.length;r++){let i=(a+r)%e.length,o=e[i];if(!isItemDisabled(o,i)&&t.typeahead?.getLabel(o,i).toLocaleLowerCase().startsWith(n))return i}return-1},handleTypeahead=(e,r)=>{if(!t.typeahead||e.key.length!==1||e.ctrlKey||e.altKey||e.metaKey)return null;let i=performance.now(),a=e.key.toLocaleLowerCase();i-c>=n&&(s=``),c=i;let o=s+a;s=o.length>1&&[...o].every(e=>e===a)?a:o;let l=findTypeaheadMatch(r,s,s.length>1);return l<0&&s.length>1&&(s=a,l=findTypeaheadMatch(r,s,!1)),l<0?null:commitIndex(r,l,`typeahead`,e)},set=e=>{let n=t.getItems();if(normalizeIndex(n),resetTypeahead(),!Number.isInteger(e)||e<0||!n.length)return o=-1,o;let r=Math.min(e,n.length-1);return isUsableIndex(n,r)?(o=r,o):(o=-1,o)},navigate=e=>navigateWithItems(t.getItems(),e),getActiveItem=()=>{let e=t.getItems(),n=normalizeIndex(e);return n>=0?e[n]:void 0},reset=()=>{o=-1,resetTypeahead()},getIndex=()=>normalizeIndex(t.getItems()),resolve=(e,t)=>typeof e==`function`?e():e??t,resolveKeyAction=n=>{let r=t.keys,o=(resolve(t.direction,`ltr`)===`rtl`?a:i)[resolve(t.orientation,`vertical`)];for(let t of[`next`,`prev`,`first`,`last`])if((r?.[t]??o[t]).some(t=>e(n,t)))return t},handleKeydown=e=>{if(e.defaultPrevented||e.isComposing||resolve(t.disabled,!1))return null;let n=t.getItems();normalizeIndex(n);let r=resolveKeyAction(e);if(r){let t=navigateWithItems(n,r,e);return e.preventDefault(),Object.freeze({change:t,handled:!0})}let i=handleTypeahead(e,n);return i?(t.typeahead?.preventDefault&&e.preventDefault(),Object.freeze({change:i,handled:!0})):null};return{getActiveItem,getIndex,handleKeydown,navigate,reset,set}},resolve=(e,t)=>typeof e==`function`?e():e??t,createQuickLookGrid=e=>{let activeIndex=()=>e.getItems().indexOf(document.activeElement),measuredColumns=()=>{let t=e.getItems(),n=t.indexOf(document.activeElement);if(n<0)return 1;let r=t[n]?.getBoundingClientRect().top??0;return Math.max(1,t.filter(e=>Math.abs(e.getBoundingClientRect().top-r)<1).length)},t=createGridNavigation({columns:e.columns??measuredColumns,getActiveIndex:e.getActiveIndex??activeIndex,getItems:e.getItems,loop:e.loop}),resolveItem=t=>{let n=e.getItems(),r=t.target instanceof HTMLElement?t.target:null,i=r===null?-1:n.findIndex(e=>e===r||e.contains(r));if(i>=0)return{index:i,item:n[i]};let a=(e.getActiveIndex??activeIndex)();return{index:a,item:a>=0?n[a]:void 0}},handleKeydown=n=>{if(n.defaultPrevented||n.isComposing||resolve(e.disabled,!1)||n.ctrlKey||n.metaKey||n.altKey)return;if(n.key===(e.inspectKey??` `)){let{index:t,item:r}=resolveItem(n);if(!r)return;n.preventDefault(),n.stopPropagation(),e.onInspect(r,t);return}let r=t.handleKeydown(n);r?.change&&r.change.item.focus()};return{handleKeydown}},resolveTarget=e=>typeof e==`function`?e():e,getDeepActiveElement=e=>{let t=e.activeElement;for(;t instanceof HTMLElement&&t.shadowRoot?.activeElement;)t=t.shadowRoot.activeElement;return t},isConnected=e=>e.isConnected,isDisabled=e=>e instanceof HTMLElement&&`disabled`in e&&!!e.disabled,isInert=e=>e.closest(`[inert]`)?!0:e instanceof HTMLElement&&e.inert,canRestoreTo=e=>isConnected(e)&&!isDisabled(e)&&!isInert(e),focusAndVerify=(e,t)=>{try{return e.focus({preventScroll:t}),getDeepActiveElement(e.ownerDocument)===e}catch{return!1}},restoreFocus=(e,t={})=>{let n;try{n=resolveTarget(e)}catch{n=null}if(n&&canRestoreTo(n)&&focusAndVerify(n,t.preventScroll))return!0;if(!t.fallback)return!1;let r;try{r=resolveTarget(t.fallback)}catch{return!1}return!r||!canRestoreTo(r)?!1:focusAndVerify(r,t.preventScroll)},rescueFocus=(e,t={})=>{let n=getDeepActiveElement(document);return n&&n!==document.body?!1:restoreFocus(e,t)},captureFocus=(e={})=>{let t=getDeepActiveElement(document),n=!e.signal?.aborted;n||(t=null);let cancel=()=>{n=!1,t=null};return e.signal?.aborted||e.signal?.addEventListener(`abort`,cancel,{once:!0}),()=>{if(!n)return!1;n=!1,e.signal?.removeEventListener(`abort`,cancel);let r=t;return t=null,r?restoreFocus(r,{fallback:e.fallback,preventScroll:e.preventScroll}):e.fallback?restoreFocus(e.fallback,{preventScroll:e.preventScroll}):!1}};export{FocusConfigError,FocusError,captureFocus,createGridNavigation,createListNavigation,createQuickLookGrid,rescueFocus,restoreFocus};
2
2
  //# sourceMappingURL=focus.js.map
package/dist/focus.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"focus.js","names":[],"sources":["../src/errors.ts","../src/grid-navigation.ts","../src/list-navigation.ts","../src/restore-focus.ts"],"sourcesContent":["/** Base class for all focus errors. Use `instanceof FocusError` to catch any focus-originated error. */\nexport class FocusError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when navigation options are invalid, such as a non-positive typeahead delay or a key assigned to two actions. */\nexport class FocusConfigError extends FocusError {}\n","import { matchKey } from '@vielzeug/keymap';\n\nimport { FocusConfigError } from './errors.js';\nimport type { MaybeGetter } from './list-navigation.js';\n\nexport type GridNavigationAction = 'first' | 'last' | 'next' | 'nextRow' | 'prev' | 'prevRow';\n\nexport type GridNavigationChange<T> = {\n readonly action: GridNavigationAction;\n readonly event?: KeyboardEvent;\n readonly index: number;\n readonly item: T;\n};\n\nexport type GridKeyResult<T> = {\n readonly change: GridNavigationChange<T> | null;\n readonly handled: true;\n};\n\n/**\n * Columns per row: a fixed count, or a getter resolved on every navigation. Responsive grids\n * read a media query or computed style; measured grids read the rendered row length.\n */\nexport type GridColumns = number | (() => number);\n\nexport type GridNavigationOptions<T> = {\n /** Columns per row, resolved on every navigation. */\n columns: GridColumns;\n direction?: MaybeGetter<'ltr' | 'rtl'>;\n disabled?: MaybeGetter<boolean>;\n /**\n * Reads the active index from the outside (usually the index of `document.activeElement`\n * among the items), so focus moved by pointer or Tab stays in sync. When omitted, the\n * navigation tracks the index internally through `set()`.\n */\n getActiveIndex?: () => number;\n getItems: () => readonly T[];\n keys?: Partial<Record<GridNavigationAction, readonly string[]>>;\n /** Wrap moves around the grid's edges by flat index. Default clamps at the edges. */\n loop?: boolean;\n};\n\nexport type GridNavigation<T> = {\n getActiveItem(): T | undefined;\n getIndex(): number;\n handleKeydown(event: KeyboardEvent): GridKeyResult<T> | null;\n navigate(action: GridNavigationAction): GridNavigationChange<T> | null;\n reset(): void;\n set(index: number): void;\n};\n\nconst DEFAULT_KEYS: Record<GridNavigationAction, readonly string[]> = {\n first: ['Home'],\n last: ['End'],\n next: ['ArrowRight'],\n nextRow: ['ArrowDown'],\n prev: ['ArrowLeft'],\n prevRow: ['ArrowUp'],\n};\n\nconst DEFAULT_KEYS_RTL: typeof DEFAULT_KEYS = {\n ...DEFAULT_KEYS,\n next: ['ArrowLeft'],\n prev: ['ArrowRight'],\n};\n\nconst GRID_ACTIONS = ['first', 'last', 'next', 'nextRow', 'prev', 'prevRow'] as const;\n\nconst resolve = <V>(value: MaybeGetter<V> | undefined, fallback: V): V =>\n typeof value === 'function' ? (value as () => V)() : (value ?? fallback);\n\n/**\n * Keyboard navigation over a two-dimensional grid of items: horizontal arrows step one\n * item, vertical arrows step one row (the column count), Home/End jump to the ends.\n *\n * Unlike `createListNavigation`, items are not skipped when disabled: skipping in two\n * dimensions would break row alignment. Focus (or otherwise activate) the item reported\n * by the returned change; `handleKeydown` calls `preventDefault()` for every recognized\n * key so unhandled arrows never scroll the page.\n */\nexport const createGridNavigation = <T>(options: GridNavigationOptions<T>): GridNavigation<T> => {\n const owners = new Map<string, GridNavigationAction>();\n\n for (const action of GRID_ACTIONS) {\n for (const key of options.keys?.[action] ?? []) {\n const owner = owners.get(key);\n if (owner && owner !== action)\n throw new FocusConfigError(`Key \"${key}\" is assigned to both ${owner} and ${action}`);\n owners.set(key, action);\n }\n }\n\n let index = -1;\n\n const resolveColumns = (): number => {\n const columns = resolve(options.columns, 1);\n if (!Number.isInteger(columns) || columns < 1)\n throw new FocusConfigError(`columns must resolve to a positive integer, got: ${columns}`);\n return columns;\n };\n\n const activeIndex = (items: readonly T[]): number => {\n const derived = options.getActiveIndex?.();\n if (derived !== undefined) index = derived >= 0 && derived < items.length ? derived : -1;\n return index;\n };\n\n const commitIndex = (\n items: readonly T[],\n nextIndex: number,\n action: GridNavigationAction,\n event?: KeyboardEvent,\n ): GridNavigationChange<T> => {\n index = nextIndex;\n return Object.freeze({ action, event, index: nextIndex, item: items[nextIndex] });\n };\n\n const navigateWithItems = (\n items: readonly T[],\n action: GridNavigationAction,\n event?: KeyboardEvent,\n ): GridNavigationChange<T> | null => {\n if (!items.length) {\n index = -1;\n return null;\n }\n\n if (action === 'first') return commitIndex(items, 0, action, event);\n if (action === 'last') return commitIndex(items, items.length - 1, action, event);\n\n const columns = resolveColumns();\n const offset = action === 'next' ? 1 : action === 'prev' ? -1 : action === 'nextRow' ? columns : -columns;\n const current = activeIndex(items);\n const base = current < 0 ? (offset > 0 ? 0 : items.length - 1) : current + offset;\n\n if (options.loop) {\n return commitIndex(items, ((base % items.length) + items.length) % items.length, action, event);\n }\n\n const clamped = Math.min(items.length - 1, Math.max(0, base));\n if (clamped === current) return null;\n\n return commitIndex(items, clamped, action, event);\n };\n\n const resolveKeyAction = (event: KeyboardEvent): GridNavigationAction | undefined => {\n const keyTable = resolve(options.direction, 'ltr') === 'rtl' ? DEFAULT_KEYS_RTL : DEFAULT_KEYS;\n\n for (const action of GRID_ACTIONS) {\n if ((options.keys?.[action] ?? keyTable[action]).some((pattern) => matchKey(event, pattern))) return action;\n }\n\n return undefined;\n };\n\n const handleKeydown = (event: KeyboardEvent): GridKeyResult<T> | null => {\n if (event.defaultPrevented || event.isComposing || resolve(options.disabled, false)) return null;\n\n const action = resolveKeyAction(event);\n if (!action) return null;\n\n const change = navigateWithItems(options.getItems(), action, event);\n event.preventDefault();\n return Object.freeze({ change, handled: true });\n };\n\n const navigate = (action: GridNavigationAction): GridNavigationChange<T> | null =>\n navigateWithItems(options.getItems(), action);\n\n const set = (nextIndex: number): void => {\n const items = options.getItems();\n\n if (!Number.isInteger(nextIndex) || nextIndex < 0 || !items.length) {\n index = -1;\n return;\n }\n\n index = Math.min(nextIndex, items.length - 1);\n };\n\n const reset = (): void => {\n index = -1;\n };\n\n const getIndex = (): number => activeIndex(options.getItems());\n\n const getActiveItem = (): T | undefined => {\n const items = options.getItems();\n const current = activeIndex(items);\n return current >= 0 ? items[current] : undefined;\n };\n\n return {\n getActiveItem,\n getIndex,\n handleKeydown,\n navigate,\n reset,\n set,\n };\n};\n","import { matchKey } from '@vielzeug/keymap';\n\nimport { FocusConfigError } from './errors.js';\n\nexport type ListNavigationAction = 'first' | 'last' | 'next' | 'prev';\nexport type ListKeyAction = ListNavigationAction | 'typeahead';\n\nexport type ListNavigationChange<T> = {\n readonly action: ListKeyAction;\n readonly event?: KeyboardEvent;\n readonly index: number;\n readonly item: T;\n};\n\nexport type ListKeyResult<T> = {\n readonly change: ListNavigationChange<T> | null;\n readonly handled: true;\n};\n\nexport type ListNavigationTypeaheadOptions<T> = {\n delayMs?: number;\n getLabel: (item: T, index: number) => string;\n preventDefault?: boolean;\n};\n\nexport type MaybeGetter<T> = T | (() => T);\n\nexport type ListNavigationOptions<T> = {\n direction?: MaybeGetter<'ltr' | 'rtl'>;\n disabled?: MaybeGetter<boolean>;\n getItems: () => readonly T[];\n isItemDisabled?: (item: T, index: number) => boolean;\n keys?: Partial<Record<ListNavigationAction, readonly string[]>>;\n loop?: boolean;\n orientation?: MaybeGetter<'both' | 'horizontal' | 'vertical'>;\n typeahead?: ListNavigationTypeaheadOptions<T>;\n};\n\nexport type ListNavigation<T> = {\n getActiveItem(): T | undefined;\n getIndex(): number;\n handleKeydown(event: KeyboardEvent): ListKeyResult<T> | null;\n navigate(action: ListNavigationAction): ListNavigationChange<T> | null;\n reset(): void;\n set(index: number): number;\n};\n\nconst DEFAULT_KEYS: Record<'both' | 'horizontal' | 'vertical', Record<ListNavigationAction, readonly string[]>> = {\n both: { first: ['Home'], last: ['End'], next: ['ArrowDown', 'ArrowRight'], prev: ['ArrowUp', 'ArrowLeft'] },\n horizontal: { first: ['Home'], last: ['End'], next: ['ArrowRight'], prev: ['ArrowLeft'] },\n vertical: { first: ['Home'], last: ['End'], next: ['ArrowDown'], prev: ['ArrowUp'] },\n};\n\nconst DEFAULT_KEYS_RTL: typeof DEFAULT_KEYS = {\n both: { first: ['Home'], last: ['End'], next: ['ArrowDown', 'ArrowLeft'], prev: ['ArrowUp', 'ArrowRight'] },\n horizontal: { first: ['Home'], last: ['End'], next: ['ArrowLeft'], prev: ['ArrowRight'] },\n vertical: DEFAULT_KEYS.vertical,\n};\n\nconst DEFAULT_TYPEAHEAD_DELAY_MS = 500;\n\nconst findForward = <T>(items: readonly T[], start: number, predicate: (item: T, index: number) => boolean): number => {\n for (let index = start; index < items.length; index++) {\n if (predicate(items[index], index)) return index;\n }\n\n return -1;\n};\n\nconst findBackward = <T>(\n items: readonly T[],\n start: number,\n predicate: (item: T, index: number) => boolean,\n): number => {\n for (let index = start; index >= 0; index--) {\n if (predicate(items[index], index)) return index;\n }\n\n return -1;\n};\n\nconst resolveTypeaheadDelay = (delay: number | undefined): number => {\n if (delay === undefined) return DEFAULT_TYPEAHEAD_DELAY_MS;\n if (!Number.isFinite(delay) || delay <= 0)\n throw new FocusConfigError('Typeahead delay must be a positive finite number');\n return delay;\n};\n\nexport const createListNavigation = <T>(options: ListNavigationOptions<T>): ListNavigation<T> => {\n const typeaheadDelay = resolveTypeaheadDelay(options.typeahead?.delayMs);\n const owners = new Map<string, ListNavigationAction>();\n\n for (const action of ['next', 'prev', 'first', 'last'] as const) {\n for (const key of options.keys?.[action] ?? []) {\n const owner = owners.get(key);\n if (owner && owner !== action)\n throw new FocusConfigError(`Key \"${key}\" is assigned to both ${owner} and ${action}`);\n owners.set(key, action);\n }\n }\n\n let index = -1;\n let typeaheadBuffer = '';\n let lastTypeaheadAt = 0;\n\n const isItemDisabled = (item: T, itemIndex: number): boolean => options.isItemDisabled?.(item, itemIndex) ?? false;\n\n const isUsableIndex = (items: readonly T[], itemIndex: number): boolean =>\n itemIndex >= 0 && itemIndex < items.length && !isItemDisabled(items[itemIndex], itemIndex);\n\n const normalizeIndex = (items: readonly T[]): number => {\n if (!isUsableIndex(items, index)) index = -1;\n\n return index;\n };\n\n const resetTypeahead = (): void => {\n typeaheadBuffer = '';\n lastTypeaheadAt = 0;\n };\n\n const commitIndex = (\n items: readonly T[],\n nextIndex: number,\n action: ListKeyAction,\n event?: KeyboardEvent,\n ): ListNavigationChange<T> | null => {\n if (!isUsableIndex(items, nextIndex)) {\n index = -1;\n\n return null;\n }\n\n index = nextIndex;\n\n return Object.freeze({ action, event, index: nextIndex, item: items[nextIndex] });\n };\n\n const findEnabledIndex = (items: readonly T[], start: number, direction: 'forward' | 'backward'): number => {\n if (direction === 'forward')\n return findForward(items, start, (item, itemIndex) => !isItemDisabled(item, itemIndex));\n\n return findBackward(items, start, (item, itemIndex) => !isItemDisabled(item, itemIndex));\n };\n\n const move = (\n items: readonly T[],\n direction: 'forward' | 'backward',\n event?: KeyboardEvent,\n ): ListNavigationChange<T> | null => {\n const current = normalizeIndex(items);\n\n if (!items.length) {\n index = -1;\n\n return null;\n }\n\n const start =\n current < 0\n ? direction === 'forward'\n ? 0\n : items.length - 1\n : direction === 'forward'\n ? current + 1\n : current - 1;\n\n const nextIndex = findEnabledIndex(items, start, direction);\n const action: ListNavigationAction = direction === 'forward' ? 'next' : 'prev';\n\n if (nextIndex >= 0) return commitIndex(items, nextIndex, action, event);\n\n if (options.loop) {\n const wrapStart = direction === 'forward' ? 0 : items.length - 1;\n const wrapped = findEnabledIndex(items, wrapStart, direction);\n\n if (wrapped >= 0) return commitIndex(items, wrapped, action, event);\n }\n\n return null;\n };\n\n const navigateWithItems = (\n items: readonly T[],\n action: ListNavigationAction,\n event?: KeyboardEvent,\n ): ListNavigationChange<T> | null => {\n resetTypeahead();\n normalizeIndex(items);\n\n if (!items.length) {\n index = -1;\n\n return null;\n }\n\n if (action === 'first') {\n return commitIndex(items, findEnabledIndex(items, 0, 'forward'), action, event);\n }\n\n if (action === 'last') {\n return commitIndex(items, findEnabledIndex(items, items.length - 1, 'backward'), action, event);\n }\n\n return move(items, action === 'next' ? 'forward' : 'backward', event);\n };\n\n const findTypeaheadMatch = (items: readonly T[], search: string, includeCurrent: boolean): number => {\n const current = normalizeIndex(items);\n const start = current < 0 ? 0 : includeCurrent ? current : current + 1;\n\n for (let offset = 0; offset < items.length; offset++) {\n const itemIndex = (start + offset) % items.length;\n const item = items[itemIndex];\n\n if (isItemDisabled(item, itemIndex)) continue;\n\n if (options.typeahead?.getLabel(item, itemIndex).toLocaleLowerCase().startsWith(search)) {\n return itemIndex;\n }\n }\n\n return -1;\n };\n\n const handleTypeahead = (event: KeyboardEvent, items: readonly T[]): ListNavigationChange<T> | null => {\n if (!options.typeahead || event.key.length !== 1 || event.ctrlKey || event.altKey || event.metaKey) return null;\n\n const now = performance.now();\n const key = event.key.toLocaleLowerCase();\n\n if (now - lastTypeaheadAt >= typeaheadDelay) typeaheadBuffer = '';\n\n lastTypeaheadAt = now;\n\n const candidate = typeaheadBuffer + key;\n const repeatedCharacter = candidate.length > 1 && [...candidate].every((character) => character === key);\n typeaheadBuffer = repeatedCharacter ? key : candidate;\n\n let nextIndex = findTypeaheadMatch(items, typeaheadBuffer, typeaheadBuffer.length > 1);\n\n if (nextIndex < 0 && typeaheadBuffer.length > 1) {\n typeaheadBuffer = key;\n nextIndex = findTypeaheadMatch(items, typeaheadBuffer, false);\n }\n\n if (nextIndex < 0) return null;\n\n return commitIndex(items, nextIndex, 'typeahead', event);\n };\n\n const set = (nextIndex: number): number => {\n const items = options.getItems();\n normalizeIndex(items);\n resetTypeahead();\n\n if (!Number.isInteger(nextIndex) || nextIndex < 0 || !items.length) {\n index = -1;\n\n return index;\n }\n\n const clamped = Math.min(nextIndex, items.length - 1);\n\n if (!isUsableIndex(items, clamped)) {\n index = -1;\n\n return index;\n }\n\n index = clamped;\n\n return index;\n };\n\n const navigate = (action: ListNavigationAction): ListNavigationChange<T> | null => {\n return navigateWithItems(options.getItems(), action);\n };\n\n const getActiveItem = (): T | undefined => {\n const items = options.getItems();\n const current = normalizeIndex(items);\n\n return current >= 0 ? items[current] : undefined;\n };\n\n const reset = (): void => {\n index = -1;\n resetTypeahead();\n };\n\n const getIndex = (): number => {\n return normalizeIndex(options.getItems());\n };\n\n const resolve = <V>(value: MaybeGetter<V> | undefined, fallback: V): V =>\n typeof value === 'function' ? (value as () => V)() : (value ?? fallback);\n\n const resolveKeyAction = (event: KeyboardEvent): ListNavigationAction | undefined => {\n const keys = options.keys;\n const keyTable = resolve(options.direction, 'ltr') === 'rtl' ? DEFAULT_KEYS_RTL : DEFAULT_KEYS;\n const defaults = keyTable[resolve(options.orientation, 'vertical')];\n\n for (const action of ['next', 'prev', 'first', 'last'] as const) {\n if ((keys?.[action] ?? defaults[action]).some((pattern) => matchKey(event, pattern))) return action;\n }\n\n return undefined;\n };\n\n const handleKeydown = (event: KeyboardEvent): ListKeyResult<T> | null => {\n if (event.defaultPrevented || event.isComposing || resolve(options.disabled, false)) return null;\n\n const items = options.getItems();\n normalizeIndex(items);\n\n const action = resolveKeyAction(event);\n\n if (action) {\n const change = navigateWithItems(items, action, event);\n event.preventDefault();\n return Object.freeze({ change, handled: true });\n }\n\n const change = handleTypeahead(event, items);\n if (!change) return null;\n if (options.typeahead?.preventDefault) event.preventDefault();\n return Object.freeze({ change, handled: true });\n };\n\n return {\n getActiveItem,\n getIndex,\n handleKeydown,\n navigate,\n reset,\n set,\n };\n};\n","export type FocusableElement = HTMLElement | SVGElement;\nexport type FocusTarget = FocusableElement | null | undefined | (() => FocusableElement | null | undefined);\n\nexport type RestoreFocusOptions = {\n fallback?: FocusTarget;\n preventScroll?: boolean;\n};\n\nexport type CaptureFocusOptions = RestoreFocusOptions & {\n signal?: AbortSignal;\n};\n\nexport type FocusRestorer = () => boolean;\n\nconst resolveTarget = (target: FocusTarget): FocusableElement | null | undefined => {\n return typeof target === 'function' ? target() : target;\n};\n\nconst getDeepActiveElement = (rootDocument: Document): Element | null => {\n let active: Element | null = rootDocument.activeElement;\n\n while (active instanceof HTMLElement && active.shadowRoot?.activeElement) {\n active = active.shadowRoot.activeElement;\n }\n\n return active;\n};\n\nconst isConnected = (target: FocusableElement): boolean => {\n // `isConnected` pierces shadow boundaries (shadow-including root is the document);\n // `ownerDocument.contains` does NOT: it would reject elements inside shadow roots.\n return target.isConnected;\n};\n\nconst isDisabled = (target: FocusableElement): boolean => {\n return target instanceof HTMLElement && 'disabled' in target && Boolean((target as HTMLInputElement).disabled);\n};\n\nconst isInert = (target: FocusableElement): boolean => {\n if (target.closest('[inert]')) return true;\n\n return target instanceof HTMLElement && target.inert;\n};\n\nconst canRestoreTo = (target: FocusableElement): boolean => {\n return isConnected(target) && !isDisabled(target) && !isInert(target);\n};\n\nconst focusAndVerify = (target: FocusableElement, preventScroll: boolean | undefined): boolean => {\n try {\n target.focus({ preventScroll });\n return getDeepActiveElement(target.ownerDocument) === target;\n } catch {\n return false;\n }\n};\n\nexport const restoreFocus = (target: FocusTarget, options: RestoreFocusOptions = {}): boolean => {\n let next: FocusableElement | null | undefined;\n try {\n next = resolveTarget(target);\n } catch {\n next = null;\n }\n\n if (next && canRestoreTo(next) && focusAndVerify(next, options.preventScroll)) {\n return true;\n }\n\n if (!options.fallback) return false;\n\n let fallback: FocusableElement | null | undefined;\n try {\n fallback = resolveTarget(options.fallback);\n } catch {\n return false;\n }\n\n if (!fallback || !canRestoreTo(fallback)) return false;\n\n return focusAndVerify(fallback, options.preventScroll);\n};\n\n/**\n * Hands focus to `target` when focus has been lost to the document body: the state left\n * behind when the focused element unmounts mid-swap (browsing a dialog, paging a list),\n * where keydown would never reach a handler and every shortcut dies. Returns `true` when\n * focus was rescued; `false` when focus is already on a real element or the target cannot\n * take focus (the `fallback` option of {@link RestoreFocusOptions} still applies).\n */\nexport const rescueFocus = (target: FocusTarget, options: RestoreFocusOptions = {}): boolean => {\n const active = getDeepActiveElement(document);\n\n if (active && active !== document.body) return false;\n\n return restoreFocus(target, options);\n};\n\nexport const captureFocus = (options: CaptureFocusOptions = {}): FocusRestorer => {\n let captured = getDeepActiveElement(document) as FocusableElement | null;\n let available = !options.signal?.aborted;\n\n if (!available) captured = null;\n\n const cancel = (): void => {\n available = false;\n captured = null;\n };\n\n if (!options.signal?.aborted) {\n options.signal?.addEventListener('abort', cancel, { once: true });\n }\n\n return () => {\n if (!available) return false;\n\n available = false;\n options.signal?.removeEventListener('abort', cancel);\n\n const target = captured;\n captured = null;\n\n if (target) {\n return restoreFocus(target, {\n fallback: options.fallback,\n preventScroll: options.preventScroll,\n });\n }\n\n if (options.fallback) {\n return restoreFocus(options.fallback, {\n preventScroll: options.preventScroll,\n });\n }\n\n return false;\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"focus.js","names":[],"sources":["../src/errors.ts","../src/grid-navigation.ts","../src/list-navigation.ts","../src/quick-look-grid.ts","../src/restore-focus.ts"],"sourcesContent":["/** Base class for all focus errors. Use `instanceof FocusError` to catch any focus-originated error. */\nexport class FocusError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when navigation options are invalid, such as a non-positive typeahead delay or a key assigned to two actions. */\nexport class FocusConfigError extends FocusError {}\n","import { matchKey } from '@vielzeug/keymap';\n\nimport { FocusConfigError } from './errors.js';\nimport type { MaybeGetter } from './list-navigation.js';\n\nexport type GridNavigationAction = 'first' | 'last' | 'next' | 'nextRow' | 'prev' | 'prevRow';\n\nexport type GridNavigationChange<T> = {\n readonly action: GridNavigationAction;\n readonly event?: KeyboardEvent;\n readonly index: number;\n readonly item: T;\n};\n\nexport type GridKeyResult<T> = {\n readonly change: GridNavigationChange<T> | null;\n readonly handled: true;\n};\n\n/**\n * Columns per row: a fixed count, or a getter resolved on every navigation. Responsive grids\n * read a media query or computed style; measured grids read the rendered row length.\n */\nexport type GridColumns = number | (() => number);\n\nexport type GridNavigationOptions<T> = {\n /** Columns per row, resolved on every navigation. */\n columns: GridColumns;\n direction?: MaybeGetter<'ltr' | 'rtl'>;\n disabled?: MaybeGetter<boolean>;\n /**\n * Reads the active index from the outside (usually the index of `document.activeElement`\n * among the items), so focus moved by pointer or Tab stays in sync. When omitted, the\n * navigation tracks the index internally through `set()`.\n */\n getActiveIndex?: () => number;\n getItems: () => readonly T[];\n keys?: Partial<Record<GridNavigationAction, readonly string[]>>;\n /** Wrap moves around the grid's edges by flat index. Default clamps at the edges. */\n loop?: boolean;\n};\n\nexport type GridNavigation<T> = {\n getActiveItem(): T | undefined;\n getIndex(): number;\n handleKeydown(event: KeyboardEvent): GridKeyResult<T> | null;\n navigate(action: GridNavigationAction): GridNavigationChange<T> | null;\n reset(): void;\n set(index: number): void;\n};\n\nconst DEFAULT_KEYS: Record<GridNavigationAction, readonly string[]> = {\n first: ['Home'],\n last: ['End'],\n next: ['ArrowRight'],\n nextRow: ['ArrowDown'],\n prev: ['ArrowLeft'],\n prevRow: ['ArrowUp'],\n};\n\nconst DEFAULT_KEYS_RTL: typeof DEFAULT_KEYS = {\n ...DEFAULT_KEYS,\n next: ['ArrowLeft'],\n prev: ['ArrowRight'],\n};\n\nconst GRID_ACTIONS = ['first', 'last', 'next', 'nextRow', 'prev', 'prevRow'] as const;\n\nconst resolve = <V>(value: MaybeGetter<V> | undefined, fallback: V): V =>\n typeof value === 'function' ? (value as () => V)() : (value ?? fallback);\n\n/**\n * Keyboard navigation over a two-dimensional grid of items: horizontal arrows step one\n * item, vertical arrows step one row (the column count), Home/End jump to the ends.\n *\n * Unlike `createListNavigation`, items are not skipped when disabled: skipping in two\n * dimensions would break row alignment. Focus (or otherwise activate) the item reported\n * by the returned change; `handleKeydown` calls `preventDefault()` for every recognized\n * key so unhandled arrows never scroll the page.\n */\nexport const createGridNavigation = <T>(options: GridNavigationOptions<T>): GridNavigation<T> => {\n const owners = new Map<string, GridNavigationAction>();\n\n for (const action of GRID_ACTIONS) {\n for (const key of options.keys?.[action] ?? []) {\n const owner = owners.get(key);\n if (owner && owner !== action)\n throw new FocusConfigError(`Key \"${key}\" is assigned to both ${owner} and ${action}`);\n owners.set(key, action);\n }\n }\n\n let index = -1;\n\n const resolveColumns = (): number => {\n const columns = resolve(options.columns, 1);\n if (!Number.isInteger(columns) || columns < 1)\n throw new FocusConfigError(`columns must resolve to a positive integer, got: ${columns}`);\n return columns;\n };\n\n const activeIndex = (items: readonly T[]): number => {\n const derived = options.getActiveIndex?.();\n if (derived !== undefined) index = derived >= 0 && derived < items.length ? derived : -1;\n return index;\n };\n\n const commitIndex = (\n items: readonly T[],\n nextIndex: number,\n action: GridNavigationAction,\n event?: KeyboardEvent,\n ): GridNavigationChange<T> => {\n index = nextIndex;\n return Object.freeze({ action, event, index: nextIndex, item: items[nextIndex] });\n };\n\n const navigateWithItems = (\n items: readonly T[],\n action: GridNavigationAction,\n event?: KeyboardEvent,\n ): GridNavigationChange<T> | null => {\n if (!items.length) {\n index = -1;\n return null;\n }\n\n if (action === 'first') return commitIndex(items, 0, action, event);\n if (action === 'last') return commitIndex(items, items.length - 1, action, event);\n\n const columns = resolveColumns();\n const offset = action === 'next' ? 1 : action === 'prev' ? -1 : action === 'nextRow' ? columns : -columns;\n const current = activeIndex(items);\n const base = current < 0 ? (offset > 0 ? 0 : items.length - 1) : current + offset;\n\n if (options.loop) {\n return commitIndex(items, ((base % items.length) + items.length) % items.length, action, event);\n }\n\n const clamped = Math.min(items.length - 1, Math.max(0, base));\n if (clamped === current) return null;\n\n return commitIndex(items, clamped, action, event);\n };\n\n const resolveKeyAction = (event: KeyboardEvent): GridNavigationAction | undefined => {\n const keyTable = resolve(options.direction, 'ltr') === 'rtl' ? DEFAULT_KEYS_RTL : DEFAULT_KEYS;\n\n for (const action of GRID_ACTIONS) {\n if ((options.keys?.[action] ?? keyTable[action]).some((pattern) => matchKey(event, pattern))) return action;\n }\n\n return undefined;\n };\n\n const handleKeydown = (event: KeyboardEvent): GridKeyResult<T> | null => {\n if (event.defaultPrevented || event.isComposing || resolve(options.disabled, false)) return null;\n\n const action = resolveKeyAction(event);\n if (!action) return null;\n\n const change = navigateWithItems(options.getItems(), action, event);\n event.preventDefault();\n return Object.freeze({ change, handled: true });\n };\n\n const navigate = (action: GridNavigationAction): GridNavigationChange<T> | null =>\n navigateWithItems(options.getItems(), action);\n\n const set = (nextIndex: number): void => {\n const items = options.getItems();\n\n if (!Number.isInteger(nextIndex) || nextIndex < 0 || !items.length) {\n index = -1;\n return;\n }\n\n index = Math.min(nextIndex, items.length - 1);\n };\n\n const reset = (): void => {\n index = -1;\n };\n\n const getIndex = (): number => activeIndex(options.getItems());\n\n const getActiveItem = (): T | undefined => {\n const items = options.getItems();\n const current = activeIndex(items);\n return current >= 0 ? items[current] : undefined;\n };\n\n return {\n getActiveItem,\n getIndex,\n handleKeydown,\n navigate,\n reset,\n set,\n };\n};\n","import { matchKey } from '@vielzeug/keymap';\n\nimport { FocusConfigError } from './errors.js';\n\nexport type ListNavigationAction = 'first' | 'last' | 'next' | 'prev';\nexport type ListKeyAction = ListNavigationAction | 'typeahead';\n\nexport type ListNavigationChange<T> = {\n readonly action: ListKeyAction;\n readonly event?: KeyboardEvent;\n readonly index: number;\n readonly item: T;\n};\n\nexport type ListKeyResult<T> = {\n readonly change: ListNavigationChange<T> | null;\n readonly handled: true;\n};\n\nexport type ListNavigationTypeaheadOptions<T> = {\n delayMs?: number;\n getLabel: (item: T, index: number) => string;\n preventDefault?: boolean;\n};\n\nexport type MaybeGetter<T> = T | (() => T);\n\nexport type ListNavigationOptions<T> = {\n direction?: MaybeGetter<'ltr' | 'rtl'>;\n disabled?: MaybeGetter<boolean>;\n getItems: () => readonly T[];\n isItemDisabled?: (item: T, index: number) => boolean;\n keys?: Partial<Record<ListNavigationAction, readonly string[]>>;\n loop?: boolean;\n orientation?: MaybeGetter<'both' | 'horizontal' | 'vertical'>;\n typeahead?: ListNavigationTypeaheadOptions<T>;\n};\n\nexport type ListNavigation<T> = {\n getActiveItem(): T | undefined;\n getIndex(): number;\n handleKeydown(event: KeyboardEvent): ListKeyResult<T> | null;\n navigate(action: ListNavigationAction): ListNavigationChange<T> | null;\n reset(): void;\n set(index: number): number;\n};\n\nconst DEFAULT_KEYS: Record<'both' | 'horizontal' | 'vertical', Record<ListNavigationAction, readonly string[]>> = {\n both: { first: ['Home'], last: ['End'], next: ['ArrowDown', 'ArrowRight'], prev: ['ArrowUp', 'ArrowLeft'] },\n horizontal: { first: ['Home'], last: ['End'], next: ['ArrowRight'], prev: ['ArrowLeft'] },\n vertical: { first: ['Home'], last: ['End'], next: ['ArrowDown'], prev: ['ArrowUp'] },\n};\n\nconst DEFAULT_KEYS_RTL: typeof DEFAULT_KEYS = {\n both: { first: ['Home'], last: ['End'], next: ['ArrowDown', 'ArrowLeft'], prev: ['ArrowUp', 'ArrowRight'] },\n horizontal: { first: ['Home'], last: ['End'], next: ['ArrowLeft'], prev: ['ArrowRight'] },\n vertical: DEFAULT_KEYS.vertical,\n};\n\nconst DEFAULT_TYPEAHEAD_DELAY_MS = 500;\n\nconst findForward = <T>(items: readonly T[], start: number, predicate: (item: T, index: number) => boolean): number => {\n for (let index = start; index < items.length; index++) {\n if (predicate(items[index], index)) return index;\n }\n\n return -1;\n};\n\nconst findBackward = <T>(\n items: readonly T[],\n start: number,\n predicate: (item: T, index: number) => boolean,\n): number => {\n for (let index = start; index >= 0; index--) {\n if (predicate(items[index], index)) return index;\n }\n\n return -1;\n};\n\nconst resolveTypeaheadDelay = (delay: number | undefined): number => {\n if (delay === undefined) return DEFAULT_TYPEAHEAD_DELAY_MS;\n if (!Number.isFinite(delay) || delay <= 0)\n throw new FocusConfigError('Typeahead delay must be a positive finite number');\n return delay;\n};\n\nexport const createListNavigation = <T>(options: ListNavigationOptions<T>): ListNavigation<T> => {\n const typeaheadDelay = resolveTypeaheadDelay(options.typeahead?.delayMs);\n const owners = new Map<string, ListNavigationAction>();\n\n for (const action of ['next', 'prev', 'first', 'last'] as const) {\n for (const key of options.keys?.[action] ?? []) {\n const owner = owners.get(key);\n if (owner && owner !== action)\n throw new FocusConfigError(`Key \"${key}\" is assigned to both ${owner} and ${action}`);\n owners.set(key, action);\n }\n }\n\n let index = -1;\n let typeaheadBuffer = '';\n let lastTypeaheadAt = 0;\n\n const isItemDisabled = (item: T, itemIndex: number): boolean => options.isItemDisabled?.(item, itemIndex) ?? false;\n\n const isUsableIndex = (items: readonly T[], itemIndex: number): boolean =>\n itemIndex >= 0 && itemIndex < items.length && !isItemDisabled(items[itemIndex], itemIndex);\n\n const normalizeIndex = (items: readonly T[]): number => {\n if (!isUsableIndex(items, index)) index = -1;\n\n return index;\n };\n\n const resetTypeahead = (): void => {\n typeaheadBuffer = '';\n lastTypeaheadAt = 0;\n };\n\n const commitIndex = (\n items: readonly T[],\n nextIndex: number,\n action: ListKeyAction,\n event?: KeyboardEvent,\n ): ListNavigationChange<T> | null => {\n if (!isUsableIndex(items, nextIndex)) {\n index = -1;\n\n return null;\n }\n\n index = nextIndex;\n\n return Object.freeze({ action, event, index: nextIndex, item: items[nextIndex] });\n };\n\n const findEnabledIndex = (items: readonly T[], start: number, direction: 'forward' | 'backward'): number => {\n if (direction === 'forward')\n return findForward(items, start, (item, itemIndex) => !isItemDisabled(item, itemIndex));\n\n return findBackward(items, start, (item, itemIndex) => !isItemDisabled(item, itemIndex));\n };\n\n const move = (\n items: readonly T[],\n direction: 'forward' | 'backward',\n event?: KeyboardEvent,\n ): ListNavigationChange<T> | null => {\n const current = normalizeIndex(items);\n\n if (!items.length) {\n index = -1;\n\n return null;\n }\n\n const start =\n current < 0\n ? direction === 'forward'\n ? 0\n : items.length - 1\n : direction === 'forward'\n ? current + 1\n : current - 1;\n\n const nextIndex = findEnabledIndex(items, start, direction);\n const action: ListNavigationAction = direction === 'forward' ? 'next' : 'prev';\n\n if (nextIndex >= 0) return commitIndex(items, nextIndex, action, event);\n\n if (options.loop) {\n const wrapStart = direction === 'forward' ? 0 : items.length - 1;\n const wrapped = findEnabledIndex(items, wrapStart, direction);\n\n if (wrapped >= 0) return commitIndex(items, wrapped, action, event);\n }\n\n return null;\n };\n\n const navigateWithItems = (\n items: readonly T[],\n action: ListNavigationAction,\n event?: KeyboardEvent,\n ): ListNavigationChange<T> | null => {\n resetTypeahead();\n normalizeIndex(items);\n\n if (!items.length) {\n index = -1;\n\n return null;\n }\n\n if (action === 'first') {\n return commitIndex(items, findEnabledIndex(items, 0, 'forward'), action, event);\n }\n\n if (action === 'last') {\n return commitIndex(items, findEnabledIndex(items, items.length - 1, 'backward'), action, event);\n }\n\n return move(items, action === 'next' ? 'forward' : 'backward', event);\n };\n\n const findTypeaheadMatch = (items: readonly T[], search: string, includeCurrent: boolean): number => {\n const current = normalizeIndex(items);\n const start = current < 0 ? 0 : includeCurrent ? current : current + 1;\n\n for (let offset = 0; offset < items.length; offset++) {\n const itemIndex = (start + offset) % items.length;\n const item = items[itemIndex];\n\n if (isItemDisabled(item, itemIndex)) continue;\n\n if (options.typeahead?.getLabel(item, itemIndex).toLocaleLowerCase().startsWith(search)) {\n return itemIndex;\n }\n }\n\n return -1;\n };\n\n const handleTypeahead = (event: KeyboardEvent, items: readonly T[]): ListNavigationChange<T> | null => {\n if (!options.typeahead || event.key.length !== 1 || event.ctrlKey || event.altKey || event.metaKey) return null;\n\n const now = performance.now();\n const key = event.key.toLocaleLowerCase();\n\n if (now - lastTypeaheadAt >= typeaheadDelay) typeaheadBuffer = '';\n\n lastTypeaheadAt = now;\n\n const candidate = typeaheadBuffer + key;\n const repeatedCharacter = candidate.length > 1 && [...candidate].every((character) => character === key);\n typeaheadBuffer = repeatedCharacter ? key : candidate;\n\n let nextIndex = findTypeaheadMatch(items, typeaheadBuffer, typeaheadBuffer.length > 1);\n\n if (nextIndex < 0 && typeaheadBuffer.length > 1) {\n typeaheadBuffer = key;\n nextIndex = findTypeaheadMatch(items, typeaheadBuffer, false);\n }\n\n if (nextIndex < 0) return null;\n\n return commitIndex(items, nextIndex, 'typeahead', event);\n };\n\n const set = (nextIndex: number): number => {\n const items = options.getItems();\n normalizeIndex(items);\n resetTypeahead();\n\n if (!Number.isInteger(nextIndex) || nextIndex < 0 || !items.length) {\n index = -1;\n\n return index;\n }\n\n const clamped = Math.min(nextIndex, items.length - 1);\n\n if (!isUsableIndex(items, clamped)) {\n index = -1;\n\n return index;\n }\n\n index = clamped;\n\n return index;\n };\n\n const navigate = (action: ListNavigationAction): ListNavigationChange<T> | null => {\n return navigateWithItems(options.getItems(), action);\n };\n\n const getActiveItem = (): T | undefined => {\n const items = options.getItems();\n const current = normalizeIndex(items);\n\n return current >= 0 ? items[current] : undefined;\n };\n\n const reset = (): void => {\n index = -1;\n resetTypeahead();\n };\n\n const getIndex = (): number => {\n return normalizeIndex(options.getItems());\n };\n\n const resolve = <V>(value: MaybeGetter<V> | undefined, fallback: V): V =>\n typeof value === 'function' ? (value as () => V)() : (value ?? fallback);\n\n const resolveKeyAction = (event: KeyboardEvent): ListNavigationAction | undefined => {\n const keys = options.keys;\n const keyTable = resolve(options.direction, 'ltr') === 'rtl' ? DEFAULT_KEYS_RTL : DEFAULT_KEYS;\n const defaults = keyTable[resolve(options.orientation, 'vertical')];\n\n for (const action of ['next', 'prev', 'first', 'last'] as const) {\n if ((keys?.[action] ?? defaults[action]).some((pattern) => matchKey(event, pattern))) return action;\n }\n\n return undefined;\n };\n\n const handleKeydown = (event: KeyboardEvent): ListKeyResult<T> | null => {\n if (event.defaultPrevented || event.isComposing || resolve(options.disabled, false)) return null;\n\n const items = options.getItems();\n normalizeIndex(items);\n\n const action = resolveKeyAction(event);\n\n if (action) {\n const change = navigateWithItems(items, action, event);\n event.preventDefault();\n return Object.freeze({ change, handled: true });\n }\n\n const change = handleTypeahead(event, items);\n if (!change) return null;\n if (options.typeahead?.preventDefault) event.preventDefault();\n return Object.freeze({ change, handled: true });\n };\n\n return {\n getActiveItem,\n getIndex,\n handleKeydown,\n navigate,\n reset,\n set,\n };\n};\n","import type { GridColumns } from './grid-navigation.js';\nimport { createGridNavigation } from './grid-navigation.js';\nimport type { MaybeGetter } from './list-navigation.js';\n\nexport type QuickLookGridOptions = {\n /**\n * Columns per row, resolved on every navigation. Defaults to the rendered row length at the\n * active item, which follows the grid's own responsive wrapping without a media query.\n */\n columns?: GridColumns;\n /** Suppresses every key: for grids the consumer has locked. */\n disabled?: MaybeGetter<boolean>;\n /** Reads the grid's items in display order, re-read on every key. */\n getItems: () => readonly HTMLElement[];\n /**\n * Reads the active index among the items when the event target is outside them, for grids\n * whose focus sits on a wrapper around the item. Defaults to the item that is (or contains)\n * `document.activeElement`.\n */\n getActiveIndex?: () => number;\n /** Key that opens the Quick Look. Default `' '` (Space), the platform preview key. */\n inspectKey?: string;\n /** Wrap moves around the grid's edges by flat index. Default clamps at the edges. */\n loop?: boolean;\n /**\n * Opens the Quick Look for one item. The keyboard path has already focused the item; the\n * consumer decides what \"detail\" means (a dialog, a zoom, a preview pane).\n */\n onInspect: (item: HTMLElement, index: number) => void;\n};\n\nexport type QuickLookGrid = {\n /**\n * Route the grid's `keydown` here. Arrow keys and Home/End move focus through the items;\n * `inspectKey` opens the Quick Look for the item under the event (or the active item),\n * cancelling the key so the focused control never activates. Modifier chords pass through.\n */\n handleKeydown(event: KeyboardEvent): void;\n};\n\nconst resolve = <V>(value: MaybeGetter<V> | undefined, fallback: V): V =>\n typeof value === 'function' ? (value as () => V)() : (value ?? fallback);\n\n/**\n * Keyboard browsing over a tile grid with a Quick Look key: the pair every card picker needs,\n * where arrows walk the tiles and Space previews the focused card instead of activating it.\n *\n * Built on `createGridNavigation`: horizontal arrows step one item, vertical arrows step one\n * row, Home/End jump to the ends, and focus follows the reported change. `inspectKey` (Space by\n * default) resolves the item under the event target, calls `preventDefault()` and\n * `stopPropagation()` so the focused button never sees the press, and reports it through\n * `onInspect`. Long-press is the touch counterpart of the same callback; pair this with a\n * pointer recognizer (for example `@vielzeug/gesture`'s long press) at the call site.\n */\nexport const createQuickLookGrid = (options: QuickLookGridOptions): QuickLookGrid => {\n const activeIndex = (): number => options.getItems().indexOf(document.activeElement as HTMLElement);\n\n /** The rendered row length at the active item: items sharing its top edge. */\n const measuredColumns = (): number => {\n const items = options.getItems();\n const active = items.indexOf(document.activeElement as HTMLElement);\n if (active < 0) return 1;\n const top = items[active]?.getBoundingClientRect().top ?? 0;\n return Math.max(1, items.filter((item) => Math.abs(item.getBoundingClientRect().top - top) < 1).length);\n };\n\n const navigation = createGridNavigation<HTMLElement>({\n columns: options.columns ?? measuredColumns,\n getActiveIndex: options.getActiveIndex ?? activeIndex,\n getItems: options.getItems,\n loop: options.loop,\n });\n\n /** The item the key belongs to: the one containing the event target, else the active item. */\n const resolveItem = (event: KeyboardEvent): { index: number; item: HTMLElement | undefined } => {\n const items = options.getItems();\n const target = event.target instanceof HTMLElement ? event.target : null;\n const byTarget = target === null ? -1 : items.findIndex((item) => item === target || item.contains(target));\n if (byTarget >= 0) return { index: byTarget, item: items[byTarget] };\n const index = (options.getActiveIndex ?? activeIndex)();\n return { index, item: index >= 0 ? items[index] : undefined };\n };\n\n const handleKeydown = (event: KeyboardEvent): void => {\n if (event.defaultPrevented || event.isComposing || resolve(options.disabled, false)) return;\n if (event.ctrlKey || event.metaKey || event.altKey) return;\n\n if (event.key === (options.inspectKey ?? ' ')) {\n const { index, item } = resolveItem(event);\n if (!item) return;\n event.preventDefault();\n event.stopPropagation();\n options.onInspect(item, index);\n return;\n }\n\n const result = navigation.handleKeydown(event);\n if (result?.change) result.change.item.focus();\n };\n\n return { handleKeydown };\n};\n","export type FocusableElement = HTMLElement | SVGElement;\nexport type FocusTarget = FocusableElement | null | undefined | (() => FocusableElement | null | undefined);\n\nexport type RestoreFocusOptions = {\n fallback?: FocusTarget;\n preventScroll?: boolean;\n};\n\nexport type CaptureFocusOptions = RestoreFocusOptions & {\n signal?: AbortSignal;\n};\n\nexport type FocusRestorer = () => boolean;\n\nconst resolveTarget = (target: FocusTarget): FocusableElement | null | undefined => {\n return typeof target === 'function' ? target() : target;\n};\n\nconst getDeepActiveElement = (rootDocument: Document): Element | null => {\n let active: Element | null = rootDocument.activeElement;\n\n while (active instanceof HTMLElement && active.shadowRoot?.activeElement) {\n active = active.shadowRoot.activeElement;\n }\n\n return active;\n};\n\nconst isConnected = (target: FocusableElement): boolean => {\n // `isConnected` pierces shadow boundaries (shadow-including root is the document);\n // `ownerDocument.contains` does NOT: it would reject elements inside shadow roots.\n return target.isConnected;\n};\n\nconst isDisabled = (target: FocusableElement): boolean => {\n return target instanceof HTMLElement && 'disabled' in target && Boolean((target as HTMLInputElement).disabled);\n};\n\nconst isInert = (target: FocusableElement): boolean => {\n if (target.closest('[inert]')) return true;\n\n return target instanceof HTMLElement && target.inert;\n};\n\nconst canRestoreTo = (target: FocusableElement): boolean => {\n return isConnected(target) && !isDisabled(target) && !isInert(target);\n};\n\nconst focusAndVerify = (target: FocusableElement, preventScroll: boolean | undefined): boolean => {\n try {\n target.focus({ preventScroll });\n return getDeepActiveElement(target.ownerDocument) === target;\n } catch {\n return false;\n }\n};\n\nexport const restoreFocus = (target: FocusTarget, options: RestoreFocusOptions = {}): boolean => {\n let next: FocusableElement | null | undefined;\n try {\n next = resolveTarget(target);\n } catch {\n next = null;\n }\n\n if (next && canRestoreTo(next) && focusAndVerify(next, options.preventScroll)) {\n return true;\n }\n\n if (!options.fallback) return false;\n\n let fallback: FocusableElement | null | undefined;\n try {\n fallback = resolveTarget(options.fallback);\n } catch {\n return false;\n }\n\n if (!fallback || !canRestoreTo(fallback)) return false;\n\n return focusAndVerify(fallback, options.preventScroll);\n};\n\n/**\n * Hands focus to `target` when focus has been lost to the document body: the state left\n * behind when the focused element unmounts mid-swap (browsing a dialog, paging a list),\n * where keydown would never reach a handler and every shortcut dies. Returns `true` when\n * focus was rescued; `false` when focus is already on a real element or the target cannot\n * take focus (the `fallback` option of {@link RestoreFocusOptions} still applies).\n */\nexport const rescueFocus = (target: FocusTarget, options: RestoreFocusOptions = {}): boolean => {\n const active = getDeepActiveElement(document);\n\n if (active && active !== document.body) return false;\n\n return restoreFocus(target, options);\n};\n\nexport const captureFocus = (options: CaptureFocusOptions = {}): FocusRestorer => {\n let captured = getDeepActiveElement(document) as FocusableElement | null;\n let available = !options.signal?.aborted;\n\n if (!available) captured = null;\n\n const cancel = (): void => {\n available = false;\n captured = null;\n };\n\n if (!options.signal?.aborted) {\n options.signal?.addEventListener('abort', cancel, { once: true });\n }\n\n return () => {\n if (!available) return false;\n\n available = false;\n options.signal?.removeEventListener('abort', cancel);\n\n const target = captured;\n captured = null;\n\n if (target) {\n return restoreFocus(target, {\n fallback: options.fallback,\n preventScroll: options.preventScroll,\n });\n }\n\n if (options.fallback) {\n return restoreFocus(options.fallback, {\n preventScroll: options.preventScroll,\n });\n }\n\n return false;\n };\n};\n"],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./grid-navigation.cjs"),n=require("./list-navigation.cjs"),r=require("./restore-focus.cjs");exports.FocusConfigError=e.FocusConfigError,exports.FocusError=e.FocusError,exports.captureFocus=r.captureFocus,exports.createGridNavigation=t.createGridNavigation,exports.createListNavigation=n.createListNavigation,exports.rescueFocus=r.rescueFocus,exports.restoreFocus=r.restoreFocus;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./grid-navigation.cjs"),n=require("./list-navigation.cjs"),r=require("./quick-look-grid.cjs"),i=require("./restore-focus.cjs");exports.FocusConfigError=e.FocusConfigError,exports.FocusError=e.FocusError,exports.captureFocus=i.captureFocus,exports.createGridNavigation=t.createGridNavigation,exports.createListNavigation=n.createListNavigation,exports.createQuickLookGrid=r.createQuickLookGrid,exports.rescueFocus=i.rescueFocus,exports.restoreFocus=i.restoreFocus;
package/dist/index.d.ts CHANGED
@@ -3,6 +3,8 @@ export type { GridColumns, GridKeyResult, GridNavigation, GridNavigationAction,
3
3
  export { createGridNavigation } from './grid-navigation.js';
4
4
  export type { ListKeyAction, ListKeyResult, ListNavigation, ListNavigationAction, ListNavigationChange, ListNavigationOptions, ListNavigationTypeaheadOptions, MaybeGetter, } from './list-navigation.js';
5
5
  export { createListNavigation } from './list-navigation.js';
6
+ export type { QuickLookGrid, QuickLookGridOptions } from './quick-look-grid.js';
7
+ export { createQuickLookGrid } from './quick-look-grid.js';
6
8
  export type { CaptureFocusOptions, FocusRestorer, FocusTarget, RestoreFocusOptions, } from './restore-focus.js';
7
9
  export { captureFocus, rescueFocus, restoreFocus } from './restore-focus.js';
8
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC3D,YAAY,EACV,WAAW,EACX,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,YAAY,EACV,aAAa,EACb,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,8BAA8B,EAC9B,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,YAAY,EACV,mBAAmB,EACnB,aAAa,EACb,WAAW,EACX,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC3D,YAAY,EACV,WAAW,EACX,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,YAAY,EACV,aAAa,EACb,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,8BAA8B,EAC9B,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAChF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,YAAY,EACV,mBAAmB,EACnB,aAAa,EACb,WAAW,EACX,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{FocusConfigError as e,FocusError as t}from"./errors.js";import{createGridNavigation as n}from"./grid-navigation.js";import{createListNavigation as r}from"./list-navigation.js";import{captureFocus as i,rescueFocus as a,restoreFocus as o}from"./restore-focus.js";export{e as FocusConfigError,t as FocusError,i as captureFocus,n as createGridNavigation,r as createListNavigation,a as rescueFocus,o as restoreFocus};
1
+ import{FocusConfigError as e,FocusError as t}from"./errors.js";import{createGridNavigation as n}from"./grid-navigation.js";import{createListNavigation as r}from"./list-navigation.js";import{createQuickLookGrid as i}from"./quick-look-grid.js";import{captureFocus as a,rescueFocus as o,restoreFocus as s}from"./restore-focus.js";export{e as FocusConfigError,t as FocusError,a as captureFocus,n as createGridNavigation,r as createListNavigation,i as createQuickLookGrid,o as rescueFocus,s as restoreFocus};
@@ -0,0 +1,2 @@
1
+ const e=require("./grid-navigation.cjs");var resolve=(e,t)=>typeof e==`function`?e():e??t,createQuickLookGrid=t=>{let activeIndex=()=>t.getItems().indexOf(document.activeElement),measuredColumns=()=>{let e=t.getItems(),n=e.indexOf(document.activeElement);if(n<0)return 1;let r=e[n]?.getBoundingClientRect().top??0;return Math.max(1,e.filter(e=>Math.abs(e.getBoundingClientRect().top-r)<1).length)},n=e.createGridNavigation({columns:t.columns??measuredColumns,getActiveIndex:t.getActiveIndex??activeIndex,getItems:t.getItems,loop:t.loop}),resolveItem=e=>{let n=t.getItems(),r=e.target instanceof HTMLElement?e.target:null,i=r===null?-1:n.findIndex(e=>e===r||e.contains(r));if(i>=0)return{index:i,item:n[i]};let a=(t.getActiveIndex??activeIndex)();return{index:a,item:a>=0?n[a]:void 0}},handleKeydown=e=>{if(e.defaultPrevented||e.isComposing||resolve(t.disabled,!1)||e.ctrlKey||e.metaKey||e.altKey)return;if(e.key===(t.inspectKey??` `)){let{index:n,item:r}=resolveItem(e);if(!r)return;e.preventDefault(),e.stopPropagation(),t.onInspect(r,n);return}let r=n.handleKeydown(e);r?.change&&r.change.item.focus()};return{handleKeydown}};exports.createQuickLookGrid=createQuickLookGrid;
2
+ //# sourceMappingURL=quick-look-grid.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quick-look-grid.cjs","names":[],"sources":["../src/quick-look-grid.ts"],"sourcesContent":["import type { GridColumns } from './grid-navigation.js';\nimport { createGridNavigation } from './grid-navigation.js';\nimport type { MaybeGetter } from './list-navigation.js';\n\nexport type QuickLookGridOptions = {\n /**\n * Columns per row, resolved on every navigation. Defaults to the rendered row length at the\n * active item, which follows the grid's own responsive wrapping without a media query.\n */\n columns?: GridColumns;\n /** Suppresses every key: for grids the consumer has locked. */\n disabled?: MaybeGetter<boolean>;\n /** Reads the grid's items in display order, re-read on every key. */\n getItems: () => readonly HTMLElement[];\n /**\n * Reads the active index among the items when the event target is outside them, for grids\n * whose focus sits on a wrapper around the item. Defaults to the item that is (or contains)\n * `document.activeElement`.\n */\n getActiveIndex?: () => number;\n /** Key that opens the Quick Look. Default `' '` (Space), the platform preview key. */\n inspectKey?: string;\n /** Wrap moves around the grid's edges by flat index. Default clamps at the edges. */\n loop?: boolean;\n /**\n * Opens the Quick Look for one item. The keyboard path has already focused the item; the\n * consumer decides what \"detail\" means (a dialog, a zoom, a preview pane).\n */\n onInspect: (item: HTMLElement, index: number) => void;\n};\n\nexport type QuickLookGrid = {\n /**\n * Route the grid's `keydown` here. Arrow keys and Home/End move focus through the items;\n * `inspectKey` opens the Quick Look for the item under the event (or the active item),\n * cancelling the key so the focused control never activates. Modifier chords pass through.\n */\n handleKeydown(event: KeyboardEvent): void;\n};\n\nconst resolve = <V>(value: MaybeGetter<V> | undefined, fallback: V): V =>\n typeof value === 'function' ? (value as () => V)() : (value ?? fallback);\n\n/**\n * Keyboard browsing over a tile grid with a Quick Look key: the pair every card picker needs,\n * where arrows walk the tiles and Space previews the focused card instead of activating it.\n *\n * Built on `createGridNavigation`: horizontal arrows step one item, vertical arrows step one\n * row, Home/End jump to the ends, and focus follows the reported change. `inspectKey` (Space by\n * default) resolves the item under the event target, calls `preventDefault()` and\n * `stopPropagation()` so the focused button never sees the press, and reports it through\n * `onInspect`. Long-press is the touch counterpart of the same callback; pair this with a\n * pointer recognizer (for example `@vielzeug/gesture`'s long press) at the call site.\n */\nexport const createQuickLookGrid = (options: QuickLookGridOptions): QuickLookGrid => {\n const activeIndex = (): number => options.getItems().indexOf(document.activeElement as HTMLElement);\n\n /** The rendered row length at the active item: items sharing its top edge. */\n const measuredColumns = (): number => {\n const items = options.getItems();\n const active = items.indexOf(document.activeElement as HTMLElement);\n if (active < 0) return 1;\n const top = items[active]?.getBoundingClientRect().top ?? 0;\n return Math.max(1, items.filter((item) => Math.abs(item.getBoundingClientRect().top - top) < 1).length);\n };\n\n const navigation = createGridNavigation<HTMLElement>({\n columns: options.columns ?? measuredColumns,\n getActiveIndex: options.getActiveIndex ?? activeIndex,\n getItems: options.getItems,\n loop: options.loop,\n });\n\n /** The item the key belongs to: the one containing the event target, else the active item. */\n const resolveItem = (event: KeyboardEvent): { index: number; item: HTMLElement | undefined } => {\n const items = options.getItems();\n const target = event.target instanceof HTMLElement ? event.target : null;\n const byTarget = target === null ? -1 : items.findIndex((item) => item === target || item.contains(target));\n if (byTarget >= 0) return { index: byTarget, item: items[byTarget] };\n const index = (options.getActiveIndex ?? activeIndex)();\n return { index, item: index >= 0 ? items[index] : undefined };\n };\n\n const handleKeydown = (event: KeyboardEvent): void => {\n if (event.defaultPrevented || event.isComposing || resolve(options.disabled, false)) return;\n if (event.ctrlKey || event.metaKey || event.altKey) return;\n\n if (event.key === (options.inspectKey ?? ' ')) {\n const { index, item } = resolveItem(event);\n if (!item) return;\n event.preventDefault();\n event.stopPropagation();\n options.onInspect(item, index);\n return;\n }\n\n const result = navigation.handleKeydown(event);\n if (result?.change) result.change.item.focus();\n };\n\n return { handleKeydown };\n};\n"],"mappings":"yCAwCA,IAAM,SAAc,EAAmC,IACrD,OAAO,GAAU,WAAc,EAAkB,EAAK,GAAS,EAapD,oBAAuB,GAAiD,CACnF,IAAM,gBAA4B,EAAQ,SAAS,CAAC,CAAC,QAAQ,SAAS,aAA4B,EAG5F,oBAAgC,CACpC,IAAM,EAAQ,EAAQ,SAAS,EACzB,EAAS,EAAM,QAAQ,SAAS,aAA4B,EAClE,GAAI,EAAS,EAAG,MAAO,GACvB,IAAM,EAAM,EAAM,EAAO,EAAE,sBAAsB,CAAC,CAAC,KAAO,EAC1D,OAAO,KAAK,IAAI,EAAG,EAAM,OAAQ,GAAS,KAAK,IAAI,EAAK,sBAAsB,CAAC,CAAC,IAAM,CAAG,EAAI,CAAC,CAAC,CAAC,MAAM,CACxG,EAEM,EAAa,EAAA,qBAAkC,CACnD,QAAS,EAAQ,SAAW,gBAC5B,eAAgB,EAAQ,gBAAkB,YAC1C,SAAU,EAAQ,SAClB,KAAM,EAAQ,IAChB,CAAC,EAGK,YAAe,GAA2E,CAC9F,IAAM,EAAQ,EAAQ,SAAS,EACzB,EAAS,EAAM,kBAAkB,YAAc,EAAM,OAAS,KAC9D,EAAW,IAAW,KAAO,GAAK,EAAM,UAAW,GAAS,IAAS,GAAU,EAAK,SAAS,CAAM,CAAC,EAC1G,GAAI,GAAY,EAAG,MAAO,CAAE,MAAO,EAAU,KAAM,EAAM,EAAU,EACnE,IAAM,GAAS,EAAQ,gBAAkB,YAAA,CAAa,EACtD,MAAO,CAAE,QAAO,KAAM,GAAS,EAAI,EAAM,GAAS,IAAA,EAAU,CAC9D,EAEM,cAAiB,GAA+B,CAEpD,GADI,EAAM,kBAAoB,EAAM,aAAe,QAAQ,EAAQ,SAAU,EAAK,GAC9E,EAAM,SAAW,EAAM,SAAW,EAAM,OAAQ,OAEpD,GAAI,EAAM,OAAS,EAAQ,YAAc,KAAM,CAC7C,GAAM,CAAE,QAAO,QAAS,YAAY,CAAK,EACzC,GAAI,CAAC,EAAM,OACX,EAAM,eAAe,EACrB,EAAM,gBAAgB,EACtB,EAAQ,UAAU,EAAM,CAAK,EAC7B,MACF,CAEA,IAAM,EAAS,EAAW,cAAc,CAAK,EACzC,GAAQ,QAAQ,EAAO,OAAO,KAAK,MAAM,CAC/C,EAEA,MAAO,CAAE,aAAc,CACzB"}
@@ -0,0 +1,49 @@
1
+ import type { GridColumns } from './grid-navigation.js';
2
+ import type { MaybeGetter } from './list-navigation.js';
3
+ export type QuickLookGridOptions = {
4
+ /**
5
+ * Columns per row, resolved on every navigation. Defaults to the rendered row length at the
6
+ * active item, which follows the grid's own responsive wrapping without a media query.
7
+ */
8
+ columns?: GridColumns;
9
+ /** Suppresses every key: for grids the consumer has locked. */
10
+ disabled?: MaybeGetter<boolean>;
11
+ /** Reads the grid's items in display order, re-read on every key. */
12
+ getItems: () => readonly HTMLElement[];
13
+ /**
14
+ * Reads the active index among the items when the event target is outside them, for grids
15
+ * whose focus sits on a wrapper around the item. Defaults to the item that is (or contains)
16
+ * `document.activeElement`.
17
+ */
18
+ getActiveIndex?: () => number;
19
+ /** Key that opens the Quick Look. Default `' '` (Space), the platform preview key. */
20
+ inspectKey?: string;
21
+ /** Wrap moves around the grid's edges by flat index. Default clamps at the edges. */
22
+ loop?: boolean;
23
+ /**
24
+ * Opens the Quick Look for one item. The keyboard path has already focused the item; the
25
+ * consumer decides what "detail" means (a dialog, a zoom, a preview pane).
26
+ */
27
+ onInspect: (item: HTMLElement, index: number) => void;
28
+ };
29
+ export type QuickLookGrid = {
30
+ /**
31
+ * Route the grid's `keydown` here. Arrow keys and Home/End move focus through the items;
32
+ * `inspectKey` opens the Quick Look for the item under the event (or the active item),
33
+ * cancelling the key so the focused control never activates. Modifier chords pass through.
34
+ */
35
+ handleKeydown(event: KeyboardEvent): void;
36
+ };
37
+ /**
38
+ * Keyboard browsing over a tile grid with a Quick Look key: the pair every card picker needs,
39
+ * where arrows walk the tiles and Space previews the focused card instead of activating it.
40
+ *
41
+ * Built on `createGridNavigation`: horizontal arrows step one item, vertical arrows step one
42
+ * row, Home/End jump to the ends, and focus follows the reported change. `inspectKey` (Space by
43
+ * default) resolves the item under the event target, calls `preventDefault()` and
44
+ * `stopPropagation()` so the focused button never sees the press, and reports it through
45
+ * `onInspect`. Long-press is the touch counterpart of the same callback; pair this with a
46
+ * pointer recognizer (for example `@vielzeug/gesture`'s long press) at the call site.
47
+ */
48
+ export declare const createQuickLookGrid: (options: QuickLookGridOptions) => QuickLookGrid;
49
+ //# sourceMappingURL=quick-look-grid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quick-look-grid.d.ts","sourceRoot":"","sources":["../src/quick-look-grid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,MAAM,MAAM,oBAAoB,GAAG;IACjC;;;OAGG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IAChC,qEAAqE;IACrE,QAAQ,EAAE,MAAM,SAAS,WAAW,EAAE,CAAC;IACvC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,MAAM,CAAC;IAC9B,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qFAAqF;IACrF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,SAAS,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B;;;;OAIG;IACH,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;CAC3C,CAAC;AAKF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,oBAAoB,KAAG,aA+CnE,CAAC"}
@@ -0,0 +1,2 @@
1
+ import{createGridNavigation as e}from"./grid-navigation.js";var resolve=(e,t)=>typeof e==`function`?e():e??t,createQuickLookGrid=t=>{let activeIndex=()=>t.getItems().indexOf(document.activeElement),measuredColumns=()=>{let e=t.getItems(),n=e.indexOf(document.activeElement);if(n<0)return 1;let r=e[n]?.getBoundingClientRect().top??0;return Math.max(1,e.filter(e=>Math.abs(e.getBoundingClientRect().top-r)<1).length)},n=e({columns:t.columns??measuredColumns,getActiveIndex:t.getActiveIndex??activeIndex,getItems:t.getItems,loop:t.loop}),resolveItem=e=>{let n=t.getItems(),r=e.target instanceof HTMLElement?e.target:null,i=r===null?-1:n.findIndex(e=>e===r||e.contains(r));if(i>=0)return{index:i,item:n[i]};let a=(t.getActiveIndex??activeIndex)();return{index:a,item:a>=0?n[a]:void 0}},handleKeydown=e=>{if(e.defaultPrevented||e.isComposing||resolve(t.disabled,!1)||e.ctrlKey||e.metaKey||e.altKey)return;if(e.key===(t.inspectKey??` `)){let{index:n,item:r}=resolveItem(e);if(!r)return;e.preventDefault(),e.stopPropagation(),t.onInspect(r,n);return}let r=n.handleKeydown(e);r?.change&&r.change.item.focus()};return{handleKeydown}};export{createQuickLookGrid};
2
+ //# sourceMappingURL=quick-look-grid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quick-look-grid.js","names":[],"sources":["../src/quick-look-grid.ts"],"sourcesContent":["import type { GridColumns } from './grid-navigation.js';\nimport { createGridNavigation } from './grid-navigation.js';\nimport type { MaybeGetter } from './list-navigation.js';\n\nexport type QuickLookGridOptions = {\n /**\n * Columns per row, resolved on every navigation. Defaults to the rendered row length at the\n * active item, which follows the grid's own responsive wrapping without a media query.\n */\n columns?: GridColumns;\n /** Suppresses every key: for grids the consumer has locked. */\n disabled?: MaybeGetter<boolean>;\n /** Reads the grid's items in display order, re-read on every key. */\n getItems: () => readonly HTMLElement[];\n /**\n * Reads the active index among the items when the event target is outside them, for grids\n * whose focus sits on a wrapper around the item. Defaults to the item that is (or contains)\n * `document.activeElement`.\n */\n getActiveIndex?: () => number;\n /** Key that opens the Quick Look. Default `' '` (Space), the platform preview key. */\n inspectKey?: string;\n /** Wrap moves around the grid's edges by flat index. Default clamps at the edges. */\n loop?: boolean;\n /**\n * Opens the Quick Look for one item. The keyboard path has already focused the item; the\n * consumer decides what \"detail\" means (a dialog, a zoom, a preview pane).\n */\n onInspect: (item: HTMLElement, index: number) => void;\n};\n\nexport type QuickLookGrid = {\n /**\n * Route the grid's `keydown` here. Arrow keys and Home/End move focus through the items;\n * `inspectKey` opens the Quick Look for the item under the event (or the active item),\n * cancelling the key so the focused control never activates. Modifier chords pass through.\n */\n handleKeydown(event: KeyboardEvent): void;\n};\n\nconst resolve = <V>(value: MaybeGetter<V> | undefined, fallback: V): V =>\n typeof value === 'function' ? (value as () => V)() : (value ?? fallback);\n\n/**\n * Keyboard browsing over a tile grid with a Quick Look key: the pair every card picker needs,\n * where arrows walk the tiles and Space previews the focused card instead of activating it.\n *\n * Built on `createGridNavigation`: horizontal arrows step one item, vertical arrows step one\n * row, Home/End jump to the ends, and focus follows the reported change. `inspectKey` (Space by\n * default) resolves the item under the event target, calls `preventDefault()` and\n * `stopPropagation()` so the focused button never sees the press, and reports it through\n * `onInspect`. Long-press is the touch counterpart of the same callback; pair this with a\n * pointer recognizer (for example `@vielzeug/gesture`'s long press) at the call site.\n */\nexport const createQuickLookGrid = (options: QuickLookGridOptions): QuickLookGrid => {\n const activeIndex = (): number => options.getItems().indexOf(document.activeElement as HTMLElement);\n\n /** The rendered row length at the active item: items sharing its top edge. */\n const measuredColumns = (): number => {\n const items = options.getItems();\n const active = items.indexOf(document.activeElement as HTMLElement);\n if (active < 0) return 1;\n const top = items[active]?.getBoundingClientRect().top ?? 0;\n return Math.max(1, items.filter((item) => Math.abs(item.getBoundingClientRect().top - top) < 1).length);\n };\n\n const navigation = createGridNavigation<HTMLElement>({\n columns: options.columns ?? measuredColumns,\n getActiveIndex: options.getActiveIndex ?? activeIndex,\n getItems: options.getItems,\n loop: options.loop,\n });\n\n /** The item the key belongs to: the one containing the event target, else the active item. */\n const resolveItem = (event: KeyboardEvent): { index: number; item: HTMLElement | undefined } => {\n const items = options.getItems();\n const target = event.target instanceof HTMLElement ? event.target : null;\n const byTarget = target === null ? -1 : items.findIndex((item) => item === target || item.contains(target));\n if (byTarget >= 0) return { index: byTarget, item: items[byTarget] };\n const index = (options.getActiveIndex ?? activeIndex)();\n return { index, item: index >= 0 ? items[index] : undefined };\n };\n\n const handleKeydown = (event: KeyboardEvent): void => {\n if (event.defaultPrevented || event.isComposing || resolve(options.disabled, false)) return;\n if (event.ctrlKey || event.metaKey || event.altKey) return;\n\n if (event.key === (options.inspectKey ?? ' ')) {\n const { index, item } = resolveItem(event);\n if (!item) return;\n event.preventDefault();\n event.stopPropagation();\n options.onInspect(item, index);\n return;\n }\n\n const result = navigation.handleKeydown(event);\n if (result?.change) result.change.item.focus();\n };\n\n return { handleKeydown };\n};\n"],"mappings":"4DAwCA,IAAM,SAAc,EAAmC,IACrD,OAAO,GAAU,WAAc,EAAkB,EAAK,GAAS,EAapD,oBAAuB,GAAiD,CACnF,IAAM,gBAA4B,EAAQ,SAAS,CAAC,CAAC,QAAQ,SAAS,aAA4B,EAG5F,oBAAgC,CACpC,IAAM,EAAQ,EAAQ,SAAS,EACzB,EAAS,EAAM,QAAQ,SAAS,aAA4B,EAClE,GAAI,EAAS,EAAG,MAAO,GACvB,IAAM,EAAM,EAAM,EAAO,EAAE,sBAAsB,CAAC,CAAC,KAAO,EAC1D,OAAO,KAAK,IAAI,EAAG,EAAM,OAAQ,GAAS,KAAK,IAAI,EAAK,sBAAsB,CAAC,CAAC,IAAM,CAAG,EAAI,CAAC,CAAC,CAAC,MAAM,CACxG,EAEM,EAAa,EAAkC,CACnD,QAAS,EAAQ,SAAW,gBAC5B,eAAgB,EAAQ,gBAAkB,YAC1C,SAAU,EAAQ,SAClB,KAAM,EAAQ,IAChB,CAAC,EAGK,YAAe,GAA2E,CAC9F,IAAM,EAAQ,EAAQ,SAAS,EACzB,EAAS,EAAM,kBAAkB,YAAc,EAAM,OAAS,KAC9D,EAAW,IAAW,KAAO,GAAK,EAAM,UAAW,GAAS,IAAS,GAAU,EAAK,SAAS,CAAM,CAAC,EAC1G,GAAI,GAAY,EAAG,MAAO,CAAE,MAAO,EAAU,KAAM,EAAM,EAAU,EACnE,IAAM,GAAS,EAAQ,gBAAkB,YAAA,CAAa,EACtD,MAAO,CAAE,QAAO,KAAM,GAAS,EAAI,EAAM,GAAS,IAAA,EAAU,CAC9D,EAEM,cAAiB,GAA+B,CAEpD,GADI,EAAM,kBAAoB,EAAM,aAAe,QAAQ,EAAQ,SAAU,EAAK,GAC9E,EAAM,SAAW,EAAM,SAAW,EAAM,OAAQ,OAEpD,GAAI,EAAM,OAAS,EAAQ,YAAc,KAAM,CAC7C,GAAM,CAAE,QAAO,QAAS,YAAY,CAAK,EACzC,GAAI,CAAC,EAAM,OACX,EAAM,eAAe,EACrB,EAAM,gBAAgB,EACtB,EAAQ,UAAU,EAAM,CAAK,EAC7B,MACF,CAEA,IAAM,EAAS,EAAW,cAAc,CAAK,EACzC,GAAQ,QAAQ,EAAO,OAAO,KAAK,MAAM,CAC/C,EAEA,MAAO,CAAE,aAAc,CACzB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielzeug/focus",
3
- "version": "26.10.1",
3
+ "version": "26.10.2",
4
4
  "description": "Framework-neutral list navigation and focus restoration primitives",
5
5
  "repository": {
6
6
  "type": "git",
@@ -41,7 +41,7 @@
41
41
  "verify:exports": "node verify-exports.mjs"
42
42
  },
43
43
  "dependencies": {
44
- "@vielzeug/keymap": "26.10.1"
44
+ "@vielzeug/keymap": "26.10.2"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/node": "^26.6.2",