diff --git a/extensions/src/platform-scripture/src/checks/inventories/character-inventory.component.tsx b/extensions/src/platform-scripture/src/checks/inventories/character-inventory.component.tsx index 283c6be5a89..e8953f178ea 100644 --- a/extensions/src/platform-scripture/src/checks/inventories/character-inventory.component.tsx +++ b/extensions/src/platform-scripture/src/checks/inventories/character-inventory.component.tsx @@ -1,18 +1,19 @@ import { useLocalizedStrings } from '@papi/frontend/react'; import { SerializedVerseRef } from '@sillsdev/scripture'; import { - Button, ColumnDef, Inventory, InventorySummaryItem, InventoryTableData, Scope, + getInventoryHeader, inventoryCountColumn, inventoryItemColumn, inventoryStatusColumn, } from 'platform-bible-react'; import { LanguageStrings, LocalizeKey } from 'platform-bible-utils'; import { useMemo } from 'react'; +import { getUnicodeValue } from './inventory-utils'; const CHARACTER_INVENTORY_STRING_KEYS: LocalizeKey[] = [ '%webView_inventory_table_header_character%', @@ -47,10 +48,11 @@ const createColumns = ( inventoryItemColumn(itemLabel), { accessorKey: 'unicodeValue', - header: () => , + accessorFn: (row) => getUnicodeValue(row.items[0]), + header: ({ column }) => getInventoryHeader(column, unicodeValueLabel), cell: ({ row }) => { const item: string = row.getValue('item'); - return item.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0'); + return getUnicodeValue(item); }, }, inventoryCountColumn(countLabel), diff --git a/extensions/src/platform-scripture/src/checks/inventories/inventory-utils.ts b/extensions/src/platform-scripture/src/checks/inventories/inventory-utils.ts new file mode 100644 index 00000000000..74346292596 --- /dev/null +++ b/extensions/src/platform-scripture/src/checks/inventories/inventory-utils.ts @@ -0,0 +1,15 @@ +import { codePointAt } from 'platform-bible-utils'; + +/** + * Converts a character to its Unicode hexadecimal representation + * + * @param char The character to convert + * @returns The Unicode value as a 4+ digit uppercase hexadecimal string (e.g., "0041" for 'A', + * "1F600" for ๐Ÿ˜€) + */ +export function getUnicodeValue(char: string): string { + if (!char || char.length === 0) return '0000'; + const codePoint = codePointAt(char, 0); + if (codePoint === undefined) return '0000'; + return codePoint.toString(16).toUpperCase().padStart(4, '0'); +} diff --git a/extensions/src/platform-scripture/src/checks/inventories/marker-inventory.component.tsx b/extensions/src/platform-scripture/src/checks/inventories/marker-inventory.component.tsx index d174021806b..c0110b061c0 100644 --- a/extensions/src/platform-scripture/src/checks/inventories/marker-inventory.component.tsx +++ b/extensions/src/platform-scripture/src/checks/inventories/marker-inventory.component.tsx @@ -2,8 +2,8 @@ import { logger } from '@papi/frontend'; import { useLocalizedStrings, useProjectData } from '@papi/frontend/react'; import { Canon, SerializedVerseRef } from '@sillsdev/scripture'; import { - Button, ColumnDef, + getInventoryHeader, Inventory, inventoryCountColumn, InventorySummaryItem, @@ -69,7 +69,8 @@ const createColumns = ( inventoryCountColumn(countLabel), { accessorKey: 'styleName', - header: () => , + accessorFn: (row) => getDescription(markerNames, row.items[0]) || unknownMarkerLabel, + header: ({ column }) => getInventoryHeader(column, styleNameLabel), cell: ({ row }) => { const marker: string = row.getValue('item'); return getDescription(markerNames, marker) || unknownMarkerLabel; diff --git a/extensions/src/platform-scripture/src/checks/inventories/punctuation-inventory.component.tsx b/extensions/src/platform-scripture/src/checks/inventories/punctuation-inventory.component.tsx index f2bed00d3d4..8cdf0d6bf78 100644 --- a/extensions/src/platform-scripture/src/checks/inventories/punctuation-inventory.component.tsx +++ b/extensions/src/platform-scripture/src/checks/inventories/punctuation-inventory.component.tsx @@ -1,18 +1,19 @@ import { useLocalizedStrings } from '@papi/frontend/react'; import { SerializedVerseRef } from '@sillsdev/scripture'; import { - Button, ColumnDef, Inventory, InventorySummaryItem, InventoryTableData, Scope, + getInventoryHeader, inventoryCountColumn, inventoryItemColumn, inventoryStatusColumn, } from 'platform-bible-react'; import { LanguageStrings, LocalizeKey } from 'platform-bible-utils'; import { useMemo } from 'react'; +import { getUnicodeValue } from './inventory-utils'; const PUNCTUATION_INVENTORY_STRING_KEYS: LocalizeKey[] = [ '%webView_inventory_table_header_count%', @@ -55,11 +56,12 @@ const createColumns = ( }, { accessorKey: 'unicodeValue', - header: () => , + accessorFn: (row) => getUnicodeValue(row.items[0]), + header: ({ column }) => getInventoryHeader(column, unicodeValueLabel), // Q: How to style the and directly? cell: ({ row }) => (
- {String(row.getValue('item')).charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')} + {getUnicodeValue(row.getValue('item'))}
), }, diff --git a/lib/platform-bible-react/dist/index.cjs b/lib/platform-bible-react/dist/index.cjs index 837da0f39ff..2622184880a 100644 --- a/lib/platform-bible-react/dist/index.cjs +++ b/lib/platform-bible-react/dist/index.cjs @@ -1,5 +1,5 @@ -"use strict";var aa=Object.defineProperty;var ia=(t,e,r)=>e in t?aa(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r;var dt=(t,e,r)=>ia(t,typeof e!="symbol"?e+"":e,r);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("react/jsx-runtime"),i=require("react"),ft=require("cmdk"),k=require("lucide-react"),la=require("clsx"),ca=require("tailwind-merge"),wa=require("@radix-ui/react-dialog"),Q=require("@sillsdev/scripture"),N=require("platform-bible-utils"),ke=require("@radix-ui/react-slot"),Zt=require("class-variance-authority"),da=require("@radix-ui/react-popover"),pa=require("@radix-ui/react-label"),ua=require("@radix-ui/react-radio-group"),x=require("lexical"),$r=require("@radix-ui/react-tooltip"),En=require("@lexical/rich-text"),xr=require("react-dom"),ma=require("@lexical/table"),fa=require("@radix-ui/react-toggle-group"),ha=require("@radix-ui/react-toggle"),Vr=require("@lexical/headless"),ga=require("@radix-ui/react-separator"),xa=require("@radix-ui/react-avatar"),Fr=require("@radix-ui/react-dropdown-menu"),ut=require("@tanstack/react-table"),ba=require("@radix-ui/react-select"),va=require("markdown-to-jsx"),bt=require("@eten-tech-foundation/platform-editor"),ya=require("@radix-ui/react-checkbox"),ja=require("@radix-ui/react-tabs"),Na=require("@radix-ui/react-menubar"),ka=require("react-hotkeys-hook"),_a=require("@radix-ui/react-context-menu"),_t=require("vaul"),Ca=require("@radix-ui/react-progress"),Sa=require("react-resizable-panels"),zr=require("sonner"),Ea=require("@radix-ui/react-slider"),Ra=require("@radix-ui/react-switch");function nt(t){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t){for(const r in t)if(r!=="default"){const o=Object.getOwnPropertyDescriptor(t,r);Object.defineProperty(e,r,o.get?o:{enumerable:!0,get:()=>t[r]})}}return e.default=t,Object.freeze(e)}const kt=nt(wa),ye=nt(da),Gr=nt(pa),Oe=nt(ua),Ve=nt($r),an=nt(fa),Br=nt(ha),Kr=nt(ga),_e=nt(xa),X=nt(Fr),Z=nt(ba),Rn=nt(ya),ht=nt(ja),W=nt(Na),J=nt(_a),Tn=nt(Ca),Pn=nt(Sa),Me=nt(Ea),Mn=nt(Ra),Ta=ca.extendTailwindMerge({prefix:"tw-"});function m(...t){return Ta(la.clsx(t))}const Ma="layoutDirection";function st(){const t=localStorage.getItem(Ma);return t==="rtl"?t:"ltr"}const Da=kt.Root,Ia=kt.Portal,qr=i.forwardRef(({className:t,...e},r)=>n.jsx(kt.Overlay,{ref:r,className:m("tw-fixed tw-inset-0 tw-z-50 tw-bg-black/80 data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0",t),...e}));qr.displayName=kt.Overlay.displayName;const Ur=i.forwardRef(({className:t,children:e,...r},o)=>{const s=st();return n.jsxs(Ia,{children:[n.jsx(qr,{}),n.jsxs(kt.Content,{ref:o,className:m("pr-twp tw-fixed tw-left-[50%] tw-top-[50%] tw-z-50 tw-grid tw-w-full tw-max-w-lg tw-translate-x-[-50%] tw-translate-y-[-50%] tw-gap-4 tw-border tw-bg-background tw-p-6 tw-shadow-lg tw-duration-200 data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[state=closed]:tw-slide-out-to-left-1/2 data-[state=closed]:tw-slide-out-to-top-[48%] data-[state=open]:tw-slide-in-from-left-1/2 data-[state=open]:tw-slide-in-from-top-[48%] sm:tw-rounded-lg",t),...r,dir:s,children:[e,n.jsxs(kt.Close,{className:m("tw-absolute tw-top-4 tw-rounded-sm tw-opacity-70 tw-ring-offset-background tw-transition-opacity hover:tw-opacity-100 focus:tw-outline-none focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-2 disabled:tw-pointer-events-none data-[state=open]:tw-bg-accent data-[state=open]:tw-text-muted-foreground",{"tw-right-4":s==="ltr"},{"tw-left-4":s==="rtl"}),children:[n.jsx(k.X,{className:"tw-h-4 tw-w-4"}),n.jsx("span",{className:"tw-sr-only",children:"Close"})]})]})]})});Ur.displayName=kt.Content.displayName;function Hr({className:t,...e}){return n.jsx("div",{className:m("tw-flex tw-flex-col tw-space-y-1.5 tw-text-center sm:tw-text-start",t),...e})}Hr.displayName="DialogHeader";const Yr=i.forwardRef(({className:t,...e},r)=>n.jsx(kt.Title,{ref:r,className:m("tw-text-lg tw-font-semibold tw-leading-none tw-tracking-tight",t),...e}));Yr.displayName=kt.Title.displayName;const Oa=i.forwardRef(({className:t,...e},r)=>n.jsx(kt.Description,{ref:r,className:m("tw-text-sm tw-text-muted-foreground",t),...e}));Oa.displayName=kt.Description.displayName;const At=i.forwardRef(({className:t,...e},r)=>n.jsx(ft.Command,{ref:r,className:m("tw-flex tw-h-full tw-w-full tw-flex-col tw-overflow-hidden tw-rounded-md tw-bg-popover tw-text-popover-foreground",t),...e}));At.displayName=ft.Command.displayName;const pe=i.forwardRef(({className:t,...e},r)=>{const o=st();return n.jsxs("div",{className:"tw-flex tw-items-center tw-border-b tw-px-3",dir:o,children:[n.jsx(k.Search,{className:"tw-me-2 tw-h-4 tw-w-4 tw-shrink-0 tw-opacity-50"}),n.jsx(ft.Command.Input,{ref:r,className:m("tw-flex tw-h-11 tw-w-full tw-rounded-md tw-bg-transparent tw-py-3 tw-text-sm tw-outline-none placeholder:tw-text-muted-foreground disabled:tw-cursor-not-allowed disabled:tw-opacity-50",t),...e})]})});pe.displayName=ft.Command.Input.displayName;const Pt=i.forwardRef(({className:t,...e},r)=>n.jsx(ft.Command.List,{ref:r,className:m("tw-max-h-[300px] tw-overflow-y-auto tw-overflow-x-hidden",t),...e}));Pt.displayName=ft.Command.List.displayName;const Ce=i.forwardRef((t,e)=>n.jsx(ft.Command.Empty,{ref:e,className:"tw-py-6 tw-text-center tw-text-sm",...t}));Ce.displayName=ft.Command.Empty.displayName;const Ot=i.forwardRef(({className:t,...e},r)=>n.jsx(ft.Command.Group,{ref:r,className:m("tw-overflow-hidden tw-p-1 tw-text-foreground [&_[cmdk-group-heading]]:tw-px-2 [&_[cmdk-group-heading]]:tw-py-1.5 [&_[cmdk-group-heading]]:tw-text-xs [&_[cmdk-group-heading]]:tw-font-medium [&_[cmdk-group-heading]]:tw-text-muted-foreground",t),...e}));Ot.displayName=ft.Command.Group.displayName;const Xr=i.forwardRef(({className:t,...e},r)=>n.jsx(ft.Command.Separator,{ref:r,className:m("tw--mx-1 tw-h-px tw-bg-border",t),...e}));Xr.displayName=ft.Command.Separator.displayName;const Ct=i.forwardRef(({className:t,...e},r)=>n.jsx(ft.Command.Item,{ref:r,className:m("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none data-[disabled=true]:tw-pointer-events-none data-[selected=true]:tw-bg-accent data-[selected=true]:tw-text-accent-foreground data-[disabled=true]:tw-opacity-50",t),...e}));Ct.displayName=ft.Command.Item.displayName;function Wr({className:t,...e}){return n.jsx("span",{className:m("tw-ms-auto tw-text-xs tw-tracking-widest tw-text-muted-foreground",t),...e})}Wr.displayName="CommandShortcut";const Jr=(t,e,r,o,s)=>{switch(t){case N.Section.OT:return e??"Old Testament";case N.Section.NT:return r??"New Testament";case N.Section.DC:return o??"Deuterocanon";case N.Section.Extra:return s??"Extra Materials";default:throw new Error(`Unknown section: ${t}`)}},Aa=(t,e,r,o,s)=>{switch(t){case N.Section.OT:return e??"OT";case N.Section.NT:return r??"NT";case N.Section.DC:return o??"DC";case N.Section.Extra:return s??"Extra";default:throw new Error(`Unknown section: ${t}`)}};function be(t,e){var o;return((o=e==null?void 0:e.get(t))==null?void 0:o.localizedName)??Q.Canon.bookIdToEnglishName(t)}function Ln(t,e){var o;return((o=e==null?void 0:e.get(t))==null?void 0:o.localizedId)??t.toUpperCase()}const Zr=Q.Canon.allBookIds.filter(t=>!Q.Canon.isObsolete(Q.Canon.bookIdToNumber(t))),ie=Object.fromEntries(Zr.map(t=>[t,Q.Canon.bookIdToEnglishName(t)]));function $n(t,e,r){const o=e.trim().toLowerCase();if(!o)return!1;const s=Q.Canon.bookIdToEnglishName(t),a=r==null?void 0:r.get(t);return!!(N.includes(s.toLowerCase(),o)||N.includes(t.toLowerCase(),o)||(a?N.includes(a.localizedName.toLowerCase(),o)||N.includes(a.localizedId.toLowerCase(),o):!1))}const Qr=i.forwardRef(({bookId:t,isSelected:e,onSelect:r,onMouseDown:o,section:s,className:a,showCheck:l=!1,localizedBookNames:c,commandValue:d},w)=>{const p=i.useRef(!1),u=()=>{p.current||r==null||r(t),setTimeout(()=>{p.current=!1},100)},h=v=>{p.current=!0,o?o(v):r==null||r(t)},f=i.useMemo(()=>be(t,c),[t,c]),g=i.useMemo(()=>Ln(t,c),[t,c]);return n.jsx("div",{className:m("tw-mx-1 tw-my-1 tw-border-b-0 tw-border-e-0 tw-border-s-2 tw-border-t-0 tw-border-solid",{"tw-border-s-red-200":s===N.Section.OT,"tw-border-s-purple-200":s===N.Section.NT,"tw-border-s-indigo-200":s===N.Section.DC,"tw-border-s-amber-200":s===N.Section.Extra}),children:n.jsxs(Ct,{ref:w,value:d||`${t} ${Q.Canon.bookIdToEnglishName(t)}`,onSelect:u,onMouseDown:h,role:"option","aria-selected":e,"aria-label":`${Q.Canon.bookIdToEnglishName(t)} (${t.toLocaleUpperCase()})`,className:a,children:[l&&n.jsx(k.Check,{className:m("tw-me-2 tw-h-4 tw-w-4 tw-flex-shrink-0",e?"tw-opacity-100":"tw-opacity-0")}),n.jsx("span",{className:"tw-min-w-0 tw-flex-1",children:f}),n.jsx("span",{className:"tw-ms-2 tw-flex-shrink-0 tw-text-xs tw-text-muted-foreground",children:g})]})})}),to=Zt.cva("pr-twp tw-inline-flex tw-items-center tw-justify-center tw-gap-2 tw-whitespace-nowrap tw-rounded-md tw-text-sm tw-font-medium tw-ring-offset-background tw-transition-colors focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-pointer-events-none disabled:tw-opacity-50 [&_svg]:tw-pointer-events-none [&_svg]:tw-size-4 [&_svg]:tw-shrink-0",{variants:{variant:{default:"tw-bg-primary tw-text-primary-foreground hover:tw-bg-primary/90",destructive:"tw-bg-destructive tw-text-destructive-foreground hover:tw-bg-destructive/90",outline:"tw-border tw-border-input tw-bg-background hover:tw-bg-accent hover:tw-text-accent-foreground",secondary:"tw-bg-secondary tw-text-secondary-foreground hover:tw-bg-secondary/80",ghost:"hover:tw-bg-accent hover:tw-text-accent-foreground",link:"tw-text-primary tw-underline-offset-4 hover:tw-underline"},size:{default:"tw-h-10 tw-px-4 tw-py-2",sm:"tw-h-9 tw-rounded-md tw-px-3",lg:"tw-h-11 tw-rounded-md tw-px-8",icon:"tw-h-10 tw-w-10"}},defaultVariants:{variant:"default",size:"default"}}),$=i.forwardRef(({className:t,variant:e,size:r,asChild:o=!1,...s},a)=>{const l=o?ke.Slot:"button";return n.jsx(l,{className:m(to({variant:e,size:r,className:t})),ref:a,...s})});$.displayName="Button";const zt=ye.Root,Gt=ye.Trigger,Pa=ye.Anchor,Lt=i.forwardRef(({className:t,align:e="center",sideOffset:r=4,...o},s)=>{const a=st();return n.jsx(ye.Portal,{children:n.jsx(ye.Content,{ref:s,align:e,sideOffset:r,className:m("tw-z-[250]","pr-twp tw-w-72 tw-rounded-md tw-border tw-bg-popover tw-p-4 tw-text-popover-foreground tw-shadow-md tw-outline-none data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2",t),...o,dir:a})})});Lt.displayName=ye.Content.displayName;function Dn(t,e,r){return`${t} ${ie[t]}${e?` ${Ln(t,e)} ${be(t,e)}`:""}${r?` ${r}`:""}`}function eo({recentSearches:t,onSearchItemSelect:e,renderItem:r=d=>String(d),getItemKey:o=d=>String(d),ariaLabel:s="Show recent searches",groupHeading:a="Recent",id:l,classNameForItems:c}){const[d,w]=i.useState(!1);if(t.length===0)return;const p=u=>{e(u),w(!1)};return n.jsxs(zt,{open:d,onOpenChange:w,children:[n.jsx(Gt,{asChild:!0,children:n.jsx($,{variant:"ghost",size:"icon",className:"tw-absolute tw-right-0 tw-top-0 tw-h-full tw-px-3 tw-py-2","aria-label":s,children:n.jsx(k.Clock,{className:"tw-h-4 tw-w-4"})})}),n.jsx(Lt,{id:l,className:"tw-w-[300px] tw-p-0",align:"start",children:n.jsx(At,{children:n.jsx(Pt,{children:n.jsx(Ot,{heading:a,children:t.map(u=>n.jsxs(Ct,{onSelect:()=>p(u),className:m("tw-flex tw-items-center",c),children:[n.jsx(k.Clock,{className:"tw-mr-2 tw-h-4 tw-w-4 tw-opacity-50"}),n.jsx("span",{children:r(u)})]},o(u)))})})})})]})}function La(t,e,r=(s,a)=>s===a,o=15){return s=>{const a=t.filter(c=>!r(c,s)),l=[s,...a.slice(0,o-1)];e(l)}}const hn={BOOK_ONLY:/^([^:\s]+(?:\s+[^:\s]+)*)$/i,BOOK_CHAPTER:/^([^:\s]+(?:\s+[^:\s]+)*)\s+(\d+)$/i,BOOK_CHAPTER_VERSE:/^([^:\s]+(?:\s+[^:\s]+)*)\s+(\d+):(\d*)$/i},$a=[hn.BOOK_ONLY,hn.BOOK_CHAPTER,hn.BOOK_CHAPTER_VERSE];function br(t){const e=/^[a-zA-Z]$/.test(t),r=/^[0-9]$/.test(t);return{isLetter:e,isDigit:r}}function Tt(t){return N.getChaptersForBook(Q.Canon.bookIdToNumber(t))}function Va(t,e,r){if(!t.trim()||e.length===0)return;const o=$a.reduce((s,a)=>{if(s)return s;const l=a.exec(t.trim());if(l){const[c,d=void 0,w=void 0]=l.slice(1);let p;const u=e.filter(h=>$n(h,c,r));if(u.length===1&&([p]=u),!p&&d){if(Q.Canon.isBookIdValid(c)){const h=c.toUpperCase();e.includes(h)&&(p=h)}if(!p&&r){const h=Array.from(r.entries()).find(([,f])=>f.localizedId.toLowerCase()===c.toLowerCase());h&&e.includes(h[0])&&([p]=h)}}if(!p&&d){const f=(g=>Object.keys(ie).find(v=>ie[v].toLowerCase()===g.toLowerCase()))(c);if(f&&e.includes(f)&&(p=f),!p&&r){const g=Array.from(r.entries()).find(([,v])=>v.localizedName.toLowerCase()===c.toLowerCase());g&&e.includes(g[0])&&([p]=g)}}if(p){let h=d?parseInt(d,10):void 0;h&&h>Tt(p)&&(h=Math.max(Tt(p),1));const f=w?parseInt(w,10):void 0;return{book:p,chapterNum:h,verseNum:f}}}},void 0);if(o)return o}function Fa(t,e,r,o){const s=i.useCallback(()=>{if(t.chapterNum>1)o({book:t.book,chapterNum:t.chapterNum-1,verseNum:1});else{const d=e.indexOf(t.book);if(d>0){const w=e[d-1],p=Math.max(Tt(w),1);o({book:w,chapterNum:p,verseNum:1})}}},[t,e,o]),a=i.useCallback(()=>{const d=Tt(t.book);if(t.chapterNum{o({book:t.book,chapterNum:t.chapterNum,verseNum:t.verseNum>1?t.verseNum-1:0})},[t,o]),c=i.useCallback(()=>{o({book:t.book,chapterNum:t.chapterNum,verseNum:t.verseNum+1})},[t,o]);return i.useMemo(()=>[{onClick:s,disabled:e.length===0||t.chapterNum===1&&e.indexOf(t.book)===0,title:"Previous chapter",icon:r==="ltr"?k.ChevronsLeft:k.ChevronsRight},{onClick:l,disabled:e.length===0||t.verseNum===0,title:"Previous verse",icon:r==="ltr"?k.ChevronLeft:k.ChevronRight},{onClick:c,disabled:e.length===0,title:"Next verse",icon:r==="ltr"?k.ChevronRight:k.ChevronLeft},{onClick:a,disabled:e.length===0||(t.chapterNum===Tt(t.book)||Tt(t.book)<=0)&&e.indexOf(t.book)===e.length-1,title:"Next chapter",icon:r==="ltr"?k.ChevronsRight:k.ChevronsLeft}],[t,e,r,s,l,c,a])}function vr({bookId:t,scrRef:e,onChapterSelect:r,setChapterRef:o,isChapterDimmed:s,className:a}){if(t)return n.jsx(Ot,{children:n.jsx("div",{className:m("tw-grid tw-grid-cols-6 tw-gap-1",a),children:Array.from({length:Tt(t)},(l,c)=>c+1).map(l=>n.jsx(Ct,{value:`${t} ${ie[t]||""} ${l}`,onSelect:()=>r(l),ref:o(l),className:m("tw-h-8 tw-w-8 tw-cursor-pointer tw-justify-center tw-rounded-md tw-text-center tw-text-sm",{"tw-bg-primary tw-text-primary-foreground":t===e.book&&l===e.chapterNum},{"tw-bg-muted/50 tw-text-muted-foreground/50":(s==null?void 0:s(l))??!1}),children:l},l))})})}function za({scrRef:t,handleSubmit:e,className:r,getActiveBookIds:o,localizedBookNames:s,localizedStrings:a,recentSearches:l,onAddRecentSearch:c,id:d}){const w=st(),[p,u]=i.useState(!1),[h,f]=i.useState(""),[g,v]=i.useState(""),[b,y]=i.useState("books"),[j,C]=i.useState(void 0),[M,F]=i.useState(!1),V=i.useRef(void 0),_=i.useRef(void 0),T=i.useRef(void 0),S=i.useRef(void 0),R=i.useRef({}),P=i.useCallback(O=>{e(O),c&&c(O)},[e,c]),L=i.useMemo(()=>o?o():Zr,[o]),I=i.useMemo(()=>({[N.Section.OT]:L.filter(K=>Q.Canon.isBookOT(K)),[N.Section.NT]:L.filter(K=>Q.Canon.isBookNT(K)),[N.Section.DC]:L.filter(K=>Q.Canon.isBookDC(K)),[N.Section.Extra]:L.filter(K=>Q.Canon.extraBooks().includes(K))}),[L]),A=i.useMemo(()=>Object.values(I).flat(),[I]),z=i.useMemo(()=>{if(!g.trim())return I;const O={[N.Section.OT]:[],[N.Section.NT]:[],[N.Section.DC]:[],[N.Section.Extra]:[]};return[N.Section.OT,N.Section.NT,N.Section.DC,N.Section.Extra].forEach(H=>{O[H]=I[H].filter(Y=>$n(Y,g,s))}),O},[I,g,s]),E=i.useMemo(()=>Va(g,A,s),[g,A,s]),B=i.useCallback(()=>{E&&(P({book:E.book,chapterNum:E.chapterNum??1,verseNum:E.verseNum??1}),u(!1),v(""),f(""))},[P,E]),at=i.useCallback(O=>{if(Tt(O)<=1){P({book:O,chapterNum:1,verseNum:1}),u(!1),v("");return}C(O),y("chapters")},[P]),lt=i.useCallback(O=>{const K=b==="chapters"?j:E==null?void 0:E.book;K&&(P({book:K,chapterNum:O,verseNum:1}),u(!1),y("books"),C(void 0),v(""))},[P,b,j,E]),Vt=i.useCallback(O=>{P(O),u(!1),v("")},[P]),ct=Fa(t,A,w,e),rt=i.useCallback(()=>{y("books"),C(void 0),setTimeout(()=>{_.current&&_.current.focus()},0)},[]),G=i.useCallback(O=>{if(!O&&b==="chapters"){rt();return}u(O),O&&(y("books"),C(void 0),v(""))},[b,rt]),{otLong:tt,ntLong:wt,dcLong:ot,extraLong:xt}={otLong:a==null?void 0:a["%scripture_section_ot_long%"],ntLong:a==null?void 0:a["%scripture_section_nt_long%"],dcLong:a==null?void 0:a["%scripture_section_dc_long%"],extraLong:a==null?void 0:a["%scripture_section_extra_long%"]},Ee=i.useCallback(O=>Jr(O,tt,wt,ot,xt),[tt,wt,ot,xt]),qt=i.useCallback(O=>E?!!E.chapterNum&&!O.toString().includes(E.chapterNum.toString()):!1,[E]),te=i.useMemo(()=>N.formatScrRef(t,s?be(t.book,s):"English"),[t,s]),Ke=i.useCallback(O=>K=>{R.current[O]=K},[]),he=i.useCallback(O=>{(O.key==="Home"||O.key==="End")&&O.stopPropagation()},[]),ee=i.useCallback(O=>{if(O.ctrlKey)return;const{isLetter:K,isDigit:H}=br(O.key);if(b==="chapters"){if(O.key==="Backspace"){O.preventDefault(),O.stopPropagation(),rt();return}if(K||H){if(O.preventDefault(),O.stopPropagation(),y("books"),C(void 0),H&&j){const Y=ie[j];v(`${Y} ${O.key}`)}else v(O.key);setTimeout(()=>{_.current&&_.current.focus()},0);return}}if((b==="chapters"||b==="books"&&E)&&["ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].includes(O.key)){const Y=b==="chapters"?j:E==null?void 0:E.book;if(!Y)return;const it=(()=>{if(!h)return 1;const ne=h.match(/(\d+)$/);return ne?parseInt(ne[1],10):0})(),Ft=Tt(Y);if(!Ft)return;let mt=it;const qe=6;switch(O.key){case"ArrowLeft":it!==0&&(mt=it>1?it-1:Ft);break;case"ArrowRight":it!==0&&(mt=it{const ne=R.current[mt];ne&&ne.scrollIntoView({block:"nearest",behavior:"smooth"})},0))}},[b,E,rt,j,h,s]),fn=i.useCallback(O=>{if(O.shiftKey||O.key==="Tab"||O.key===" ")return;const{isLetter:K,isDigit:H}=br(O.key);(K||H)&&(O.preventDefault(),v(Y=>Y+O.key),_.current.focus(),F(!1))},[]);return i.useLayoutEffect(()=>{const O=setTimeout(()=>{if(p&&b==="books"&&T.current&&S.current){const K=T.current,H=S.current,Y=H.offsetTop,it=K.clientHeight,Ft=H.clientHeight,mt=Y-it/2+Ft/2;K.scrollTo({top:Math.max(0,mt),behavior:"smooth"}),f(Dn(t.book))}},0);return()=>{clearTimeout(O)}},[p,b,g,E,t.book]),i.useLayoutEffect(()=>{if(b==="chapters"&&j){const O=j===t.book;setTimeout(()=>{if(T.current)if(O){const K=R.current[t.chapterNum];K&&K.scrollIntoView({block:"center",behavior:"smooth"})}else T.current.scrollTo({top:0});V.current&&V.current.focus()},0)}},[b,j,E,t.book,t.chapterNum]),n.jsxs(zt,{open:p,onOpenChange:G,children:[n.jsx(Gt,{asChild:!0,children:n.jsx($,{"aria-label":"book-chapter-trigger",variant:"outline",role:"combobox","aria-expanded":p,className:m("tw-h-8 tw-w-full tw-min-w-16 tw-max-w-48 tw-overflow-hidden tw-px-1",r),children:n.jsx("span",{className:"tw-truncate",children:te})})}),n.jsx(Lt,{id:d,forceMount:!0,className:"tw-w-[280px] tw-p-0",align:"center",children:n.jsxs(At,{ref:V,onKeyDown:ee,loop:!0,value:h,onValueChange:f,shouldFilter:!1,children:[b==="books"?n.jsxs("div",{className:"tw-flex tw-items-end",children:[n.jsxs("div",{className:"tw-relative tw-flex-1",children:[n.jsx(pe,{ref:_,value:g,onValueChange:v,onKeyDown:he,onFocus:()=>F(!1),className:l&&l.length>0?"!tw-pr-10":""}),l&&l.length>0&&n.jsx(eo,{recentSearches:l,onSearchItemSelect:Vt,renderItem:O=>N.formatScrRef(O,"English"),getItemKey:O=>`${O.book}-${O.chapterNum}-${O.verseNum}`,ariaLabel:a==null?void 0:a["%history_recentSearches_ariaLabel%"],groupHeading:a==null?void 0:a["%history_recent%"]})]}),n.jsx("div",{className:"tw-flex tw-items-center tw-gap-1 tw-border-b tw-pe-2",children:ct.map(({onClick:O,disabled:K,title:H,icon:Y})=>n.jsx($,{variant:"ghost",size:"sm",onClick:()=>{F(!0),O()},disabled:K,className:"tw-h-10 tw-w-4 tw-p-0",title:H,onKeyDown:fn,children:n.jsx(Y,{})},H))})]}):n.jsxs("div",{className:"tw-flex tw-items-center tw-border-b tw-px-3 tw-py-2",children:[n.jsx($,{variant:"ghost",size:"sm",onClick:rt,className:"tw-mr-2 tw-h-6 tw-w-6 tw-p-0",tabIndex:-1,children:w==="ltr"?n.jsx(k.ArrowLeft,{className:"tw-h-4 tw-w-4"}):n.jsx(k.ArrowRight,{className:"tw-h-4 tw-w-4"})}),j&&n.jsx("span",{tabIndex:-1,className:"tw-text-sm tw-font-medium",children:be(j,s)})]}),!M&&n.jsxs(Pt,{ref:T,children:[b==="books"&&n.jsxs(n.Fragment,{children:[!E&&Object.entries(z).map(([O,K])=>{if(K.length!==0)return n.jsx(Ot,{heading:Ee(O),children:K.map(H=>n.jsx(Qr,{bookId:H,onSelect:Y=>at(Y),section:N.getSectionForBook(H),commandValue:`${H} ${ie[H]}`,ref:H===t.book?S:void 0,localizedBookNames:s},H))},O)}),E&&n.jsx(Ot,{children:n.jsx(Ct,{value:`${E.book} ${ie[E.book]} ${E.chapterNum||""}:${E.verseNum||""})}`,onSelect:B,className:"tw-font-semibold tw-text-primary",children:N.formatScrRef({book:E.book,chapterNum:E.chapterNum??1,verseNum:E.verseNum??1},s?Ln(E.book,s):void 0)},"top-match")}),E&&Tt(E.book)>1&&n.jsxs(n.Fragment,{children:[n.jsx("div",{className:"tw-mb-2 tw-px-3 tw-text-sm tw-font-medium tw-text-muted-foreground",children:be(E.book,s)}),n.jsx(vr,{bookId:E.book,scrRef:t,onChapterSelect:lt,setChapterRef:Ke,isChapterDimmed:qt,className:"tw-px-4 tw-pb-4"})]})]}),b==="chapters"&&j&&n.jsx(vr,{bookId:j,scrRef:t,onChapterSelect:lt,setChapterRef:Ke,className:"tw-p-4"})]})]})})]})}const Ga=Object.freeze(["%scripture_section_ot_long%","%scripture_section_nt_long%","%scripture_section_dc_long%","%scripture_section_extra_long%","%history_recent%","%history_recentSearches_ariaLabel%"]),Ba=Zt.cva("tw-text-sm tw-font-medium tw-leading-none peer-disabled:tw-cursor-not-allowed peer-disabled:tw-opacity-70"),et=i.forwardRef(({className:t,...e},r)=>n.jsx(Gr.Root,{ref:r,className:m("pr-twp",Ba(),t),...e}));et.displayName=Gr.Root.displayName;const ln=i.forwardRef(({className:t,...e},r)=>{const o=st();return n.jsx(Oe.Root,{className:m("pr-twp tw-grid tw-gap-2",t),...e,ref:r,dir:o})});ln.displayName=Oe.Root.displayName;const Ae=i.forwardRef(({className:t,...e},r)=>n.jsx(Oe.Item,{ref:r,className:m("pr-twp tw-aspect-square tw-h-4 tw-w-4 tw-rounded-full tw-border tw-border-primary tw-text-primary tw-ring-offset-background focus:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-cursor-not-allowed disabled:tw-opacity-50",t),...e,children:n.jsx(Oe.Indicator,{className:"tw-flex tw-items-center tw-justify-center",children:n.jsx(k.Circle,{className:"tw-h-2.5 tw-w-2.5 tw-fill-current tw-text-current"})})}));Ae.displayName=Oe.Item.displayName;function Ka(t){return typeof t=="string"?t:typeof t=="number"?t.toString():t.label}function Je({id:t,options:e=[],className:r,buttonClassName:o,popoverContentClassName:s,value:a,onChange:l=()=>{},getOptionLabel:c=Ka,getButtonLabel:d,icon:w=void 0,buttonPlaceholder:p="",textPlaceholder:u="",commandEmptyMessage:h="No option found",buttonVariant:f="outline",alignDropDown:g="start",isDisabled:v=!1,ariaLabel:b,...y}){const[j,C]=i.useState(!1),M=d??c,F=_=>_.length>0&&typeof _[0]=="object"&&"options"in _[0],V=(_,T)=>{const S=c(_),R=typeof _=="object"&&"secondaryLabel"in _?_.secondaryLabel:void 0,P=`${T??""}${S}${R??""}`;return n.jsxs(Ct,{value:S,onSelect:()=>{l(_),C(!1)},className:"tw-flex tw-items-center",children:[n.jsx(k.Check,{className:m("tw-me-2 tw-h-4 tw-w-4 tw-shrink-0",{"tw-opacity-0":!a||c(a)!==S})}),n.jsxs("span",{className:"tw-flex-1 tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap",children:[S,R&&n.jsxs("span",{className:"tw-text-muted-foreground",children:[" ยท ",R]})]})]},P)};return n.jsxs(zt,{open:j,onOpenChange:C,...y,children:[n.jsx(Gt,{asChild:!0,children:n.jsxs($,{variant:f,role:"combobox","aria-expanded":j,"aria-label":b,id:t,className:m("tw-flex tw-w-[200px] tw-items-center tw-justify-between tw-overflow-hidden",o??r),disabled:v,children:[n.jsxs("div",{className:"tw-flex tw-min-w-0 tw-flex-1 tw-items-center tw-overflow-hidden",children:[w&&n.jsx("div",{className:"tw-shrink-0 tw-pe-2",children:w}),n.jsx("span",{className:m("tw-min-w-0 tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-text-start"),children:a?M(a):p})]}),n.jsx(k.ChevronDown,{className:"tw-ms-2 tw-h-4 tw-w-4 tw-shrink-0 tw-opacity-50"})]})}),n.jsx(Lt,{align:g,className:m("tw-w-[200px] tw-p-0",s),children:n.jsxs(At,{children:[n.jsx(pe,{placeholder:u,className:"tw-text-inherit"}),n.jsx(Ce,{children:h}),n.jsx(Pt,{children:F(e)?e.map(_=>n.jsx(Ot,{heading:_.groupHeading,children:_.options.map(T=>V(T,_.groupHeading))},_.groupHeading)):e.map(_=>V(_))})]})})]})}function no({startChapter:t,endChapter:e,handleSelectStartChapter:r,handleSelectEndChapter:o,isDisabled:s=!1,chapterCount:a}){const l=i.useMemo(()=>Array.from({length:a},(w,p)=>p+1),[a]),c=w=>{r(w),w>e&&o(w)},d=w=>{o(w),ww.toString(),value:t},"start chapter"),n.jsx(et,{htmlFor:"end-chapters-combobox",children:"to"}),n.jsx(Je,{isDisabled:s,onChange:d,buttonClassName:"tw-ms-2 tw-w-20",options:l,getOptionLabel:w=>w.toString(),value:e},"end chapter")]})}var ro=(t=>(t.CURRENT_BOOK="current book",t.CHOOSE_BOOKS="choose books",t))(ro||{});const qa=Object.freeze(["%webView_bookSelector_currentBook%","%webView_bookSelector_choose%","%webView_bookSelector_chooseBooks%"]),gn=(t,e)=>t[e]??e;function Ua({handleBookSelectionModeChange:t,currentBookName:e,onSelectBooks:r,selectedBookIds:o,chapterCount:s,endChapter:a,handleSelectEndChapter:l,startChapter:c,handleSelectStartChapter:d,localizedStrings:w}){const p=gn(w,"%webView_bookSelector_currentBook%"),u=gn(w,"%webView_bookSelector_choose%"),h=gn(w,"%webView_bookSelector_chooseBooks%"),[f,g]=i.useState("current book"),v=b=>{g(b),t(b)};return n.jsx(ln,{className:"pr-twp tw-flex",value:f,onValueChange:b=>v(b),children:n.jsxs("div",{className:"tw-flex tw-w-full tw-flex-col tw-gap-4",children:[n.jsxs("div",{className:"tw-grid tw-grid-cols-[25%,25%,50%]",children:[n.jsxs("div",{className:"tw-flex tw-items-center",children:[n.jsx(Ae,{value:"current book"}),n.jsx(et,{className:"tw-ms-1",children:p})]}),n.jsx(et,{className:"tw-flex tw-items-center",children:e}),n.jsx("div",{className:"tw-flex tw-items-center tw-justify-end",children:n.jsx(no,{isDisabled:f==="choose books",handleSelectStartChapter:d,handleSelectEndChapter:l,chapterCount:s,startChapter:c,endChapter:a})})]}),n.jsxs("div",{className:"tw-grid tw-grid-cols-[25%,50%,25%]",children:[n.jsxs("div",{className:"tw-flex tw-items-center",children:[n.jsx(Ae,{value:"choose books"}),n.jsx(et,{className:"tw-ms-1",children:h})]}),n.jsx(et,{className:"tw-flex tw-items-center",children:o.map(b=>Q.Canon.bookIdToEnglishName(b)).join(", ")}),n.jsx($,{disabled:f==="current book",onClick:()=>r(),children:u})]})]})})}const oo=i.createContext(null);function Ha(t,e){return{getTheme:function(){return e??null}}}function $t(){const t=i.useContext(oo);return t==null&&function(e,...r){const o=new URL("https://lexical.dev/docs/error"),s=new URLSearchParams;s.append("code",e);for(const a of r)s.append("v",a);throw o.search=s.toString(),Error(`Minified Lexical error #${e}; visit ${o.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}(8),t}const so=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0,Ya=so?i.useLayoutEffect:i.useEffect,Ue={tag:x.HISTORY_MERGE_TAG};function Xa({initialConfig:t,children:e}){const r=i.useMemo(()=>{const{theme:o,namespace:s,nodes:a,onError:l,editorState:c,html:d}=t,w=Ha(null,o),p=x.createEditor({editable:t.editable,html:d,namespace:s,nodes:a,onError:u=>l(u,p),theme:o});return function(u,h){if(h!==null){if(h===void 0)u.update(()=>{const f=x.$getRoot();if(f.isEmpty()){const g=x.$createParagraphNode();f.append(g);const v=so?document.activeElement:null;(x.$getSelection()!==null||v!==null&&v===u.getRootElement())&&g.select()}},Ue);else if(h!==null)switch(typeof h){case"string":{const f=u.parseEditorState(h);u.setEditorState(f,Ue);break}case"object":u.setEditorState(h,Ue);break;case"function":u.update(()=>{x.$getRoot().isEmpty()&&h(u)},Ue)}}}(p,c),[p,w]},[]);return Ya(()=>{const o=t.editable,[s]=r;s.setEditable(o===void 0||o)},[]),n.jsx(oo.Provider,{value:r,children:e})}const Wa=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?i.useLayoutEffect:i.useEffect;function Ja({ignoreHistoryMergeTagChange:t=!0,ignoreSelectionChange:e=!1,onChange:r}){const[o]=$t();return Wa(()=>{if(r)return o.registerUpdateListener(({editorState:s,dirtyElements:a,dirtyLeaves:l,prevEditorState:c,tags:d})=>{e&&a.size===0&&l.size===0||t&&d.has(x.HISTORY_MERGE_TAG)||c.isEmpty()||r(s,o,d)})},[o,t,e,r]),null}const Vn={ltr:"tw-text-left",rtl:"tw-text-right",heading:{h1:"tw-scroll-m-20 tw-text-4xl tw-font-extrabold tw-tracking-tight lg:tw-text-5xl",h2:"tw-scroll-m-20 tw-border-b tw-pb-2 tw-text-3xl tw-font-semibold tw-tracking-tight first:tw-mt-0",h3:"tw-scroll-m-20 tw-text-2xl tw-font-semibold tw-tracking-tight",h4:"tw-scroll-m-20 tw-text-xl tw-font-semibold tw-tracking-tight",h5:"tw-scroll-m-20 tw-text-lg tw-font-semibold tw-tracking-tight",h6:"tw-scroll-m-20 tw-text-base tw-font-semibold tw-tracking-tight"},paragraph:"tw-outline-none",quote:"tw-mt-6 tw-border-l-2 tw-pl-6 tw-italic",link:"tw-text-blue-600 hover:tw-underline hover:tw-cursor-pointer",list:{checklist:"tw-relative",listitem:"tw-mx-8",listitemChecked:'tw-relative tw-mx-2 tw-px-6 tw-list-none tw-outline-none tw-line-through before:tw-content-[""] before:tw-w-4 before:tw-h-4 before:tw-top-0.5 before:tw-left-0 before:tw-cursor-pointer before:tw-block before:tw-bg-cover before:tw-absolute before:tw-border before:tw-border-primary before:tw-rounded before:tw-bg-primary before:tw-bg-no-repeat after:tw-content-[""] after:tw-cursor-pointer after:tw-border-white after:tw-border-solid after:tw-absolute after:tw-block after:tw-top-[6px] after:tw-w-[3px] after:tw-left-[7px] after:tw-right-[7px] after:tw-h-[6px] after:tw-rotate-45 after:tw-border-r-2 after:tw-border-b-2 after:tw-border-l-0 after:tw-border-t-0',listitemUnchecked:'tw-relative tw-mx-2 tw-px-6 tw-list-none tw-outline-none before:tw-content-[""] before:tw-w-4 before:tw-h-4 before:tw-top-0.5 before:tw-left-0 before:tw-cursor-pointer before:tw-block before:tw-bg-cover before:tw-absolute before:tw-border before:tw-border-primary before:tw-rounded',nested:{listitem:"tw-list-none before:tw-hidden after:tw-hidden"},ol:"tw-m-0 tw-p-0 tw-list-decimal [&>li]:tw-mt-2",olDepth:["tw-list-outside !tw-list-decimal","tw-list-outside !tw-list-[upper-roman]","tw-list-outside !tw-list-[lower-roman]","tw-list-outside !tw-list-[upper-alpha]","tw-list-outside !tw-list-[lower-alpha]"],ul:"tw-m-0 tw-p-0 tw-list-outside [&>li]:tw-mt-2",ulDepth:["tw-list-outside !tw-list-disc","tw-list-outside !tw-list-disc","tw-list-outside !tw-list-disc","tw-list-outside !tw-list-disc","tw-list-outside !tw-list-disc"]},hashtag:"tw-text-blue-600 tw-bg-blue-100 tw-rounded-md tw-px-1",text:{bold:"tw-font-bold",code:"tw-bg-gray-100 tw-p-1 tw-rounded-md",italic:"tw-italic",strikethrough:"tw-line-through",subscript:"tw-sub",superscript:"tw-sup",underline:"tw-underline",underlineStrikethrough:"tw-underline tw-line-through"},image:"tw-relative tw-inline-block tw-user-select-none tw-cursor-default editor-image",inlineImage:"tw-relative tw-inline-block tw-user-select-none tw-cursor-default inline-editor-image",keyword:"tw-text-purple-900 tw-font-bold",code:"EditorTheme__code",codeHighlight:{atrule:"EditorTheme__tokenAttr",attr:"EditorTheme__tokenAttr",boolean:"EditorTheme__tokenProperty",builtin:"EditorTheme__tokenSelector",cdata:"EditorTheme__tokenComment",char:"EditorTheme__tokenSelector",class:"EditorTheme__tokenFunction","class-name":"EditorTheme__tokenFunction",comment:"EditorTheme__tokenComment",constant:"EditorTheme__tokenProperty",deleted:"EditorTheme__tokenProperty",doctype:"EditorTheme__tokenComment",entity:"EditorTheme__tokenOperator",function:"EditorTheme__tokenFunction",important:"EditorTheme__tokenVariable",inserted:"EditorTheme__tokenSelector",keyword:"EditorTheme__tokenAttr",namespace:"EditorTheme__tokenVariable",number:"EditorTheme__tokenProperty",operator:"EditorTheme__tokenOperator",prolog:"EditorTheme__tokenComment",property:"EditorTheme__tokenProperty",punctuation:"EditorTheme__tokenPunctuation",regex:"EditorTheme__tokenVariable",selector:"EditorTheme__tokenSelector",string:"EditorTheme__tokenSelector",symbol:"EditorTheme__tokenProperty",tag:"EditorTheme__tokenProperty",url:"EditorTheme__tokenOperator",variable:"EditorTheme__tokenVariable"},characterLimit:"!tw-bg-destructive/50",table:"EditorTheme__table tw-w-fit tw-overflow-scroll tw-border-collapse",tableCell:"EditorTheme__tableCell tw-w-24 tw-relative tw-border tw-px-4 tw-py-2 tw-text-left [&[align=center]]:tw-text-center [&[align=right]]:tw-text-right",tableCellActionButton:"EditorTheme__tableCellActionButton tw-bg-background tw-block tw-border-0 tw-rounded-2xl tw-w-5 tw-h-5 tw-text-foreground tw-cursor-pointer",tableCellActionButtonContainer:"EditorTheme__tableCellActionButtonContainer tw-block tw-right-1 tw-top-1.5 tw-absolute tw-z-10 tw-w-5 tw-h-5",tableCellEditing:"EditorTheme__tableCellEditing tw-rounded-sm tw-shadow-sm",tableCellHeader:"EditorTheme__tableCellHeader tw-bg-muted tw-border tw-px-4 tw-py-2 tw-text-left tw-font-bold [&[align=center]]:tw-text-center [&[align=right]]:tw-text-right",tableCellPrimarySelected:"EditorTheme__tableCellPrimarySelected tw-border tw-border-primary tw-border-solid tw-block tw-h-[calc(100%-2px)] tw-w-[calc(100%-2px)] tw-absolute tw--left-[1px] tw--top-[1px] tw-z-10 ",tableCellResizer:"EditorTheme__tableCellResizer tw-absolute tw--right-1 tw-h-full tw-w-2 tw-cursor-ew-resize tw-z-10 tw-top-0",tableCellSelected:"EditorTheme__tableCellSelected tw-bg-muted",tableCellSortedIndicator:"EditorTheme__tableCellSortedIndicator tw-block tw-opacity-50 tw-absolute tw-bottom-0 tw-left-0 tw-w-full tw-h-1 tw-bg-muted",tableResizeRuler:"EditorTheme__tableCellResizeRuler tw-block tw-absolute tw-w-[1px] tw-h-full tw-bg-primary tw-top-0",tableRowStriping:"EditorTheme__tableRowStriping tw-m-0 tw-border-t tw-p-0 even:tw-bg-muted",tableSelected:"EditorTheme__tableSelected tw-ring-2 tw-ring-primary tw-ring-offset-2",tableSelection:"EditorTheme__tableSelection tw-bg-transparent",layoutItem:"tw-border tw-border-dashed tw-px-4 tw-py-2",layoutContainer:"tw-grid tw-gap-2.5 tw-my-2.5 tw-mx-0",autocomplete:"tw-text-muted-foreground",blockCursor:"",embedBlock:{base:"tw-user-select-none",focus:"tw-ring-2 tw-ring-primary tw-ring-offset-2"},hr:'tw-p-0.5 tw-border-none tw-my-1 tw-mx-0 tw-cursor-pointer after:tw-content-[""] after:tw-block after:tw-h-0.5 after:tw-bg-muted selected:tw-ring-2 selected:tw-ring-primary selected:tw-ring-offset-2 selected:tw-user-select-none',indent:"[--lexical-indent-base-value:40px]",mark:"",markOverlap:""},vt=Ve.Provider,Nt=Ve.Root,Dt=Ve.Trigger,yt=i.forwardRef(({className:t,sideOffset:e=4,...r},o)=>n.jsx(Ve.Content,{ref:o,sideOffset:e,className:m("pr-twp tw-z-50 tw-overflow-hidden tw-rounded-md tw-border tw-bg-popover tw-px-3 tw-py-1.5 tw-text-sm tw-text-popover-foreground tw-shadow-md tw-animate-in tw-fade-in-0 tw-zoom-in-95 data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=closed]:tw-zoom-out-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2",t),...r}));yt.displayName=Ve.Content.displayName;const Fn=[En.HeadingNode,x.ParagraphNode,x.TextNode,En.QuoteNode],Za=i.createContext(null),xn={didCatch:!1,error:null};class Qa extends i.Component{constructor(e){super(e),this.resetErrorBoundary=this.resetErrorBoundary.bind(this),this.state=xn}static getDerivedStateFromError(e){return{didCatch:!0,error:e}}resetErrorBoundary(){const{error:e}=this.state;if(e!==null){for(var r,o,s=arguments.length,a=new Array(s),l=0;l0&&arguments[0]!==void 0?arguments[0]:[],e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:[];return t.length!==e.length||t.some((r,o)=>!Object.is(r,e[o]))}function ei({children:t,onError:e}){return n.jsx(Qa,{fallback:n.jsx("div",{style:{border:"1px solid #f00",color:"#f00",padding:"8px"},children:"An error was thrown."}),onError:e,children:t})}const ni=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?i.useLayoutEffect:i.useEffect;function ri(t){return{initialValueFn:()=>t.isEditable(),subscribe:e=>t.registerEditableListener(e)}}function oi(){return function(t){const[e]=$t(),r=i.useMemo(()=>t(e),[e,t]),[o,s]=i.useState(()=>r.initialValueFn()),a=i.useRef(o);return ni(()=>{const{initialValueFn:l,subscribe:c}=r,d=l();return a.current!==d&&(a.current=d,s(d)),c(w=>{a.current=w,s(w)})},[r,t]),o}(ri)}function si(t,e,r="self"){const o=t.getStartEndPoints();if(e.isSelected(t)&&!x.$isTokenOrSegmented(e)&&o!==null){const[s,a]=o,l=t.isBackward(),c=s.getNode(),d=a.getNode(),w=e.is(c),p=e.is(d);if(w||p){const[u,h]=x.$getCharacterOffsets(t),f=c.is(d),g=e.is(l?d:c),v=e.is(l?c:d);let b,y=0;f?(y=u>h?h:u,b=u>h?u:h):g?(y=l?h:u,b=void 0):v&&(y=0,b=l?u:h);const j=e.__text.slice(y,b);j!==e.__text&&(r==="clone"&&(e=x.$cloneWithPropertiesEphemeral(e)),e.__text=j)}}return e}function ai(t,...e){const r=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",t);for(const s of e)o.append("v",s);throw r.search=o.toString(),Error(`Minified Lexical error #${t}; visit ${r.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}const ao=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0,ii=ao&&"documentMode"in document?document.documentMode:null;!(!ao||!("InputEvent"in window)||ii)&&"getTargetRanges"in new window.InputEvent("input");function yr(t){const e=x.$findMatchingParent(t,r=>x.$isElementNode(r)&&!r.isInline());return x.$isElementNode(e)||ai(4,t.__key),e}const li=Symbol.for("preact-signals");function cn(){if(Ht>1)return void Ht--;let t,e=!1;for(;De!==void 0;){let r=De;for(De=void 0,In++;r!==void 0;){const o=r.o;if(r.o=void 0,r.f&=-3,!(8&r.f)&&io(r))try{r.c()}catch(s){e||(t=s,e=!0)}r=o}}if(In=0,Ht--,e)throw t}function ci(t){if(Ht>0)return t();Ht++;try{return t()}finally{cn()}}let q,De;function jr(t){const e=q;q=void 0;try{return t()}finally{q=e}}let Ht=0,In=0,Xe=0;function Nr(t){if(q===void 0)return;let e=t.n;return e===void 0||e.t!==q?(e={i:0,S:t,p:q.s,n:void 0,t:q,e:void 0,x:void 0,r:e},q.s!==void 0&&(q.s.n=e),q.s=e,t.n=e,32&q.f&&t.S(e),e):e.i===-1?(e.i=0,e.n!==void 0&&(e.n.p=e.p,e.p!==void 0&&(e.p.n=e.n),e.p=q.s,e.n=void 0,q.s.n=e,q.s=e),e):void 0}function pt(t,e){this.v=t,this.i=0,this.n=void 0,this.t=void 0,this.W=e==null?void 0:e.watched,this.Z=e==null?void 0:e.unwatched,this.name=e==null?void 0:e.name}function Pe(t,e){return new pt(t,e)}function io(t){for(let e=t.s;e!==void 0;e=e.n)if(e.S.i!==e.i||!e.S.h()||e.S.i!==e.i)return!0;return!1}function kr(t){for(let e=t.s;e!==void 0;e=e.n){const r=e.S.n;if(r!==void 0&&(e.r=r),e.S.n=e,e.i=-1,e.n===void 0){t.s=e;break}}}function lo(t){let e,r=t.s;for(;r!==void 0;){const o=r.p;r.i===-1?(r.S.U(r),o!==void 0&&(o.n=r.n),r.n!==void 0&&(r.n.p=o)):e=r,r.S.n=r.r,r.r!==void 0&&(r.r=void 0),r=o}t.s=e}function se(t,e){pt.call(this,void 0),this.x=t,this.s=void 0,this.g=Xe-1,this.f=4,this.W=e==null?void 0:e.watched,this.Z=e==null?void 0:e.unwatched,this.name=e==null?void 0:e.name}function wi(t,e){return new se(t,e)}function co(t){const e=t.u;if(t.u=void 0,typeof e=="function"){Ht++;const r=q;q=void 0;try{e()}catch(o){throw t.f&=-2,t.f|=8,zn(t),o}finally{q=r,cn()}}}function zn(t){for(let e=t.s;e!==void 0;e=e.n)e.S.U(e);t.x=void 0,t.s=void 0,co(t)}function di(t){if(q!==this)throw new Error("Out-of-order effect");lo(this),q=t,this.f&=-2,8&this.f&&zn(this),cn()}function xe(t,e){this.x=t,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32,this.name=e==null?void 0:e.name}function Xt(t,e){const r=new xe(t,e);try{r.c()}catch(s){throw r.d(),s}const o=r.d.bind(r);return o[Symbol.dispose]=o,o}function wn(t,e={}){const r={};for(const o in t){const s=e[o],a=Pe(s===void 0?t[o]:s);r[o]=a}return r}pt.prototype.brand=li,pt.prototype.h=function(){return!0},pt.prototype.S=function(t){const e=this.t;e!==t&&t.e===void 0&&(t.x=e,this.t=t,e!==void 0?e.e=t:jr(()=>{var r;(r=this.W)==null||r.call(this)}))},pt.prototype.U=function(t){if(this.t!==void 0){const e=t.e,r=t.x;e!==void 0&&(e.x=r,t.e=void 0),r!==void 0&&(r.e=e,t.x=void 0),t===this.t&&(this.t=r,r===void 0&&jr(()=>{var o;(o=this.Z)==null||o.call(this)}))}},pt.prototype.subscribe=function(t){return Xt(()=>{const e=this.value,r=q;q=void 0;try{t(e)}finally{q=r}},{name:"sub"})},pt.prototype.valueOf=function(){return this.value},pt.prototype.toString=function(){return this.value+""},pt.prototype.toJSON=function(){return this.value},pt.prototype.peek=function(){const t=q;q=void 0;try{return this.value}finally{q=t}},Object.defineProperty(pt.prototype,"value",{get(){const t=Nr(this);return t!==void 0&&(t.i=this.i),this.v},set(t){if(t!==this.v){if(In>100)throw new Error("Cycle detected");this.v=t,this.i++,Xe++,Ht++;try{for(let e=this.t;e!==void 0;e=e.x)e.t.N()}finally{cn()}}}}),se.prototype=new pt,se.prototype.h=function(){if(this.f&=-3,1&this.f)return!1;if((36&this.f)==32||(this.f&=-5,this.g===Xe))return!0;if(this.g=Xe,this.f|=1,this.i>0&&!io(this))return this.f&=-2,!0;const t=q;try{kr(this),q=this;const e=this.x();(16&this.f||this.v!==e||this.i===0)&&(this.v=e,this.f&=-17,this.i++)}catch(e){this.v=e,this.f|=16,this.i++}return q=t,lo(this),this.f&=-2,!0},se.prototype.S=function(t){if(this.t===void 0){this.f|=36;for(let e=this.s;e!==void 0;e=e.n)e.S.S(e)}pt.prototype.S.call(this,t)},se.prototype.U=function(t){if(this.t!==void 0&&(pt.prototype.U.call(this,t),this.t===void 0)){this.f&=-33;for(let e=this.s;e!==void 0;e=e.n)e.S.U(e)}},se.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(let t=this.t;t!==void 0;t=t.x)t.t.N()}},Object.defineProperty(se.prototype,"value",{get(){if(1&this.f)throw new Error("Cycle detected");const t=Nr(this);if(this.h(),t!==void 0&&(t.i=this.i),16&this.f)throw this.v;return this.v}}),xe.prototype.c=function(){const t=this.S();try{if(8&this.f||this.x===void 0)return;const e=this.x();typeof e=="function"&&(this.u=e)}finally{t()}},xe.prototype.S=function(){if(1&this.f)throw new Error("Cycle detected");this.f|=1,this.f&=-9,co(this),kr(this),Ht++;const t=q;return q=this,di.bind(this,t)},xe.prototype.N=function(){2&this.f||(this.f|=2,this.o=De,De=this)},xe.prototype.d=function(){this.f|=8,1&this.f||zn(this)},xe.prototype.dispose=function(){this.d()};x.defineExtension({build:(t,e,r)=>wn(e),config:x.safeCast({defaultSelection:"rootEnd",disabled:!1}),name:"@lexical/extension/AutoFocus",register(t,e,r){const o=r.getOutput();return Xt(()=>o.disabled.value?void 0:t.registerRootListener(s=>{t.focus(()=>{const a=document.activeElement;s===null||a!==null&&s.contains(a)||s.focus({preventScroll:!0})},{defaultSelection:o.defaultSelection.peek()})}))}});function wo(){const t=x.$getRoot(),e=x.$getSelection(),r=x.$createParagraphNode();t.clear(),t.append(r),e!==null&&r.select(),x.$isRangeSelection(e)&&(e.format=0)}function po(t,e=wo){return t.registerCommand(x.CLEAR_EDITOR_COMMAND,r=>(t.update(e),!0),x.COMMAND_PRIORITY_EDITOR)}x.defineExtension({build:(t,e,r)=>wn(e),config:x.safeCast({$onClear:wo}),name:"@lexical/extension/ClearEditor",register(t,e,r){const{$onClear:o}=r.getOutput();return Xt(()=>po(t,o.value))}});function pi(t){return(typeof t.nodes=="function"?t.nodes():t.nodes)||[]}function uo(t,e){let r;return Pe(t(),{unwatched(){r&&(r(),r=void 0)},watched(){this.value=t(),r=e(this)}})}const On=x.defineExtension({build:t=>uo(()=>t.getEditorState(),e=>t.registerUpdateListener(r=>{e.value=r.editorState})),name:"@lexical/extension/EditorState"});function U(t,...e){const r=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",t);for(const s of e)o.append("v",s);throw r.search=o.toString(),Error(`Minified Lexical error #${t}; visit ${r.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}function mo(t,e){if(t&&e&&!Array.isArray(e)&&typeof t=="object"&&typeof e=="object"){const r=t,o=e;for(const s in o)r[s]=mo(r[s],o[s]);return t}return e}const Gn=0,An=1,fo=2,bn=3,He=4,ge=5,vn=6,Re=7;function yn(t){return t.id===Gn}function ho(t){return t.id===fo}function ui(t){return function(e){return e.id===An}(t)||U(305,String(t.id),String(An)),Object.assign(t,{id:fo})}const mi=new Set;class fi{constructor(e,r){dt(this,"builder");dt(this,"configs");dt(this,"_dependency");dt(this,"_peerNameSet");dt(this,"extension");dt(this,"state");dt(this,"_signal");this.builder=e,this.extension=r,this.configs=new Set,this.state={id:Gn}}mergeConfigs(){let e=this.extension.config||{};const r=this.extension.mergeConfig?this.extension.mergeConfig.bind(this.extension):x.shallowMergeConfig;for(const o of this.configs)e=r(e,o);return e}init(e){const r=this.state;ho(r)||U(306,String(r.id));const o={getDependency:this.getInitDependency.bind(this),getDirectDependentNames:this.getDirectDependentNames.bind(this),getPeer:this.getInitPeer.bind(this),getPeerNameSet:this.getPeerNameSet.bind(this)},s={...o,getDependency:this.getDependency.bind(this),getInitResult:this.getInitResult.bind(this),getPeer:this.getPeer.bind(this)},a=function(c,d,w){return Object.assign(c,{config:d,id:bn,registerState:w})}(r,this.mergeConfigs(),o);let l;this.state=a,this.extension.init&&(l=this.extension.init(e,a.config,o)),this.state=function(c,d,w){return Object.assign(c,{id:He,initResult:d,registerState:w})}(a,l,s)}build(e){const r=this.state;let o;r.id!==He&&U(307,String(r.id),String(ge)),this.extension.build&&(o=this.extension.build(e,r.config,r.registerState));const s={...r.registerState,getOutput:()=>o,getSignal:this.getSignal.bind(this)};this.state=function(a,l,c){return Object.assign(a,{id:ge,output:l,registerState:c})}(r,o,s)}register(e,r){this._signal=r;const o=this.state;o.id!==ge&&U(308,String(o.id),String(ge));const s=this.extension.register&&this.extension.register(e,o.config,o.registerState);return this.state=function(a){return Object.assign(a,{id:vn})}(o),()=>{const a=this.state;a.id!==Re&&U(309,String(o.id),String(Re)),this.state=function(l){return Object.assign(l,{id:ge})}(a),s&&s()}}afterRegistration(e){const r=this.state;let o;return r.id!==vn&&U(310,String(r.id),String(vn)),this.extension.afterRegistration&&(o=this.extension.afterRegistration(e,r.config,r.registerState)),this.state=function(s){return Object.assign(s,{id:Re})}(r),o}getSignal(){return this._signal===void 0&&U(311),this._signal}getInitResult(){this.extension.init===void 0&&U(312,this.extension.name);const e=this.state;return function(r){return r.id>=He}(e)||U(313,String(e.id),String(He)),e.initResult}getInitPeer(e){const r=this.builder.extensionNameMap.get(e);return r?r.getExtensionInitDependency():void 0}getExtensionInitDependency(){const e=this.state;return function(r){return r.id>=bn}(e)||U(314,String(e.id),String(bn)),{config:e.config}}getPeer(e){const r=this.builder.extensionNameMap.get(e);return r?r.getExtensionDependency():void 0}getInitDependency(e){const r=this.builder.getExtensionRep(e);return r===void 0&&U(315,this.extension.name,e.name),r.getExtensionInitDependency()}getDependency(e){const r=this.builder.getExtensionRep(e);return r===void 0&&U(315,this.extension.name,e.name),r.getExtensionDependency()}getState(){const e=this.state;return function(r){return r.id>=Re}(e)||U(316,String(e.id),String(Re)),e}getDirectDependentNames(){return this.builder.incomingEdges.get(this.extension.name)||mi}getPeerNameSet(){let e=this._peerNameSet;return e||(e=new Set((this.extension.peerDependencies||[]).map(([r])=>r)),this._peerNameSet=e),e}getExtensionDependency(){if(!this._dependency){const e=this.state;(function(r){return r.id>=ge})(e)||U(317,this.extension.name),this._dependency={config:e.config,init:e.initResult,output:e.output}}return this._dependency}}const _r={tag:x.HISTORY_MERGE_TAG};function hi(){const t=x.$getRoot();t.isEmpty()&&t.append(x.$createParagraphNode())}const gi=x.defineExtension({config:x.safeCast({setOptions:_r,updateOptions:_r}),init:({$initialEditorState:t=hi})=>({$initialEditorState:t,initialized:!1}),afterRegistration(t,{updateOptions:e,setOptions:r},o){const s=o.getInitResult();if(!s.initialized){s.initialized=!0;const{$initialEditorState:a}=s;if(x.$isEditorState(a))t.setEditorState(a,r);else if(typeof a=="function")t.update(()=>{a(t)},e);else if(a&&(typeof a=="string"||typeof a=="object")){const l=t.parseEditorState(a);t.setEditorState(l,r)}}return()=>{}},name:"@lexical/extension/InitialState",nodes:[x.RootNode,x.TextNode,x.LineBreakNode,x.TabNode,x.ParagraphNode]}),Cr=Symbol.for("@lexical/extension/LexicalBuilder");function Sr(){}function xi(t){throw t}function Ye(t){return Array.isArray(t)?t:[t]}const jn="0.40.0+prod.esm";class Ie{constructor(e){dt(this,"roots");dt(this,"extensionNameMap");dt(this,"outgoingConfigEdges");dt(this,"incomingEdges");dt(this,"conflicts");dt(this,"_sortedExtensionReps");dt(this,"PACKAGE_VERSION");this.outgoingConfigEdges=new Map,this.incomingEdges=new Map,this.extensionNameMap=new Map,this.conflicts=new Map,this.PACKAGE_VERSION=jn,this.roots=e;for(const r of e)this.addExtension(r)}static fromExtensions(e){const r=[Ye(gi)];for(const o of e)r.push(Ye(o));return new Ie(r)}static maybeFromEditor(e){const r=e[Cr];return r&&(r.PACKAGE_VERSION!==jn&&U(292,r.PACKAGE_VERSION,jn),r instanceof Ie||U(293)),r}static fromEditor(e){const r=Ie.maybeFromEditor(e);return r===void 0&&U(294),r}constructEditor(){const{$initialEditorState:e,onError:r,...o}=this.buildCreateEditorArgs(),s=Object.assign(x.createEditor({...o,...r?{onError:a=>{r(a,s)}}:{}}),{[Cr]:this});for(const a of this.sortedExtensionReps())a.build(s);return s}buildEditor(){let e=Sr;function r(){try{e()}finally{e=Sr}}const o=Object.assign(this.constructEditor(),{dispose:r,[Symbol.dispose]:r});return e=x.mergeRegister(this.registerEditor(o),()=>o.setRootElement(null)),o}hasExtensionByName(e){return this.extensionNameMap.has(e)}getExtensionRep(e){const r=this.extensionNameMap.get(e.name);if(r)return r.extension!==e&&U(295,e.name),r}addEdge(e,r,o){const s=this.outgoingConfigEdges.get(e);s?s.set(r,o):this.outgoingConfigEdges.set(e,new Map([[r,o]]));const a=this.incomingEdges.get(r);a?a.add(e):this.incomingEdges.set(r,new Set([e]))}addExtension(e){this._sortedExtensionReps!==void 0&&U(296);const r=Ye(e),[o]=r;typeof o.name!="string"&&U(297,typeof o.name);let s=this.extensionNameMap.get(o.name);if(s!==void 0&&s.extension!==o&&U(298,o.name),!s){s=new fi(this,o),this.extensionNameMap.set(o.name,s);const a=this.conflicts.get(o.name);typeof a=="string"&&U(299,o.name,a);for(const l of o.conflictsWith||[])this.extensionNameMap.has(l)&&U(299,o.name,l),this.conflicts.set(l,o.name);for(const l of o.dependencies||[]){const c=Ye(l);this.addEdge(o.name,c[0].name,c.slice(1)),this.addExtension(c)}for(const[l,c]of o.peerDependencies||[])this.addEdge(o.name,l,c?[c]:[])}}sortedExtensionReps(){if(this._sortedExtensionReps)return this._sortedExtensionReps;const e=[],r=(o,s)=>{let a=o.state;if(ho(a))return;const l=o.extension.name;var c;yn(a)||U(300,l,s||"[unknown]"),yn(c=a)||U(304,String(c.id),String(Gn)),a=Object.assign(c,{id:An}),o.state=a;const d=this.outgoingConfigEdges.get(l);if(d)for(const w of d.keys()){const p=this.extensionNameMap.get(w);p&&r(p,l)}a=ui(a),o.state=a,e.push(o)};for(const o of this.extensionNameMap.values())yn(o.state)&&r(o);for(const o of e)for(const[s,a]of this.outgoingConfigEdges.get(o.extension.name)||[])if(a.length>0){const l=this.extensionNameMap.get(s);if(l)for(const c of a)l.configs.add(c)}for(const[o,...s]of this.roots)if(s.length>0){const a=this.extensionNameMap.get(o.name);a===void 0&&U(301,o.name);for(const l of s)a.configs.add(l)}return this._sortedExtensionReps=e,this._sortedExtensionReps}registerEditor(e){const r=this.sortedExtensionReps(),o=new AbortController,s=[()=>o.abort()],a=o.signal;for(const l of r){const c=l.register(e,a);c&&s.push(c)}for(const l of r){const c=l.afterRegistration(e);c&&s.push(c)}return x.mergeRegister(...s)}buildCreateEditorArgs(){const e={},r=new Set,o=new Map,s=new Map,a={},l={},c=this.sortedExtensionReps();for(const p of c){const{extension:u}=p;if(u.onError!==void 0&&(e.onError=u.onError),u.disableEvents!==void 0&&(e.disableEvents=u.disableEvents),u.parentEditor!==void 0&&(e.parentEditor=u.parentEditor),u.editable!==void 0&&(e.editable=u.editable),u.namespace!==void 0&&(e.namespace=u.namespace),u.$initialEditorState!==void 0&&(e.$initialEditorState=u.$initialEditorState),u.nodes)for(const h of pi(u)){if(typeof h!="function"){const f=o.get(h.replace);f&&U(302,u.name,h.replace.name,f.extension.name),o.set(h.replace,p)}r.add(h)}if(u.html){if(u.html.export)for(const[h,f]of u.html.export.entries())s.set(h,f);u.html.import&&Object.assign(a,u.html.import)}u.theme&&mo(l,u.theme)}Object.keys(l).length>0&&(e.theme=l),r.size&&(e.nodes=[...r]);const d=Object.keys(a).length>0,w=s.size>0;(d||w)&&(e.html={},d&&(e.html.import=a),w&&(e.html.export=s));for(const p of c)p.init(e);return e.onError||(e.onError=xi),e}}const bi=new Set,Er=x.defineExtension({build(t,e,r){const o=r.getDependency(On).output,s=Pe({watchedNodeKeys:new Map}),a=uo(()=>{},()=>Xt(()=>{const l=a.peek(),{watchedNodeKeys:c}=s.value;let d,w=!1;o.value.read(()=>{if(x.$getSelection())for(const[p,u]of c.entries()){if(u.size===0){c.delete(p);continue}const h=x.$getNodeByKey(p),f=h&&h.isSelected()||!1;w=w||f!==(!!l&&l.has(p)),f&&(d=d||new Set,d.add(p))}}),!w&&d&&l&&d.size===l.size||(a.value=d)}));return{watchNodeKey:function(l){const c=wi(()=>(a.value||bi).has(l)),{watchedNodeKeys:d}=s.peek();let w=d.get(l);const p=w!==void 0;return w=w||new Set,w.add(c),p||(d.set(l,w),s.value={watchedNodeKeys:d}),c}}},dependencies:[On],name:"@lexical/extension/NodeSelection"});x.createCommand("INSERT_HORIZONTAL_RULE_COMMAND");class je extends x.DecoratorNode{static getType(){return"horizontalrule"}static clone(e){return new je(e.__key)}static importJSON(e){return go().updateFromJSON(e)}static importDOM(){return{hr:()=>({conversion:vi,priority:0})}}exportDOM(){return{element:document.createElement("hr")}}createDOM(e){const r=document.createElement("hr");return x.addClassNamesToElement(r,e.theme.hr),r}getTextContent(){return` -`}isInline(){return!1}updateDOM(){return!1}}function vi(){return{node:go()}}function go(){return x.$create(je)}function yi(t){return t instanceof je}x.defineExtension({dependencies:[On,Er],name:"@lexical/extension/HorizontalRule",nodes:()=>[je],register(t,e,r){const{watchNodeKey:o}=r.getDependency(Er).output,s=Pe({nodeSelections:new Map}),a=t._config.theme.hrSelected??"selected";return x.mergeRegister(t.registerCommand(x.CLICK_COMMAND,l=>{if(x.isDOMNode(l.target)){const c=x.$getNodeFromDOMNode(l.target);if(yi(c))return function(d,w=!1){const p=x.$getSelection(),u=d.isSelected(),h=d.getKey();let f;w&&x.$isNodeSelection(p)?f=p:(f=x.$createNodeSelection(),x.$setSelection(f)),u?f.delete(h):f.add(h)}(c,l.shiftKey),!0}return!1},x.COMMAND_PRIORITY_LOW),t.registerMutationListener(je,(l,c)=>{ci(()=>{let d=!1;const{nodeSelections:w}=s.peek();for(const[p,u]of l.entries())if(u==="destroyed")w.delete(p),d=!0;else{const h=w.get(p),f=t.getElementByKey(p);h?h.domNode.value=f:(d=!0,w.set(p,{domNode:Pe(f),selectedSignal:o(p)}))}d&&(s.value={nodeSelections:w})})}),Xt(()=>{const l=[];for(const{domNode:c,selectedSignal:d}of s.value.nodeSelections.values())l.push(Xt(()=>{const w=c.value;w&&(d.value?x.addClassNamesToElement(w,a):x.removeClassNamesFromElement(w,a))}));return x.mergeRegister(...l)}))}});function ji(t,e){return x.mergeRegister(t.registerCommand(x.KEY_TAB_COMMAND,r=>{const o=x.$getSelection();if(!x.$isRangeSelection(o))return!1;r.preventDefault();const s=function(a){if(a.getNodes().filter(u=>x.$isBlockElementNode(u)&&u.canIndent()).length>0)return!0;const l=a.anchor,c=a.focus,d=c.isBefore(l)?c:l,w=d.getNode(),p=yr(w);if(p.canIndent()){const u=p.getKey();let h=x.$createRangeSelection();if(h.anchor.set(u,0,"element"),h.focus.set(u,0,"element"),h=x.$normalizeSelection__EXPERIMENTAL(h),h.anchor.is(d))return!0}return!1}(o)?r.shiftKey?x.OUTDENT_CONTENT_COMMAND:x.INDENT_CONTENT_COMMAND:x.INSERT_TAB_COMMAND;return t.dispatchCommand(s,void 0)},x.COMMAND_PRIORITY_EDITOR),t.registerCommand(x.INDENT_CONTENT_COMMAND,()=>{const r=typeof e=="number"?e:e?e.peek():null;if(r==null)return!1;const o=x.$getSelection();if(!x.$isRangeSelection(o))return!1;const s=o.getNodes().map(a=>yr(a).getIndent());return Math.max(...s)+1>=r},x.COMMAND_PRIORITY_CRITICAL))}x.defineExtension({build:(t,e,r)=>wn(e),config:x.safeCast({disabled:!1,maxIndent:null}),name:"@lexical/extension/TabIndentation",register(t,e,r){const{disabled:o,maxIndent:s}=r.getOutput();return Xt(()=>{if(!o.value)return ji(t,s)})}});const Ni=x.defineExtension({name:"@lexical/react/ReactProvider"});function ki(){return x.$getRoot().getTextContent()}function _i(t,e=!0){if(t)return!1;let r=ki();return e&&(r=r.trim()),r===""}function Ci(t){if(!_i(t,!1))return!1;const e=x.$getRoot().getChildren(),r=e.length;if(r>1)return!1;for(let o=0;oCi(t)}function bo(t){const e=window.location.origin,r=o=>{if(o.origin!==e)return;const s=t.getRootElement();if(document.activeElement!==s)return;const a=o.data;if(typeof a=="string"){let l;try{l=JSON.parse(a)}catch{return}if(l&&l.protocol==="nuanria_messaging"&&l.type==="request"){const c=l.payload;if(c&&c.functionId==="makeChanges"){const d=c.args;if(d){const[w,p,u,h,f]=d;t.update(()=>{const g=x.$getSelection();if(x.$isRangeSelection(g)){const v=g.anchor;let b=v.getNode(),y=0,j=0;if(x.$isTextNode(b)&&w>=0&&p>=0&&(y=w,j=w+p,g.setTextNodeRange(b,y,b,j)),y===j&&u===""||(g.insertRawText(u),b=v.getNode()),x.$isTextNode(b)){y=h,j=h+f;const C=b.getTextContentSize();y=y>C?C:y,j=j>C?C:j,g.setTextNodeRange(b,y,b,j)}o.stopImmediatePropagation()}})}}}}};return window.addEventListener("message",r,!0),()=>{window.removeEventListener("message",r,!0)}}x.defineExtension({build:(t,e,r)=>wn(e),config:x.safeCast({disabled:typeof window>"u"}),name:"@lexical/dragon",register:(t,e,r)=>Xt(()=>r.getOutput().disabled.value?void 0:bo(t))});function Si(t,...e){const r=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",t);for(const s of e)o.append("v",s);throw r.search=o.toString(),Error(`Minified Lexical error #${t}; visit ${r.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}const Bn=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?i.useLayoutEffect:i.useEffect;function Ei({editor:t,ErrorBoundary:e}){return function(r,o){const[s,a]=i.useState(()=>r.getDecorators());return Bn(()=>r.registerDecoratorListener(l=>{xr.flushSync(()=>{a(l)})}),[r]),i.useEffect(()=>{a(r.getDecorators())},[r]),i.useMemo(()=>{const l=[],c=Object.keys(s);for(let d=0;dr._onError(h),children:n.jsx(i.Suspense,{fallback:null,children:s[w]})}),u=r.getElementByKey(w);u!==null&&l.push(xr.createPortal(p,u,w))}return l},[o,s,r])}(t,e)}function Ri({editor:t,ErrorBoundary:e}){return function(r){const o=Ie.maybeFromEditor(r);if(o&&o.hasExtensionByName(Ni.name)){for(const s of["@lexical/plain-text","@lexical/rich-text"])o.hasExtensionByName(s)&&Si(320,s);return!0}return!1}(t)?null:n.jsx(Ei,{editor:t,ErrorBoundary:e})}function Rr(t){return t.getEditorState().read(xo(t.isComposing()))}function Ti({contentEditable:t,placeholder:e=null,ErrorBoundary:r}){const[o]=$t();return function(s){Bn(()=>x.mergeRegister(En.registerRichText(s),bo(s)),[s])}(o),n.jsxs(n.Fragment,{children:[t,n.jsx(Mi,{content:e}),n.jsx(Ri,{editor:o,ErrorBoundary:r})]})}function Mi({content:t}){const[e]=$t(),r=function(s){const[a,l]=i.useState(()=>Rr(s));return Bn(()=>{function c(){const d=Rr(s);l(d)}return c(),x.mergeRegister(s.registerUpdateListener(()=>{c()}),s.registerEditableListener(()=>{c()}))},[s]),a}(e),o=oi();return r?typeof t=="function"?t(o):t:null}function Di({defaultSelection:t}){const[e]=$t();return i.useEffect(()=>{e.focus(()=>{const r=document.activeElement,o=e.getRootElement();o===null||r!==null&&o.contains(r)||o.focus({preventScroll:!0})},{defaultSelection:t})},[t,e]),null}const Ii=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?i.useLayoutEffect:i.useEffect;function Oi({onClear:t}){const[e]=$t();return Ii(()=>po(e,t),[e,t]),null}const vo=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?i.useLayoutEffect:i.useEffect;function Ai({editor:t,ariaActiveDescendant:e,ariaAutoComplete:r,ariaControls:o,ariaDescribedBy:s,ariaErrorMessage:a,ariaExpanded:l,ariaInvalid:c,ariaLabel:d,ariaLabelledBy:w,ariaMultiline:p,ariaOwns:u,ariaRequired:h,autoCapitalize:f,className:g,id:v,role:b="textbox",spellCheck:y=!0,style:j,tabIndex:C,"data-testid":M,...F},V){const[_,T]=i.useState(t.isEditable()),S=i.useCallback(P=>{P&&P.ownerDocument&&P.ownerDocument.defaultView?t.setRootElement(P):t.setRootElement(null)},[t]),R=i.useMemo(()=>function(...P){return L=>{for(const I of P)typeof I=="function"?I(L):I!=null&&(I.current=L)}}(V,S),[S,V]);return vo(()=>(T(t.isEditable()),t.registerEditableListener(P=>{T(P)})),[t]),n.jsx("div",{"aria-activedescendant":_?e:void 0,"aria-autocomplete":_?r:"none","aria-controls":_?o:void 0,"aria-describedby":s,...a!=null?{"aria-errormessage":a}:{},"aria-expanded":_&&b==="combobox"?!!l:void 0,...c!=null?{"aria-invalid":c}:{},"aria-label":d,"aria-labelledby":w,"aria-multiline":p,"aria-owns":_?u:void 0,"aria-readonly":!_||void 0,"aria-required":h,autoCapitalize:f,className:g,contentEditable:_,"data-testid":M,id:v,ref:R,role:b,spellCheck:y,style:j,tabIndex:C,...F})}const Pi=i.forwardRef(Ai);function Tr(t){return t.getEditorState().read(xo(t.isComposing()))}const Li=i.forwardRef($i);function $i(t,e){const{placeholder:r,...o}=t,[s]=$t();return n.jsxs(n.Fragment,{children:[n.jsx(Pi,{editor:s,...o,ref:e}),r!=null&&n.jsx(Vi,{editor:s,content:r})]})}function Vi({content:t,editor:e}){const r=function(l){const[c,d]=i.useState(()=>Tr(l));return vo(()=>{function w(){const p=Tr(l);d(p)}return w(),x.mergeRegister(l.registerUpdateListener(()=>{w()}),l.registerEditableListener(()=>{w()}))},[l]),c}(e),[o,s]=i.useState(e.isEditable());if(i.useLayoutEffect(()=>(s(e.isEditable()),e.registerEditableListener(l=>{s(l)})),[e]),!r)return null;let a=null;return typeof t=="function"?a=t(o):t!==null&&(a=t),a===null?null:n.jsx("div",{"aria-hidden":!0,children:a})}function Fi({placeholder:t,className:e,placeholderClassName:r}){return n.jsx(Li,{className:e??"ContentEditable__root tw-relative tw-block tw-min-h-11 tw-overflow-auto tw-px-3 tw-py-3 tw-text-sm tw-outline-none","aria-placeholder":t,placeholder:n.jsx("div",{className:r??"tw-pointer-events-none tw-absolute tw-top-0 tw-select-none tw-overflow-hidden tw-text-ellipsis tw-px-3 tw-py-3 tw-text-sm tw-text-muted-foreground",children:t})})}const yo=i.createContext(void 0);function zi({activeEditor:t,$updateToolbar:e,blockType:r,setBlockType:o,showModal:s,children:a}){const l=i.useMemo(()=>({activeEditor:t,$updateToolbar:e,blockType:r,setBlockType:o,showModal:s}),[t,e,r,o,s]);return n.jsx(yo.Provider,{value:l,children:a})}function jo(){const t=i.useContext(yo);if(!t)throw new Error("useToolbarContext must be used within a ToolbarContext provider");return t}function Gi(){const[t,e]=i.useState(void 0),r=i.useCallback(()=>{e(void 0)},[]),o=i.useMemo(()=>{if(t===void 0)return;const{title:a,content:l}=t;return n.jsx(Da,{open:!0,onOpenChange:r,children:n.jsxs(Ur,{children:[n.jsx(Hr,{children:n.jsx(Yr,{children:a})}),l]})})},[t,r]),s=i.useCallback((a,l,c=!1)=>{e({closeOnClickOutside:c,content:l(r),title:a})},[r]);return[o,s]}function Bi({children:t}){const[e]=$t(),[r,o]=i.useState(e),[s,a]=i.useState("paragraph"),[l,c]=Gi(),d=()=>{};return i.useEffect(()=>r.registerCommand(x.SELECTION_CHANGE_COMMAND,(w,p)=>(o(p),!1),x.COMMAND_PRIORITY_CRITICAL),[r]),n.jsxs(zi,{activeEditor:r,$updateToolbar:d,blockType:s,setBlockType:a,showModal:c,children:[l,t({blockType:s})]})}function Ki(t){const[e]=$t(),{activeEditor:r}=jo();i.useEffect(()=>r.registerCommand(x.SELECTION_CHANGE_COMMAND,()=>{const o=x.$getSelection();return o&&t(o),!1},x.COMMAND_PRIORITY_CRITICAL),[e,t]),i.useEffect(()=>{r.getEditorState().read(()=>{const o=x.$getSelection();o&&t(o)})},[r,t])}const No=Zt.cva("pr-twp tw-inline-flex tw-items-center tw-justify-center tw-rounded-md tw-text-sm tw-font-medium tw-ring-offset-background tw-transition-colors hover:tw-bg-muted hover:tw-text-muted-foreground focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-pointer-events-none disabled:tw-opacity-50 data-[state=on]:tw-bg-accent data-[state=on]:tw-text-accent-foreground",{variants:{variant:{default:"tw-bg-transparent",outline:"tw-border tw-border-input tw-bg-transparent hover:tw-bg-accent hover:tw-text-accent-foreground"},size:{default:"tw-h-10 tw-px-3",sm:"tw-h-9 tw-px-2.5",lg:"tw-h-11 tw-px-5"}},defaultVariants:{variant:"default",size:"default"}}),qi=i.forwardRef(({className:t,variant:e,size:r,...o},s)=>n.jsx(Br.Root,{ref:s,className:m(No({variant:e,size:r,className:t})),...o}));qi.displayName=Br.Root.displayName;const ko=i.createContext({size:"default",variant:"default"}),dn=i.forwardRef(({className:t,variant:e,size:r,children:o,...s},a)=>{const l=st();return n.jsx(an.Root,{ref:a,className:m("pr-twp tw-flex tw-items-center tw-justify-center tw-gap-1",t),...s,dir:l,children:n.jsx(ko.Provider,{value:{variant:e,size:r},children:o})})});dn.displayName=an.Root.displayName;const ve=i.forwardRef(({className:t,children:e,variant:r,size:o,...s},a)=>{const l=i.useContext(ko);return n.jsx(an.Item,{ref:a,className:m(No({variant:l.variant||r,size:l.size||o}),t),...s,children:e})});ve.displayName=an.Item.displayName;const Mr=[{format:"bold",icon:k.BoldIcon,label:"Bold"},{format:"italic",icon:k.ItalicIcon,label:"Italic"},{format:"underline",icon:k.UnderlineIcon,label:"Underline"},{format:"strikethrough",icon:k.StrikethroughIcon,label:"Strikethrough"}];function Ui(){const{activeEditor:t}=jo(),[e,r]=i.useState([]),o=i.useCallback(s=>{if(x.$isRangeSelection(s)||ma.$isTableSelection(s)){const a=[];Mr.forEach(({format:l})=>{s.hasFormat(l)&&a.push(l)}),r(l=>l.length!==a.length||!a.every(c=>l.includes(c))?a:l)}},[]);return Ki(o),n.jsx(dn,{type:"multiple",value:e,onValueChange:r,variant:"outline",size:"sm",children:Mr.map(({format:s,icon:a,label:l})=>n.jsx(ve,{value:s,"aria-label":l,onClick:()=>{t.dispatchCommand(x.FORMAT_TEXT_COMMAND,s)},children:n.jsx(a,{className:"tw-h-4 tw-w-4"})},s))})}function Hi({onClear:t}){const[e]=$t();i.useEffect(()=>{t&&t(()=>{e.dispatchCommand(x.CLEAR_EDITOR_COMMAND,void 0)})},[e,t])}function Yi({placeholder:t="Start typing ...",autoFocus:e=!1,onClear:r}){const[,o]=i.useState(void 0),s=a=>{a!==void 0&&o(a)};return n.jsxs("div",{className:"tw-relative",children:[n.jsx(Bi,{children:()=>n.jsx("div",{className:"tw-sticky tw-top-0 tw-z-10 tw-flex tw-gap-2 tw-overflow-auto tw-border-b tw-p-1",children:n.jsx(Ui,{})})}),n.jsxs("div",{className:"tw-relative",children:[n.jsx(Ti,{contentEditable:n.jsx("div",{ref:s,children:n.jsx(Fi,{placeholder:t})}),ErrorBoundary:ei}),e&&n.jsx(Di,{defaultSelection:"rootEnd"}),n.jsx(Hi,{onClear:r}),n.jsx(Oi,{})]})]})}const Xi={namespace:"commentEditor",theme:Vn,nodes:Fn,onError:t=>{console.error(t)}};function Ze({editorState:t,editorSerializedState:e,onChange:r,onSerializedChange:o,placeholder:s="Start typingโ€ฆ",autoFocus:a=!1,onClear:l,className:c}){return n.jsx("div",{className:m("pr-twp tw-overflow-hidden tw-rounded-lg tw-border tw-bg-background tw-shadow",c),children:n.jsx(Xa,{initialConfig:{...Xi,...t?{editorState:t}:{},...e?{editorState:JSON.stringify(e)}:{}},children:n.jsxs(vt,{children:[n.jsx(Yi,{placeholder:s,autoFocus:a,onClear:l}),n.jsx(Ja,{ignoreSelectionChange:!0,onChange:d=>{r==null||r(d),o==null||o(d.toJSON())}})]})})})}function Wi(t,e){const r=x.isDOMDocumentNode(e)?e.body.childNodes:e.childNodes;let o=[];const s=[];for(const a of r)if(!Co.has(a.nodeName)){const l=So(a,t,s,!1);l!==null&&(o=o.concat(l))}return function(a){for(const l of a)l.getNextSibling()instanceof x.ArtificialNode__DO_NOT_USE&&l.insertAfter(x.$createLineBreakNode());for(const l of a){const c=l.getChildren();for(const d of c)l.insertBefore(d);l.remove()}}(s),o}function Ji(t,e){if(typeof document>"u"||typeof window>"u"&&global.window===void 0)throw new Error("To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom before calling this function.");const r=document.createElement("div"),o=x.$getRoot().getChildren();for(let s=0;s{const g=new x.ArtificialNode__DO_NOT_USE;return r.push(g),g}:x.$createParagraphNode)),c==null?h.length>0?l=l.concat(h):x.isBlockDomNode(t)&&function(g){return g.nextSibling==null||g.previousSibling==null?!1:x.isInlineDomNode(g.nextSibling)&&x.isInlineDomNode(g.previousSibling)}(t)&&(l=l.concat(x.$createLineBreakNode())):x.$isElementNode(c)&&c.append(...h),l}function Zi(t,e,r){const o=t.style.textAlign,s=[];let a=[];for(let l=0;le&&"text"in e&&e.text.trim().length>0?!0:!e||!("children"in e)?!1:Ro(e.children)):!1}function jt(t){var e;return(e=t==null?void 0:t.root)!=null&&e.children?Ro(t.root.children):!1}function Qi(t){if(!t||t.trim()==="")throw new Error("Input HTML is empty");const e=Vr.createHeadlessEditor({namespace:"EditorUtils",theme:Vn,nodes:Fn,onError:o=>{console.error(o)}});let r;if(e.update(()=>{const s=new DOMParser().parseFromString(t,"text/html"),a=Wi(e,s);x.$getRoot().clear(),x.$insertNodes(a)},{discrete:!0}),e.getEditorState().read(()=>{r=e.getEditorState().toJSON()}),!r)throw new Error("Failed to convert HTML to editor state");return r}function Qe(t){const e=Vr.createHeadlessEditor({namespace:"EditorUtils",theme:Vn,nodes:Fn,onError:s=>{console.error(s)}}),r=e.parseEditorState(JSON.stringify(t));e.setEditorState(r);let o="";return e.getEditorState().read(()=>{o=Ji(e)}),o=o.replace(/\s+style="[^"]*"/g,"").replace(/\s+class="[^"]*"/g,"").replace(/(.*?)<\/span>/g,"$1").replace(/]*>(.*?)<\/strong><\/b>/g,"$1").replace(/]*>(.*?)<\/b><\/strong>/g,"$1").replace(/]*>(.*?)<\/em><\/i>/g,"$1").replace(/]*>(.*?)<\/i><\/em>/g,"$1").replace(/]*>(.*?)<\/span><\/u>/g,"$1").replace(/]*>(.*?)<\/span><\/s>/g,"$1").replace(//gi,"
"),o}function Kn(t){return["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End"].includes(t.key)?(t.stopPropagation(),!0):!1}const tl={root:{children:[{children:[{detail:0,format:0,mode:"normal",style:"",text:"",type:"text",version:1}],direction:"ltr",format:"",indent:0,type:"paragraph",version:1,textFormat:0,textStyle:""}],direction:"ltr",format:"",indent:0,type:"root",version:1}};function Nn(t,e){return t===""?e["%commentEditor_unassigned%"]??"Unassigned":t==="Team"?e["%commentEditor_team%"]??"Team":t}function el({assignableUsers:t,onSave:e,onClose:r,localizedStrings:o}){const[s,a]=i.useState(tl),[l,c]=i.useState(void 0),[d,w]=i.useState(!1),p=i.useRef(void 0),u=i.useRef(null);i.useEffect(()=>{let y=!0;const j=u.current;if(!j)return;const C=setTimeout(()=>{y&&Eo(j)},300);return()=>{y=!1,clearTimeout(C)}},[]);const h=i.useCallback(()=>{if(!jt(s))return;const y=Qe(s);e(y,l)},[s,e,l]),f=o["%commentEditor_placeholder%"]??"Type your comment here...",g=o["%commentEditor_saveButton_tooltip%"]??"Save comment",v=o["%commentEditor_cancelButton_tooltip%"]??"Cancel",b=o["%commentEditor_assignTo_label%"]??"Assign to";return n.jsxs("div",{className:"pr-twp tw-grid tw-gap-3",children:[n.jsxs("div",{className:"tw-flex tw-items-center tw-justify-between",children:[n.jsx("span",{className:"tw-text-sm tw-font-medium",children:b}),n.jsxs("div",{className:"tw-flex tw-gap-2",children:[n.jsx(vt,{children:n.jsxs(Nt,{children:[n.jsx(Dt,{asChild:!0,children:n.jsx($,{onClick:r,className:"tw-h-6 tw-w-6",size:"icon",variant:"secondary",children:n.jsx(k.X,{})})}),n.jsx(yt,{children:n.jsx("p",{children:v})})]})}),n.jsx(vt,{children:n.jsxs(Nt,{children:[n.jsx(Dt,{asChild:!0,children:n.jsx($,{onClick:h,className:"tw-h-6 tw-w-6",size:"icon",variant:"default",disabled:!jt(s),children:n.jsx(k.Check,{})})}),n.jsx(yt,{children:n.jsx("p",{children:g})})]})})]})]}),n.jsx("div",{className:"tw-flex tw-items-center tw-gap-2",children:n.jsxs(zt,{open:d,onOpenChange:w,children:[n.jsx(Gt,{asChild:!0,children:n.jsxs($,{variant:"outline",className:"tw-flex tw-w-full tw-items-center tw-justify-start tw-gap-2",disabled:t.length===0,children:[n.jsx(k.AtSign,{className:"tw-h-4 tw-w-4"}),n.jsx("span",{children:Nn(l!==void 0?l:"",o)})]})}),n.jsx(Lt,{className:"tw-w-auto tw-p-0",align:"start",onKeyDown:y=>{y.key==="Escape"&&(y.stopPropagation(),w(!1))},children:n.jsx(At,{children:n.jsx(Pt,{children:t.map(y=>n.jsx(Ct,{onSelect:()=>{c(y===""?void 0:y),w(!1)},className:"tw-flex tw-items-center",children:n.jsx("span",{children:Nn(y,o)})},y||"unassigned"))})})})]})}),n.jsx("div",{ref:u,role:"textbox",tabIndex:-1,className:"tw-outline-none",onKeyDownCapture:y=>{if(y.key==="Escape")y.preventDefault(),y.stopPropagation(),r();else if(y.key==="Enter"){const j=/Macintosh/i.test(navigator.userAgent);(j&&y.metaKey||!j&&y.ctrlKey)&&(y.preventDefault(),y.stopPropagation(),jt(s)&&h())}},onKeyDown:y=>{Kn(y),(y.key==="Enter"||y.key===" ")&&y.stopPropagation()},children:n.jsx(Ze,{editorSerializedState:s,onSerializedChange:y=>a(y),placeholder:f,onClear:y=>{p.current=y}})})]})}const nl=Object.freeze(["%commentEditor_placeholder%","%commentEditor_saveButton_tooltip%","%commentEditor_cancelButton_tooltip%","%commentEditor_assignTo_label%","%commentEditor_unassigned%","%commentEditor_team%"]),rl=["%comment_assign_team%","%comment_assign_unassigned%","%comment_assigned_to%","%comment_assigning_to%","%comment_dateAtTime%","%comment_date_today%","%comment_date_yesterday%","%comment_deleteComment%","%comment_editComment%","%comment_replyOrAssign%","%comment_reopenResolved%","%comment_status_resolved%","%comment_status_todo%","%comment_thread_multiple_replies%","%comment_thread_single_reply%"],ol=["input","select","textarea","button"],sl=["button","textbox"],To=({options:t,onFocusChange:e,onOptionSelect:r,onCharacterPress:o})=>{const s=i.useRef(null),[a,l]=i.useState(void 0),[c,d]=i.useState(void 0),w=i.useCallback(f=>{l(f);const g=t.find(b=>b.id===f);g&&(e==null||e(g));const v=document.getElementById(f);v&&(v.scrollIntoView({block:"center"}),v.focus()),s.current&&s.current.setAttribute("aria-activedescendant",f)},[e,t]),p=i.useCallback(f=>{const g=t.find(v=>v.id===f);g&&(d(v=>v===f?void 0:f),r==null||r(g))},[r,t]),u=f=>{if(!f)return!1;const g=f.tagName.toLowerCase();if(f.isContentEditable||ol.includes(g))return!0;const v=f.getAttribute("role");if(v&&sl.includes(v))return!0;const b=f.getAttribute("tabindex");return b!==void 0&&b!=="-1"},h=i.useCallback(f=>{var _;const g=f.target,v=T=>T?document.getElementById(T):void 0,b=v(c),y=v(a);if(!!(b&&g&&b.contains(g)&&g!==b)&&u(g)){if(f.key==="Escape"||f.key==="ArrowLeft"&&!g.isContentEditable){if(c){f.preventDefault(),f.stopPropagation();const T=t.find(S=>S.id===c);T&&w(T.id)}return}if(f.key==="ArrowDown"||f.key==="ArrowUp"){if(!b)return;const T=Array.from(b.querySelectorAll('button:not([disabled]), input:not([disabled]):not([type="hidden"]), textarea:not([disabled]), select:not([disabled]), [href], [tabindex]:not([tabindex="-1"])'));if(T.length===0)return;const S=T.findIndex(P=>P===g);if(S===-1)return;let R;f.key==="ArrowDown"?R=Math.min(S+1,T.length-1):R=Math.max(S-1,0),R!==S&&(f.preventDefault(),f.stopPropagation(),(_=T[R])==null||_.focus());return}return}const M=t.findIndex(T=>T.id===a);let F=M;switch(f.key){case"ArrowDown":F=Math.min(M+1,t.length-1),f.preventDefault();break;case"ArrowUp":F=Math.max(M-1,0),f.preventDefault();break;case"Home":F=0,f.preventDefault();break;case"End":F=t.length-1,f.preventDefault();break;case" ":case"Enter":a&&p(a),f.preventDefault(),f.stopPropagation();return;case"ArrowRight":{const T=y;if(T){const S=T.querySelector('input:not([disabled]):not([type="hidden"]), textarea:not([disabled]), select:not([disabled])'),R=T.querySelector('button:not([disabled]), [href], [tabindex]:not([tabindex="-1"]), [contenteditable="true"]'),P=S??R;if(P){f.preventDefault(),P.focus();return}}break}default:f.key.length===1&&!f.metaKey&&!f.ctrlKey&&!f.altKey&&(u(g)||(o==null||o(f.key),f.preventDefault()));return}const V=t[F];V&&w(V.id)},[t,w,a,c,p,o]);return{listboxRef:s,activeId:a,selectedId:c,handleKeyDown:h,focusOption:w}},Mo=Zt.cva("pr-twp tw-inline-flex tw-items-center tw-rounded-full tw-px-2.5 tw-py-0.5 tw-text-xs tw-font-semibold tw-transition-colors focus:tw-outline-none focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-2",{variants:{variant:{default:"tw-border tw-border-transparent tw-bg-primary tw-text-primary-foreground hover:tw-bg-primary/80",secondary:"tw-border tw-border-transparent tw-bg-secondary tw-text-secondary-foreground hover:tw-bg-secondary/80",muted:"tw-border tw-border-transparent tw-bg-muted tw-text-muted-foreground hover:tw-bg-muted/80",destructive:"tw-border tw-border-transparent tw-bg-destructive tw-text-destructive-foreground hover:tw-bg-destructive/80",outline:"tw-border tw-text-foreground",blueIndicator:"tw-w-[5px] tw-h-[5px] tw-bg-blue-400 tw-px-0",mutedIndicator:"tw-w-[5px] tw-h-[5px] tw-bg-zinc-400 tw-px-0",ghost:"hover:tw-bg-accent hover:tw-text-accent-foreground tw-text-mu"}},defaultVariants:{variant:"default"}}),le=i.forwardRef(({className:t,variant:e,...r},o)=>n.jsx("div",{ref:o,className:m("pr-twp",Mo({variant:e}),t),...r}));le.displayName="Badge";const qn=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,className:m("pr-twp tw-rounded-lg tw-border tw-bg-card tw-text-card-foreground tw-shadow-sm",t),...e}));qn.displayName="Card";const Do=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,className:m("pr-twp tw-flex tw-flex-col tw-space-y-1.5 tw-p-6",t),...e}));Do.displayName="CardHeader";const Io=i.forwardRef(({className:t,...e},r)=>n.jsx("h3",{ref:r,className:m("pr-twp tw-text-2xl tw-font-semibold tw-leading-none tw-tracking-tight",t),...e,children:e.children}));Io.displayName="CardTitle";const Oo=i.forwardRef(({className:t,...e},r)=>n.jsx("p",{ref:r,className:m("pr-twp tw-text-sm tw-text-muted-foreground",t),...e}));Oo.displayName="CardDescription";const Un=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,className:m("pr-twp tw-p-6 tw-pt-0",t),...e}));Un.displayName="CardContent";const Ao=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,className:m("pr-twp tw-flex tw-items-center tw-p-6 tw-pt-0",t),...e}));Ao.displayName="CardFooter";const ce=i.forwardRef(({className:t,orientation:e="horizontal",decorative:r=!0,...o},s)=>n.jsx(Kr.Root,{ref:s,decorative:r,orientation:e,className:m("pr-twp tw-shrink-0 tw-bg-border",e==="horizontal"?"tw-h-[1px] tw-w-full":"tw-h-full tw-w-[1px]",t),...o}));ce.displayName=Kr.Root.displayName;const Hn=i.forwardRef(({className:t,...e},r)=>n.jsx(_e.Root,{ref:r,className:m("pr-twp tw-relative tw-flex tw-h-10 tw-w-10 tw-shrink-0 tw-overflow-hidden tw-rounded-full",t),...e}));Hn.displayName=_e.Root.displayName;const Po=i.forwardRef(({className:t,...e},r)=>n.jsx(_e.Image,{ref:r,className:m("pr-twp tw-aspect-square tw-h-full tw-w-full",t),...e}));Po.displayName=_e.Image.displayName;const Yn=i.forwardRef(({className:t,...e},r)=>n.jsx(_e.Fallback,{ref:r,className:m("pr-twp tw-flex tw-h-full tw-w-full tw-items-center tw-justify-center tw-rounded-full tw-bg-muted",t),...e}));Yn.displayName=_e.Fallback.displayName;const Xn=i.createContext(void 0);function St(){const t=i.useContext(Xn);if(!t)throw new Error("useMenuContext must be used within a MenuContext.Provider.");return t}const Bt=Zt.cva("",{variants:{variant:{default:"",muted:"hover:tw-bg-muted hover:tw-text-foreground focus:tw-bg-muted focus:tw-text-foreground data-[state=open]:tw-bg-muted data-[state=open]:tw-text-foreground"}},defaultVariants:{variant:"default"}}),ue=X.Trigger,Wn=X.Group,Lo=X.Portal,$o=X.Sub,Vo=X.RadioGroup;function Qt({variant:t="default",...e}){const r=i.useMemo(()=>({variant:t}),[t]);return n.jsx(Xn.Provider,{value:r,children:n.jsx(X.Root,{...e})})}const Jn=i.forwardRef(({className:t,inset:e,children:r,...o},s)=>{const a=St();return n.jsxs(X.SubTrigger,{ref:s,className:m("tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none focus:tw-bg-accent data-[state=open]:tw-bg-accent",e&&"tw-pl-8",t,Bt({variant:a.variant})),...o,children:[r,n.jsx(k.ChevronRight,{className:"tw-ml-auto tw-h-4 tw-w-4"})]})});Jn.displayName=X.SubTrigger.displayName;const Zn=i.forwardRef(({className:t,...e},r)=>n.jsx(X.SubContent,{ref:r,className:m("pr-twp tw-z-50 tw-min-w-[8rem] tw-overflow-hidden tw-rounded-md tw-border tw-bg-popover tw-p-1 tw-text-popover-foreground tw-shadow-lg data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2",t),...e}));Zn.displayName=X.SubContent.displayName;const Kt=i.forwardRef(({className:t,sideOffset:e=4,children:r,...o},s)=>{const a=st();return n.jsx(X.Portal,{children:n.jsx(X.Content,{ref:s,sideOffset:e,className:m("pr-twp tw-z-50 tw-min-w-[8rem] tw-overflow-hidden tw-rounded-md tw-border tw-bg-popover tw-p-1 tw-text-popover-foreground tw-shadow-md data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2",t),...o,children:n.jsx("div",{dir:a,children:r})})})});Kt.displayName=X.Content.displayName;const Le=i.forwardRef(({className:t,inset:e,...r},o)=>{const s=st(),a=St();return n.jsx(X.Item,{ref:o,className:m("tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none tw-transition-colors focus:tw-bg-accent data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",e&&"tw-pl-8",t,Bt({variant:a.variant})),...r,dir:s})});Le.displayName=X.Item.displayName;const It=i.forwardRef(({className:t,children:e,checked:r,...o},s)=>{const a=St();return n.jsxs(X.CheckboxItem,{ref:s,className:m("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pe-2 tw-ps-8 tw-text-sm tw-outline-none tw-transition-colors focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",t,Bt({variant:a.variant})),checked:r,...o,children:[n.jsx("span",{className:"tw-absolute tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center ltr:tw-left-2 rtl:tw-right-2",children:n.jsx(X.ItemIndicator,{children:n.jsx(k.Check,{className:"tw-h-4 tw-w-4"})})}),e]})});It.displayName=X.CheckboxItem.displayName;const Qn=i.forwardRef(({className:t,children:e,...r},o)=>{const s=St();return n.jsxs(X.RadioItem,{ref:o,className:m("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pe-2 tw-ps-8 tw-text-sm tw-outline-none tw-transition-colors focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",t,Bt({variant:s.variant})),...r,children:[n.jsx("span",{className:"tw-absolute tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center ltr:tw-left-2 rtl:tw-right-2",children:n.jsx(X.ItemIndicator,{children:n.jsx(k.Circle,{className:"tw-h-2 tw-w-2 tw-fill-current"})})}),e]})});Qn.displayName=X.RadioItem.displayName;const Se=i.forwardRef(({className:t,inset:e,...r},o)=>n.jsx(X.Label,{ref:o,className:m("tw-px-2 tw-py-1.5 tw-text-sm tw-font-semibold",e&&"tw-pl-8",t),...r}));Se.displayName=X.Label.displayName;const me=i.forwardRef(({className:t,...e},r)=>n.jsx(X.Separator,{ref:r,className:m("tw--mx-1 tw-my-1 tw-h-px tw-bg-muted",t),...e}));me.displayName=X.Separator.displayName;function Fo({className:t,...e}){return n.jsx("span",{className:m("tw-ms-auto tw-text-xs tw-tracking-widest tw-opacity-60",t),...e})}Fo.displayName="DropdownMenuShortcut";function We(t,e){return t===""?e["%comment_assign_unassigned%"]??"Unassigned":t==="Team"?e["%comment_assign_team%"]??"Team":t}function Dr({comment:t,isReply:e=!1,localizedStrings:r,isThreadExpanded:o=!1,handleUpdateComment:s,handleDeleteComment:a,onEditingChange:l,canEditOrDelete:c=!1}){const[d,w]=i.useState(!1),[p,u]=i.useState(),h=i.useRef(null);i.useEffect(()=>{if(!d)return;let M=!0;const F=h.current;if(!F)return;const V=setTimeout(()=>{M&&Eo(F)},300);return()=>{M=!1,clearTimeout(V)}},[d]);const f=i.useCallback(()=>{w(!1),u(void 0),l==null||l(!1)},[l]),g=i.useCallback(async()=>{if(!p||!s)return;await s(t.id,Qe(p))&&(w(!1),u(void 0),l==null||l(!1))},[p,s,t.id,l]),v=i.useMemo(()=>{const M=new Date(t.date),F=N.formatRelativeDate(M,r["%comment_date_today%"],r["%comment_date_yesterday%"]),V=M.toLocaleTimeString(void 0,{hour:"numeric",minute:"2-digit"});return N.formatReplacementString(r["%comment_dateAtTime%"],{date:F,time:V})},[t.date,r]),b=i.useMemo(()=>t.user,[t.user]),y=i.useMemo(()=>t.user.split(" ").map(M=>M[0]).join("").toUpperCase().slice(0,2),[t.user]),j=i.useMemo(()=>N.sanitizeHtml(t.contents),[t.contents]),C=i.useMemo(()=>{if(o&&c)return n.jsxs(n.Fragment,{children:[n.jsxs(Le,{onClick:()=>{w(!0),u(Qi(t.contents)),l==null||l(!0)},children:[n.jsx(k.Pencil,{className:"tw-me-2 tw-h-4 tw-w-4"}),r["%comment_editComment%"]]}),n.jsxs(Le,{onClick:async()=>{a&&await a(t.id)},children:[n.jsx(k.Trash2,{className:"tw-me-2 tw-h-4 tw-w-4"}),r["%comment_deleteComment%"]]})]})},[c,o,r,t.contents,t.id,a,l]);return n.jsxs("div",{className:m("tw-flex tw-w-full tw-flex-row tw-items-baseline tw-gap-3 tw-space-y-3",{"tw-text-sm":e}),children:[n.jsx(Hn,{className:"tw-h-8 tw-w-8",children:n.jsx(Yn,{className:"tw-text-xs tw-font-medium",children:y})}),n.jsxs("div",{className:"tw-flex tw-flex-1 tw-flex-col tw-gap-2",children:[n.jsxs("div",{className:"tw-flex tw-w-full tw-flex-row tw-flex-wrap tw-items-baseline tw-gap-x-2",children:[n.jsx("p",{className:"tw-text-sm tw-font-medium",children:b}),n.jsx("p",{className:"tw-text-xs tw-font-normal tw-text-muted-foreground",children:v}),n.jsx("div",{className:"tw-flex-1"}),e&&t.assignedUser!==void 0&&n.jsxs(le,{variant:"secondary",className:"tw-text-xs tw-font-normal",children:["โ†’ ",We(t.assignedUser,r)]})]}),d&&n.jsxs("div",{role:"textbox",tabIndex:-1,className:"tw-flex tw-flex-col tw-gap-2",ref:h,onKeyDownCapture:M=>{M.key==="Escape"?(M.preventDefault(),M.stopPropagation(),f()):M.key==="Enter"&&M.shiftKey&&(M.preventDefault(),M.stopPropagation(),jt(p)&&g())},onKeyDown:M=>{Kn(M),(M.key==="Enter"||M.key===" ")&&M.stopPropagation()},children:[n.jsx(Ze,{className:m('[&_[data-lexical-editor="true"]>blockquote]:tw-mt-0 [&_[data-lexical-editor="true"]>blockquote]:tw-border-s-0 [&_[data-lexical-editor="true"]>blockquote]:tw-ps-0 [&_[data-lexical-editor="true"]>blockquote]:tw-font-normal [&_[data-lexical-editor="true"]>blockquote]:tw-not-italic [&_[data-lexical-editor="true"]>blockquote]:tw-text-foreground'),editorSerializedState:p,onSerializedChange:M=>u(M)}),n.jsxs("div",{className:"tw-flex tw-flex-row tw-items-start tw-justify-end tw-gap-2",children:[n.jsx($,{size:"icon",onClick:f,variant:"outline",className:"tw-flex tw-items-center tw-justify-center tw-rounded-md",children:n.jsx(k.X,{})}),n.jsx($,{size:"icon",onClick:g,className:"tw-flex tw-items-center tw-justify-center tw-rounded-md",disabled:!jt(p),children:n.jsx(k.ArrowUp,{})})]})]}),!d&&n.jsxs(n.Fragment,{children:[t.status==="Resolved"&&n.jsx("div",{className:"tw-text-sm tw-italic",children:r["%comment_status_resolved%"]}),t.status==="Todo"&&e&&n.jsx("div",{className:"tw-text-sm tw-italic",children:r["%comment_status_todo%"]}),n.jsx("div",{className:m("tw-prose tw-items-start tw-gap-2 tw-break-words tw-text-sm tw-font-normal tw-text-foreground","tw-max-w-none","[&>blockquote]:tw-border-s-0 [&>blockquote]:tw-p-0 [&>blockquote]:tw-ps-0 [&>blockquote]:tw-font-normal [&>blockquote]:tw-not-italic [&>blockquote]:tw-text-foreground","tw-prose-quoteless",{"tw-line-clamp-3":!o}),dangerouslySetInnerHTML:{__html:j}})]})]}),C&&n.jsxs(Qt,{children:[n.jsx(ue,{asChild:!0,children:n.jsx($,{variant:"ghost",size:"icon",children:n.jsx(k.MoreHorizontal,{})})}),n.jsx(Kt,{align:"end",children:C})]})]})}const Ir={root:{children:[{children:[{detail:0,format:0,mode:"normal",style:"",text:"",type:"text",version:1}],direction:"ltr",format:"",indent:0,type:"paragraph",version:1,textFormat:0,textStyle:""}],direction:"ltr",format:"",indent:0,type:"root",version:1}};function al({classNameForVerseText:t,comments:e,localizedStrings:r,isSelected:o=!1,verseRef:s,assignedUser:a,currentUser:l,handleSelectThread:c,threadId:d,thread:w,threadStatus:p,handleAddCommentToThread:u,handleUpdateComment:h,handleDeleteComment:f,handleReadStatusChange:g,assignableUsers:v,canUserAddCommentToThread:b,canUserAssignThreadCallback:y,canUserResolveThreadCallback:j,canUserEditOrDeleteCommentCallback:C,isRead:M=!1,autoReadDelay:F=5,onVerseRefClick:V}){const[_,T]=i.useState(Ir),S=o,[R,P]=i.useState(!1),[L,I]=i.useState(!1),[A,z]=i.useState(!1),[E,B]=i.useState(void 0),[at,lt]=i.useState(!1),[Vt,ct]=i.useState(!1),[rt,G]=i.useState(M),[tt,wt]=i.useState(!1),ot=i.useRef(void 0),[xt,Ee]=i.useState(new Map);i.useEffect(()=>{let D=!0;return(async()=>{const Et=j?await j(d):!1;D&&ct(Et)})(),()=>{D=!1}},[d,j]),i.useEffect(()=>{let D=!0;if(!o){lt(!1),Ee(new Map);return}return(async()=>{const Et=y?await y(d):!1;D&<(Et)})(),()=>{D=!1}},[o,d,y]);const qt=i.useMemo(()=>e.filter(D=>!D.deleted),[e]);i.useEffect(()=>{let D=!0;if(!o||!C){Ee(new Map);return}return(async()=>{const Et=new Map;await Promise.all(qt.map(async gr=>{const sa=await C(gr.id);D&&Et.set(gr.id,sa)})),D&&Ee(Et)})(),()=>{D=!1}},[o,qt,C]);const te=i.useMemo(()=>qt[0],[qt]),Ke=i.useRef(null),he=i.useRef(void 0),ee=i.useCallback(()=>{var D;(D=he.current)==null||D.call(he),T(Ir)},[]),fn=i.useCallback(()=>{const D=!rt;G(D),wt(!D),g==null||g(d,D)},[rt,g,d]);i.useEffect(()=>{P(!1)},[o]),i.useEffect(()=>{if(o&&!rt&&!tt){const D=setTimeout(()=>{G(!0),g==null||g(d,!0)},F*1e3);return ot.current=D,()=>clearTimeout(D)}ot.current&&(clearTimeout(ot.current),ot.current=void 0)},[o,rt,tt,F,d,g]);const O=i.useMemo(()=>({singleReply:r["%comment_thread_single_reply%"],multipleReplies:r["%comment_thread_multiple_replies%"]}),[r]),K=i.useMemo(()=>{if(a===void 0)return;if(a==="")return r["%comment_assign_unassigned%"]??"Unassigned";const D=We(a,r);return N.formatReplacementString(r["%comment_assigned_to%"],{assignedUser:D})},[a,r]),H=i.useMemo(()=>qt.slice(1),[qt]),Y=i.useMemo(()=>H.length??0,[H.length]),it=i.useMemo(()=>Y>0,[Y]),Ft=i.useMemo(()=>R||Y<=2?H:H.slice(-2),[H,Y,R]),mt=i.useMemo(()=>R||Y<=2?0:Y-2,[Y,R]),qe=i.useMemo(()=>Y===1?O.singleReply:N.formatReplacementString(O.multipleReplies,{count:Y}),[Y,O]),ne=i.useMemo(()=>mt===1?O.singleReply:N.formatReplacementString(O.multipleReplies,{count:mt}),[mt,O]),fr=i.useCallback(async()=>{const D=jt(_)?Qe(_):void 0;if(E!==void 0){await u({threadId:d,contents:D,assignedUser:E})&&(B(void 0),D&&ee());return}D&&await u({threadId:d,contents:D})&&ee()},[ee,_,u,E,d]),hr=i.useCallback(async D=>{const re=jt(_)?Qe(_):void 0,Et=await u({...D,contents:re,assignedUser:E??D.assignedUser});return Et&&re&&ee(),Et&&E!==void 0&&B(void 0),Et},[ee,_,u,E]);return n.jsx(qn,{role:"option","aria-selected":o,id:d,className:m("tw-group tw-w-full tw-rounded-none tw-border-none tw-p-4 tw-outline-none tw-transition-all tw-duration-200 focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-1 focus:tw-ring-offset-background",{"tw-cursor-pointer hover:tw-shadow-md":!o},{"tw-bg-primary-foreground":!o&&p!=="Resolved"&&rt,"tw-bg-background":o&&p!=="Resolved"&&rt,"tw-bg-muted":p==="Resolved","tw-bg-blue-50":!rt&&!o&&p!=="Resolved"}),onClick:()=>{c(d)},tabIndex:-1,children:n.jsxs(Un,{className:"tw-flex tw-flex-col tw-gap-2 tw-p-0",children:[n.jsxs("div",{className:"tw-flex tw-flex-col tw-content-center tw-items-start tw-gap-4",children:[n.jsxs("div",{className:"tw-flex tw-items-center tw-gap-2",children:[K&&n.jsx(le,{className:"tw-rounded-sm tw-bg-input tw-text-sm tw-font-normal tw-text-primary hover:tw-bg-input",children:K}),n.jsx($,{variant:"ghost",size:"icon",onClick:D=>{D.stopPropagation(),fn()},className:"tw-text-muted-foreground tw-transition hover:tw-text-foreground","aria-label":rt?"Mark as unread":"Mark as read",children:rt?n.jsx(k.MailOpen,{}):n.jsx(k.Mail,{})}),Vt&&p!=="Resolved"&&n.jsx($,{variant:"ghost",size:"icon",className:m("tw-ms-auto","tw-text-primary tw-transition-opacity tw-duration-200 hover:tw-bg-primary/10","tw-opacity-0 group-hover:tw-opacity-100"),onClick:D=>{D.stopPropagation(),hr({threadId:d,status:"Resolved"})},"aria-label":"Resolve thread",children:n.jsx(k.Check,{className:"tw-h-4 tw-w-4"})})]}),n.jsx("div",{className:"tw-flex tw-max-w-full tw-flex-wrap tw-items-baseline tw-gap-2",children:n.jsxs("p",{ref:Ke,className:m("tw-flex-1 tw-overflow-hidden tw-text-ellipsis tw-text-sm tw-font-normal tw-text-muted-foreground",{"tw-overflow-visible tw-text-clip tw-whitespace-normal tw-break-words":S},{"tw-whitespace-nowrap":!S}),children:[s&&V?n.jsx($,{variant:"ghost",size:"sm",className:"tw-h-auto tw-px-1 tw-py-0 tw-text-sm tw-font-normal tw-text-muted-foreground",onClick:D=>{D.stopPropagation(),V(w)},children:s}):s,n.jsxs("span",{className:t,children:[te.contextBefore,n.jsx("span",{className:"tw-font-bold",children:te.selectedText}),te.contextAfter]})]})}),n.jsx(Dr,{comment:te,localizedStrings:r,isThreadExpanded:o,threadStatus:p,handleAddCommentToThread:hr,handleUpdateComment:h,handleDeleteComment:f,onEditingChange:I,canEditOrDelete:xt.get(te.id)??!1,canUserResolveThread:Vt})]}),n.jsxs(n.Fragment,{children:[it&&!o&&n.jsxs("div",{className:"tw-flex tw-items-center tw-gap-5",children:[n.jsx("div",{className:"tw-w-8",children:n.jsx(ce,{})}),n.jsx("p",{className:"tw-text-sm tw-text-muted-foreground",children:qe})]}),!o&&jt(_)&&n.jsx(Ze,{editorSerializedState:_,onSerializedChange:D=>T(D),placeholder:r["%comment_replyOrAssign%"]}),o&&n.jsxs(n.Fragment,{children:[mt>0&&n.jsxs("div",{className:"tw-flex tw-cursor-pointer tw-items-center tw-gap-5 tw-py-2",onClick:D=>{D.stopPropagation(),P(!0)},role:"button",tabIndex:0,onKeyDown:D=>{(D.key==="Enter"||D.key===" ")&&(D.preventDefault(),D.stopPropagation(),P(!0))},children:[n.jsx("div",{className:"tw-w-8",children:n.jsx(ce,{})}),n.jsxs("div",{className:"tw-flex tw-items-center tw-gap-2",children:[n.jsx("p",{className:"tw-text-sm tw-text-muted-foreground",children:ne}),R?n.jsx(k.ChevronUp,{}):n.jsx(k.ChevronDown,{})]})]}),Ft.map(D=>n.jsx("div",{children:n.jsx(Dr,{comment:D,localizedStrings:r,isReply:!0,isThreadExpanded:o,handleUpdateComment:h,handleDeleteComment:f,onEditingChange:I,canEditOrDelete:xt.get(D.id)??!1})},D.id)),b!==!1&&(!L||jt(_))&&n.jsxs("div",{role:"textbox",tabIndex:-1,className:"tw-w-full tw-space-y-2",onClick:D=>D.stopPropagation(),onKeyDownCapture:D=>{D.key==="Enter"&&D.shiftKey&&(D.preventDefault(),D.stopPropagation(),(jt(_)||E!==void 0)&&fr())},onKeyDown:D=>{Kn(D),(D.key==="Enter"||D.key===" ")&&D.stopPropagation()},children:[n.jsx(Ze,{editorSerializedState:_,onSerializedChange:D=>T(D),placeholder:p==="Resolved"?r["%comment_reopenResolved%"]:r["%comment_replyOrAssign%"],autoFocus:!0,onClear:D=>{he.current=D}}),n.jsxs("div",{className:"tw-flex tw-flex-row tw-items-center tw-justify-end tw-gap-2",children:[E!==void 0&&n.jsx("span",{className:"tw-flex-1 tw-text-sm tw-text-muted-foreground",children:N.formatReplacementString(r["%comment_assigning_to%"]??"Assigning to: {assignedUser}",{assignedUser:We(E,r)})}),n.jsxs(zt,{open:A,onOpenChange:z,children:[n.jsx(Gt,{asChild:!0,children:n.jsx($,{size:"icon",variant:"outline",className:"tw-flex tw-items-center tw-justify-center tw-rounded-md",disabled:!at||!v||v.length===0||!v.includes(l),"aria-label":"Assign user",children:n.jsx(k.AtSign,{})})}),n.jsx(Lt,{className:"tw-w-auto tw-p-0",align:"end",onKeyDown:D=>{D.key==="Escape"&&(D.stopPropagation(),z(!1))},children:n.jsx(At,{children:n.jsx(Pt,{children:v==null?void 0:v.map(D=>n.jsx(Ct,{onSelect:()=>{B(D!==a?D:void 0),z(!1)},className:"tw-flex tw-items-center",children:n.jsx("span",{children:We(D,r)})},D||"unassigned"))})})})]}),n.jsx($,{size:"icon",onClick:fr,className:"tw-flex tw-items-center tw-justify-center tw-rounded-md",disabled:!jt(_)&&E===void 0,"aria-label":"Submit comment",children:n.jsx(k.ArrowUp,{})})]})]})]})]})]})})}function il({className:t="",classNameForVerseText:e,threads:r,currentUser:o,localizedStrings:s,handleAddCommentToThread:a,handleUpdateComment:l,handleDeleteComment:c,handleReadStatusChange:d,assignableUsers:w,canUserAddCommentToThread:p,canUserAssignThreadCallback:u,canUserResolveThreadCallback:h,canUserEditOrDeleteCommentCallback:f,selectedThreadId:g,onSelectedThreadChange:v,onVerseRefClick:b}){const[y,j]=i.useState(new Set),[C,M]=i.useState();i.useEffect(()=>{g&&(j(I=>new Set(I).add(g)),M(g))},[g]);const F=r.filter(I=>I.comments.some(A=>!A.deleted)),V=F.map(I=>({id:I.id})),_=i.useCallback(I=>{j(A=>new Set(A).add(I.id)),M(I.id),v==null||v(I.id)},[v]),T=i.useCallback(I=>{const A=y.has(I);j(z=>{const E=new Set(z);return E.has(I)?E.delete(I):E.add(I),E}),M(I),v==null||v(A?void 0:I)},[y,v]),{listboxRef:S,activeId:R,handleKeyDown:P}=To({options:V,onOptionSelect:_}),L=i.useCallback(I=>{I.key==="Escape"?(C&&y.has(C)&&(j(A=>{const z=new Set(A);return z.delete(C),z}),M(void 0),v==null||v(void 0)),I.preventDefault(),I.stopPropagation()):P(I)},[C,y,P,v]);return n.jsx("div",{id:"comment-list",role:"listbox",tabIndex:0,ref:S,"aria-activedescendant":R??void 0,"aria-label":"Comments",className:m("tw-flex tw-w-full tw-flex-col tw-space-y-3 tw-outline-none focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-1 focus:tw-ring-offset-background",t),onKeyDown:L,children:F.map(I=>n.jsx("div",{id:I.id,className:m({"tw-opacity-60":I.status==="Resolved"}),children:n.jsx(al,{classNameForVerseText:e,comments:I.comments,localizedStrings:s,verseRef:I.verseRef,handleSelectThread:T,threadId:I.id,thread:I,isRead:I.isRead,isSelected:y.has(I.id),currentUser:o,assignedUser:I.assignedUser,threadStatus:I.status,handleAddCommentToThread:a,handleUpdateComment:l,handleDeleteComment:c,handleReadStatusChange:d,assignableUsers:w,canUserAddCommentToThread:p,canUserAssignThreadCallback:u,canUserResolveThreadCallback:h,canUserEditOrDeleteCommentCallback:f,onVerseRefClick:b})},I.id))})}function ll({table:t}){return n.jsxs(Qt,{children:[n.jsx(Fr.DropdownMenuTrigger,{asChild:!0,children:n.jsxs($,{variant:"outline",size:"sm",className:"tw-ml-auto tw-hidden tw-h-8 lg:tw-flex",children:[n.jsx(k.FilterIcon,{className:"tw-mr-2 tw-h-4 tw-w-4"}),"View"]})}),n.jsxs(Kt,{align:"end",className:"tw-w-[150px]",children:[n.jsx(Se,{children:"Toggle columns"}),n.jsx(me,{}),t.getAllColumns().filter(e=>e.getCanHide()).map(e=>n.jsx(It,{className:"tw-capitalize",checked:e.getIsVisible(),onCheckedChange:r=>e.toggleVisibility(!!r),children:e.id},e.id))]})]})}const we=Z.Root,zo=Z.Group,de=Z.Value,Go=Zt.cva("tw-flex tw-h-10 tw-w-full tw-items-center tw-justify-between tw-rounded-md tw-border tw-border-input tw-bg-background tw-px-3 tw-py-2 tw-text-sm tw-ring-offset-background placeholder:tw-text-muted-foreground focus:tw-outline-none focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-2 disabled:tw-cursor-not-allowed disabled:tw-opacity-50 [&>span]:tw-line-clamp-1",{variants:{size:{default:"tw-h-10 tw-px-4 tw-py-2",sm:"tw-h-8 tw-rounded-md tw-px-3",lg:"tw-h-11 tw-rounded-md tw-px-8",icon:"tw-h-10 tw-w-10"}},defaultVariants:{size:"default"}}),Wt=i.forwardRef(({className:t,children:e,size:r,...o},s)=>{const a=st();return n.jsxs(Z.Trigger,{className:m(Go({size:r,className:t})),ref:s,...o,dir:a,children:[e,n.jsx(Z.Icon,{asChild:!0,children:n.jsx(k.ChevronDown,{className:"tw-h-4 tw-w-4 tw-opacity-50"})})]})});Wt.displayName=Z.Trigger.displayName;const tr=i.forwardRef(({className:t,...e},r)=>n.jsx(Z.ScrollUpButton,{ref:r,className:m("tw-flex tw-cursor-default tw-items-center tw-justify-center tw-py-1",t),...e,children:n.jsx(k.ChevronUp,{className:"tw-h-4 tw-w-4"})}));tr.displayName=Z.ScrollUpButton.displayName;const er=i.forwardRef(({className:t,...e},r)=>n.jsx(Z.ScrollDownButton,{ref:r,className:m("tw-flex tw-cursor-default tw-items-center tw-justify-center tw-py-1",t),...e,children:n.jsx(k.ChevronDown,{className:"tw-h-4 tw-w-4"})}));er.displayName=Z.ScrollDownButton.displayName;const Jt=i.forwardRef(({className:t,children:e,position:r="popper",...o},s)=>{const a=st();return n.jsx(Z.Portal,{children:n.jsxs(Z.Content,{ref:s,className:m("pr-twp tw-relative tw-z-50 tw-max-h-96 tw-min-w-[8rem] tw-overflow-hidden tw-rounded-md tw-border tw-bg-popover tw-text-popover-foreground tw-shadow-md data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2",r==="popper"&&"data-[side=bottom]:tw-translate-y-1 data-[side=left]:tw--translate-x-1 data-[side=right]:tw-translate-x-1 data-[side=top]:tw--translate-y-1",t),position:r,...o,children:[n.jsx(tr,{}),n.jsx(Z.Viewport,{className:m("tw-p-1",r==="popper"&&"tw-h-[var(--radix-select-trigger-height)] tw-w-full tw-min-w-[var(--radix-select-trigger-width)]"),children:n.jsx("div",{dir:a,children:e})}),n.jsx(er,{})]})})});Jt.displayName=Z.Content.displayName;const Bo=i.forwardRef(({className:t,...e},r)=>n.jsx(Z.Label,{ref:r,className:m("tw-py-1.5 tw-pl-8 tw-pr-2 tw-text-sm tw-font-semibold",t),...e}));Bo.displayName=Z.Label.displayName;const gt=i.forwardRef(({className:t,children:e,...r},o)=>n.jsxs(Z.Item,{ref:o,className:m("tw-relative tw-flex tw-w-full tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pe-2 tw-ps-8 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",t),...r,children:[n.jsx("span",{className:"tw-absolute tw-start-2 tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center",children:n.jsx(Z.ItemIndicator,{children:n.jsx(k.Check,{className:"tw-h-4 tw-w-4"})})}),n.jsx(Z.ItemText,{children:e})]}));gt.displayName=Z.Item.displayName;const Ko=i.forwardRef(({className:t,...e},r)=>n.jsx(Z.Separator,{ref:r,className:m("tw--mx-1 tw-my-1 tw-h-px tw-bg-muted",t),...e}));Ko.displayName=Z.Separator.displayName;function cl({table:t}){return n.jsx("div",{className:"tw-flex tw-items-center tw-justify-between tw-px-2 tw-pb-3 tw-pt-3",children:n.jsxs("div",{className:"tw-flex tw-items-center tw-space-x-6 lg:tw-space-x-8",children:[n.jsxs("div",{className:"tw-flex-1 tw-text-sm tw-text-muted-foreground",children:[t.getFilteredSelectedRowModel().rows.length," of"," ",t.getFilteredRowModel().rows.length," row(s) selected"]}),n.jsxs("div",{className:"tw-flex tw-items-center tw-space-x-2",children:[n.jsx("p",{className:"tw-text-nowrap tw-text-sm tw-font-medium",children:"Rows per page"}),n.jsxs(we,{value:`${t.getState().pagination.pageSize}`,onValueChange:e=>{t.setPageSize(Number(e))},children:[n.jsx(Wt,{className:"tw-h-8 tw-w-[70px]",children:n.jsx(de,{placeholder:t.getState().pagination.pageSize})}),n.jsx(Jt,{side:"top",children:[10,20,30,40,50].map(e=>n.jsx(gt,{value:`${e}`,children:e},e))})]})]}),n.jsxs("div",{className:"tw-flex tw-w-[100px] tw-items-center tw-justify-center tw-text-sm tw-font-medium",children:["Page ",t.getState().pagination.pageIndex+1," of ",t.getPageCount()]}),n.jsxs("div",{className:"tw-flex tw-items-center tw-space-x-2",children:[n.jsxs($,{variant:"outline",size:"icon",className:"tw-hidden tw-h-8 tw-w-8 tw-p-0 lg:tw-flex",onClick:()=>t.setPageIndex(0),disabled:!t.getCanPreviousPage(),children:[n.jsx("span",{className:"tw-sr-only",children:"Go to first page"}),n.jsx(k.ArrowLeftIcon,{className:"tw-h-4 tw-w-4"})]}),n.jsxs($,{variant:"outline",size:"icon",className:"tw-h-8 tw-w-8 tw-p-0",onClick:()=>t.previousPage(),disabled:!t.getCanPreviousPage(),children:[n.jsx("span",{className:"tw-sr-only",children:"Go to previous page"}),n.jsx(k.ChevronLeftIcon,{className:"tw-h-4 tw-w-4"})]}),n.jsxs($,{variant:"outline",size:"icon",className:"tw-h-8 tw-w-8 tw-p-0",onClick:()=>t.nextPage(),disabled:!t.getCanNextPage(),children:[n.jsx("span",{className:"tw-sr-only",children:"Go to next page"}),n.jsx(k.ChevronRightIcon,{className:"tw-h-4 tw-w-4"})]}),n.jsxs($,{variant:"outline",size:"icon",className:"tw-hidden tw-h-8 tw-w-8 tw-p-0 lg:tw-flex",onClick:()=>t.setPageIndex(t.getPageCount()-1),disabled:!t.getCanNextPage(),children:[n.jsx("span",{className:"tw-sr-only",children:"Go to last page"}),n.jsx(k.ArrowRightIcon,{className:"tw-h-4 tw-w-4"})]})]})]})})}const Or=` +"use strict";var aa=Object.defineProperty;var ia=(t,e,r)=>e in t?aa(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r;var dt=(t,e,r)=>ia(t,typeof e!="symbol"?e+"":e,r);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("react/jsx-runtime"),i=require("react"),ft=require("cmdk"),k=require("lucide-react"),la=require("clsx"),ca=require("tailwind-merge"),wa=require("@radix-ui/react-dialog"),Q=require("@sillsdev/scripture"),N=require("platform-bible-utils"),ke=require("@radix-ui/react-slot"),Zt=require("class-variance-authority"),da=require("@radix-ui/react-popover"),pa=require("@radix-ui/react-label"),ua=require("@radix-ui/react-radio-group"),x=require("lexical"),Vr=require("@radix-ui/react-tooltip"),Sn=require("@lexical/rich-text"),br=require("react-dom"),ma=require("@lexical/table"),fa=require("@radix-ui/react-toggle-group"),ha=require("@radix-ui/react-toggle"),Fr=require("@lexical/headless"),ga=require("@radix-ui/react-separator"),xa=require("@radix-ui/react-avatar"),zr=require("@radix-ui/react-dropdown-menu"),ut=require("@tanstack/react-table"),ba=require("@radix-ui/react-select"),va=require("markdown-to-jsx"),yt=require("@eten-tech-foundation/platform-editor"),ya=require("@radix-ui/react-checkbox"),ja=require("@radix-ui/react-tabs"),Na=require("@radix-ui/react-menubar"),ka=require("react-hotkeys-hook"),_a=require("@radix-ui/react-context-menu"),Ct=require("vaul"),Ca=require("@radix-ui/react-progress"),Ea=require("react-resizable-panels"),Gr=require("sonner"),Sa=require("@radix-ui/react-slider"),Ra=require("@radix-ui/react-switch");function nt(t){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t){for(const r in t)if(r!=="default"){const o=Object.getOwnPropertyDescriptor(t,r);Object.defineProperty(e,r,o.get?o:{enumerable:!0,get:()=>t[r]})}}return e.default=t,Object.freeze(e)}const _t=nt(wa),ye=nt(da),Br=nt(pa),Ae=nt(ua),_e=nt(Vr),ln=nt(fa),Kr=nt(ha),qr=nt(ga),Ce=nt(xa),X=nt(zr),Z=nt(ba),Rn=nt(ya),ht=nt(ja),W=nt(Na),J=nt(_a),Tn=nt(Ca),Pn=nt(Ea),De=nt(Sa),Mn=nt(Ra),Ta=ca.extendTailwindMerge({prefix:"tw-"});function m(...t){return Ta(la.clsx(t))}const Ma="layoutDirection";function st(){const t=localStorage.getItem(Ma);return t==="rtl"?t:"ltr"}const Da=_t.Root,Ia=_t.Portal,Ur=i.forwardRef(({className:t,...e},r)=>n.jsx(_t.Overlay,{ref:r,className:m("tw-fixed tw-inset-0 tw-z-50 tw-bg-black/80 data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0",t),...e}));Ur.displayName=_t.Overlay.displayName;const Hr=i.forwardRef(({className:t,children:e,...r},o)=>{const s=st();return n.jsxs(Ia,{children:[n.jsx(Ur,{}),n.jsxs(_t.Content,{ref:o,className:m("pr-twp tw-fixed tw-left-[50%] tw-top-[50%] tw-z-50 tw-grid tw-w-full tw-max-w-lg tw-translate-x-[-50%] tw-translate-y-[-50%] tw-gap-4 tw-border tw-bg-background tw-p-6 tw-shadow-lg tw-duration-200 data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[state=closed]:tw-slide-out-to-left-1/2 data-[state=closed]:tw-slide-out-to-top-[48%] data-[state=open]:tw-slide-in-from-left-1/2 data-[state=open]:tw-slide-in-from-top-[48%] sm:tw-rounded-lg",t),...r,dir:s,children:[e,n.jsxs(_t.Close,{className:m("tw-absolute tw-top-4 tw-rounded-sm tw-opacity-70 tw-ring-offset-background tw-transition-opacity hover:tw-opacity-100 focus:tw-outline-none focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-2 disabled:tw-pointer-events-none data-[state=open]:tw-bg-accent data-[state=open]:tw-text-muted-foreground",{"tw-right-4":s==="ltr"},{"tw-left-4":s==="rtl"}),children:[n.jsx(k.X,{className:"tw-h-4 tw-w-4"}),n.jsx("span",{className:"tw-sr-only",children:"Close"})]})]})]})});Hr.displayName=_t.Content.displayName;function Yr({className:t,...e}){return n.jsx("div",{className:m("tw-flex tw-flex-col tw-space-y-1.5 tw-text-center sm:tw-text-start",t),...e})}Yr.displayName="DialogHeader";const Xr=i.forwardRef(({className:t,...e},r)=>n.jsx(_t.Title,{ref:r,className:m("tw-text-lg tw-font-semibold tw-leading-none tw-tracking-tight",t),...e}));Xr.displayName=_t.Title.displayName;const Oa=i.forwardRef(({className:t,...e},r)=>n.jsx(_t.Description,{ref:r,className:m("tw-text-sm tw-text-muted-foreground",t),...e}));Oa.displayName=_t.Description.displayName;const At=i.forwardRef(({className:t,...e},r)=>n.jsx(ft.Command,{ref:r,className:m("tw-flex tw-h-full tw-w-full tw-flex-col tw-overflow-hidden tw-rounded-md tw-bg-popover tw-text-popover-foreground",t),...e}));At.displayName=ft.Command.displayName;const pe=i.forwardRef(({className:t,...e},r)=>{const o=st();return n.jsxs("div",{className:"tw-flex tw-items-center tw-border-b tw-px-3",dir:o,children:[n.jsx(k.Search,{className:"tw-me-2 tw-h-4 tw-w-4 tw-shrink-0 tw-opacity-50"}),n.jsx(ft.Command.Input,{ref:r,className:m("tw-flex tw-h-11 tw-w-full tw-rounded-md tw-bg-transparent tw-py-3 tw-text-sm tw-outline-none placeholder:tw-text-muted-foreground disabled:tw-cursor-not-allowed disabled:tw-opacity-50",t),...e})]})});pe.displayName=ft.Command.Input.displayName;const Pt=i.forwardRef(({className:t,...e},r)=>n.jsx(ft.Command.List,{ref:r,className:m("tw-max-h-[300px] tw-overflow-y-auto tw-overflow-x-hidden",t),...e}));Pt.displayName=ft.Command.List.displayName;const Ee=i.forwardRef((t,e)=>n.jsx(ft.Command.Empty,{ref:e,className:"tw-py-6 tw-text-center tw-text-sm",...t}));Ee.displayName=ft.Command.Empty.displayName;const Ot=i.forwardRef(({className:t,...e},r)=>n.jsx(ft.Command.Group,{ref:r,className:m("tw-overflow-hidden tw-p-1 tw-text-foreground [&_[cmdk-group-heading]]:tw-px-2 [&_[cmdk-group-heading]]:tw-py-1.5 [&_[cmdk-group-heading]]:tw-text-xs [&_[cmdk-group-heading]]:tw-font-medium [&_[cmdk-group-heading]]:tw-text-muted-foreground",t),...e}));Ot.displayName=ft.Command.Group.displayName;const Wr=i.forwardRef(({className:t,...e},r)=>n.jsx(ft.Command.Separator,{ref:r,className:m("tw--mx-1 tw-h-px tw-bg-border",t),...e}));Wr.displayName=ft.Command.Separator.displayName;const Et=i.forwardRef(({className:t,...e},r)=>n.jsx(ft.Command.Item,{ref:r,className:m("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none data-[disabled=true]:tw-pointer-events-none data-[selected=true]:tw-bg-accent data-[selected=true]:tw-text-accent-foreground data-[disabled=true]:tw-opacity-50",t),...e}));Et.displayName=ft.Command.Item.displayName;function Jr({className:t,...e}){return n.jsx("span",{className:m("tw-ms-auto tw-text-xs tw-tracking-widest tw-text-muted-foreground",t),...e})}Jr.displayName="CommandShortcut";const Zr=(t,e,r,o,s)=>{switch(t){case N.Section.OT:return e??"Old Testament";case N.Section.NT:return r??"New Testament";case N.Section.DC:return o??"Deuterocanon";case N.Section.Extra:return s??"Extra Materials";default:throw new Error(`Unknown section: ${t}`)}},Aa=(t,e,r,o,s)=>{switch(t){case N.Section.OT:return e??"OT";case N.Section.NT:return r??"NT";case N.Section.DC:return o??"DC";case N.Section.Extra:return s??"Extra";default:throw new Error(`Unknown section: ${t}`)}};function be(t,e){var o;return((o=e==null?void 0:e.get(t))==null?void 0:o.localizedName)??Q.Canon.bookIdToEnglishName(t)}function Ln(t,e){var o;return((o=e==null?void 0:e.get(t))==null?void 0:o.localizedId)??t.toUpperCase()}const Qr=Q.Canon.allBookIds.filter(t=>!Q.Canon.isObsolete(Q.Canon.bookIdToNumber(t))),ie=Object.fromEntries(Qr.map(t=>[t,Q.Canon.bookIdToEnglishName(t)]));function $n(t,e,r){const o=e.trim().toLowerCase();if(!o)return!1;const s=Q.Canon.bookIdToEnglishName(t),a=r==null?void 0:r.get(t);return!!(N.includes(s.toLowerCase(),o)||N.includes(t.toLowerCase(),o)||(a?N.includes(a.localizedName.toLowerCase(),o)||N.includes(a.localizedId.toLowerCase(),o):!1))}const to=i.forwardRef(({bookId:t,isSelected:e,onSelect:r,onMouseDown:o,section:s,className:a,showCheck:l=!1,localizedBookNames:c,commandValue:d},w)=>{const p=i.useRef(!1),u=()=>{p.current||r==null||r(t),setTimeout(()=>{p.current=!1},100)},h=v=>{p.current=!0,o?o(v):r==null||r(t)},f=i.useMemo(()=>be(t,c),[t,c]),g=i.useMemo(()=>Ln(t,c),[t,c]);return n.jsx("div",{className:m("tw-mx-1 tw-my-1 tw-border-b-0 tw-border-e-0 tw-border-s-2 tw-border-t-0 tw-border-solid",{"tw-border-s-red-200":s===N.Section.OT,"tw-border-s-purple-200":s===N.Section.NT,"tw-border-s-indigo-200":s===N.Section.DC,"tw-border-s-amber-200":s===N.Section.Extra}),children:n.jsxs(Et,{ref:w,value:d||`${t} ${Q.Canon.bookIdToEnglishName(t)}`,onSelect:u,onMouseDown:h,role:"option","aria-selected":e,"aria-label":`${Q.Canon.bookIdToEnglishName(t)} (${t.toLocaleUpperCase()})`,className:a,children:[l&&n.jsx(k.Check,{className:m("tw-me-2 tw-h-4 tw-w-4 tw-flex-shrink-0",e?"tw-opacity-100":"tw-opacity-0")}),n.jsx("span",{className:"tw-min-w-0 tw-flex-1",children:f}),n.jsx("span",{className:"tw-ms-2 tw-flex-shrink-0 tw-text-xs tw-text-muted-foreground",children:g})]})})}),Vn=Zt.cva("pr-twp tw-inline-flex tw-items-center tw-justify-center tw-gap-2 tw-whitespace-nowrap tw-rounded-md tw-text-sm tw-font-medium tw-ring-offset-background tw-transition-colors focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-pointer-events-none disabled:tw-opacity-50 [&_svg]:tw-pointer-events-none [&_svg]:tw-size-4 [&_svg]:tw-shrink-0",{variants:{variant:{default:"tw-bg-primary tw-text-primary-foreground hover:tw-bg-primary/90",destructive:"tw-bg-destructive tw-text-destructive-foreground hover:tw-bg-destructive/90",outline:"tw-border tw-border-input tw-bg-background hover:tw-bg-accent hover:tw-text-accent-foreground",secondary:"tw-bg-secondary tw-text-secondary-foreground hover:tw-bg-secondary/80",ghost:"hover:tw-bg-accent hover:tw-text-accent-foreground",link:"tw-text-primary tw-underline-offset-4 hover:tw-underline"},size:{default:"tw-h-10 tw-px-4 tw-py-2",sm:"tw-h-9 tw-rounded-md tw-px-3",lg:"tw-h-11 tw-rounded-md tw-px-8",icon:"tw-h-10 tw-w-10"}},defaultVariants:{variant:"default",size:"default"}}),V=i.forwardRef(({className:t,variant:e,size:r,asChild:o=!1,...s},a)=>{const l=o?ke.Slot:"button";return n.jsx(l,{className:m(Vn({variant:e,size:r,className:t})),ref:a,...s})});V.displayName="Button";const zt=ye.Root,Gt=ye.Trigger,Pa=ye.Anchor,Lt=i.forwardRef(({className:t,align:e="center",sideOffset:r=4,...o},s)=>{const a=st();return n.jsx(ye.Portal,{children:n.jsx(ye.Content,{ref:s,align:e,sideOffset:r,className:m("tw-z-[250]","pr-twp tw-w-72 tw-rounded-md tw-border tw-bg-popover tw-p-4 tw-text-popover-foreground tw-shadow-md tw-outline-none data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2",t),...o,dir:a})})});Lt.displayName=ye.Content.displayName;function Dn(t,e,r){return`${t} ${ie[t]}${e?` ${Ln(t,e)} ${be(t,e)}`:""}${r?` ${r}`:""}`}function eo({recentSearches:t,onSearchItemSelect:e,renderItem:r=d=>String(d),getItemKey:o=d=>String(d),ariaLabel:s="Show recent searches",groupHeading:a="Recent",id:l,classNameForItems:c}){const[d,w]=i.useState(!1);if(t.length===0)return;const p=u=>{e(u),w(!1)};return n.jsxs(zt,{open:d,onOpenChange:w,children:[n.jsx(Gt,{asChild:!0,children:n.jsx(V,{variant:"ghost",size:"icon",className:"tw-absolute tw-right-0 tw-top-0 tw-h-full tw-px-3 tw-py-2","aria-label":s,children:n.jsx(k.Clock,{className:"tw-h-4 tw-w-4"})})}),n.jsx(Lt,{id:l,className:"tw-w-[300px] tw-p-0",align:"start",children:n.jsx(At,{children:n.jsx(Pt,{children:n.jsx(Ot,{heading:a,children:t.map(u=>n.jsxs(Et,{onSelect:()=>p(u),className:m("tw-flex tw-items-center",c),children:[n.jsx(k.Clock,{className:"tw-mr-2 tw-h-4 tw-w-4 tw-opacity-50"}),n.jsx("span",{children:r(u)})]},o(u)))})})})})]})}function La(t,e,r=(s,a)=>s===a,o=15){return s=>{const a=t.filter(c=>!r(c,s)),l=[s,...a.slice(0,o-1)];e(l)}}const hn={BOOK_ONLY:/^([^:\s]+(?:\s+[^:\s]+)*)$/i,BOOK_CHAPTER:/^([^:\s]+(?:\s+[^:\s]+)*)\s+(\d+)$/i,BOOK_CHAPTER_VERSE:/^([^:\s]+(?:\s+[^:\s]+)*)\s+(\d+):(\d*)$/i},$a=[hn.BOOK_ONLY,hn.BOOK_CHAPTER,hn.BOOK_CHAPTER_VERSE];function vr(t){const e=/^[a-zA-Z]$/.test(t),r=/^[0-9]$/.test(t);return{isLetter:e,isDigit:r}}function Mt(t){return N.getChaptersForBook(Q.Canon.bookIdToNumber(t))}function Va(t,e,r){if(!t.trim()||e.length===0)return;const o=$a.reduce((s,a)=>{if(s)return s;const l=a.exec(t.trim());if(l){const[c,d=void 0,w=void 0]=l.slice(1);let p;const u=e.filter(h=>$n(h,c,r));if(u.length===1&&([p]=u),!p&&d){if(Q.Canon.isBookIdValid(c)){const h=c.toUpperCase();e.includes(h)&&(p=h)}if(!p&&r){const h=Array.from(r.entries()).find(([,f])=>f.localizedId.toLowerCase()===c.toLowerCase());h&&e.includes(h[0])&&([p]=h)}}if(!p&&d){const f=(g=>Object.keys(ie).find(v=>ie[v].toLowerCase()===g.toLowerCase()))(c);if(f&&e.includes(f)&&(p=f),!p&&r){const g=Array.from(r.entries()).find(([,v])=>v.localizedName.toLowerCase()===c.toLowerCase());g&&e.includes(g[0])&&([p]=g)}}if(p){let h=d?parseInt(d,10):void 0;h&&h>Mt(p)&&(h=Math.max(Mt(p),1));const f=w?parseInt(w,10):void 0;return{book:p,chapterNum:h,verseNum:f}}}},void 0);if(o)return o}function Fa(t,e,r,o){const s=i.useCallback(()=>{if(t.chapterNum>1)o({book:t.book,chapterNum:t.chapterNum-1,verseNum:1});else{const d=e.indexOf(t.book);if(d>0){const w=e[d-1],p=Math.max(Mt(w),1);o({book:w,chapterNum:p,verseNum:1})}}},[t,e,o]),a=i.useCallback(()=>{const d=Mt(t.book);if(t.chapterNum{o({book:t.book,chapterNum:t.chapterNum,verseNum:t.verseNum>1?t.verseNum-1:0})},[t,o]),c=i.useCallback(()=>{o({book:t.book,chapterNum:t.chapterNum,verseNum:t.verseNum+1})},[t,o]);return i.useMemo(()=>[{onClick:s,disabled:e.length===0||t.chapterNum===1&&e.indexOf(t.book)===0,title:"Previous chapter",icon:r==="ltr"?k.ChevronsLeft:k.ChevronsRight},{onClick:l,disabled:e.length===0||t.verseNum===0,title:"Previous verse",icon:r==="ltr"?k.ChevronLeft:k.ChevronRight},{onClick:c,disabled:e.length===0,title:"Next verse",icon:r==="ltr"?k.ChevronRight:k.ChevronLeft},{onClick:a,disabled:e.length===0||(t.chapterNum===Mt(t.book)||Mt(t.book)<=0)&&e.indexOf(t.book)===e.length-1,title:"Next chapter",icon:r==="ltr"?k.ChevronsRight:k.ChevronsLeft}],[t,e,r,s,l,c,a])}function yr({bookId:t,scrRef:e,onChapterSelect:r,setChapterRef:o,isChapterDimmed:s,className:a}){if(t)return n.jsx(Ot,{children:n.jsx("div",{className:m("tw-grid tw-grid-cols-6 tw-gap-1",a),children:Array.from({length:Mt(t)},(l,c)=>c+1).map(l=>n.jsx(Et,{value:`${t} ${ie[t]||""} ${l}`,onSelect:()=>r(l),ref:o(l),className:m("tw-h-8 tw-w-8 tw-cursor-pointer tw-justify-center tw-rounded-md tw-text-center tw-text-sm",{"tw-bg-primary tw-text-primary-foreground":t===e.book&&l===e.chapterNum},{"tw-bg-muted/50 tw-text-muted-foreground/50":(s==null?void 0:s(l))??!1}),children:l},l))})})}function za({scrRef:t,handleSubmit:e,className:r,getActiveBookIds:o,localizedBookNames:s,localizedStrings:a,recentSearches:l,onAddRecentSearch:c,id:d}){const w=st(),[p,u]=i.useState(!1),[h,f]=i.useState(""),[g,v]=i.useState(""),[b,y]=i.useState("books"),[j,C]=i.useState(void 0),[M,F]=i.useState(!1),$=i.useRef(void 0),_=i.useRef(void 0),T=i.useRef(void 0),E=i.useRef(void 0),R=i.useRef({}),P=i.useCallback(O=>{e(O),c&&c(O)},[e,c]),L=i.useMemo(()=>o?o():Qr,[o]),I=i.useMemo(()=>({[N.Section.OT]:L.filter(K=>Q.Canon.isBookOT(K)),[N.Section.NT]:L.filter(K=>Q.Canon.isBookNT(K)),[N.Section.DC]:L.filter(K=>Q.Canon.isBookDC(K)),[N.Section.Extra]:L.filter(K=>Q.Canon.extraBooks().includes(K))}),[L]),A=i.useMemo(()=>Object.values(I).flat(),[I]),z=i.useMemo(()=>{if(!g.trim())return I;const O={[N.Section.OT]:[],[N.Section.NT]:[],[N.Section.DC]:[],[N.Section.Extra]:[]};return[N.Section.OT,N.Section.NT,N.Section.DC,N.Section.Extra].forEach(H=>{O[H]=I[H].filter(Y=>$n(Y,g,s))}),O},[I,g,s]),S=i.useMemo(()=>Va(g,A,s),[g,A,s]),B=i.useCallback(()=>{S&&(P({book:S.book,chapterNum:S.chapterNum??1,verseNum:S.verseNum??1}),u(!1),v(""),f(""))},[P,S]),at=i.useCallback(O=>{if(Mt(O)<=1){P({book:O,chapterNum:1,verseNum:1}),u(!1),v("");return}C(O),y("chapters")},[P]),lt=i.useCallback(O=>{const K=b==="chapters"?j:S==null?void 0:S.book;K&&(P({book:K,chapterNum:O,verseNum:1}),u(!1),y("books"),C(void 0),v(""))},[P,b,j,S]),Vt=i.useCallback(O=>{P(O),u(!1),v("")},[P]),ct=Fa(t,A,w,e),rt=i.useCallback(()=>{y("books"),C(void 0),setTimeout(()=>{_.current&&_.current.focus()},0)},[]),G=i.useCallback(O=>{if(!O&&b==="chapters"){rt();return}u(O),O&&(y("books"),C(void 0),v(""))},[b,rt]),{otLong:tt,ntLong:wt,dcLong:ot,extraLong:vt}={otLong:a==null?void 0:a["%scripture_section_ot_long%"],ntLong:a==null?void 0:a["%scripture_section_nt_long%"],dcLong:a==null?void 0:a["%scripture_section_dc_long%"],extraLong:a==null?void 0:a["%scripture_section_extra_long%"]},Re=i.useCallback(O=>Zr(O,tt,wt,ot,vt),[tt,wt,ot,vt]),qt=i.useCallback(O=>S?!!S.chapterNum&&!O.toString().includes(S.chapterNum.toString()):!1,[S]),te=i.useMemo(()=>N.formatScrRef(t,s?be(t.book,s):"English"),[t,s]),qe=i.useCallback(O=>K=>{R.current[O]=K},[]),he=i.useCallback(O=>{(O.key==="Home"||O.key==="End")&&O.stopPropagation()},[]),ee=i.useCallback(O=>{if(O.ctrlKey)return;const{isLetter:K,isDigit:H}=vr(O.key);if(b==="chapters"){if(O.key==="Backspace"){O.preventDefault(),O.stopPropagation(),rt();return}if(K||H){if(O.preventDefault(),O.stopPropagation(),y("books"),C(void 0),H&&j){const Y=ie[j];v(`${Y} ${O.key}`)}else v(O.key);setTimeout(()=>{_.current&&_.current.focus()},0);return}}if((b==="chapters"||b==="books"&&S)&&["ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].includes(O.key)){const Y=b==="chapters"?j:S==null?void 0:S.book;if(!Y)return;const it=(()=>{if(!h)return 1;const ne=h.match(/(\d+)$/);return ne?parseInt(ne[1],10):0})(),Ft=Mt(Y);if(!Ft)return;let mt=it;const Ue=6;switch(O.key){case"ArrowLeft":it!==0&&(mt=it>1?it-1:Ft);break;case"ArrowRight":it!==0&&(mt=it{const ne=R.current[mt];ne&&ne.scrollIntoView({block:"nearest",behavior:"smooth"})},0))}},[b,S,rt,j,h,s]),fn=i.useCallback(O=>{if(O.shiftKey||O.key==="Tab"||O.key===" ")return;const{isLetter:K,isDigit:H}=vr(O.key);(K||H)&&(O.preventDefault(),v(Y=>Y+O.key),_.current.focus(),F(!1))},[]);return i.useLayoutEffect(()=>{const O=setTimeout(()=>{if(p&&b==="books"&&T.current&&E.current){const K=T.current,H=E.current,Y=H.offsetTop,it=K.clientHeight,Ft=H.clientHeight,mt=Y-it/2+Ft/2;K.scrollTo({top:Math.max(0,mt),behavior:"smooth"}),f(Dn(t.book))}},0);return()=>{clearTimeout(O)}},[p,b,g,S,t.book]),i.useLayoutEffect(()=>{if(b==="chapters"&&j){const O=j===t.book;setTimeout(()=>{if(T.current)if(O){const K=R.current[t.chapterNum];K&&K.scrollIntoView({block:"center",behavior:"smooth"})}else T.current.scrollTo({top:0});$.current&&$.current.focus()},0)}},[b,j,S,t.book,t.chapterNum]),n.jsxs(zt,{open:p,onOpenChange:G,children:[n.jsx(Gt,{asChild:!0,children:n.jsx(V,{"aria-label":"book-chapter-trigger",variant:"outline",role:"combobox","aria-expanded":p,className:m("tw-h-8 tw-w-full tw-min-w-16 tw-max-w-48 tw-overflow-hidden tw-px-1",r),children:n.jsx("span",{className:"tw-truncate",children:te})})}),n.jsx(Lt,{id:d,forceMount:!0,className:"tw-w-[280px] tw-p-0",align:"center",children:n.jsxs(At,{ref:$,onKeyDown:ee,loop:!0,value:h,onValueChange:f,shouldFilter:!1,children:[b==="books"?n.jsxs("div",{className:"tw-flex tw-items-end",children:[n.jsxs("div",{className:"tw-relative tw-flex-1",children:[n.jsx(pe,{ref:_,value:g,onValueChange:v,onKeyDown:he,onFocus:()=>F(!1),className:l&&l.length>0?"!tw-pr-10":""}),l&&l.length>0&&n.jsx(eo,{recentSearches:l,onSearchItemSelect:Vt,renderItem:O=>N.formatScrRef(O,"English"),getItemKey:O=>`${O.book}-${O.chapterNum}-${O.verseNum}`,ariaLabel:a==null?void 0:a["%history_recentSearches_ariaLabel%"],groupHeading:a==null?void 0:a["%history_recent%"]})]}),n.jsx("div",{className:"tw-flex tw-items-center tw-gap-1 tw-border-b tw-pe-2",children:ct.map(({onClick:O,disabled:K,title:H,icon:Y})=>n.jsx(V,{variant:"ghost",size:"sm",onClick:()=>{F(!0),O()},disabled:K,className:"tw-h-10 tw-w-4 tw-p-0",title:H,onKeyDown:fn,children:n.jsx(Y,{})},H))})]}):n.jsxs("div",{className:"tw-flex tw-items-center tw-border-b tw-px-3 tw-py-2",children:[n.jsx(V,{variant:"ghost",size:"sm",onClick:rt,className:"tw-mr-2 tw-h-6 tw-w-6 tw-p-0",tabIndex:-1,children:w==="ltr"?n.jsx(k.ArrowLeft,{className:"tw-h-4 tw-w-4"}):n.jsx(k.ArrowRight,{className:"tw-h-4 tw-w-4"})}),j&&n.jsx("span",{tabIndex:-1,className:"tw-text-sm tw-font-medium",children:be(j,s)})]}),!M&&n.jsxs(Pt,{ref:T,children:[b==="books"&&n.jsxs(n.Fragment,{children:[!S&&Object.entries(z).map(([O,K])=>{if(K.length!==0)return n.jsx(Ot,{heading:Re(O),children:K.map(H=>n.jsx(to,{bookId:H,onSelect:Y=>at(Y),section:N.getSectionForBook(H),commandValue:`${H} ${ie[H]}`,ref:H===t.book?E:void 0,localizedBookNames:s},H))},O)}),S&&n.jsx(Ot,{children:n.jsx(Et,{value:`${S.book} ${ie[S.book]} ${S.chapterNum||""}:${S.verseNum||""})}`,onSelect:B,className:"tw-font-semibold tw-text-primary",children:N.formatScrRef({book:S.book,chapterNum:S.chapterNum??1,verseNum:S.verseNum??1},s?Ln(S.book,s):void 0)},"top-match")}),S&&Mt(S.book)>1&&n.jsxs(n.Fragment,{children:[n.jsx("div",{className:"tw-mb-2 tw-px-3 tw-text-sm tw-font-medium tw-text-muted-foreground",children:be(S.book,s)}),n.jsx(yr,{bookId:S.book,scrRef:t,onChapterSelect:lt,setChapterRef:qe,isChapterDimmed:qt,className:"tw-px-4 tw-pb-4"})]})]}),b==="chapters"&&j&&n.jsx(yr,{bookId:j,scrRef:t,onChapterSelect:lt,setChapterRef:qe,className:"tw-p-4"})]})]})})]})}const Ga=Object.freeze(["%scripture_section_ot_long%","%scripture_section_nt_long%","%scripture_section_dc_long%","%scripture_section_extra_long%","%history_recent%","%history_recentSearches_ariaLabel%"]),Ba=Zt.cva("tw-text-sm tw-font-medium tw-leading-none peer-disabled:tw-cursor-not-allowed peer-disabled:tw-opacity-70"),et=i.forwardRef(({className:t,...e},r)=>n.jsx(Br.Root,{ref:r,className:m("pr-twp",Ba(),t),...e}));et.displayName=Br.Root.displayName;const cn=i.forwardRef(({className:t,...e},r)=>{const o=st();return n.jsx(Ae.Root,{className:m("pr-twp tw-grid tw-gap-2",t),...e,ref:r,dir:o})});cn.displayName=Ae.Root.displayName;const Pe=i.forwardRef(({className:t,...e},r)=>n.jsx(Ae.Item,{ref:r,className:m("pr-twp tw-aspect-square tw-h-4 tw-w-4 tw-rounded-full tw-border tw-border-primary tw-text-primary tw-ring-offset-background focus:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-cursor-not-allowed disabled:tw-opacity-50",t),...e,children:n.jsx(Ae.Indicator,{className:"tw-flex tw-items-center tw-justify-center",children:n.jsx(k.Circle,{className:"tw-h-2.5 tw-w-2.5 tw-fill-current tw-text-current"})})}));Pe.displayName=Ae.Item.displayName;function Ka(t){return typeof t=="string"?t:typeof t=="number"?t.toString():t.label}function Ze({id:t,options:e=[],className:r,buttonClassName:o,popoverContentClassName:s,value:a,onChange:l=()=>{},getOptionLabel:c=Ka,getButtonLabel:d,icon:w=void 0,buttonPlaceholder:p="",textPlaceholder:u="",commandEmptyMessage:h="No option found",buttonVariant:f="outline",alignDropDown:g="start",isDisabled:v=!1,ariaLabel:b,...y}){const[j,C]=i.useState(!1),M=d??c,F=_=>_.length>0&&typeof _[0]=="object"&&"options"in _[0],$=(_,T)=>{const E=c(_),R=typeof _=="object"&&"secondaryLabel"in _?_.secondaryLabel:void 0,P=`${T??""}${E}${R??""}`;return n.jsxs(Et,{value:E,onSelect:()=>{l(_),C(!1)},className:"tw-flex tw-items-center",children:[n.jsx(k.Check,{className:m("tw-me-2 tw-h-4 tw-w-4 tw-shrink-0",{"tw-opacity-0":!a||c(a)!==E})}),n.jsxs("span",{className:"tw-flex-1 tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap",children:[E,R&&n.jsxs("span",{className:"tw-text-muted-foreground",children:[" ยท ",R]})]})]},P)};return n.jsxs(zt,{open:j,onOpenChange:C,...y,children:[n.jsx(Gt,{asChild:!0,children:n.jsxs(V,{variant:f,role:"combobox","aria-expanded":j,"aria-label":b,id:t,className:m("tw-flex tw-w-[200px] tw-items-center tw-justify-between tw-overflow-hidden",o??r),disabled:v,children:[n.jsxs("div",{className:"tw-flex tw-min-w-0 tw-flex-1 tw-items-center tw-overflow-hidden",children:[w&&n.jsx("div",{className:"tw-shrink-0 tw-pe-2",children:w}),n.jsx("span",{className:m("tw-min-w-0 tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-text-start"),children:a?M(a):p})]}),n.jsx(k.ChevronDown,{className:"tw-ms-2 tw-h-4 tw-w-4 tw-shrink-0 tw-opacity-50"})]})}),n.jsx(Lt,{align:g,className:m("tw-w-[200px] tw-p-0",s),children:n.jsxs(At,{children:[n.jsx(pe,{placeholder:u,className:"tw-text-inherit"}),n.jsx(Ee,{children:h}),n.jsx(Pt,{children:F(e)?e.map(_=>n.jsx(Ot,{heading:_.groupHeading,children:_.options.map(T=>$(T,_.groupHeading))},_.groupHeading)):e.map(_=>$(_))})]})})]})}function no({startChapter:t,endChapter:e,handleSelectStartChapter:r,handleSelectEndChapter:o,isDisabled:s=!1,chapterCount:a}){const l=i.useMemo(()=>Array.from({length:a},(w,p)=>p+1),[a]),c=w=>{r(w),w>e&&o(w)},d=w=>{o(w),ww.toString(),value:t},"start chapter"),n.jsx(et,{htmlFor:"end-chapters-combobox",children:"to"}),n.jsx(Ze,{isDisabled:s,onChange:d,buttonClassName:"tw-ms-2 tw-w-20",options:l,getOptionLabel:w=>w.toString(),value:e},"end chapter")]})}var ro=(t=>(t.CURRENT_BOOK="current book",t.CHOOSE_BOOKS="choose books",t))(ro||{});const qa=Object.freeze(["%webView_bookSelector_currentBook%","%webView_bookSelector_choose%","%webView_bookSelector_chooseBooks%"]),gn=(t,e)=>t[e]??e;function Ua({handleBookSelectionModeChange:t,currentBookName:e,onSelectBooks:r,selectedBookIds:o,chapterCount:s,endChapter:a,handleSelectEndChapter:l,startChapter:c,handleSelectStartChapter:d,localizedStrings:w}){const p=gn(w,"%webView_bookSelector_currentBook%"),u=gn(w,"%webView_bookSelector_choose%"),h=gn(w,"%webView_bookSelector_chooseBooks%"),[f,g]=i.useState("current book"),v=b=>{g(b),t(b)};return n.jsx(cn,{className:"pr-twp tw-flex",value:f,onValueChange:b=>v(b),children:n.jsxs("div",{className:"tw-flex tw-w-full tw-flex-col tw-gap-4",children:[n.jsxs("div",{className:"tw-grid tw-grid-cols-[25%,25%,50%]",children:[n.jsxs("div",{className:"tw-flex tw-items-center",children:[n.jsx(Pe,{value:"current book"}),n.jsx(et,{className:"tw-ms-1",children:p})]}),n.jsx(et,{className:"tw-flex tw-items-center",children:e}),n.jsx("div",{className:"tw-flex tw-items-center tw-justify-end",children:n.jsx(no,{isDisabled:f==="choose books",handleSelectStartChapter:d,handleSelectEndChapter:l,chapterCount:s,startChapter:c,endChapter:a})})]}),n.jsxs("div",{className:"tw-grid tw-grid-cols-[25%,50%,25%]",children:[n.jsxs("div",{className:"tw-flex tw-items-center",children:[n.jsx(Pe,{value:"choose books"}),n.jsx(et,{className:"tw-ms-1",children:h})]}),n.jsx(et,{className:"tw-flex tw-items-center",children:o.map(b=>Q.Canon.bookIdToEnglishName(b)).join(", ")}),n.jsx(V,{disabled:f==="current book",onClick:()=>r(),children:u})]})]})})}const oo=i.createContext(null);function Ha(t,e){return{getTheme:function(){return e??null}}}function $t(){const t=i.useContext(oo);return t==null&&function(e,...r){const o=new URL("https://lexical.dev/docs/error"),s=new URLSearchParams;s.append("code",e);for(const a of r)s.append("v",a);throw o.search=s.toString(),Error(`Minified Lexical error #${e}; visit ${o.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}(8),t}const so=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0,Ya=so?i.useLayoutEffect:i.useEffect,He={tag:x.HISTORY_MERGE_TAG};function Xa({initialConfig:t,children:e}){const r=i.useMemo(()=>{const{theme:o,namespace:s,nodes:a,onError:l,editorState:c,html:d}=t,w=Ha(null,o),p=x.createEditor({editable:t.editable,html:d,namespace:s,nodes:a,onError:u=>l(u,p),theme:o});return function(u,h){if(h!==null){if(h===void 0)u.update(()=>{const f=x.$getRoot();if(f.isEmpty()){const g=x.$createParagraphNode();f.append(g);const v=so?document.activeElement:null;(x.$getSelection()!==null||v!==null&&v===u.getRootElement())&&g.select()}},He);else if(h!==null)switch(typeof h){case"string":{const f=u.parseEditorState(h);u.setEditorState(f,He);break}case"object":u.setEditorState(h,He);break;case"function":u.update(()=>{x.$getRoot().isEmpty()&&h(u)},He)}}}(p,c),[p,w]},[]);return Ya(()=>{const o=t.editable,[s]=r;s.setEditable(o===void 0||o)},[]),n.jsx(oo.Provider,{value:r,children:e})}const Wa=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?i.useLayoutEffect:i.useEffect;function Ja({ignoreHistoryMergeTagChange:t=!0,ignoreSelectionChange:e=!1,onChange:r}){const[o]=$t();return Wa(()=>{if(r)return o.registerUpdateListener(({editorState:s,dirtyElements:a,dirtyLeaves:l,prevEditorState:c,tags:d})=>{e&&a.size===0&&l.size===0||t&&d.has(x.HISTORY_MERGE_TAG)||c.isEmpty()||r(s,o,d)})},[o,t,e,r]),null}const Fn={ltr:"tw-text-left",rtl:"tw-text-right",heading:{h1:"tw-scroll-m-20 tw-text-4xl tw-font-extrabold tw-tracking-tight lg:tw-text-5xl",h2:"tw-scroll-m-20 tw-border-b tw-pb-2 tw-text-3xl tw-font-semibold tw-tracking-tight first:tw-mt-0",h3:"tw-scroll-m-20 tw-text-2xl tw-font-semibold tw-tracking-tight",h4:"tw-scroll-m-20 tw-text-xl tw-font-semibold tw-tracking-tight",h5:"tw-scroll-m-20 tw-text-lg tw-font-semibold tw-tracking-tight",h6:"tw-scroll-m-20 tw-text-base tw-font-semibold tw-tracking-tight"},paragraph:"tw-outline-none",quote:"tw-mt-6 tw-border-l-2 tw-pl-6 tw-italic",link:"tw-text-blue-600 hover:tw-underline hover:tw-cursor-pointer",list:{checklist:"tw-relative",listitem:"tw-mx-8",listitemChecked:'tw-relative tw-mx-2 tw-px-6 tw-list-none tw-outline-none tw-line-through before:tw-content-[""] before:tw-w-4 before:tw-h-4 before:tw-top-0.5 before:tw-left-0 before:tw-cursor-pointer before:tw-block before:tw-bg-cover before:tw-absolute before:tw-border before:tw-border-primary before:tw-rounded before:tw-bg-primary before:tw-bg-no-repeat after:tw-content-[""] after:tw-cursor-pointer after:tw-border-white after:tw-border-solid after:tw-absolute after:tw-block after:tw-top-[6px] after:tw-w-[3px] after:tw-left-[7px] after:tw-right-[7px] after:tw-h-[6px] after:tw-rotate-45 after:tw-border-r-2 after:tw-border-b-2 after:tw-border-l-0 after:tw-border-t-0',listitemUnchecked:'tw-relative tw-mx-2 tw-px-6 tw-list-none tw-outline-none before:tw-content-[""] before:tw-w-4 before:tw-h-4 before:tw-top-0.5 before:tw-left-0 before:tw-cursor-pointer before:tw-block before:tw-bg-cover before:tw-absolute before:tw-border before:tw-border-primary before:tw-rounded',nested:{listitem:"tw-list-none before:tw-hidden after:tw-hidden"},ol:"tw-m-0 tw-p-0 tw-list-decimal [&>li]:tw-mt-2",olDepth:["tw-list-outside !tw-list-decimal","tw-list-outside !tw-list-[upper-roman]","tw-list-outside !tw-list-[lower-roman]","tw-list-outside !tw-list-[upper-alpha]","tw-list-outside !tw-list-[lower-alpha]"],ul:"tw-m-0 tw-p-0 tw-list-outside [&>li]:tw-mt-2",ulDepth:["tw-list-outside !tw-list-disc","tw-list-outside !tw-list-disc","tw-list-outside !tw-list-disc","tw-list-outside !tw-list-disc","tw-list-outside !tw-list-disc"]},hashtag:"tw-text-blue-600 tw-bg-blue-100 tw-rounded-md tw-px-1",text:{bold:"tw-font-bold",code:"tw-bg-gray-100 tw-p-1 tw-rounded-md",italic:"tw-italic",strikethrough:"tw-line-through",subscript:"tw-sub",superscript:"tw-sup",underline:"tw-underline",underlineStrikethrough:"tw-underline tw-line-through"},image:"tw-relative tw-inline-block tw-user-select-none tw-cursor-default editor-image",inlineImage:"tw-relative tw-inline-block tw-user-select-none tw-cursor-default inline-editor-image",keyword:"tw-text-purple-900 tw-font-bold",code:"EditorTheme__code",codeHighlight:{atrule:"EditorTheme__tokenAttr",attr:"EditorTheme__tokenAttr",boolean:"EditorTheme__tokenProperty",builtin:"EditorTheme__tokenSelector",cdata:"EditorTheme__tokenComment",char:"EditorTheme__tokenSelector",class:"EditorTheme__tokenFunction","class-name":"EditorTheme__tokenFunction",comment:"EditorTheme__tokenComment",constant:"EditorTheme__tokenProperty",deleted:"EditorTheme__tokenProperty",doctype:"EditorTheme__tokenComment",entity:"EditorTheme__tokenOperator",function:"EditorTheme__tokenFunction",important:"EditorTheme__tokenVariable",inserted:"EditorTheme__tokenSelector",keyword:"EditorTheme__tokenAttr",namespace:"EditorTheme__tokenVariable",number:"EditorTheme__tokenProperty",operator:"EditorTheme__tokenOperator",prolog:"EditorTheme__tokenComment",property:"EditorTheme__tokenProperty",punctuation:"EditorTheme__tokenPunctuation",regex:"EditorTheme__tokenVariable",selector:"EditorTheme__tokenSelector",string:"EditorTheme__tokenSelector",symbol:"EditorTheme__tokenProperty",tag:"EditorTheme__tokenProperty",url:"EditorTheme__tokenOperator",variable:"EditorTheme__tokenVariable"},characterLimit:"!tw-bg-destructive/50",table:"EditorTheme__table tw-w-fit tw-overflow-scroll tw-border-collapse",tableCell:"EditorTheme__tableCell tw-w-24 tw-relative tw-border tw-px-4 tw-py-2 tw-text-left [&[align=center]]:tw-text-center [&[align=right]]:tw-text-right",tableCellActionButton:"EditorTheme__tableCellActionButton tw-bg-background tw-block tw-border-0 tw-rounded-2xl tw-w-5 tw-h-5 tw-text-foreground tw-cursor-pointer",tableCellActionButtonContainer:"EditorTheme__tableCellActionButtonContainer tw-block tw-right-1 tw-top-1.5 tw-absolute tw-z-10 tw-w-5 tw-h-5",tableCellEditing:"EditorTheme__tableCellEditing tw-rounded-sm tw-shadow-sm",tableCellHeader:"EditorTheme__tableCellHeader tw-bg-muted tw-border tw-px-4 tw-py-2 tw-text-left tw-font-bold [&[align=center]]:tw-text-center [&[align=right]]:tw-text-right",tableCellPrimarySelected:"EditorTheme__tableCellPrimarySelected tw-border tw-border-primary tw-border-solid tw-block tw-h-[calc(100%-2px)] tw-w-[calc(100%-2px)] tw-absolute tw--left-[1px] tw--top-[1px] tw-z-10 ",tableCellResizer:"EditorTheme__tableCellResizer tw-absolute tw--right-1 tw-h-full tw-w-2 tw-cursor-ew-resize tw-z-10 tw-top-0",tableCellSelected:"EditorTheme__tableCellSelected tw-bg-muted",tableCellSortedIndicator:"EditorTheme__tableCellSortedIndicator tw-block tw-opacity-50 tw-absolute tw-bottom-0 tw-left-0 tw-w-full tw-h-1 tw-bg-muted",tableResizeRuler:"EditorTheme__tableCellResizeRuler tw-block tw-absolute tw-w-[1px] tw-h-full tw-bg-primary tw-top-0",tableRowStriping:"EditorTheme__tableRowStriping tw-m-0 tw-border-t tw-p-0 even:tw-bg-muted",tableSelected:"EditorTheme__tableSelected tw-ring-2 tw-ring-primary tw-ring-offset-2",tableSelection:"EditorTheme__tableSelection tw-bg-transparent",layoutItem:"tw-border tw-border-dashed tw-px-4 tw-py-2",layoutContainer:"tw-grid tw-gap-2.5 tw-my-2.5 tw-mx-0",autocomplete:"tw-text-muted-foreground",blockCursor:"",embedBlock:{base:"tw-user-select-none",focus:"tw-ring-2 tw-ring-primary tw-ring-offset-2"},hr:'tw-p-0.5 tw-border-none tw-my-1 tw-mx-0 tw-cursor-pointer after:tw-content-[""] after:tw-block after:tw-h-0.5 after:tw-bg-muted selected:tw-ring-2 selected:tw-ring-primary selected:tw-ring-offset-2 selected:tw-user-select-none',indent:"[--lexical-indent-base-value:40px]",mark:"",markOverlap:""},xt=_e.Provider,jt=_e.Root,Nt=i.forwardRef(({className:t,variant:e,...r},o)=>n.jsx(_e.Trigger,{ref:o,className:e?m(Vn({variant:e}),t):t,...r}));Nt.displayName=_e.Trigger.displayName;const bt=i.forwardRef(({className:t,sideOffset:e=4,...r},o)=>n.jsx(_e.Content,{ref:o,sideOffset:e,className:m("pr-twp tw-z-50 tw-overflow-hidden tw-rounded-md tw-border tw-bg-popover tw-px-3 tw-py-1.5 tw-text-sm tw-text-popover-foreground tw-shadow-md tw-animate-in tw-fade-in-0 tw-zoom-in-95 data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=closed]:tw-zoom-out-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2",t),...r}));bt.displayName=_e.Content.displayName;const zn=[Sn.HeadingNode,x.ParagraphNode,x.TextNode,Sn.QuoteNode],Za=i.createContext(null),xn={didCatch:!1,error:null};class Qa extends i.Component{constructor(e){super(e),this.resetErrorBoundary=this.resetErrorBoundary.bind(this),this.state=xn}static getDerivedStateFromError(e){return{didCatch:!0,error:e}}resetErrorBoundary(){const{error:e}=this.state;if(e!==null){for(var r,o,s=arguments.length,a=new Array(s),l=0;l0&&arguments[0]!==void 0?arguments[0]:[],e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:[];return t.length!==e.length||t.some((r,o)=>!Object.is(r,e[o]))}function ei({children:t,onError:e}){return n.jsx(Qa,{fallback:n.jsx("div",{style:{border:"1px solid #f00",color:"#f00",padding:"8px"},children:"An error was thrown."}),onError:e,children:t})}const ni=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?i.useLayoutEffect:i.useEffect;function ri(t){return{initialValueFn:()=>t.isEditable(),subscribe:e=>t.registerEditableListener(e)}}function oi(){return function(t){const[e]=$t(),r=i.useMemo(()=>t(e),[e,t]),[o,s]=i.useState(()=>r.initialValueFn()),a=i.useRef(o);return ni(()=>{const{initialValueFn:l,subscribe:c}=r,d=l();return a.current!==d&&(a.current=d,s(d)),c(w=>{a.current=w,s(w)})},[r,t]),o}(ri)}function si(t,e,r="self"){const o=t.getStartEndPoints();if(e.isSelected(t)&&!x.$isTokenOrSegmented(e)&&o!==null){const[s,a]=o,l=t.isBackward(),c=s.getNode(),d=a.getNode(),w=e.is(c),p=e.is(d);if(w||p){const[u,h]=x.$getCharacterOffsets(t),f=c.is(d),g=e.is(l?d:c),v=e.is(l?c:d);let b,y=0;f?(y=u>h?h:u,b=u>h?u:h):g?(y=l?h:u,b=void 0):v&&(y=0,b=l?u:h);const j=e.__text.slice(y,b);j!==e.__text&&(r==="clone"&&(e=x.$cloneWithPropertiesEphemeral(e)),e.__text=j)}}return e}function ai(t,...e){const r=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",t);for(const s of e)o.append("v",s);throw r.search=o.toString(),Error(`Minified Lexical error #${t}; visit ${r.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}const ao=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0,ii=ao&&"documentMode"in document?document.documentMode:null;!(!ao||!("InputEvent"in window)||ii)&&"getTargetRanges"in new window.InputEvent("input");function jr(t){const e=x.$findMatchingParent(t,r=>x.$isElementNode(r)&&!r.isInline());return x.$isElementNode(e)||ai(4,t.__key),e}const li=Symbol.for("preact-signals");function wn(){if(Ht>1)return void Ht--;let t,e=!1;for(;Ie!==void 0;){let r=Ie;for(Ie=void 0,In++;r!==void 0;){const o=r.o;if(r.o=void 0,r.f&=-3,!(8&r.f)&&io(r))try{r.c()}catch(s){e||(t=s,e=!0)}r=o}}if(In=0,Ht--,e)throw t}function ci(t){if(Ht>0)return t();Ht++;try{return t()}finally{wn()}}let q,Ie;function Nr(t){const e=q;q=void 0;try{return t()}finally{q=e}}let Ht=0,In=0,We=0;function kr(t){if(q===void 0)return;let e=t.n;return e===void 0||e.t!==q?(e={i:0,S:t,p:q.s,n:void 0,t:q,e:void 0,x:void 0,r:e},q.s!==void 0&&(q.s.n=e),q.s=e,t.n=e,32&q.f&&t.S(e),e):e.i===-1?(e.i=0,e.n!==void 0&&(e.n.p=e.p,e.p!==void 0&&(e.p.n=e.n),e.p=q.s,e.n=void 0,q.s.n=e,q.s=e),e):void 0}function pt(t,e){this.v=t,this.i=0,this.n=void 0,this.t=void 0,this.W=e==null?void 0:e.watched,this.Z=e==null?void 0:e.unwatched,this.name=e==null?void 0:e.name}function Le(t,e){return new pt(t,e)}function io(t){for(let e=t.s;e!==void 0;e=e.n)if(e.S.i!==e.i||!e.S.h()||e.S.i!==e.i)return!0;return!1}function _r(t){for(let e=t.s;e!==void 0;e=e.n){const r=e.S.n;if(r!==void 0&&(e.r=r),e.S.n=e,e.i=-1,e.n===void 0){t.s=e;break}}}function lo(t){let e,r=t.s;for(;r!==void 0;){const o=r.p;r.i===-1?(r.S.U(r),o!==void 0&&(o.n=r.n),r.n!==void 0&&(r.n.p=o)):e=r,r.S.n=r.r,r.r!==void 0&&(r.r=void 0),r=o}t.s=e}function se(t,e){pt.call(this,void 0),this.x=t,this.s=void 0,this.g=We-1,this.f=4,this.W=e==null?void 0:e.watched,this.Z=e==null?void 0:e.unwatched,this.name=e==null?void 0:e.name}function wi(t,e){return new se(t,e)}function co(t){const e=t.u;if(t.u=void 0,typeof e=="function"){Ht++;const r=q;q=void 0;try{e()}catch(o){throw t.f&=-2,t.f|=8,Gn(t),o}finally{q=r,wn()}}}function Gn(t){for(let e=t.s;e!==void 0;e=e.n)e.S.U(e);t.x=void 0,t.s=void 0,co(t)}function di(t){if(q!==this)throw new Error("Out-of-order effect");lo(this),q=t,this.f&=-2,8&this.f&&Gn(this),wn()}function xe(t,e){this.x=t,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32,this.name=e==null?void 0:e.name}function Xt(t,e){const r=new xe(t,e);try{r.c()}catch(s){throw r.d(),s}const o=r.d.bind(r);return o[Symbol.dispose]=o,o}function dn(t,e={}){const r={};for(const o in t){const s=e[o],a=Le(s===void 0?t[o]:s);r[o]=a}return r}pt.prototype.brand=li,pt.prototype.h=function(){return!0},pt.prototype.S=function(t){const e=this.t;e!==t&&t.e===void 0&&(t.x=e,this.t=t,e!==void 0?e.e=t:Nr(()=>{var r;(r=this.W)==null||r.call(this)}))},pt.prototype.U=function(t){if(this.t!==void 0){const e=t.e,r=t.x;e!==void 0&&(e.x=r,t.e=void 0),r!==void 0&&(r.e=e,t.x=void 0),t===this.t&&(this.t=r,r===void 0&&Nr(()=>{var o;(o=this.Z)==null||o.call(this)}))}},pt.prototype.subscribe=function(t){return Xt(()=>{const e=this.value,r=q;q=void 0;try{t(e)}finally{q=r}},{name:"sub"})},pt.prototype.valueOf=function(){return this.value},pt.prototype.toString=function(){return this.value+""},pt.prototype.toJSON=function(){return this.value},pt.prototype.peek=function(){const t=q;q=void 0;try{return this.value}finally{q=t}},Object.defineProperty(pt.prototype,"value",{get(){const t=kr(this);return t!==void 0&&(t.i=this.i),this.v},set(t){if(t!==this.v){if(In>100)throw new Error("Cycle detected");this.v=t,this.i++,We++,Ht++;try{for(let e=this.t;e!==void 0;e=e.x)e.t.N()}finally{wn()}}}}),se.prototype=new pt,se.prototype.h=function(){if(this.f&=-3,1&this.f)return!1;if((36&this.f)==32||(this.f&=-5,this.g===We))return!0;if(this.g=We,this.f|=1,this.i>0&&!io(this))return this.f&=-2,!0;const t=q;try{_r(this),q=this;const e=this.x();(16&this.f||this.v!==e||this.i===0)&&(this.v=e,this.f&=-17,this.i++)}catch(e){this.v=e,this.f|=16,this.i++}return q=t,lo(this),this.f&=-2,!0},se.prototype.S=function(t){if(this.t===void 0){this.f|=36;for(let e=this.s;e!==void 0;e=e.n)e.S.S(e)}pt.prototype.S.call(this,t)},se.prototype.U=function(t){if(this.t!==void 0&&(pt.prototype.U.call(this,t),this.t===void 0)){this.f&=-33;for(let e=this.s;e!==void 0;e=e.n)e.S.U(e)}},se.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(let t=this.t;t!==void 0;t=t.x)t.t.N()}},Object.defineProperty(se.prototype,"value",{get(){if(1&this.f)throw new Error("Cycle detected");const t=kr(this);if(this.h(),t!==void 0&&(t.i=this.i),16&this.f)throw this.v;return this.v}}),xe.prototype.c=function(){const t=this.S();try{if(8&this.f||this.x===void 0)return;const e=this.x();typeof e=="function"&&(this.u=e)}finally{t()}},xe.prototype.S=function(){if(1&this.f)throw new Error("Cycle detected");this.f|=1,this.f&=-9,co(this),_r(this),Ht++;const t=q;return q=this,di.bind(this,t)},xe.prototype.N=function(){2&this.f||(this.f|=2,this.o=Ie,Ie=this)},xe.prototype.d=function(){this.f|=8,1&this.f||Gn(this)},xe.prototype.dispose=function(){this.d()};x.defineExtension({build:(t,e,r)=>dn(e),config:x.safeCast({defaultSelection:"rootEnd",disabled:!1}),name:"@lexical/extension/AutoFocus",register(t,e,r){const o=r.getOutput();return Xt(()=>o.disabled.value?void 0:t.registerRootListener(s=>{t.focus(()=>{const a=document.activeElement;s===null||a!==null&&s.contains(a)||s.focus({preventScroll:!0})},{defaultSelection:o.defaultSelection.peek()})}))}});function wo(){const t=x.$getRoot(),e=x.$getSelection(),r=x.$createParagraphNode();t.clear(),t.append(r),e!==null&&r.select(),x.$isRangeSelection(e)&&(e.format=0)}function po(t,e=wo){return t.registerCommand(x.CLEAR_EDITOR_COMMAND,r=>(t.update(e),!0),x.COMMAND_PRIORITY_EDITOR)}x.defineExtension({build:(t,e,r)=>dn(e),config:x.safeCast({$onClear:wo}),name:"@lexical/extension/ClearEditor",register(t,e,r){const{$onClear:o}=r.getOutput();return Xt(()=>po(t,o.value))}});function pi(t){return(typeof t.nodes=="function"?t.nodes():t.nodes)||[]}function uo(t,e){let r;return Le(t(),{unwatched(){r&&(r(),r=void 0)},watched(){this.value=t(),r=e(this)}})}const On=x.defineExtension({build:t=>uo(()=>t.getEditorState(),e=>t.registerUpdateListener(r=>{e.value=r.editorState})),name:"@lexical/extension/EditorState"});function U(t,...e){const r=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",t);for(const s of e)o.append("v",s);throw r.search=o.toString(),Error(`Minified Lexical error #${t}; visit ${r.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}function mo(t,e){if(t&&e&&!Array.isArray(e)&&typeof t=="object"&&typeof e=="object"){const r=t,o=e;for(const s in o)r[s]=mo(r[s],o[s]);return t}return e}const Bn=0,An=1,fo=2,bn=3,Ye=4,ge=5,vn=6,Te=7;function yn(t){return t.id===Bn}function ho(t){return t.id===fo}function ui(t){return function(e){return e.id===An}(t)||U(305,String(t.id),String(An)),Object.assign(t,{id:fo})}const mi=new Set;class fi{constructor(e,r){dt(this,"builder");dt(this,"configs");dt(this,"_dependency");dt(this,"_peerNameSet");dt(this,"extension");dt(this,"state");dt(this,"_signal");this.builder=e,this.extension=r,this.configs=new Set,this.state={id:Bn}}mergeConfigs(){let e=this.extension.config||{};const r=this.extension.mergeConfig?this.extension.mergeConfig.bind(this.extension):x.shallowMergeConfig;for(const o of this.configs)e=r(e,o);return e}init(e){const r=this.state;ho(r)||U(306,String(r.id));const o={getDependency:this.getInitDependency.bind(this),getDirectDependentNames:this.getDirectDependentNames.bind(this),getPeer:this.getInitPeer.bind(this),getPeerNameSet:this.getPeerNameSet.bind(this)},s={...o,getDependency:this.getDependency.bind(this),getInitResult:this.getInitResult.bind(this),getPeer:this.getPeer.bind(this)},a=function(c,d,w){return Object.assign(c,{config:d,id:bn,registerState:w})}(r,this.mergeConfigs(),o);let l;this.state=a,this.extension.init&&(l=this.extension.init(e,a.config,o)),this.state=function(c,d,w){return Object.assign(c,{id:Ye,initResult:d,registerState:w})}(a,l,s)}build(e){const r=this.state;let o;r.id!==Ye&&U(307,String(r.id),String(ge)),this.extension.build&&(o=this.extension.build(e,r.config,r.registerState));const s={...r.registerState,getOutput:()=>o,getSignal:this.getSignal.bind(this)};this.state=function(a,l,c){return Object.assign(a,{id:ge,output:l,registerState:c})}(r,o,s)}register(e,r){this._signal=r;const o=this.state;o.id!==ge&&U(308,String(o.id),String(ge));const s=this.extension.register&&this.extension.register(e,o.config,o.registerState);return this.state=function(a){return Object.assign(a,{id:vn})}(o),()=>{const a=this.state;a.id!==Te&&U(309,String(o.id),String(Te)),this.state=function(l){return Object.assign(l,{id:ge})}(a),s&&s()}}afterRegistration(e){const r=this.state;let o;return r.id!==vn&&U(310,String(r.id),String(vn)),this.extension.afterRegistration&&(o=this.extension.afterRegistration(e,r.config,r.registerState)),this.state=function(s){return Object.assign(s,{id:Te})}(r),o}getSignal(){return this._signal===void 0&&U(311),this._signal}getInitResult(){this.extension.init===void 0&&U(312,this.extension.name);const e=this.state;return function(r){return r.id>=Ye}(e)||U(313,String(e.id),String(Ye)),e.initResult}getInitPeer(e){const r=this.builder.extensionNameMap.get(e);return r?r.getExtensionInitDependency():void 0}getExtensionInitDependency(){const e=this.state;return function(r){return r.id>=bn}(e)||U(314,String(e.id),String(bn)),{config:e.config}}getPeer(e){const r=this.builder.extensionNameMap.get(e);return r?r.getExtensionDependency():void 0}getInitDependency(e){const r=this.builder.getExtensionRep(e);return r===void 0&&U(315,this.extension.name,e.name),r.getExtensionInitDependency()}getDependency(e){const r=this.builder.getExtensionRep(e);return r===void 0&&U(315,this.extension.name,e.name),r.getExtensionDependency()}getState(){const e=this.state;return function(r){return r.id>=Te}(e)||U(316,String(e.id),String(Te)),e}getDirectDependentNames(){return this.builder.incomingEdges.get(this.extension.name)||mi}getPeerNameSet(){let e=this._peerNameSet;return e||(e=new Set((this.extension.peerDependencies||[]).map(([r])=>r)),this._peerNameSet=e),e}getExtensionDependency(){if(!this._dependency){const e=this.state;(function(r){return r.id>=ge})(e)||U(317,this.extension.name),this._dependency={config:e.config,init:e.initResult,output:e.output}}return this._dependency}}const Cr={tag:x.HISTORY_MERGE_TAG};function hi(){const t=x.$getRoot();t.isEmpty()&&t.append(x.$createParagraphNode())}const gi=x.defineExtension({config:x.safeCast({setOptions:Cr,updateOptions:Cr}),init:({$initialEditorState:t=hi})=>({$initialEditorState:t,initialized:!1}),afterRegistration(t,{updateOptions:e,setOptions:r},o){const s=o.getInitResult();if(!s.initialized){s.initialized=!0;const{$initialEditorState:a}=s;if(x.$isEditorState(a))t.setEditorState(a,r);else if(typeof a=="function")t.update(()=>{a(t)},e);else if(a&&(typeof a=="string"||typeof a=="object")){const l=t.parseEditorState(a);t.setEditorState(l,r)}}return()=>{}},name:"@lexical/extension/InitialState",nodes:[x.RootNode,x.TextNode,x.LineBreakNode,x.TabNode,x.ParagraphNode]}),Er=Symbol.for("@lexical/extension/LexicalBuilder");function Sr(){}function xi(t){throw t}function Xe(t){return Array.isArray(t)?t:[t]}const jn="0.40.0+prod.esm";class Oe{constructor(e){dt(this,"roots");dt(this,"extensionNameMap");dt(this,"outgoingConfigEdges");dt(this,"incomingEdges");dt(this,"conflicts");dt(this,"_sortedExtensionReps");dt(this,"PACKAGE_VERSION");this.outgoingConfigEdges=new Map,this.incomingEdges=new Map,this.extensionNameMap=new Map,this.conflicts=new Map,this.PACKAGE_VERSION=jn,this.roots=e;for(const r of e)this.addExtension(r)}static fromExtensions(e){const r=[Xe(gi)];for(const o of e)r.push(Xe(o));return new Oe(r)}static maybeFromEditor(e){const r=e[Er];return r&&(r.PACKAGE_VERSION!==jn&&U(292,r.PACKAGE_VERSION,jn),r instanceof Oe||U(293)),r}static fromEditor(e){const r=Oe.maybeFromEditor(e);return r===void 0&&U(294),r}constructEditor(){const{$initialEditorState:e,onError:r,...o}=this.buildCreateEditorArgs(),s=Object.assign(x.createEditor({...o,...r?{onError:a=>{r(a,s)}}:{}}),{[Er]:this});for(const a of this.sortedExtensionReps())a.build(s);return s}buildEditor(){let e=Sr;function r(){try{e()}finally{e=Sr}}const o=Object.assign(this.constructEditor(),{dispose:r,[Symbol.dispose]:r});return e=x.mergeRegister(this.registerEditor(o),()=>o.setRootElement(null)),o}hasExtensionByName(e){return this.extensionNameMap.has(e)}getExtensionRep(e){const r=this.extensionNameMap.get(e.name);if(r)return r.extension!==e&&U(295,e.name),r}addEdge(e,r,o){const s=this.outgoingConfigEdges.get(e);s?s.set(r,o):this.outgoingConfigEdges.set(e,new Map([[r,o]]));const a=this.incomingEdges.get(r);a?a.add(e):this.incomingEdges.set(r,new Set([e]))}addExtension(e){this._sortedExtensionReps!==void 0&&U(296);const r=Xe(e),[o]=r;typeof o.name!="string"&&U(297,typeof o.name);let s=this.extensionNameMap.get(o.name);if(s!==void 0&&s.extension!==o&&U(298,o.name),!s){s=new fi(this,o),this.extensionNameMap.set(o.name,s);const a=this.conflicts.get(o.name);typeof a=="string"&&U(299,o.name,a);for(const l of o.conflictsWith||[])this.extensionNameMap.has(l)&&U(299,o.name,l),this.conflicts.set(l,o.name);for(const l of o.dependencies||[]){const c=Xe(l);this.addEdge(o.name,c[0].name,c.slice(1)),this.addExtension(c)}for(const[l,c]of o.peerDependencies||[])this.addEdge(o.name,l,c?[c]:[])}}sortedExtensionReps(){if(this._sortedExtensionReps)return this._sortedExtensionReps;const e=[],r=(o,s)=>{let a=o.state;if(ho(a))return;const l=o.extension.name;var c;yn(a)||U(300,l,s||"[unknown]"),yn(c=a)||U(304,String(c.id),String(Bn)),a=Object.assign(c,{id:An}),o.state=a;const d=this.outgoingConfigEdges.get(l);if(d)for(const w of d.keys()){const p=this.extensionNameMap.get(w);p&&r(p,l)}a=ui(a),o.state=a,e.push(o)};for(const o of this.extensionNameMap.values())yn(o.state)&&r(o);for(const o of e)for(const[s,a]of this.outgoingConfigEdges.get(o.extension.name)||[])if(a.length>0){const l=this.extensionNameMap.get(s);if(l)for(const c of a)l.configs.add(c)}for(const[o,...s]of this.roots)if(s.length>0){const a=this.extensionNameMap.get(o.name);a===void 0&&U(301,o.name);for(const l of s)a.configs.add(l)}return this._sortedExtensionReps=e,this._sortedExtensionReps}registerEditor(e){const r=this.sortedExtensionReps(),o=new AbortController,s=[()=>o.abort()],a=o.signal;for(const l of r){const c=l.register(e,a);c&&s.push(c)}for(const l of r){const c=l.afterRegistration(e);c&&s.push(c)}return x.mergeRegister(...s)}buildCreateEditorArgs(){const e={},r=new Set,o=new Map,s=new Map,a={},l={},c=this.sortedExtensionReps();for(const p of c){const{extension:u}=p;if(u.onError!==void 0&&(e.onError=u.onError),u.disableEvents!==void 0&&(e.disableEvents=u.disableEvents),u.parentEditor!==void 0&&(e.parentEditor=u.parentEditor),u.editable!==void 0&&(e.editable=u.editable),u.namespace!==void 0&&(e.namespace=u.namespace),u.$initialEditorState!==void 0&&(e.$initialEditorState=u.$initialEditorState),u.nodes)for(const h of pi(u)){if(typeof h!="function"){const f=o.get(h.replace);f&&U(302,u.name,h.replace.name,f.extension.name),o.set(h.replace,p)}r.add(h)}if(u.html){if(u.html.export)for(const[h,f]of u.html.export.entries())s.set(h,f);u.html.import&&Object.assign(a,u.html.import)}u.theme&&mo(l,u.theme)}Object.keys(l).length>0&&(e.theme=l),r.size&&(e.nodes=[...r]);const d=Object.keys(a).length>0,w=s.size>0;(d||w)&&(e.html={},d&&(e.html.import=a),w&&(e.html.export=s));for(const p of c)p.init(e);return e.onError||(e.onError=xi),e}}const bi=new Set,Rr=x.defineExtension({build(t,e,r){const o=r.getDependency(On).output,s=Le({watchedNodeKeys:new Map}),a=uo(()=>{},()=>Xt(()=>{const l=a.peek(),{watchedNodeKeys:c}=s.value;let d,w=!1;o.value.read(()=>{if(x.$getSelection())for(const[p,u]of c.entries()){if(u.size===0){c.delete(p);continue}const h=x.$getNodeByKey(p),f=h&&h.isSelected()||!1;w=w||f!==(!!l&&l.has(p)),f&&(d=d||new Set,d.add(p))}}),!w&&d&&l&&d.size===l.size||(a.value=d)}));return{watchNodeKey:function(l){const c=wi(()=>(a.value||bi).has(l)),{watchedNodeKeys:d}=s.peek();let w=d.get(l);const p=w!==void 0;return w=w||new Set,w.add(c),p||(d.set(l,w),s.value={watchedNodeKeys:d}),c}}},dependencies:[On],name:"@lexical/extension/NodeSelection"});x.createCommand("INSERT_HORIZONTAL_RULE_COMMAND");class je extends x.DecoratorNode{static getType(){return"horizontalrule"}static clone(e){return new je(e.__key)}static importJSON(e){return go().updateFromJSON(e)}static importDOM(){return{hr:()=>({conversion:vi,priority:0})}}exportDOM(){return{element:document.createElement("hr")}}createDOM(e){const r=document.createElement("hr");return x.addClassNamesToElement(r,e.theme.hr),r}getTextContent(){return` +`}isInline(){return!1}updateDOM(){return!1}}function vi(){return{node:go()}}function go(){return x.$create(je)}function yi(t){return t instanceof je}x.defineExtension({dependencies:[On,Rr],name:"@lexical/extension/HorizontalRule",nodes:()=>[je],register(t,e,r){const{watchNodeKey:o}=r.getDependency(Rr).output,s=Le({nodeSelections:new Map}),a=t._config.theme.hrSelected??"selected";return x.mergeRegister(t.registerCommand(x.CLICK_COMMAND,l=>{if(x.isDOMNode(l.target)){const c=x.$getNodeFromDOMNode(l.target);if(yi(c))return function(d,w=!1){const p=x.$getSelection(),u=d.isSelected(),h=d.getKey();let f;w&&x.$isNodeSelection(p)?f=p:(f=x.$createNodeSelection(),x.$setSelection(f)),u?f.delete(h):f.add(h)}(c,l.shiftKey),!0}return!1},x.COMMAND_PRIORITY_LOW),t.registerMutationListener(je,(l,c)=>{ci(()=>{let d=!1;const{nodeSelections:w}=s.peek();for(const[p,u]of l.entries())if(u==="destroyed")w.delete(p),d=!0;else{const h=w.get(p),f=t.getElementByKey(p);h?h.domNode.value=f:(d=!0,w.set(p,{domNode:Le(f),selectedSignal:o(p)}))}d&&(s.value={nodeSelections:w})})}),Xt(()=>{const l=[];for(const{domNode:c,selectedSignal:d}of s.value.nodeSelections.values())l.push(Xt(()=>{const w=c.value;w&&(d.value?x.addClassNamesToElement(w,a):x.removeClassNamesFromElement(w,a))}));return x.mergeRegister(...l)}))}});function ji(t,e){return x.mergeRegister(t.registerCommand(x.KEY_TAB_COMMAND,r=>{const o=x.$getSelection();if(!x.$isRangeSelection(o))return!1;r.preventDefault();const s=function(a){if(a.getNodes().filter(u=>x.$isBlockElementNode(u)&&u.canIndent()).length>0)return!0;const l=a.anchor,c=a.focus,d=c.isBefore(l)?c:l,w=d.getNode(),p=jr(w);if(p.canIndent()){const u=p.getKey();let h=x.$createRangeSelection();if(h.anchor.set(u,0,"element"),h.focus.set(u,0,"element"),h=x.$normalizeSelection__EXPERIMENTAL(h),h.anchor.is(d))return!0}return!1}(o)?r.shiftKey?x.OUTDENT_CONTENT_COMMAND:x.INDENT_CONTENT_COMMAND:x.INSERT_TAB_COMMAND;return t.dispatchCommand(s,void 0)},x.COMMAND_PRIORITY_EDITOR),t.registerCommand(x.INDENT_CONTENT_COMMAND,()=>{const r=typeof e=="number"?e:e?e.peek():null;if(r==null)return!1;const o=x.$getSelection();if(!x.$isRangeSelection(o))return!1;const s=o.getNodes().map(a=>jr(a).getIndent());return Math.max(...s)+1>=r},x.COMMAND_PRIORITY_CRITICAL))}x.defineExtension({build:(t,e,r)=>dn(e),config:x.safeCast({disabled:!1,maxIndent:null}),name:"@lexical/extension/TabIndentation",register(t,e,r){const{disabled:o,maxIndent:s}=r.getOutput();return Xt(()=>{if(!o.value)return ji(t,s)})}});const Ni=x.defineExtension({name:"@lexical/react/ReactProvider"});function ki(){return x.$getRoot().getTextContent()}function _i(t,e=!0){if(t)return!1;let r=ki();return e&&(r=r.trim()),r===""}function Ci(t){if(!_i(t,!1))return!1;const e=x.$getRoot().getChildren(),r=e.length;if(r>1)return!1;for(let o=0;oCi(t)}function bo(t){const e=window.location.origin,r=o=>{if(o.origin!==e)return;const s=t.getRootElement();if(document.activeElement!==s)return;const a=o.data;if(typeof a=="string"){let l;try{l=JSON.parse(a)}catch{return}if(l&&l.protocol==="nuanria_messaging"&&l.type==="request"){const c=l.payload;if(c&&c.functionId==="makeChanges"){const d=c.args;if(d){const[w,p,u,h,f]=d;t.update(()=>{const g=x.$getSelection();if(x.$isRangeSelection(g)){const v=g.anchor;let b=v.getNode(),y=0,j=0;if(x.$isTextNode(b)&&w>=0&&p>=0&&(y=w,j=w+p,g.setTextNodeRange(b,y,b,j)),y===j&&u===""||(g.insertRawText(u),b=v.getNode()),x.$isTextNode(b)){y=h,j=h+f;const C=b.getTextContentSize();y=y>C?C:y,j=j>C?C:j,g.setTextNodeRange(b,y,b,j)}o.stopImmediatePropagation()}})}}}}};return window.addEventListener("message",r,!0),()=>{window.removeEventListener("message",r,!0)}}x.defineExtension({build:(t,e,r)=>dn(e),config:x.safeCast({disabled:typeof window>"u"}),name:"@lexical/dragon",register:(t,e,r)=>Xt(()=>r.getOutput().disabled.value?void 0:bo(t))});function Ei(t,...e){const r=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",t);for(const s of e)o.append("v",s);throw r.search=o.toString(),Error(`Minified Lexical error #${t}; visit ${r.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}const Kn=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?i.useLayoutEffect:i.useEffect;function Si({editor:t,ErrorBoundary:e}){return function(r,o){const[s,a]=i.useState(()=>r.getDecorators());return Kn(()=>r.registerDecoratorListener(l=>{br.flushSync(()=>{a(l)})}),[r]),i.useEffect(()=>{a(r.getDecorators())},[r]),i.useMemo(()=>{const l=[],c=Object.keys(s);for(let d=0;dr._onError(h),children:n.jsx(i.Suspense,{fallback:null,children:s[w]})}),u=r.getElementByKey(w);u!==null&&l.push(br.createPortal(p,u,w))}return l},[o,s,r])}(t,e)}function Ri({editor:t,ErrorBoundary:e}){return function(r){const o=Oe.maybeFromEditor(r);if(o&&o.hasExtensionByName(Ni.name)){for(const s of["@lexical/plain-text","@lexical/rich-text"])o.hasExtensionByName(s)&&Ei(320,s);return!0}return!1}(t)?null:n.jsx(Si,{editor:t,ErrorBoundary:e})}function Tr(t){return t.getEditorState().read(xo(t.isComposing()))}function Ti({contentEditable:t,placeholder:e=null,ErrorBoundary:r}){const[o]=$t();return function(s){Kn(()=>x.mergeRegister(Sn.registerRichText(s),bo(s)),[s])}(o),n.jsxs(n.Fragment,{children:[t,n.jsx(Mi,{content:e}),n.jsx(Ri,{editor:o,ErrorBoundary:r})]})}function Mi({content:t}){const[e]=$t(),r=function(s){const[a,l]=i.useState(()=>Tr(s));return Kn(()=>{function c(){const d=Tr(s);l(d)}return c(),x.mergeRegister(s.registerUpdateListener(()=>{c()}),s.registerEditableListener(()=>{c()}))},[s]),a}(e),o=oi();return r?typeof t=="function"?t(o):t:null}function Di({defaultSelection:t}){const[e]=$t();return i.useEffect(()=>{e.focus(()=>{const r=document.activeElement,o=e.getRootElement();o===null||r!==null&&o.contains(r)||o.focus({preventScroll:!0})},{defaultSelection:t})},[t,e]),null}const Ii=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?i.useLayoutEffect:i.useEffect;function Oi({onClear:t}){const[e]=$t();return Ii(()=>po(e,t),[e,t]),null}const vo=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?i.useLayoutEffect:i.useEffect;function Ai({editor:t,ariaActiveDescendant:e,ariaAutoComplete:r,ariaControls:o,ariaDescribedBy:s,ariaErrorMessage:a,ariaExpanded:l,ariaInvalid:c,ariaLabel:d,ariaLabelledBy:w,ariaMultiline:p,ariaOwns:u,ariaRequired:h,autoCapitalize:f,className:g,id:v,role:b="textbox",spellCheck:y=!0,style:j,tabIndex:C,"data-testid":M,...F},$){const[_,T]=i.useState(t.isEditable()),E=i.useCallback(P=>{P&&P.ownerDocument&&P.ownerDocument.defaultView?t.setRootElement(P):t.setRootElement(null)},[t]),R=i.useMemo(()=>function(...P){return L=>{for(const I of P)typeof I=="function"?I(L):I!=null&&(I.current=L)}}($,E),[E,$]);return vo(()=>(T(t.isEditable()),t.registerEditableListener(P=>{T(P)})),[t]),n.jsx("div",{"aria-activedescendant":_?e:void 0,"aria-autocomplete":_?r:"none","aria-controls":_?o:void 0,"aria-describedby":s,...a!=null?{"aria-errormessage":a}:{},"aria-expanded":_&&b==="combobox"?!!l:void 0,...c!=null?{"aria-invalid":c}:{},"aria-label":d,"aria-labelledby":w,"aria-multiline":p,"aria-owns":_?u:void 0,"aria-readonly":!_||void 0,"aria-required":h,autoCapitalize:f,className:g,contentEditable:_,"data-testid":M,id:v,ref:R,role:b,spellCheck:y,style:j,tabIndex:C,...F})}const Pi=i.forwardRef(Ai);function Mr(t){return t.getEditorState().read(xo(t.isComposing()))}const Li=i.forwardRef($i);function $i(t,e){const{placeholder:r,...o}=t,[s]=$t();return n.jsxs(n.Fragment,{children:[n.jsx(Pi,{editor:s,...o,ref:e}),r!=null&&n.jsx(Vi,{editor:s,content:r})]})}function Vi({content:t,editor:e}){const r=function(l){const[c,d]=i.useState(()=>Mr(l));return vo(()=>{function w(){const p=Mr(l);d(p)}return w(),x.mergeRegister(l.registerUpdateListener(()=>{w()}),l.registerEditableListener(()=>{w()}))},[l]),c}(e),[o,s]=i.useState(e.isEditable());if(i.useLayoutEffect(()=>(s(e.isEditable()),e.registerEditableListener(l=>{s(l)})),[e]),!r)return null;let a=null;return typeof t=="function"?a=t(o):t!==null&&(a=t),a===null?null:n.jsx("div",{"aria-hidden":!0,children:a})}function Fi({placeholder:t,className:e,placeholderClassName:r}){return n.jsx(Li,{className:e??"ContentEditable__root tw-relative tw-block tw-min-h-11 tw-overflow-auto tw-px-3 tw-py-3 tw-text-sm tw-outline-none","aria-placeholder":t,placeholder:n.jsx("div",{className:r??"tw-pointer-events-none tw-absolute tw-top-0 tw-select-none tw-overflow-hidden tw-text-ellipsis tw-px-3 tw-py-3 tw-text-sm tw-text-muted-foreground",children:t})})}const yo=i.createContext(void 0);function zi({activeEditor:t,$updateToolbar:e,blockType:r,setBlockType:o,showModal:s,children:a}){const l=i.useMemo(()=>({activeEditor:t,$updateToolbar:e,blockType:r,setBlockType:o,showModal:s}),[t,e,r,o,s]);return n.jsx(yo.Provider,{value:l,children:a})}function jo(){const t=i.useContext(yo);if(!t)throw new Error("useToolbarContext must be used within a ToolbarContext provider");return t}function Gi(){const[t,e]=i.useState(void 0),r=i.useCallback(()=>{e(void 0)},[]),o=i.useMemo(()=>{if(t===void 0)return;const{title:a,content:l}=t;return n.jsx(Da,{open:!0,onOpenChange:r,children:n.jsxs(Hr,{children:[n.jsx(Yr,{children:n.jsx(Xr,{children:a})}),l]})})},[t,r]),s=i.useCallback((a,l,c=!1)=>{e({closeOnClickOutside:c,content:l(r),title:a})},[r]);return[o,s]}function Bi({children:t}){const[e]=$t(),[r,o]=i.useState(e),[s,a]=i.useState("paragraph"),[l,c]=Gi(),d=()=>{};return i.useEffect(()=>r.registerCommand(x.SELECTION_CHANGE_COMMAND,(w,p)=>(o(p),!1),x.COMMAND_PRIORITY_CRITICAL),[r]),n.jsxs(zi,{activeEditor:r,$updateToolbar:d,blockType:s,setBlockType:a,showModal:c,children:[l,t({blockType:s})]})}function Ki(t){const[e]=$t(),{activeEditor:r}=jo();i.useEffect(()=>r.registerCommand(x.SELECTION_CHANGE_COMMAND,()=>{const o=x.$getSelection();return o&&t(o),!1},x.COMMAND_PRIORITY_CRITICAL),[e,t]),i.useEffect(()=>{r.getEditorState().read(()=>{const o=x.$getSelection();o&&t(o)})},[r,t])}const No=Zt.cva("pr-twp tw-inline-flex tw-items-center tw-justify-center tw-rounded-md tw-text-sm tw-font-medium tw-ring-offset-background tw-transition-colors hover:tw-bg-muted hover:tw-text-muted-foreground focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-pointer-events-none disabled:tw-opacity-50 data-[state=on]:tw-bg-accent data-[state=on]:tw-text-accent-foreground",{variants:{variant:{default:"tw-bg-transparent",outline:"tw-border tw-border-input tw-bg-transparent hover:tw-bg-accent hover:tw-text-accent-foreground"},size:{default:"tw-h-10 tw-px-3",sm:"tw-h-9 tw-px-2.5",lg:"tw-h-11 tw-px-5"}},defaultVariants:{variant:"default",size:"default"}}),qi=i.forwardRef(({className:t,variant:e,size:r,...o},s)=>n.jsx(Kr.Root,{ref:s,className:m(No({variant:e,size:r,className:t})),...o}));qi.displayName=Kr.Root.displayName;const ko=i.createContext({size:"default",variant:"default"}),pn=i.forwardRef(({className:t,variant:e,size:r,children:o,...s},a)=>{const l=st();return n.jsx(ln.Root,{ref:a,className:m("pr-twp tw-flex tw-items-center tw-justify-center tw-gap-1",t),...s,dir:l,children:n.jsx(ko.Provider,{value:{variant:e,size:r},children:o})})});pn.displayName=ln.Root.displayName;const ve=i.forwardRef(({className:t,children:e,variant:r,size:o,...s},a)=>{const l=i.useContext(ko);return n.jsx(ln.Item,{ref:a,className:m(No({variant:l.variant||r,size:l.size||o}),t),...s,children:e})});ve.displayName=ln.Item.displayName;const Dr=[{format:"bold",icon:k.BoldIcon,label:"Bold"},{format:"italic",icon:k.ItalicIcon,label:"Italic"},{format:"underline",icon:k.UnderlineIcon,label:"Underline"},{format:"strikethrough",icon:k.StrikethroughIcon,label:"Strikethrough"}];function Ui(){const{activeEditor:t}=jo(),[e,r]=i.useState([]),o=i.useCallback(s=>{if(x.$isRangeSelection(s)||ma.$isTableSelection(s)){const a=[];Dr.forEach(({format:l})=>{s.hasFormat(l)&&a.push(l)}),r(l=>l.length!==a.length||!a.every(c=>l.includes(c))?a:l)}},[]);return Ki(o),n.jsx(pn,{type:"multiple",value:e,onValueChange:r,variant:"outline",size:"sm",children:Dr.map(({format:s,icon:a,label:l})=>n.jsx(ve,{value:s,"aria-label":l,onClick:()=>{t.dispatchCommand(x.FORMAT_TEXT_COMMAND,s)},children:n.jsx(a,{className:"tw-h-4 tw-w-4"})},s))})}function Hi({onClear:t}){const[e]=$t();i.useEffect(()=>{t&&t(()=>{e.dispatchCommand(x.CLEAR_EDITOR_COMMAND,void 0)})},[e,t])}function Yi({placeholder:t="Start typing ...",autoFocus:e=!1,onClear:r}){const[,o]=i.useState(void 0),s=a=>{a!==void 0&&o(a)};return n.jsxs("div",{className:"tw-relative",children:[n.jsx(Bi,{children:()=>n.jsx("div",{className:"tw-sticky tw-top-0 tw-z-10 tw-flex tw-gap-2 tw-overflow-auto tw-border-b tw-p-1",children:n.jsx(Ui,{})})}),n.jsxs("div",{className:"tw-relative",children:[n.jsx(Ti,{contentEditable:n.jsx("div",{ref:s,children:n.jsx(Fi,{placeholder:t})}),ErrorBoundary:ei}),e&&n.jsx(Di,{defaultSelection:"rootEnd"}),n.jsx(Hi,{onClear:r}),n.jsx(Oi,{})]})]})}const Xi={namespace:"commentEditor",theme:Fn,nodes:zn,onError:t=>{console.error(t)}};function Qe({editorState:t,editorSerializedState:e,onChange:r,onSerializedChange:o,placeholder:s="Start typingโ€ฆ",autoFocus:a=!1,onClear:l,className:c}){return n.jsx("div",{className:m("pr-twp tw-overflow-hidden tw-rounded-lg tw-border tw-bg-background tw-shadow",c),children:n.jsx(Xa,{initialConfig:{...Xi,...t?{editorState:t}:{},...e?{editorState:JSON.stringify(e)}:{}},children:n.jsxs(xt,{children:[n.jsx(Yi,{placeholder:s,autoFocus:a,onClear:l}),n.jsx(Ja,{ignoreSelectionChange:!0,onChange:d=>{r==null||r(d),o==null||o(d.toJSON())}})]})})})}function Wi(t,e){const r=x.isDOMDocumentNode(e)?e.body.childNodes:e.childNodes;let o=[];const s=[];for(const a of r)if(!Co.has(a.nodeName)){const l=Eo(a,t,s,!1);l!==null&&(o=o.concat(l))}return function(a){for(const l of a)l.getNextSibling()instanceof x.ArtificialNode__DO_NOT_USE&&l.insertAfter(x.$createLineBreakNode());for(const l of a){const c=l.getChildren();for(const d of c)l.insertBefore(d);l.remove()}}(s),o}function Ji(t,e){if(typeof document>"u"||typeof window>"u"&&global.window===void 0)throw new Error("To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom before calling this function.");const r=document.createElement("div"),o=x.$getRoot().getChildren();for(let s=0;s{const g=new x.ArtificialNode__DO_NOT_USE;return r.push(g),g}:x.$createParagraphNode)),c==null?h.length>0?l=l.concat(h):x.isBlockDomNode(t)&&function(g){return g.nextSibling==null||g.previousSibling==null?!1:x.isInlineDomNode(g.nextSibling)&&x.isInlineDomNode(g.previousSibling)}(t)&&(l=l.concat(x.$createLineBreakNode())):x.$isElementNode(c)&&c.append(...h),l}function Zi(t,e,r){const o=t.style.textAlign,s=[];let a=[];for(let l=0;le&&"text"in e&&e.text.trim().length>0?!0:!e||!("children"in e)?!1:Ro(e.children)):!1}function kt(t){var e;return(e=t==null?void 0:t.root)!=null&&e.children?Ro(t.root.children):!1}function Qi(t){if(!t||t.trim()==="")throw new Error("Input HTML is empty");const e=Fr.createHeadlessEditor({namespace:"EditorUtils",theme:Fn,nodes:zn,onError:o=>{console.error(o)}});let r;if(e.update(()=>{const s=new DOMParser().parseFromString(t,"text/html"),a=Wi(e,s);x.$getRoot().clear(),x.$insertNodes(a)},{discrete:!0}),e.getEditorState().read(()=>{r=e.getEditorState().toJSON()}),!r)throw new Error("Failed to convert HTML to editor state");return r}function tn(t){const e=Fr.createHeadlessEditor({namespace:"EditorUtils",theme:Fn,nodes:zn,onError:s=>{console.error(s)}}),r=e.parseEditorState(JSON.stringify(t));e.setEditorState(r);let o="";return e.getEditorState().read(()=>{o=Ji(e)}),o=o.replace(/\s+style="[^"]*"/g,"").replace(/\s+class="[^"]*"/g,"").replace(/(.*?)<\/span>/g,"$1").replace(/]*>(.*?)<\/strong><\/b>/g,"$1").replace(/]*>(.*?)<\/b><\/strong>/g,"$1").replace(/]*>(.*?)<\/em><\/i>/g,"$1").replace(/]*>(.*?)<\/i><\/em>/g,"$1").replace(/]*>(.*?)<\/span><\/u>/g,"$1").replace(/]*>(.*?)<\/span><\/s>/g,"$1").replace(//gi,"
"),o}function qn(t){return["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End"].includes(t.key)?(t.stopPropagation(),!0):!1}const tl={root:{children:[{children:[{detail:0,format:0,mode:"normal",style:"",text:"",type:"text",version:1}],direction:"ltr",format:"",indent:0,type:"paragraph",version:1,textFormat:0,textStyle:""}],direction:"ltr",format:"",indent:0,type:"root",version:1}};function Nn(t,e){return t===""?e["%commentEditor_unassigned%"]??"Unassigned":t==="Team"?e["%commentEditor_team%"]??"Team":t}function el({assignableUsers:t,onSave:e,onClose:r,localizedStrings:o}){const[s,a]=i.useState(tl),[l,c]=i.useState(void 0),[d,w]=i.useState(!1),p=i.useRef(void 0),u=i.useRef(null);i.useEffect(()=>{let y=!0;const j=u.current;if(!j)return;const C=setTimeout(()=>{y&&So(j)},300);return()=>{y=!1,clearTimeout(C)}},[]);const h=i.useCallback(()=>{if(!kt(s))return;const y=tn(s);e(y,l)},[s,e,l]),f=o["%commentEditor_placeholder%"]??"Type your comment here...",g=o["%commentEditor_saveButton_tooltip%"]??"Save comment",v=o["%commentEditor_cancelButton_tooltip%"]??"Cancel",b=o["%commentEditor_assignTo_label%"]??"Assign to";return n.jsxs("div",{className:"pr-twp tw-grid tw-gap-3",children:[n.jsxs("div",{className:"tw-flex tw-items-center tw-justify-between",children:[n.jsx("span",{className:"tw-text-sm tw-font-medium",children:b}),n.jsxs("div",{className:"tw-flex tw-gap-2",children:[n.jsx(xt,{children:n.jsxs(jt,{children:[n.jsx(Nt,{asChild:!0,children:n.jsx(V,{onClick:r,className:"tw-h-6 tw-w-6",size:"icon",variant:"secondary",children:n.jsx(k.X,{})})}),n.jsx(bt,{children:n.jsx("p",{children:v})})]})}),n.jsx(xt,{children:n.jsxs(jt,{children:[n.jsx(Nt,{asChild:!0,children:n.jsx(V,{onClick:h,className:"tw-h-6 tw-w-6",size:"icon",variant:"default",disabled:!kt(s),children:n.jsx(k.Check,{})})}),n.jsx(bt,{children:n.jsx("p",{children:g})})]})})]})]}),n.jsx("div",{className:"tw-flex tw-items-center tw-gap-2",children:n.jsxs(zt,{open:d,onOpenChange:w,children:[n.jsx(Gt,{asChild:!0,children:n.jsxs(V,{variant:"outline",className:"tw-flex tw-w-full tw-items-center tw-justify-start tw-gap-2",disabled:t.length===0,children:[n.jsx(k.AtSign,{className:"tw-h-4 tw-w-4"}),n.jsx("span",{children:Nn(l!==void 0?l:"",o)})]})}),n.jsx(Lt,{className:"tw-w-auto tw-p-0",align:"start",onKeyDown:y=>{y.key==="Escape"&&(y.stopPropagation(),w(!1))},children:n.jsx(At,{children:n.jsx(Pt,{children:t.map(y=>n.jsx(Et,{onSelect:()=>{c(y===""?void 0:y),w(!1)},className:"tw-flex tw-items-center",children:n.jsx("span",{children:Nn(y,o)})},y||"unassigned"))})})})]})}),n.jsx("div",{ref:u,role:"textbox",tabIndex:-1,className:"tw-outline-none",onKeyDownCapture:y=>{if(y.key==="Escape")y.preventDefault(),y.stopPropagation(),r();else if(y.key==="Enter"){const j=/Macintosh/i.test(navigator.userAgent);(j&&y.metaKey||!j&&y.ctrlKey)&&(y.preventDefault(),y.stopPropagation(),kt(s)&&h())}},onKeyDown:y=>{qn(y),(y.key==="Enter"||y.key===" ")&&y.stopPropagation()},children:n.jsx(Qe,{editorSerializedState:s,onSerializedChange:y=>a(y),placeholder:f,onClear:y=>{p.current=y}})})]})}const nl=Object.freeze(["%commentEditor_placeholder%","%commentEditor_saveButton_tooltip%","%commentEditor_cancelButton_tooltip%","%commentEditor_assignTo_label%","%commentEditor_unassigned%","%commentEditor_team%"]),rl=["%comment_assign_team%","%comment_assign_unassigned%","%comment_assigned_to%","%comment_assigning_to%","%comment_dateAtTime%","%comment_date_today%","%comment_date_yesterday%","%comment_deleteComment%","%comment_editComment%","%comment_replyOrAssign%","%comment_reopenResolved%","%comment_status_resolved%","%comment_status_todo%","%comment_thread_multiple_replies%","%comment_thread_single_reply%"],ol=["input","select","textarea","button"],sl=["button","textbox"],To=({options:t,onFocusChange:e,onOptionSelect:r,onCharacterPress:o})=>{const s=i.useRef(null),[a,l]=i.useState(void 0),[c,d]=i.useState(void 0),w=i.useCallback(f=>{l(f);const g=t.find(b=>b.id===f);g&&(e==null||e(g));const v=document.getElementById(f);v&&(v.scrollIntoView({block:"center"}),v.focus()),s.current&&s.current.setAttribute("aria-activedescendant",f)},[e,t]),p=i.useCallback(f=>{const g=t.find(v=>v.id===f);g&&(d(v=>v===f?void 0:f),r==null||r(g))},[r,t]),u=f=>{if(!f)return!1;const g=f.tagName.toLowerCase();if(f.isContentEditable||ol.includes(g))return!0;const v=f.getAttribute("role");if(v&&sl.includes(v))return!0;const b=f.getAttribute("tabindex");return b!==void 0&&b!=="-1"},h=i.useCallback(f=>{var _;const g=f.target,v=T=>T?document.getElementById(T):void 0,b=v(c),y=v(a);if(!!(b&&g&&b.contains(g)&&g!==b)&&u(g)){if(f.key==="Escape"||f.key==="ArrowLeft"&&!g.isContentEditable){if(c){f.preventDefault(),f.stopPropagation();const T=t.find(E=>E.id===c);T&&w(T.id)}return}if(f.key==="ArrowDown"||f.key==="ArrowUp"){if(!b)return;const T=Array.from(b.querySelectorAll('button:not([disabled]), input:not([disabled]):not([type="hidden"]), textarea:not([disabled]), select:not([disabled]), [href], [tabindex]:not([tabindex="-1"])'));if(T.length===0)return;const E=T.findIndex(P=>P===g);if(E===-1)return;let R;f.key==="ArrowDown"?R=Math.min(E+1,T.length-1):R=Math.max(E-1,0),R!==E&&(f.preventDefault(),f.stopPropagation(),(_=T[R])==null||_.focus());return}return}const M=t.findIndex(T=>T.id===a);let F=M;switch(f.key){case"ArrowDown":F=Math.min(M+1,t.length-1),f.preventDefault();break;case"ArrowUp":F=Math.max(M-1,0),f.preventDefault();break;case"Home":F=0,f.preventDefault();break;case"End":F=t.length-1,f.preventDefault();break;case" ":case"Enter":a&&p(a),f.preventDefault(),f.stopPropagation();return;case"ArrowRight":{const T=y;if(T){const E=T.querySelector('input:not([disabled]):not([type="hidden"]), textarea:not([disabled]), select:not([disabled])'),R=T.querySelector('button:not([disabled]), [href], [tabindex]:not([tabindex="-1"]), [contenteditable="true"]'),P=E??R;if(P){f.preventDefault(),P.focus();return}}break}default:f.key.length===1&&!f.metaKey&&!f.ctrlKey&&!f.altKey&&(u(g)||(o==null||o(f.key),f.preventDefault()));return}const $=t[F];$&&w($.id)},[t,w,a,c,p,o]);return{listboxRef:s,activeId:a,selectedId:c,handleKeyDown:h,focusOption:w}},Mo=Zt.cva("pr-twp tw-inline-flex tw-items-center tw-rounded-full tw-px-2.5 tw-py-0.5 tw-text-xs tw-font-semibold tw-transition-colors focus:tw-outline-none focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-2",{variants:{variant:{default:"tw-border tw-border-transparent tw-bg-primary tw-text-primary-foreground hover:tw-bg-primary/80",secondary:"tw-border tw-border-transparent tw-bg-secondary tw-text-secondary-foreground hover:tw-bg-secondary/80",muted:"tw-border tw-border-transparent tw-bg-muted tw-text-muted-foreground hover:tw-bg-muted/80",destructive:"tw-border tw-border-transparent tw-bg-destructive tw-text-destructive-foreground hover:tw-bg-destructive/80",outline:"tw-border tw-text-foreground",blueIndicator:"tw-w-[5px] tw-h-[5px] tw-bg-blue-400 tw-px-0",mutedIndicator:"tw-w-[5px] tw-h-[5px] tw-bg-zinc-400 tw-px-0",ghost:"hover:tw-bg-accent hover:tw-text-accent-foreground tw-text-mu"}},defaultVariants:{variant:"default"}}),le=i.forwardRef(({className:t,variant:e,...r},o)=>n.jsx("div",{ref:o,className:m("pr-twp",Mo({variant:e}),t),...r}));le.displayName="Badge";const Un=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,className:m("pr-twp tw-rounded-lg tw-border tw-bg-card tw-text-card-foreground tw-shadow-sm",t),...e}));Un.displayName="Card";const Do=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,className:m("pr-twp tw-flex tw-flex-col tw-space-y-1.5 tw-p-6",t),...e}));Do.displayName="CardHeader";const Io=i.forwardRef(({className:t,...e},r)=>n.jsx("h3",{ref:r,className:m("pr-twp tw-text-2xl tw-font-semibold tw-leading-none tw-tracking-tight",t),...e,children:e.children}));Io.displayName="CardTitle";const Oo=i.forwardRef(({className:t,...e},r)=>n.jsx("p",{ref:r,className:m("pr-twp tw-text-sm tw-text-muted-foreground",t),...e}));Oo.displayName="CardDescription";const Hn=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,className:m("pr-twp tw-p-6 tw-pt-0",t),...e}));Hn.displayName="CardContent";const Ao=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,className:m("pr-twp tw-flex tw-items-center tw-p-6 tw-pt-0",t),...e}));Ao.displayName="CardFooter";const ce=i.forwardRef(({className:t,orientation:e="horizontal",decorative:r=!0,...o},s)=>n.jsx(qr.Root,{ref:s,decorative:r,orientation:e,className:m("pr-twp tw-shrink-0 tw-bg-border",e==="horizontal"?"tw-h-[1px] tw-w-full":"tw-h-full tw-w-[1px]",t),...o}));ce.displayName=qr.Root.displayName;const Yn=i.forwardRef(({className:t,...e},r)=>n.jsx(Ce.Root,{ref:r,className:m("pr-twp tw-relative tw-flex tw-h-10 tw-w-10 tw-shrink-0 tw-overflow-hidden tw-rounded-full",t),...e}));Yn.displayName=Ce.Root.displayName;const Po=i.forwardRef(({className:t,...e},r)=>n.jsx(Ce.Image,{ref:r,className:m("pr-twp tw-aspect-square tw-h-full tw-w-full",t),...e}));Po.displayName=Ce.Image.displayName;const Xn=i.forwardRef(({className:t,...e},r)=>n.jsx(Ce.Fallback,{ref:r,className:m("pr-twp tw-flex tw-h-full tw-w-full tw-items-center tw-justify-center tw-rounded-full tw-bg-muted",t),...e}));Xn.displayName=Ce.Fallback.displayName;const Wn=i.createContext(void 0);function St(){const t=i.useContext(Wn);if(!t)throw new Error("useMenuContext must be used within a MenuContext.Provider.");return t}const Bt=Zt.cva("",{variants:{variant:{default:"",muted:"hover:tw-bg-muted hover:tw-text-foreground focus:tw-bg-muted focus:tw-text-foreground data-[state=open]:tw-bg-muted data-[state=open]:tw-text-foreground"}},defaultVariants:{variant:"default"}}),ue=X.Trigger,Jn=X.Group,Lo=X.Portal,$o=X.Sub,Vo=X.RadioGroup;function Qt({variant:t="default",...e}){const r=i.useMemo(()=>({variant:t}),[t]);return n.jsx(Wn.Provider,{value:r,children:n.jsx(X.Root,{...e})})}const Zn=i.forwardRef(({className:t,inset:e,children:r,...o},s)=>{const a=St();return n.jsxs(X.SubTrigger,{ref:s,className:m("tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none focus:tw-bg-accent data-[state=open]:tw-bg-accent",e&&"tw-pl-8",t,Bt({variant:a.variant})),...o,children:[r,n.jsx(k.ChevronRight,{className:"tw-ml-auto tw-h-4 tw-w-4"})]})});Zn.displayName=X.SubTrigger.displayName;const Qn=i.forwardRef(({className:t,...e},r)=>n.jsx(X.SubContent,{ref:r,className:m("pr-twp tw-z-50 tw-min-w-[8rem] tw-overflow-hidden tw-rounded-md tw-border tw-bg-popover tw-p-1 tw-text-popover-foreground tw-shadow-lg data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2",t),...e}));Qn.displayName=X.SubContent.displayName;const Kt=i.forwardRef(({className:t,sideOffset:e=4,children:r,...o},s)=>{const a=st();return n.jsx(X.Portal,{children:n.jsx(X.Content,{ref:s,sideOffset:e,className:m("pr-twp tw-z-50 tw-min-w-[8rem] tw-overflow-hidden tw-rounded-md tw-border tw-bg-popover tw-p-1 tw-text-popover-foreground tw-shadow-md data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2",t),...o,children:n.jsx("div",{dir:a,children:r})})})});Kt.displayName=X.Content.displayName;const $e=i.forwardRef(({className:t,inset:e,...r},o)=>{const s=st(),a=St();return n.jsx(X.Item,{ref:o,className:m("tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none tw-transition-colors focus:tw-bg-accent data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",e&&"tw-pl-8",t,Bt({variant:a.variant})),...r,dir:s})});$e.displayName=X.Item.displayName;const It=i.forwardRef(({className:t,children:e,checked:r,...o},s)=>{const a=St();return n.jsxs(X.CheckboxItem,{ref:s,className:m("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pe-2 tw-ps-8 tw-text-sm tw-outline-none tw-transition-colors focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",t,Bt({variant:a.variant})),checked:r,...o,children:[n.jsx("span",{className:"tw-absolute tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center ltr:tw-left-2 rtl:tw-right-2",children:n.jsx(X.ItemIndicator,{children:n.jsx(k.Check,{className:"tw-h-4 tw-w-4"})})}),e]})});It.displayName=X.CheckboxItem.displayName;const tr=i.forwardRef(({className:t,children:e,...r},o)=>{const s=St();return n.jsxs(X.RadioItem,{ref:o,className:m("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pe-2 tw-ps-8 tw-text-sm tw-outline-none tw-transition-colors focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",t,Bt({variant:s.variant})),...r,children:[n.jsx("span",{className:"tw-absolute tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center ltr:tw-left-2 rtl:tw-right-2",children:n.jsx(X.ItemIndicator,{children:n.jsx(k.Circle,{className:"tw-h-2 tw-w-2 tw-fill-current"})})}),e]})});tr.displayName=X.RadioItem.displayName;const Se=i.forwardRef(({className:t,inset:e,...r},o)=>n.jsx(X.Label,{ref:o,className:m("tw-px-2 tw-py-1.5 tw-text-sm tw-font-semibold",e&&"tw-pl-8",t),...r}));Se.displayName=X.Label.displayName;const me=i.forwardRef(({className:t,...e},r)=>n.jsx(X.Separator,{ref:r,className:m("tw--mx-1 tw-my-1 tw-h-px tw-bg-muted",t),...e}));me.displayName=X.Separator.displayName;function Fo({className:t,...e}){return n.jsx("span",{className:m("tw-ms-auto tw-text-xs tw-tracking-widest tw-opacity-60",t),...e})}Fo.displayName="DropdownMenuShortcut";function Je(t,e){return t===""?e["%comment_assign_unassigned%"]??"Unassigned":t==="Team"?e["%comment_assign_team%"]??"Team":t}function Ir({comment:t,isReply:e=!1,localizedStrings:r,isThreadExpanded:o=!1,handleUpdateComment:s,handleDeleteComment:a,onEditingChange:l,canEditOrDelete:c=!1}){const[d,w]=i.useState(!1),[p,u]=i.useState(),h=i.useRef(null);i.useEffect(()=>{if(!d)return;let M=!0;const F=h.current;if(!F)return;const $=setTimeout(()=>{M&&So(F)},300);return()=>{M=!1,clearTimeout($)}},[d]);const f=i.useCallback(()=>{w(!1),u(void 0),l==null||l(!1)},[l]),g=i.useCallback(async()=>{if(!p||!s)return;await s(t.id,tn(p))&&(w(!1),u(void 0),l==null||l(!1))},[p,s,t.id,l]),v=i.useMemo(()=>{const M=new Date(t.date),F=N.formatRelativeDate(M,r["%comment_date_today%"],r["%comment_date_yesterday%"]),$=M.toLocaleTimeString(void 0,{hour:"numeric",minute:"2-digit"});return N.formatReplacementString(r["%comment_dateAtTime%"],{date:F,time:$})},[t.date,r]),b=i.useMemo(()=>t.user,[t.user]),y=i.useMemo(()=>t.user.split(" ").map(M=>M[0]).join("").toUpperCase().slice(0,2),[t.user]),j=i.useMemo(()=>N.sanitizeHtml(t.contents),[t.contents]),C=i.useMemo(()=>{if(o&&c)return n.jsxs(n.Fragment,{children:[n.jsxs($e,{onClick:()=>{w(!0),u(Qi(t.contents)),l==null||l(!0)},children:[n.jsx(k.Pencil,{className:"tw-me-2 tw-h-4 tw-w-4"}),r["%comment_editComment%"]]}),n.jsxs($e,{onClick:async()=>{a&&await a(t.id)},children:[n.jsx(k.Trash2,{className:"tw-me-2 tw-h-4 tw-w-4"}),r["%comment_deleteComment%"]]})]})},[c,o,r,t.contents,t.id,a,l]);return n.jsxs("div",{className:m("tw-flex tw-w-full tw-flex-row tw-items-baseline tw-gap-3 tw-space-y-3",{"tw-text-sm":e}),children:[n.jsx(Yn,{className:"tw-h-8 tw-w-8",children:n.jsx(Xn,{className:"tw-text-xs tw-font-medium",children:y})}),n.jsxs("div",{className:"tw-flex tw-flex-1 tw-flex-col tw-gap-2",children:[n.jsxs("div",{className:"tw-flex tw-w-full tw-flex-row tw-flex-wrap tw-items-baseline tw-gap-x-2",children:[n.jsx("p",{className:"tw-text-sm tw-font-medium",children:b}),n.jsx("p",{className:"tw-text-xs tw-font-normal tw-text-muted-foreground",children:v}),n.jsx("div",{className:"tw-flex-1"}),e&&t.assignedUser!==void 0&&n.jsxs(le,{variant:"secondary",className:"tw-text-xs tw-font-normal",children:["โ†’ ",Je(t.assignedUser,r)]})]}),d&&n.jsxs("div",{role:"textbox",tabIndex:-1,className:"tw-flex tw-flex-col tw-gap-2",ref:h,onKeyDownCapture:M=>{M.key==="Escape"?(M.preventDefault(),M.stopPropagation(),f()):M.key==="Enter"&&M.shiftKey&&(M.preventDefault(),M.stopPropagation(),kt(p)&&g())},onKeyDown:M=>{qn(M),(M.key==="Enter"||M.key===" ")&&M.stopPropagation()},children:[n.jsx(Qe,{className:m('[&_[data-lexical-editor="true"]>blockquote]:tw-mt-0 [&_[data-lexical-editor="true"]>blockquote]:tw-border-s-0 [&_[data-lexical-editor="true"]>blockquote]:tw-ps-0 [&_[data-lexical-editor="true"]>blockquote]:tw-font-normal [&_[data-lexical-editor="true"]>blockquote]:tw-not-italic [&_[data-lexical-editor="true"]>blockquote]:tw-text-foreground'),editorSerializedState:p,onSerializedChange:M=>u(M)}),n.jsxs("div",{className:"tw-flex tw-flex-row tw-items-start tw-justify-end tw-gap-2",children:[n.jsx(V,{size:"icon",onClick:f,variant:"outline",className:"tw-flex tw-items-center tw-justify-center tw-rounded-md",children:n.jsx(k.X,{})}),n.jsx(V,{size:"icon",onClick:g,className:"tw-flex tw-items-center tw-justify-center tw-rounded-md",disabled:!kt(p),children:n.jsx(k.ArrowUp,{})})]})]}),!d&&n.jsxs(n.Fragment,{children:[t.status==="Resolved"&&n.jsx("div",{className:"tw-text-sm tw-italic",children:r["%comment_status_resolved%"]}),t.status==="Todo"&&e&&n.jsx("div",{className:"tw-text-sm tw-italic",children:r["%comment_status_todo%"]}),n.jsx("div",{className:m("tw-prose tw-items-start tw-gap-2 tw-break-words tw-text-sm tw-font-normal tw-text-foreground","tw-max-w-none","[&>blockquote]:tw-border-s-0 [&>blockquote]:tw-p-0 [&>blockquote]:tw-ps-0 [&>blockquote]:tw-font-normal [&>blockquote]:tw-not-italic [&>blockquote]:tw-text-foreground","tw-prose-quoteless",{"tw-line-clamp-3":!o}),dangerouslySetInnerHTML:{__html:j}})]})]}),C&&n.jsxs(Qt,{children:[n.jsx(ue,{asChild:!0,children:n.jsx(V,{variant:"ghost",size:"icon",children:n.jsx(k.MoreHorizontal,{})})}),n.jsx(Kt,{align:"end",children:C})]})]})}const Or={root:{children:[{children:[{detail:0,format:0,mode:"normal",style:"",text:"",type:"text",version:1}],direction:"ltr",format:"",indent:0,type:"paragraph",version:1,textFormat:0,textStyle:""}],direction:"ltr",format:"",indent:0,type:"root",version:1}};function al({classNameForVerseText:t,comments:e,localizedStrings:r,isSelected:o=!1,verseRef:s,assignedUser:a,currentUser:l,handleSelectThread:c,threadId:d,thread:w,threadStatus:p,handleAddCommentToThread:u,handleUpdateComment:h,handleDeleteComment:f,handleReadStatusChange:g,assignableUsers:v,canUserAddCommentToThread:b,canUserAssignThreadCallback:y,canUserResolveThreadCallback:j,canUserEditOrDeleteCommentCallback:C,isRead:M=!1,autoReadDelay:F=5,onVerseRefClick:$}){const[_,T]=i.useState(Or),E=o,[R,P]=i.useState(!1),[L,I]=i.useState(!1),[A,z]=i.useState(!1),[S,B]=i.useState(void 0),[at,lt]=i.useState(!1),[Vt,ct]=i.useState(!1),[rt,G]=i.useState(M),[tt,wt]=i.useState(!1),ot=i.useRef(void 0),[vt,Re]=i.useState(new Map);i.useEffect(()=>{let D=!0;return(async()=>{const Rt=j?await j(d):!1;D&&ct(Rt)})(),()=>{D=!1}},[d,j]),i.useEffect(()=>{let D=!0;if(!o){lt(!1),Re(new Map);return}return(async()=>{const Rt=y?await y(d):!1;D&<(Rt)})(),()=>{D=!1}},[o,d,y]);const qt=i.useMemo(()=>e.filter(D=>!D.deleted),[e]);i.useEffect(()=>{let D=!0;if(!o||!C){Re(new Map);return}return(async()=>{const Rt=new Map;await Promise.all(qt.map(async xr=>{const sa=await C(xr.id);D&&Rt.set(xr.id,sa)})),D&&Re(Rt)})(),()=>{D=!1}},[o,qt,C]);const te=i.useMemo(()=>qt[0],[qt]),qe=i.useRef(null),he=i.useRef(void 0),ee=i.useCallback(()=>{var D;(D=he.current)==null||D.call(he),T(Or)},[]),fn=i.useCallback(()=>{const D=!rt;G(D),wt(!D),g==null||g(d,D)},[rt,g,d]);i.useEffect(()=>{P(!1)},[o]),i.useEffect(()=>{if(o&&!rt&&!tt){const D=setTimeout(()=>{G(!0),g==null||g(d,!0)},F*1e3);return ot.current=D,()=>clearTimeout(D)}ot.current&&(clearTimeout(ot.current),ot.current=void 0)},[o,rt,tt,F,d,g]);const O=i.useMemo(()=>({singleReply:r["%comment_thread_single_reply%"],multipleReplies:r["%comment_thread_multiple_replies%"]}),[r]),K=i.useMemo(()=>{if(a===void 0)return;if(a==="")return r["%comment_assign_unassigned%"]??"Unassigned";const D=Je(a,r);return N.formatReplacementString(r["%comment_assigned_to%"],{assignedUser:D})},[a,r]),H=i.useMemo(()=>qt.slice(1),[qt]),Y=i.useMemo(()=>H.length??0,[H.length]),it=i.useMemo(()=>Y>0,[Y]),Ft=i.useMemo(()=>R||Y<=2?H:H.slice(-2),[H,Y,R]),mt=i.useMemo(()=>R||Y<=2?0:Y-2,[Y,R]),Ue=i.useMemo(()=>Y===1?O.singleReply:N.formatReplacementString(O.multipleReplies,{count:Y}),[Y,O]),ne=i.useMemo(()=>mt===1?O.singleReply:N.formatReplacementString(O.multipleReplies,{count:mt}),[mt,O]),hr=i.useCallback(async()=>{const D=kt(_)?tn(_):void 0;if(S!==void 0){await u({threadId:d,contents:D,assignedUser:S})&&(B(void 0),D&&ee());return}D&&await u({threadId:d,contents:D})&&ee()},[ee,_,u,S,d]),gr=i.useCallback(async D=>{const re=kt(_)?tn(_):void 0,Rt=await u({...D,contents:re,assignedUser:S??D.assignedUser});return Rt&&re&&ee(),Rt&&S!==void 0&&B(void 0),Rt},[ee,_,u,S]);return n.jsx(Un,{role:"option","aria-selected":o,id:d,className:m("tw-group tw-w-full tw-rounded-none tw-border-none tw-p-4 tw-outline-none tw-transition-all tw-duration-200 focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-1 focus:tw-ring-offset-background",{"tw-cursor-pointer hover:tw-shadow-md":!o},{"tw-bg-primary-foreground":!o&&p!=="Resolved"&&rt,"tw-bg-background":o&&p!=="Resolved"&&rt,"tw-bg-muted":p==="Resolved","tw-bg-blue-50":!rt&&!o&&p!=="Resolved"}),onClick:()=>{c(d)},tabIndex:-1,children:n.jsxs(Hn,{className:"tw-flex tw-flex-col tw-gap-2 tw-p-0",children:[n.jsxs("div",{className:"tw-flex tw-flex-col tw-content-center tw-items-start tw-gap-4",children:[n.jsxs("div",{className:"tw-flex tw-items-center tw-gap-2",children:[K&&n.jsx(le,{className:"tw-rounded-sm tw-bg-input tw-text-sm tw-font-normal tw-text-primary hover:tw-bg-input",children:K}),n.jsx(V,{variant:"ghost",size:"icon",onClick:D=>{D.stopPropagation(),fn()},className:"tw-text-muted-foreground tw-transition hover:tw-text-foreground","aria-label":rt?"Mark as unread":"Mark as read",children:rt?n.jsx(k.MailOpen,{}):n.jsx(k.Mail,{})}),Vt&&p!=="Resolved"&&n.jsx(V,{variant:"ghost",size:"icon",className:m("tw-ms-auto","tw-text-primary tw-transition-opacity tw-duration-200 hover:tw-bg-primary/10","tw-opacity-0 group-hover:tw-opacity-100"),onClick:D=>{D.stopPropagation(),gr({threadId:d,status:"Resolved"})},"aria-label":"Resolve thread",children:n.jsx(k.Check,{className:"tw-h-4 tw-w-4"})})]}),n.jsx("div",{className:"tw-flex tw-max-w-full tw-flex-wrap tw-items-baseline tw-gap-2",children:n.jsxs("p",{ref:qe,className:m("tw-flex-1 tw-overflow-hidden tw-text-ellipsis tw-text-sm tw-font-normal tw-text-muted-foreground",{"tw-overflow-visible tw-text-clip tw-whitespace-normal tw-break-words":E},{"tw-whitespace-nowrap":!E}),children:[s&&$?n.jsx(V,{variant:"ghost",size:"sm",className:"tw-h-auto tw-px-1 tw-py-0 tw-text-sm tw-font-normal tw-text-muted-foreground",onClick:D=>{D.stopPropagation(),$(w)},children:s}):s,n.jsxs("span",{className:t,children:[te.contextBefore,n.jsx("span",{className:"tw-font-bold",children:te.selectedText}),te.contextAfter]})]})}),n.jsx(Ir,{comment:te,localizedStrings:r,isThreadExpanded:o,threadStatus:p,handleAddCommentToThread:gr,handleUpdateComment:h,handleDeleteComment:f,onEditingChange:I,canEditOrDelete:vt.get(te.id)??!1,canUserResolveThread:Vt})]}),n.jsxs(n.Fragment,{children:[it&&!o&&n.jsxs("div",{className:"tw-flex tw-items-center tw-gap-5",children:[n.jsx("div",{className:"tw-w-8",children:n.jsx(ce,{})}),n.jsx("p",{className:"tw-text-sm tw-text-muted-foreground",children:Ue})]}),!o&&kt(_)&&n.jsx(Qe,{editorSerializedState:_,onSerializedChange:D=>T(D),placeholder:r["%comment_replyOrAssign%"]}),o&&n.jsxs(n.Fragment,{children:[mt>0&&n.jsxs("div",{className:"tw-flex tw-cursor-pointer tw-items-center tw-gap-5 tw-py-2",onClick:D=>{D.stopPropagation(),P(!0)},role:"button",tabIndex:0,onKeyDown:D=>{(D.key==="Enter"||D.key===" ")&&(D.preventDefault(),D.stopPropagation(),P(!0))},children:[n.jsx("div",{className:"tw-w-8",children:n.jsx(ce,{})}),n.jsxs("div",{className:"tw-flex tw-items-center tw-gap-2",children:[n.jsx("p",{className:"tw-text-sm tw-text-muted-foreground",children:ne}),R?n.jsx(k.ChevronUp,{}):n.jsx(k.ChevronDown,{})]})]}),Ft.map(D=>n.jsx("div",{children:n.jsx(Ir,{comment:D,localizedStrings:r,isReply:!0,isThreadExpanded:o,handleUpdateComment:h,handleDeleteComment:f,onEditingChange:I,canEditOrDelete:vt.get(D.id)??!1})},D.id)),b!==!1&&(!L||kt(_))&&n.jsxs("div",{role:"textbox",tabIndex:-1,className:"tw-w-full tw-space-y-2",onClick:D=>D.stopPropagation(),onKeyDownCapture:D=>{D.key==="Enter"&&D.shiftKey&&(D.preventDefault(),D.stopPropagation(),(kt(_)||S!==void 0)&&hr())},onKeyDown:D=>{qn(D),(D.key==="Enter"||D.key===" ")&&D.stopPropagation()},children:[n.jsx(Qe,{editorSerializedState:_,onSerializedChange:D=>T(D),placeholder:p==="Resolved"?r["%comment_reopenResolved%"]:r["%comment_replyOrAssign%"],autoFocus:!0,onClear:D=>{he.current=D}}),n.jsxs("div",{className:"tw-flex tw-flex-row tw-items-center tw-justify-end tw-gap-2",children:[S!==void 0&&n.jsx("span",{className:"tw-flex-1 tw-text-sm tw-text-muted-foreground",children:N.formatReplacementString(r["%comment_assigning_to%"]??"Assigning to: {assignedUser}",{assignedUser:Je(S,r)})}),n.jsxs(zt,{open:A,onOpenChange:z,children:[n.jsx(Gt,{asChild:!0,children:n.jsx(V,{size:"icon",variant:"outline",className:"tw-flex tw-items-center tw-justify-center tw-rounded-md",disabled:!at||!v||v.length===0||!v.includes(l),"aria-label":"Assign user",children:n.jsx(k.AtSign,{})})}),n.jsx(Lt,{className:"tw-w-auto tw-p-0",align:"end",onKeyDown:D=>{D.key==="Escape"&&(D.stopPropagation(),z(!1))},children:n.jsx(At,{children:n.jsx(Pt,{children:v==null?void 0:v.map(D=>n.jsx(Et,{onSelect:()=>{B(D!==a?D:void 0),z(!1)},className:"tw-flex tw-items-center",children:n.jsx("span",{children:Je(D,r)})},D||"unassigned"))})})})]}),n.jsx(V,{size:"icon",onClick:hr,className:"tw-flex tw-items-center tw-justify-center tw-rounded-md",disabled:!kt(_)&&S===void 0,"aria-label":"Submit comment",children:n.jsx(k.ArrowUp,{})})]})]})]})]})]})})}function il({className:t="",classNameForVerseText:e,threads:r,currentUser:o,localizedStrings:s,handleAddCommentToThread:a,handleUpdateComment:l,handleDeleteComment:c,handleReadStatusChange:d,assignableUsers:w,canUserAddCommentToThread:p,canUserAssignThreadCallback:u,canUserResolveThreadCallback:h,canUserEditOrDeleteCommentCallback:f,selectedThreadId:g,onSelectedThreadChange:v,onVerseRefClick:b}){const[y,j]=i.useState(new Set),[C,M]=i.useState();i.useEffect(()=>{g&&(j(I=>new Set(I).add(g)),M(g))},[g]);const F=r.filter(I=>I.comments.some(A=>!A.deleted)),$=F.map(I=>({id:I.id})),_=i.useCallback(I=>{j(A=>new Set(A).add(I.id)),M(I.id),v==null||v(I.id)},[v]),T=i.useCallback(I=>{const A=y.has(I);j(z=>{const S=new Set(z);return S.has(I)?S.delete(I):S.add(I),S}),M(I),v==null||v(A?void 0:I)},[y,v]),{listboxRef:E,activeId:R,handleKeyDown:P}=To({options:$,onOptionSelect:_}),L=i.useCallback(I=>{I.key==="Escape"?(C&&y.has(C)&&(j(A=>{const z=new Set(A);return z.delete(C),z}),M(void 0),v==null||v(void 0)),I.preventDefault(),I.stopPropagation()):P(I)},[C,y,P,v]);return n.jsx("div",{id:"comment-list",role:"listbox",tabIndex:0,ref:E,"aria-activedescendant":R??void 0,"aria-label":"Comments",className:m("tw-flex tw-w-full tw-flex-col tw-space-y-3 tw-outline-none focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-1 focus:tw-ring-offset-background",t),onKeyDown:L,children:F.map(I=>n.jsx("div",{id:I.id,className:m({"tw-opacity-60":I.status==="Resolved"}),children:n.jsx(al,{classNameForVerseText:e,comments:I.comments,localizedStrings:s,verseRef:I.verseRef,handleSelectThread:T,threadId:I.id,thread:I,isRead:I.isRead,isSelected:y.has(I.id),currentUser:o,assignedUser:I.assignedUser,threadStatus:I.status,handleAddCommentToThread:a,handleUpdateComment:l,handleDeleteComment:c,handleReadStatusChange:d,assignableUsers:w,canUserAddCommentToThread:p,canUserAssignThreadCallback:u,canUserResolveThreadCallback:h,canUserEditOrDeleteCommentCallback:f,onVerseRefClick:b})},I.id))})}function ll({table:t}){return n.jsxs(Qt,{children:[n.jsx(zr.DropdownMenuTrigger,{asChild:!0,children:n.jsxs(V,{variant:"outline",size:"sm",className:"tw-ml-auto tw-hidden tw-h-8 lg:tw-flex",children:[n.jsx(k.FilterIcon,{className:"tw-mr-2 tw-h-4 tw-w-4"}),"View"]})}),n.jsxs(Kt,{align:"end",className:"tw-w-[150px]",children:[n.jsx(Se,{children:"Toggle columns"}),n.jsx(me,{}),t.getAllColumns().filter(e=>e.getCanHide()).map(e=>n.jsx(It,{className:"tw-capitalize",checked:e.getIsVisible(),onCheckedChange:r=>e.toggleVisibility(!!r),children:e.id},e.id))]})]})}const we=Z.Root,zo=Z.Group,de=Z.Value,Go=Zt.cva("tw-flex tw-h-10 tw-w-full tw-items-center tw-justify-between tw-rounded-md tw-border tw-border-input tw-bg-background tw-px-3 tw-py-2 tw-text-sm tw-ring-offset-background placeholder:tw-text-muted-foreground focus:tw-outline-none focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-2 disabled:tw-cursor-not-allowed disabled:tw-opacity-50 [&>span]:tw-line-clamp-1",{variants:{size:{default:"tw-h-10 tw-px-4 tw-py-2",sm:"tw-h-8 tw-rounded-md tw-px-3",lg:"tw-h-11 tw-rounded-md tw-px-8",icon:"tw-h-10 tw-w-10"}},defaultVariants:{size:"default"}}),Wt=i.forwardRef(({className:t,children:e,size:r,...o},s)=>{const a=st();return n.jsxs(Z.Trigger,{className:m(Go({size:r,className:t})),ref:s,...o,dir:a,children:[e,n.jsx(Z.Icon,{asChild:!0,children:n.jsx(k.ChevronDown,{className:"tw-h-4 tw-w-4 tw-opacity-50"})})]})});Wt.displayName=Z.Trigger.displayName;const er=i.forwardRef(({className:t,...e},r)=>n.jsx(Z.ScrollUpButton,{ref:r,className:m("tw-flex tw-cursor-default tw-items-center tw-justify-center tw-py-1",t),...e,children:n.jsx(k.ChevronUp,{className:"tw-h-4 tw-w-4"})}));er.displayName=Z.ScrollUpButton.displayName;const nr=i.forwardRef(({className:t,...e},r)=>n.jsx(Z.ScrollDownButton,{ref:r,className:m("tw-flex tw-cursor-default tw-items-center tw-justify-center tw-py-1",t),...e,children:n.jsx(k.ChevronDown,{className:"tw-h-4 tw-w-4"})}));nr.displayName=Z.ScrollDownButton.displayName;const Jt=i.forwardRef(({className:t,children:e,position:r="popper",...o},s)=>{const a=st();return n.jsx(Z.Portal,{children:n.jsxs(Z.Content,{ref:s,className:m("pr-twp tw-relative tw-z-50 tw-max-h-96 tw-min-w-[8rem] tw-overflow-hidden tw-rounded-md tw-border tw-bg-popover tw-text-popover-foreground tw-shadow-md data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2",r==="popper"&&"data-[side=bottom]:tw-translate-y-1 data-[side=left]:tw--translate-x-1 data-[side=right]:tw-translate-x-1 data-[side=top]:tw--translate-y-1",t),position:r,...o,children:[n.jsx(er,{}),n.jsx(Z.Viewport,{className:m("tw-p-1",r==="popper"&&"tw-h-[var(--radix-select-trigger-height)] tw-w-full tw-min-w-[var(--radix-select-trigger-width)]"),children:n.jsx("div",{dir:a,children:e})}),n.jsx(nr,{})]})})});Jt.displayName=Z.Content.displayName;const Bo=i.forwardRef(({className:t,...e},r)=>n.jsx(Z.Label,{ref:r,className:m("tw-py-1.5 tw-pl-8 tw-pr-2 tw-text-sm tw-font-semibold",t),...e}));Bo.displayName=Z.Label.displayName;const gt=i.forwardRef(({className:t,children:e,...r},o)=>n.jsxs(Z.Item,{ref:o,className:m("tw-relative tw-flex tw-w-full tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pe-2 tw-ps-8 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",t),...r,children:[n.jsx("span",{className:"tw-absolute tw-start-2 tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center",children:n.jsx(Z.ItemIndicator,{children:n.jsx(k.Check,{className:"tw-h-4 tw-w-4"})})}),n.jsx(Z.ItemText,{children:e})]}));gt.displayName=Z.Item.displayName;const Ko=i.forwardRef(({className:t,...e},r)=>n.jsx(Z.Separator,{ref:r,className:m("tw--mx-1 tw-my-1 tw-h-px tw-bg-muted",t),...e}));Ko.displayName=Z.Separator.displayName;function cl({table:t}){return n.jsx("div",{className:"tw-flex tw-items-center tw-justify-between tw-px-2 tw-pb-3 tw-pt-3",children:n.jsxs("div",{className:"tw-flex tw-items-center tw-space-x-6 lg:tw-space-x-8",children:[n.jsxs("div",{className:"tw-flex-1 tw-text-sm tw-text-muted-foreground",children:[t.getFilteredSelectedRowModel().rows.length," of"," ",t.getFilteredRowModel().rows.length," row(s) selected"]}),n.jsxs("div",{className:"tw-flex tw-items-center tw-space-x-2",children:[n.jsx("p",{className:"tw-text-nowrap tw-text-sm tw-font-medium",children:"Rows per page"}),n.jsxs(we,{value:`${t.getState().pagination.pageSize}`,onValueChange:e=>{t.setPageSize(Number(e))},children:[n.jsx(Wt,{className:"tw-h-8 tw-w-[70px]",children:n.jsx(de,{placeholder:t.getState().pagination.pageSize})}),n.jsx(Jt,{side:"top",children:[10,20,30,40,50].map(e=>n.jsx(gt,{value:`${e}`,children:e},e))})]})]}),n.jsxs("div",{className:"tw-flex tw-w-[100px] tw-items-center tw-justify-center tw-text-sm tw-font-medium",children:["Page ",t.getState().pagination.pageIndex+1," of ",t.getPageCount()]}),n.jsxs("div",{className:"tw-flex tw-items-center tw-space-x-2",children:[n.jsxs(V,{variant:"outline",size:"icon",className:"tw-hidden tw-h-8 tw-w-8 tw-p-0 lg:tw-flex",onClick:()=>t.setPageIndex(0),disabled:!t.getCanPreviousPage(),children:[n.jsx("span",{className:"tw-sr-only",children:"Go to first page"}),n.jsx(k.ArrowLeftIcon,{className:"tw-h-4 tw-w-4"})]}),n.jsxs(V,{variant:"outline",size:"icon",className:"tw-h-8 tw-w-8 tw-p-0",onClick:()=>t.previousPage(),disabled:!t.getCanPreviousPage(),children:[n.jsx("span",{className:"tw-sr-only",children:"Go to previous page"}),n.jsx(k.ChevronLeftIcon,{className:"tw-h-4 tw-w-4"})]}),n.jsxs(V,{variant:"outline",size:"icon",className:"tw-h-8 tw-w-8 tw-p-0",onClick:()=>t.nextPage(),disabled:!t.getCanNextPage(),children:[n.jsx("span",{className:"tw-sr-only",children:"Go to next page"}),n.jsx(k.ChevronRightIcon,{className:"tw-h-4 tw-w-4"})]}),n.jsxs(V,{variant:"outline",size:"icon",className:"tw-hidden tw-h-8 tw-w-8 tw-p-0 lg:tw-flex",onClick:()=>t.setPageIndex(t.getPageCount()-1),disabled:!t.getCanNextPage(),children:[n.jsx("span",{className:"tw-sr-only",children:"Go to last page"}),n.jsx(k.ArrowRightIcon,{className:"tw-h-4 tw-w-4"})]})]})]})})}const Ar=` a[href], area[href], input:not([disabled]), @@ -11,33 +11,7 @@ embed, [contenteditable], tr:not([disabled]) -`;function wl(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)}function $e(t,e){const r=e?`${Or}, ${e}`:Or;return Array.from(t.querySelectorAll(r)).filter(o=>!o.hasAttribute("disabled")&&!o.getAttribute("aria-hidden")&&wl(o))}const Fe=i.forwardRef(({className:t,stickyHeader:e,...r},o)=>{const s=i.useRef(null);i.useEffect(()=>{typeof o=="function"?o(s.current):o&&"current"in o&&(o.current=s.current)},[o]),i.useEffect(()=>{const l=s.current;if(!l)return;const c=()=>{requestAnimationFrame(()=>{$e(l,'[tabindex]:not([tabindex="-1"])').forEach(p=>{p.setAttribute("tabindex","-1")})})};c();const d=new MutationObserver(()=>{c()});return d.observe(l,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["tabindex"]}),()=>{d.disconnect()}},[]);const a=l=>{const{current:c}=s;if(c){if(l.key==="ArrowDown"){l.preventDefault(),$e(c)[0].focus();return}l.key===" "&&document.activeElement===c&&l.preventDefault()}};return n.jsx("div",{className:m("pr-twp tw-relative tw-w-full",{"tw-p-1":e}),children:n.jsx("table",{tabIndex:0,onKeyDown:a,ref:s,className:m("tw-w-full tw-caption-bottom tw-text-sm tw-outline-none","focus:tw-relative focus:tw-z-10 focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-1 focus:tw-ring-offset-background",t),"aria-label":"Table","aria-labelledby":"table-label",...r})})});Fe.displayName="Table";const ze=i.forwardRef(({className:t,stickyHeader:e,...r},o)=>n.jsx("thead",{ref:o,className:m({"tw-sticky tw-top-[-1px] tw-z-20 tw-bg-background tw-drop-shadow-sm":e},"[&_tr]:tw-border-b",t),...r}));ze.displayName="TableHeader";const Ge=i.forwardRef(({className:t,...e},r)=>n.jsx("tbody",{ref:r,className:m("[&_tr:last-child]:tw-border-0",t),...e}));Ge.displayName="TableBody";const qo=i.forwardRef(({className:t,...e},r)=>n.jsx("tfoot",{ref:r,className:m("tw-border-t tw-bg-muted/50 tw-font-medium [&>tr]:last:tw-border-b-0",t),...e}));qo.displayName="TableFooter";function dl(t){i.useEffect(()=>{const e=t.current;if(!e)return;const r=o=>{if(e.contains(document.activeElement)){if(o.key==="ArrowRight"||o.key==="ArrowLeft"){o.preventDefault(),o.stopPropagation();const s=t.current?$e(t.current):[],a=s.indexOf(document.activeElement),l=o.key==="ArrowRight"?a+1:a-1;l>=0&&l{e.removeEventListener("keydown",r)}},[t])}function pl(t,e,r){let o;return r==="ArrowLeft"&&e>0?o=t[e-1]:r==="ArrowRight"&&eo.focus()),!0):!1}function ul(t,e,r){let o;return r==="ArrowDown"&&e0&&(o=t[e-1]),o?(requestAnimationFrame(()=>o.focus()),!0):!1}const Mt=i.forwardRef(({className:t,onKeyDown:e,onSelect:r,setFocusAlsoRunsSelect:o=!1,...s},a)=>{const l=i.useRef(null);i.useEffect(()=>{typeof a=="function"?a(l.current):a&&"current"in a&&(a.current=l.current)},[a]),dl(l);const c=i.useMemo(()=>l.current?$e(l.current):[],[l]),d=i.useCallback(p=>{const{current:u}=l;if(!u||!u.parentElement)return;const h=u.closest("table"),f=h?$e(h).filter(b=>b.tagName==="TR"):[],g=f.indexOf(u),v=c.indexOf(document.activeElement);if(p.key==="ArrowDown"||p.key==="ArrowUp")p.preventDefault(),ul(f,g,p.key);else if(p.key==="ArrowLeft"||p.key==="ArrowRight")p.preventDefault(),pl(c,v,p.key);else if(p.key==="Escape"){p.preventDefault();const b=u.closest("table");b&&b.focus()}e==null||e(p)},[l,c,e]),w=i.useCallback(p=>{o&&(r==null||r(p))},[o,r]);return n.jsx("tr",{ref:l,tabIndex:-1,onKeyDown:d,onFocus:w,className:m("tw-border-b tw-outline-none tw-transition-colors hover:tw-bg-muted/50","focus:tw-relative focus:tw-z-10 focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-1 focus:tw-ring-offset-background","data-[state=selected]:tw-bg-muted",t),...s})});Mt.displayName="TableRow";const Ne=i.forwardRef(({className:t,...e},r)=>n.jsx("th",{ref:r,className:m("tw-h-12 tw-px-4 tw-text-start tw-align-middle tw-font-medium tw-text-muted-foreground [&:has([role=checkbox])]:tw-pe-0",t),...e}));Ne.displayName="TableHead";const Yt=i.forwardRef(({className:t,...e},r)=>n.jsx("td",{ref:r,className:m("tw-p-4 tw-align-middle [&:has([role=checkbox])]:tw-pe-0",t),...e}));Yt.displayName="TableCell";const Uo=i.forwardRef(({className:t,...e},r)=>n.jsx("caption",{ref:r,className:m("tw-mt-4 tw-text-sm tw-text-muted-foreground",t),...e}));Uo.displayName="TableCaption";function tn({className:t,...e}){return n.jsx("div",{className:m("pr-twp tw-animate-pulse tw-rounded-md tw-bg-muted",t),...e})}function Ho({columns:t,data:e,enablePagination:r=!1,showPaginationControls:o=!1,showColumnVisibilityControls:s=!1,stickyHeader:a=!1,onRowClickHandler:l=()=>{},id:c,isLoading:d=!1,noResultsMessage:w}){var V;const[p,u]=i.useState([]),[h,f]=i.useState([]),[g,v]=i.useState({}),[b,y]=i.useState({}),j=i.useMemo(()=>e??[],[e]),C=ut.useReactTable({data:j,columns:t,getCoreRowModel:ut.getCoreRowModel(),...r&&{getPaginationRowModel:ut.getPaginationRowModel()},onSortingChange:u,getSortedRowModel:ut.getSortedRowModel(),onColumnFiltersChange:f,getFilteredRowModel:ut.getFilteredRowModel(),onColumnVisibilityChange:v,onRowSelectionChange:y,state:{sorting:p,columnFilters:h,columnVisibility:g,rowSelection:b}}),M=C.getVisibleFlatColumns();let F;return d?F=Array.from({length:10}).map((S,R)=>`skeleton-row-${R}`).map(S=>n.jsx(Mt,{children:n.jsx(Yt,{colSpan:M.length??t.length,className:"tw-border-0 tw-p-0",children:n.jsx("div",{className:"tw-w-full tw-py-2",children:n.jsx(tn,{className:"tw-h-14 tw-w-full tw-rounded-md"})})})},S)):((V=C.getRowModel().rows)==null?void 0:V.length)>0?F=C.getRowModel().rows.map(_=>n.jsx(Mt,{onClick:()=>l(_,C),"data-state":_.getIsSelected()&&"selected",children:_.getVisibleCells().map(T=>n.jsx(Yt,{children:ut.flexRender(T.column.columnDef.cell,T.getContext())},T.id))},_.id)):F=n.jsx(Mt,{children:n.jsx(Yt,{colSpan:t.length,className:"tw-h-24 tw-text-center",children:w})}),n.jsxs("div",{className:"pr-twp",id:c,children:[s&&n.jsx(ll,{table:C}),n.jsxs(Fe,{stickyHeader:a,children:[n.jsx(ze,{stickyHeader:a,children:C.getHeaderGroups().map(_=>n.jsx(Mt,{children:_.headers.map(T=>n.jsx(Ne,{children:T.isPlaceholder?void 0:ut.flexRender(T.column.columnDef.header,T.getContext())},T.id))},_.id))}),n.jsx(Ge,{children:F})]}),r&&n.jsxs("div",{className:"tw-flex tw-items-center tw-justify-end tw-space-x-2 tw-py-4",children:[n.jsx($,{variant:"outline",size:"sm",onClick:()=>C.previousPage(),disabled:!C.getCanPreviousPage(),children:"Previous"}),n.jsx($,{variant:"outline",size:"sm",onClick:()=>C.nextPage(),disabled:!C.getCanNextPage(),children:"Next"})]}),r&&o&&n.jsx(cl,{table:C})]})}function ml({id:t,markdown:e,className:r,anchorTarget:o,truncate:s}){const a=i.useMemo(()=>({overrides:{a:{props:{target:o}}}}),[o]);return n.jsx("div",{id:t,className:m("pr-twp tw-prose",{"tw-line-clamp-3 tw-max-h-10 tw-overflow-hidden tw-text-ellipsis tw-break-words":s},r),children:n.jsx(va,{options:a,children:e})})}const Yo=Object.freeze(["%webView_error_dump_header%","%webView_error_dump_info_message%"]),Ar=(t,e)=>t[e]??e;function Xo({errorDetails:t,handleCopyNotify:e,localizedStrings:r,id:o}){const s=Ar(r,"%webView_error_dump_header%"),a=Ar(r,"%webView_error_dump_info_message%");function l(){navigator.clipboard.writeText(t),e&&e()}return n.jsxs("div",{id:o,className:"tw-inline-flex tw-w-full tw-flex-col tw-items-start tw-justify-start tw-gap-4",children:[n.jsxs("div",{className:"tw-inline-flex tw-items-start tw-justify-start tw-gap-4 tw-self-stretch",children:[n.jsxs("div",{className:"tw-inline-flex tw-flex-1 tw-flex-col tw-items-start tw-justify-start",children:[n.jsx("div",{className:"tw-text-color-text tw-justify-center tw-text-center tw-text-lg tw-font-semibold tw-leading-loose",children:s}),n.jsx("div",{className:"tw-justify-center tw-self-stretch tw-text-sm tw-font-normal tw-leading-tight tw-text-muted-foreground",children:a})]}),n.jsx($,{variant:"secondary",size:"icon",className:"size-8",onClick:()=>l(),children:n.jsx(k.Copy,{})})]}),n.jsx("div",{className:"tw-prose tw-w-full",children:n.jsx("pre",{className:"tw-text-xs",children:t})})]})}const fl=Object.freeze([...Yo,"%webView_error_dump_copied_message%"]);function hl({errorDetails:t,handleCopyNotify:e,localizedStrings:r,children:o,className:s,id:a}){const[l,c]=i.useState(!1),d=()=>{c(!0),e&&e()},w=p=>{p||c(!1)};return n.jsxs(zt,{onOpenChange:w,children:[n.jsx(Gt,{asChild:!0,children:o}),n.jsxs(Lt,{id:a,className:m("tw-min-w-80 tw-max-w-96",s),children:[l&&r["%webView_error_dump_copied_message%"]&&n.jsx(et,{children:r["%webView_error_dump_copied_message%"]}),n.jsx(Xo,{errorDetails:t,handleCopyNotify:d,localizedStrings:r})]})]})}var Wo=(t=>(t[t.Check=0]="Check",t[t.Radio=1]="Radio",t))(Wo||{});function gl({id:t,label:e,groups:r}){const[o,s]=i.useState(Object.fromEntries(r.map((w,p)=>w.itemType===0?[p,[]]:void 0).filter(w=>!!w))),[a,l]=i.useState({}),c=(w,p)=>{const u=!o[w][p];s(f=>(f[w][p]=u,{...f}));const h=r[w].items[p];h.onUpdate(h.id,u)},d=(w,p)=>{l(h=>(h[w]=p,{...h}));const u=r[w].items.find(h=>h.id===p);u?u.onUpdate(p):console.error(`Could not find dropdown radio item with id '${p}'!`)};return n.jsx("div",{id:t,children:n.jsxs(Qt,{children:[n.jsx(ue,{asChild:!0,children:n.jsxs($,{variant:"default",children:[n.jsx(k.Filter,{size:16,className:"tw-mr-2 tw-h-4 tw-w-4"}),e,n.jsx(k.ChevronDown,{size:16,className:"tw-ml-2 tw-h-4 tw-w-4"})]})}),n.jsx(Kt,{children:r.map((w,p)=>n.jsxs("div",{children:[n.jsx(Se,{children:w.label}),n.jsx(Wn,{children:w.itemType===0?n.jsx(n.Fragment,{children:w.items.map((u,h)=>n.jsx("div",{children:n.jsx(It,{checked:o[p][h],onCheckedChange:()=>c(p,h),children:u.label})},u.id))}):n.jsx(Vo,{value:a[p],onValueChange:u=>d(p,u),children:w.items.map(u=>n.jsx("div",{children:n.jsx(Qn,{value:u.id,children:u.label})},u.id))})}),n.jsx(me,{})]},w.label))})]})})}function xl({id:t,category:e,downloads:r,languages:o,moreInfoUrl:s,handleMoreInfoLinkClick:a,supportUrl:l,handleSupportLinkClick:c}){const d=new N.NumberFormat("en",{notation:"compact",compactDisplay:"short"}).format(Object.values(r).reduce((p,u)=>p+u,0)),w=()=>{window.scrollTo(0,document.body.scrollHeight)};return n.jsxs("div",{id:t,className:"pr-twp tw-flex tw-items-center tw-justify-center tw-gap-4 tw-divide-x tw-border-b tw-border-t tw-py-2 tw-text-center",children:[e&&n.jsxs("div",{className:"tw-flex tw-flex-col tw-items-center tw-gap-1",children:[n.jsx("div",{className:"tw-flex",children:n.jsx("span",{className:"tw-text-xs tw-font-semibold tw-text-foreground",children:e})}),n.jsx("span",{className:"tw-text-xs tw-text-foreground",children:"CATEGORY"})]}),n.jsxs("div",{className:"tw-flex tw-flex-col tw-items-center tw-gap-1 tw-ps-4",children:[n.jsxs("div",{className:"tw-flex tw-gap-1",children:[n.jsx(k.User,{className:"tw-h-4 tw-w-4"}),n.jsx("span",{className:"tw-text-xs tw-font-semibold tw-text-foreground",children:d})]}),n.jsx("span",{className:"tw-text-xs tw-text-foreground",children:"USERS"})]}),n.jsxs("div",{className:"tw-flex tw-flex-col tw-items-center tw-gap-1 tw-ps-4",children:[n.jsx("div",{className:"tw-flex tw-gap-2",children:o.slice(0,3).map(p=>n.jsx("span",{className:"tw-text-xs tw-font-semibold tw-text-foreground",children:p.toUpperCase()},p))}),o.length>3&&n.jsxs("button",{type:"button",onClick:()=>w(),className:"tw-text-xs tw-text-foreground tw-underline",children:["+",o.length-3," more languages"]})]}),(s||l)&&n.jsxs("div",{className:"tw-flex tw-flex-col tw-gap-1 tw-ps-4",children:[s&&n.jsx("div",{className:"tw-flex tw-gap-1",children:n.jsxs($,{onClick:()=>a(),variant:"link",className:"tw-flex tw-h-auto tw-gap-1 tw-py-0 tw-text-xs tw-font-semibold tw-text-foreground",children:["Website",n.jsx(k.Link,{className:"tw-h-4 tw-w-4"})]})}),l&&n.jsx("div",{className:"tw-flex tw-gap-1",children:n.jsxs($,{onClick:()=>c(),variant:"link",className:"tw-flex tw-h-auto tw-gap-1 tw-py-0 tw-text-xs tw-font-semibold tw-text-foreground",children:["Support",n.jsx(k.CircleHelp,{className:"tw-h-4 tw-w-4"})]})})]})]})}function bl({id:t,versionHistory:e}){const[r,o]=i.useState(!1),s=new Date;function a(c){const d=new Date(c),w=new Date(s.getTime()-d.getTime()),p=w.getUTCFullYear()-1970,u=w.getUTCMonth(),h=w.getUTCDate()-1;let f="";return p>0?f=`${p.toString()} year${p===1?"":"s"} ago`:u>0?f=`${u.toString()} month${u===1?"":"s"} ago`:h===0?f="today":f=`${h.toString()} day${h===1?"":"s"} ago`,f}const l=Object.entries(e).sort((c,d)=>d[0].localeCompare(c[0]));return n.jsxs("div",{className:"pr-twp",id:t,children:[n.jsx("h3",{className:"tw-text-md tw-font-semibold",children:"What`s New"}),n.jsx("ul",{className:"tw-list-disc tw-pl-5 tw-pr-4 tw-text-xs tw-text-foreground",children:(r?l:l.slice(0,5)).map(c=>n.jsxs("div",{className:"tw-mt-3 tw-flex tw-justify-between",children:[n.jsx("div",{className:"tw-text-foreground",children:n.jsx("li",{className:"tw-prose tw-text-xs",children:n.jsx("span",{children:c[1].description})})}),n.jsxs("div",{className:"tw-justify-end tw-text-right",children:[n.jsxs("div",{children:["Version ",c[0]]}),n.jsx("div",{children:a(c[1].date)})]})]},c[0]))}),l.length>5&&n.jsx("button",{type:"button",onClick:()=>o(!r),className:"tw-text-xs tw-text-foreground tw-underline",children:r?"Show Less Version History":"Show All Version History"})]})}function vl({id:t,publisherDisplayName:e,fileSize:r,locales:o,versionHistory:s,currentVersion:a}){const l=i.useMemo(()=>N.formatBytes(r),[r]),d=(w=>{const p=new Intl.DisplayNames(N.getCurrentLocale(),{type:"language"});return w.map(u=>p.of(u))})(o);return n.jsx("div",{id:t,className:"pr-twp tw-border-t tw-py-2",children:n.jsxs("div",{className:"tw-flex tw-flex-col tw-gap-2 tw-divide-y",children:[Object.entries(s).length>0&&n.jsx(bl,{versionHistory:s}),n.jsxs("div",{className:"tw-flex tw-flex-col tw-gap-2 tw-py-2",children:[n.jsx("h2",{className:"tw-text-md tw-font-semibold",children:"Information"}),n.jsxs("div",{className:"tw-flex tw-items-start tw-justify-between tw-text-xs tw-text-foreground",children:[n.jsxs("p",{className:"tw-flex tw-flex-col tw-justify-start tw-gap-1",children:[n.jsx("span",{children:"Publisher"}),n.jsx("span",{className:"tw-font-semibold",children:e}),n.jsx("span",{children:"Size"}),n.jsx("span",{className:"tw-font-semibold",children:l})]}),n.jsx("div",{className:"tw-flex tw-w-3/4 tw-items-center tw-justify-between tw-text-xs tw-text-foreground",children:n.jsxs("p",{className:"tw-flex tw-flex-col tw-justify-start tw-gap-1",children:[n.jsx("span",{children:"Version"}),n.jsx("span",{className:"tw-font-semibold",children:a}),n.jsx("span",{children:"Languages"}),n.jsx("span",{className:"tw-font-semibold",children:d.join(", ")})]})})]})]})]})})}function Jo({entries:t,selected:e,onChange:r,placeholder:o,hasToggleAllFeature:s=!1,selectAllText:a="Select All",clearAllText:l="Clear All",commandEmptyMessage:c="No entries found",customSelectedText:d,isOpen:w=void 0,onOpenChange:p=void 0,isDisabled:u=!1,sortSelected:h=!1,icon:f=void 0,className:g=void 0,variant:v="ghost",id:b}){const[y,j]=i.useState(!1),C=i.useCallback(R=>{var L;const P=(L=t.find(I=>I.label===R))==null?void 0:L.value;P&&r(e.includes(P)?e.filter(I=>I!==P):[...e,P])},[t,e,r]),M=()=>d||o,F=i.useMemo(()=>{if(!h)return t;const R=t.filter(L=>L.starred).sort((L,I)=>L.label.localeCompare(I.label)),P=t.filter(L=>!L.starred).sort((L,I)=>{const A=e.includes(L.value),z=e.includes(I.value);return A&&!z?-1:!A&&z?1:L.label.localeCompare(I.label)});return[...R,...P]},[t,e,h]),V=()=>{r(t.map(R=>R.value))},_=()=>{r([])},T=w??y,S=p??j;return n.jsx("div",{id:b,className:g,children:n.jsxs(zt,{open:T,onOpenChange:S,children:[n.jsx(Gt,{asChild:!0,children:n.jsxs($,{variant:v,role:"combobox","aria-expanded":T,className:"tw-group tw-w-full tw-justify-between",disabled:u,children:[n.jsxs("div",{className:"tw-flex tw-min-w-0 tw-flex-1 tw-items-center tw-gap-2",children:[f&&n.jsx("div",{className:"tw-ml-2 tw-h-4 tw-w-4 tw-shrink-0 tw-opacity-50",children:n.jsx("span",{className:"tw-flex tw-h-full tw-w-full tw-items-center tw-justify-center",children:f})}),n.jsx("span",{className:m("tw-min-w-0 tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-text-start tw-font-normal"),children:M()})]}),n.jsx(k.ChevronsUpDown,{className:"tw-ml-2 tw-h-4 tw-w-4 tw-shrink-0 tw-opacity-50"})]})}),n.jsx(Lt,{align:"start",className:"tw-w-full tw-p-0",children:n.jsxs(At,{children:[n.jsx(pe,{placeholder:`Search ${o.toLowerCase()}...`}),s&&n.jsxs("div",{className:"tw-flex tw-justify-between tw-border-b tw-p-2",children:[n.jsx($,{variant:"ghost",size:"sm",onClick:V,children:a}),n.jsx($,{variant:"ghost",size:"sm",onClick:_,children:l})]}),n.jsxs(Pt,{children:[n.jsx(Ce,{children:c}),n.jsx(Ot,{children:F.map(R=>n.jsxs(Ct,{value:R.label,onSelect:C,className:"tw-flex tw-items-center tw-gap-2",children:[n.jsx("div",{className:"w-4",children:n.jsx(k.Check,{className:m("tw-h-4 tw-w-4",e.includes(R.value)?"tw-opacity-100":"tw-opacity-0")})}),R.starred&&n.jsx(k.Star,{className:"tw-h-4 tw-w-4"}),n.jsx("div",{className:"tw-flex-grow",children:R.label}),R.secondaryLabel&&n.jsx("div",{className:"tw-text-end tw-text-muted-foreground",children:R.secondaryLabel})]},R.label))})]})]})})]})})}function yl({entries:t,selected:e,onChange:r,placeholder:o,commandEmptyMessage:s,customSelectedText:a,isDisabled:l,sortSelected:c,icon:d,className:w,badgesPlaceholder:p,id:u}){return n.jsxs("div",{id:u,className:"tw-flex tw-items-center tw-gap-2",children:[n.jsx(Jo,{entries:t,selected:e,onChange:r,placeholder:o,commandEmptyMessage:s,customSelectedText:a,isDisabled:l,sortSelected:c,icon:d,className:w}),e.length>0?n.jsx("div",{className:"tw-flex tw-flex-wrap tw-items-center tw-gap-2",children:e.map(h=>{var f;return n.jsxs(le,{variant:"muted",className:"tw-flex tw-items-center tw-gap-1",children:[n.jsx($,{variant:"ghost",size:"icon",className:"tw-h-4 tw-w-4 tw-p-0 hover:tw-bg-transparent",onClick:()=>r(e.filter(g=>g!==h)),children:n.jsx(k.X,{className:"tw-h-3 tw-w-3"})}),(f=t.find(g=>g.value===h))==null?void 0:f.label]},h)})}):n.jsx(et,{children:p})]})}const fe=i.forwardRef(({className:t,type:e,...r},o)=>n.jsx("input",{type:e,className:m("pr-twp tw-flex tw-h-10 tw-rounded-md tw-border tw-border-input tw-bg-background tw-px-3 tw-py-2 tw-text-sm tw-ring-offset-background file:tw-border-0 file:tw-bg-transparent file:tw-text-sm file:tw-font-medium file:tw-text-foreground placeholder:tw-text-muted-foreground focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-cursor-not-allowed disabled:tw-opacity-50",t),ref:o,...r}));fe.displayName="Input";const jl=(t,e,r)=>t==="generated"?n.jsxs(n.Fragment,{children:[n.jsx("p",{children:"+"})," ",e["%footnoteEditor_callerDropdown_item_generated%"]]}):t==="hidden"?n.jsxs(n.Fragment,{children:[n.jsx("p",{children:"-"})," ",e["%footnoteEditor_callerDropdown_item_hidden%"]]}):n.jsxs(n.Fragment,{children:[n.jsx("p",{children:r})," ",e["%footnoteEditor_callerDropdown_item_custom%"]]});function Nl({callerType:t,updateCallerType:e,customCaller:r,updateCustomCaller:o,localizedStrings:s}){const a=i.useRef(null),l=i.useRef(null),c=i.useRef(!1),[d,w]=i.useState(t),[p,u]=i.useState(r),[h,f]=i.useState(!1);i.useEffect(()=>{w(t)},[t]),i.useEffect(()=>{p!==r&&u(r)},[r]);const g=b=>{c.current=!1,f(b),b||(d!=="custom"||p?(e(d),o(p)):(w(t),u(r)))},v=b=>{var y,j,C,M;b.stopPropagation(),document.activeElement===l.current&&b.key==="ArrowDown"||b.key==="ArrowRight"?((y=a.current)==null||y.focus(),c.current=!0):document.activeElement===a.current&&b.key==="ArrowUp"?((j=l.current)==null||j.focus(),c.current=!1):document.activeElement===a.current&&b.key==="ArrowLeft"&&((C=a.current)==null?void 0:C.selectionStart)===0&&((M=l.current)==null||M.focus(),c.current=!1),d==="custom"&&b.key==="Enter"&&(document.activeElement===l.current||document.activeElement===a.current)&&g(!1)};return n.jsxs(Qt,{open:h,onOpenChange:g,children:[n.jsx(vt,{children:n.jsxs(Nt,{children:[n.jsx(Dt,{asChild:!0,children:n.jsx(ue,{asChild:!0,children:n.jsx($,{variant:"outline",className:"tw-h-6",children:jl(t,s,r)})})}),n.jsx(yt,{children:s["%footnoteEditor_callerDropdown_tooltip%"]})]})}),n.jsxs(Kt,{className:"tw-z-[300]",onClick:()=>{c.current&&(c.current=!1)},onKeyDown:v,onMouseMove:()=>{var b;c.current&&((b=a.current)==null||b.focus())},children:[n.jsx(Se,{children:s["%footnoteEditor_callerDropdown_label%"]}),n.jsx(me,{}),n.jsx(It,{checked:d==="generated",onCheckedChange:()=>w("generated"),children:n.jsxs("div",{className:"tw-flex tw-w-full tw-justify-between",children:[n.jsx("span",{children:s["%footnoteEditor_callerDropdown_item_generated%"]}),n.jsx("span",{className:"tw-w-10 tw-text-center",children:bt.GENERATOR_NOTE_CALLER})]})}),n.jsx(It,{checked:d==="hidden",onCheckedChange:()=>w("hidden"),children:n.jsxs("div",{className:"tw-flex tw-w-full tw-justify-between",children:[n.jsx("span",{children:s["%footnoteEditor_callerDropdown_item_hidden%"]}),n.jsx("span",{className:"tw-w-10 tw-text-center",children:bt.HIDDEN_NOTE_CALLER})]})}),n.jsx(It,{ref:l,checked:d==="custom",onCheckedChange:()=>w("custom"),onClick:b=>{var y;b.stopPropagation(),c.current=!0,(y=a.current)==null||y.focus()},onSelect:b=>b.preventDefault(),children:n.jsxs("div",{className:"tw-flex tw-w-full tw-justify-between",children:[n.jsx("span",{children:s["%footnoteEditor_callerDropdown_item_custom%"]}),n.jsx(fe,{tabIndex:0,onMouseDown:b=>{b.stopPropagation(),w("custom"),c.current=!0},ref:a,className:"tw-h-auto tw-w-10 tw-p-0 tw-text-center",value:p,onKeyDown:b=>{b.key==="Enter"||b.key==="ArrowUp"||b.key==="ArrowDown"||b.key==="ArrowLeft"||b.key==="ArrowRight"||b.stopPropagation()},maxLength:1,onChange:b=>u(b.target.value)})]})})]})]})}const kl=(t,e)=>t==="f"?n.jsxs(n.Fragment,{children:[n.jsx(k.FunctionSquare,{})," ",e["%footnoteEditor_noteType_footnote_label%"]]}):t==="fe"?n.jsxs(n.Fragment,{children:[n.jsx(k.SquareSigma,{})," ",e["%footnoteEditor_noteType_endNote_label%"]]}):n.jsxs(n.Fragment,{children:[n.jsx(k.SquareX,{})," ",e["%footnoteEditor_noteType_crossReference_label%"]]}),_l=(t,e)=>{if(t==="x")return e["%footnoteEditor_noteType_crossReference_label%"];let r=e["%footnoteEditor_noteType_endNote_label%"];return t==="f"&&(r=e["%footnoteEditor_noteType_footnote_label%"]),N.formatReplacementString(e["%footnoteEditor_noteType_tooltip%"]??"",{noteType:r})};function Cl({noteType:t,handleNoteTypeChange:e,localizedStrings:r,isTypeSwitchable:o}){return n.jsxs(Qt,{children:[n.jsx(vt,{children:n.jsxs(Nt,{children:[n.jsx($r.TooltipTrigger,{asChild:!0,children:n.jsx(ue,{asChild:!0,children:n.jsx($,{variant:"outline",className:"tw-h-6",children:kl(t,r)})})}),n.jsx(yt,{children:n.jsx("p",{children:_l(t,r)})})]})}),n.jsxs(Kt,{className:"tw-z-[300]",children:[n.jsx(Se,{children:r["%footnoteEditor_noteTypeDropdown_label%"]}),n.jsx(me,{}),n.jsxs(It,{disabled:t!=="x"&&!o,checked:t==="x",onCheckedChange:()=>e("x"),className:"tw-gap-2",children:[n.jsx(k.SquareX,{}),n.jsx("span",{children:r["%footnoteEditor_noteType_crossReference_label%"]})]}),n.jsxs(It,{disabled:t==="x"&&!o,checked:t==="f",onCheckedChange:()=>e("f"),className:"tw-gap-2",children:[n.jsx(k.FunctionSquare,{}),n.jsx("span",{children:r["%footnoteEditor_noteType_footnote_label%"]})]}),n.jsxs(It,{disabled:t==="x"&&!o,checked:t==="fe",onCheckedChange:()=>e("fe"),className:"tw-gap-2",children:[n.jsx(k.SquareSigma,{}),n.jsx("span",{children:r["%footnoteEditor_noteType_endNote_label%"]})]})]})]})}function Sl(t){var r;const e=(r=t.attributes)==null?void 0:r.char;e.style&&(e.style==="ft"&&(e.style="xt"),e.style==="fr"&&(e.style="xo"),e.style==="fq"&&(e.style="xq"))}function El(t){var r;const e=(r=t.attributes)==null?void 0:r.char;e.style&&(e.style==="xt"&&(e.style="ft"),e.style==="xo"&&(e.style="fr"),e.style==="xq"&&(e.style="fq"))}const Rl={type:"USJ",version:"3.1",content:[{type:"para"}]};function Tl({classNameForEditor:t,noteOps:e,onSave:r,onClose:o,scrRef:s,noteKey:a,editorOptions:l,localizedStrings:c}){const d=i.useRef(null),w=i.createRef(),[p,u]=i.useState("generated"),[h,f]=i.useState("*"),[g,v]=i.useState("f"),[b,y]=i.useState(!1),j=i.useMemo(()=>({...l,markerMenuTrigger:l.markerMenuTrigger??"\\",hasExternalUI:!0,view:{...l.view??bt.getDefaultViewOptions(),noteMode:"expanded"}}),[l]);i.useEffect(()=>{var _;(_=d.current)==null||_.focus()}),i.useEffect(()=>{var S,R;let _;const T=e==null?void 0:e.at(0);if(T&&bt.isInsertEmbedOpOfType("note",T)){const P=(S=T.insert.note)==null?void 0:S.caller;let L="custom";P===bt.GENERATOR_NOTE_CALLER?L="generated":P===bt.HIDDEN_NOTE_CALLER?L="hidden":P&&f(P),u(L),v(((R=T.insert.note)==null?void 0:R.style)??"f"),_=setTimeout(()=>{var I;(I=d.current)==null||I.applyUpdate([T])},0)}return()=>{_&&clearTimeout(_)}},[e,a]);const C=i.useCallback(()=>{var T,S;const _=(S=(T=d.current)==null?void 0:T.getNoteOps(0))==null?void 0:S.at(0);_&&bt.isInsertEmbedOpOfType("note",_)&&(_.insert.note&&(p==="custom"?_.insert.note.caller=h:_.insert.note.caller=p==="generated"?bt.GENERATOR_NOTE_CALLER:bt.HIDDEN_NOTE_CALLER),r([_]))},[p,h,r]),M=()=>{var T;const _=(T=w.current)==null?void 0:T.getElementsByClassName("editor-input")[0];_!=null&&_.textContent&&navigator.clipboard.writeText(_.textContent)},F=_=>{var S,R,P,L,I;v(_);const T=(R=(S=d.current)==null?void 0:S.getNoteOps(0))==null?void 0:R.at(0);if(T&&bt.isInsertEmbedOpOfType("note",T)){T.insert.note&&(T.insert.note.style=_);const A=(L=(P=T.insert.note)==null?void 0:P.contents)==null?void 0:L.ops;g!=="x"&&_==="x"?A==null||A.forEach(z=>Sl(z)):g==="x"&&_!=="x"&&(A==null||A.forEach(z=>El(z))),(I=d.current)==null||I.applyUpdate([T,{delete:1}])}},V=_=>{var S,R,P,L,I;const T=(R=(S=d.current)==null?void 0:S.getNoteOps(0))==null?void 0:R.at(0);if(T&&bt.isInsertEmbedOpOfType("note",T)){_.content.length>1&&setTimeout(()=>{var E;(E=d.current)==null||E.applyUpdate([{retain:2},{delete:1}])},0);const A=(P=T.insert.note)==null?void 0:P.style,z=(I=(L=T.insert.note)==null?void 0:L.contents)==null?void 0:I.ops;A||y(!1),y(A==="x"?!!(z!=null&&z.every(E=>{var at,lt;if(!((at=E.attributes)!=null&&at.char))return!0;const B=((lt=E.attributes)==null?void 0:lt.char).style;return B==="xt"||B==="xo"||B==="xq"})):!!(z!=null&&z.every(E=>{var at,lt;if(!((at=E.attributes)!=null&&at.char))return!0;const B=((lt=E.attributes)==null?void 0:lt.char).style;return B==="ft"||B==="fr"||B==="fq"})))}else y(!1)};return n.jsxs("div",{className:"footnote-editor tw-grid tw-gap-[12px]",children:[n.jsxs("div",{className:"tw-flex",children:[n.jsxs("div",{className:"tw-flex tw-gap-4",children:[n.jsx(Cl,{isTypeSwitchable:b,noteType:g,handleNoteTypeChange:F,localizedStrings:c}),n.jsx(Nl,{callerType:p,updateCallerType:u,customCaller:h,updateCustomCaller:f,localizedStrings:c})]}),n.jsxs("div",{className:"tw-flex tw-w-full tw-justify-end tw-gap-4",children:[n.jsx(vt,{children:n.jsxs(Nt,{children:[n.jsx(Dt,{asChild:!0,children:n.jsx($,{onClick:o,className:"tw-h-6 tw-w-6",size:"icon",variant:"secondary",children:n.jsx(k.X,{})})}),n.jsx(yt,{children:n.jsx("p",{children:c["%footnoteEditor_cancelButton_tooltip%"]})})]})}),n.jsx(vt,{children:n.jsxs(Nt,{children:[n.jsx(Dt,{asChild:!0,children:n.jsx($,{onClick:C,className:"tw-h-6 tw-w-6",size:"icon",variant:"default",children:n.jsx(k.Check,{})})}),n.jsx(yt,{children:c["%footnoteEditor_saveButton_tooltip%"]})]})})]})]}),n.jsxs("div",{ref:w,className:"tw-relative tw-rounded-[6px] tw-border-2 tw-border-ring",children:[n.jsx("div",{className:t,children:n.jsx(bt.Editorial,{options:j,onUsjChange:V,defaultUsj:Rl,onScrRefChange:()=>{},scrRef:s,ref:d})}),n.jsx("div",{className:"tw-absolute tw-bottom-0 tw-right-0",children:n.jsx(vt,{children:n.jsxs(Nt,{children:[n.jsx(Dt,{asChild:!0,children:n.jsx($,{onClick:M,className:"tw-h-6 tw-w-6",variant:"ghost",size:"icon",children:n.jsx(k.Copy,{})})}),n.jsx(yt,{children:n.jsx("p",{children:c["%footnoteEditor_copyButton_tooltip%"]})})]})})})]})]})}const Ml=Object.freeze(["%footnoteEditor_callerDropdown_label%","%footnoteEditor_callerDropdown_item_generated%","%footnoteEditor_callerDropdown_item_hidden%","%footnoteEditor_callerDropdown_item_custom%","%footnoteEditor_callerDropdown_tooltip%","%footnoteEditor_cancelButton_tooltip%","%footnoteEditor_copyButton_tooltip%","%footnoteEditor_noteType_crossReference_label%","%footnoteEditor_noteType_endNote_label%","%footnoteEditor_noteType_footnote_label%","%footnoteEditor_noteType_tooltip%","%footnoteEditor_noteTypeDropdown_label%","%footnoteEditor_saveButton_tooltip%"]);function Zo(t,e){if(!e||e.length===0)return t??"empty";const r=e.find(s=>typeof s=="string");if(r)return`key-${t??"unknown"}-${r.slice(0,10)}`;const o=typeof e[0]=="string"?"impossible":e[0].marker??"unknown";return`key-${t??"unknown"}-${o}`}function Dl(t,e,r=!0,o=void 0){if(!e||e.length===0)return;const s=[],a=[];let l=[];return e.forEach(c=>{typeof c!="string"&&c.marker==="fp"?(l.length>0&&a.push(l),l=[c]):l.push(c)}),l.length>0&&a.push(l),a.map((c,d)=>{const w=d===a.length-1;return n.jsxs("p",{children:[nr(t,c,r,!0,s),w&&o]},Zo(t,c))})}function nr(t,e,r=!0,o=!0,s=[]){if(!(!e||e.length===0))return e.map(a=>{if(typeof a=="string"){const l=`${t}-text-${a.slice(0,10)}`;if(o){const c=m(`usfm_${t}`);return n.jsx("span",{className:c,children:a},l)}return n.jsxs("span",{className:"tw-inline-flex tw-items-center tw-gap-1 tw-underline tw-decoration-destructive",children:[n.jsx(k.AlertCircle,{className:"tw-h-4 tw-w-4 tw-fill-destructive"}),n.jsx("span",{children:a}),n.jsx(k.AlertCircle,{className:"tw-h-4 tw-w-4 tw-fill-destructive"})]},l)}return Il(a,Zo(`${t}\\${a.marker}`,[a]),r,[...s,t??"unknown"])})}function Il(t,e,r,o=[]){const{marker:s}=t;return n.jsxs("span",{children:[s?r&&n.jsx("span",{className:"marker",children:`\\${s} `}):n.jsx(k.AlertCircle,{className:"tw-text-error tw-mr-1 tw-inline-block tw-h-4 tw-w-4","aria-label":"Missing marker"}),nr(s,t.content,r,!0,[...o,s??"unknown"])]},e)}function Qo({footnote:t,layout:e="horizontal",formatCaller:r,showMarkers:o=!0}){const s=r?r(t.caller):t.caller,a=s!==t.caller;let l,c=t.content;Array.isArray(t.content)&&t.content.length>0&&typeof t.content[0]!="string"&&(t.content[0].marker==="fr"||t.content[0].marker==="xo")&&([l,...c]=t.content);const d=o?n.jsx("span",{className:"marker",children:`\\${t.marker} `}):void 0,w=o?n.jsx("span",{className:"marker",children:` \\${t.marker}*`}):void 0,p=s&&n.jsxs("span",{className:m("note-caller tw-inline-block",{formatted:a}),children:[s," "]}),u=l&&n.jsxs(n.Fragment,{children:[nr(t.marker,[l],o,!1)," "]}),h=e==="horizontal"?"horizontal":"vertical",f=o?"marker-visible":"",g=e==="horizontal"?"tw-col-span-1":"tw-col-span-2 tw-col-start-1 tw-row-start-2",v=m(h,f);return n.jsxs(n.Fragment,{children:[n.jsxs("div",{className:m("textual-note-header tw-col-span-1 tw-w-fit tw-text-nowrap",v),children:[d,p]}),n.jsx("div",{className:m("textual-note-header tw-col-span-1 tw-w-fit tw-text-nowrap",v),children:u}),n.jsx("div",{className:m("textual-note-body tw-flex tw-flex-col tw-gap-1",g,v),children:c&&c.length>0&&n.jsx(n.Fragment,{children:Dl(t.marker,c,o,w)})})]})}function Ol({className:t,classNameForItems:e,footnotes:r,layout:o="horizontal",listId:s,selectedFootnote:a,showMarkers:l=!0,suppressFormatting:c=!1,formatCaller:d,onFootnoteSelected:w}){const p=d??N.getFormatCallerFunction(r,void 0),u=(j,C)=>{w==null||w(j,C,s)},h=a?r.findIndex(j=>j===a):-1,[f,g]=i.useState(h),v=(j,C,M)=>{if(r.length)switch(j.key){case"Enter":case" ":j.preventDefault(),w==null||w(C,M,s);break}},b=j=>{if(r.length)switch(j.key){case"ArrowDown":j.preventDefault(),g(C=>Math.min(C+1,r.length-1));break;case"ArrowUp":j.preventDefault(),g(C=>Math.max(C-1,0));break}},y=i.useRef([]);return i.useEffect(()=>{var j;f>=0&&f{const M=j===a,F=`${s}-${C}`;return n.jsxs(n.Fragment,{children:[n.jsx("li",{ref:V=>{y.current[C]=V},role:"option","aria-selected":M,"data-marker":j.marker,"data-state":M?"selected":void 0,tabIndex:C===f?0:-1,className:m("tw-gap-x-3 tw-gap-y-1 tw-p-2 data-[state=selected]:tw-bg-muted",w&&"hover:tw-bg-muted/50","tw-w-full tw-rounded-sm tw-border-0 tw-bg-transparent tw-shadow-none","focus:tw-outline-none focus-visible:tw-outline-none","focus-visible:tw-ring-offset-0.5 focus-visible:tw-relative focus-visible:tw-z-10 focus-visible:tw-ring-2 focus-visible:tw-ring-ring","tw-grid tw-grid-flow-col tw-grid-cols-subgrid",o==="horizontal"?"tw-col-span-3":"tw-col-span-2 tw-row-span-2",e),onClick:()=>u(j,C),onKeyDown:V=>v(V,j,C),children:n.jsx(Qo,{footnote:j,layout:o,formatCaller:()=>p(j.caller,C),showMarkers:l})},F),Cr&&e.push(t.substring(r,s.index)),e.push(n.jsx("strong",{children:s[1]},s.index)),r=o.lastIndex;return r0?e:[t]}function Pl({occurrenceData:t,setScriptureReference:e,localizedStrings:r,classNameForText:o}){const s=r["%webView_inventory_occurrences_table_header_reference%"],a=r["%webView_inventory_occurrences_table_header_occurrence%"],l=i.useMemo(()=>{const c=[],d=new Set;return t.forEach(w=>{const p=`${w.reference.book}:${w.reference.chapterNum}:${w.reference.verseNum}:${w.text}`;d.has(p)||(d.add(p),c.push(w))}),c},[t]);return n.jsxs(Fe,{stickyHeader:!0,children:[n.jsx(ze,{stickyHeader:!0,children:n.jsxs(Mt,{children:[n.jsx(Ne,{children:s}),n.jsx(Ne,{children:a})]})}),n.jsx(Ge,{children:l.length>0&&l.map(c=>n.jsxs(Mt,{onClick:()=>{e(c.reference)},children:[n.jsx(Yt,{children:N.formatScrRef(c.reference,"English")}),n.jsx(Yt,{className:o,children:Al(c.text)})]},`${c.reference.book} ${c.reference.chapterNum}:${c.reference.verseNum}-${c.text}`))})]})}const pn=i.forwardRef(({className:t,...e},r)=>n.jsx(Rn.Root,{ref:r,className:m("tw-peer pr-twp tw-h-4 tw-w-4 tw-shrink-0 tw-rounded-sm tw-border tw-border-primary tw-ring-offset-background focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-cursor-not-allowed disabled:tw-opacity-50 data-[state=checked]:tw-bg-primary data-[state=checked]:tw-text-primary-foreground",t),...e,children:n.jsx(Rn.Indicator,{className:m("tw-flex tw-items-center tw-justify-center tw-text-current"),children:n.jsx(k.Check,{className:"tw-h-4 tw-w-4"})})}));pn.displayName=Rn.Root.displayName;const un=t=>t==="asc"?n.jsx(k.ArrowUpIcon,{className:"tw-ms-2 tw-h-4 tw-w-4"}):t==="desc"?n.jsx(k.ArrowDownIcon,{className:"tw-ms-2 tw-h-4 tw-w-4"}):n.jsx(k.ArrowUpDownIcon,{className:"tw-ms-2 tw-h-4 tw-w-4"}),Ll=t=>({accessorKey:"item",accessorFn:e=>e.items[0],header:({column:e})=>n.jsxs($,{variant:"ghost",onClick:()=>e.toggleSorting(void 0),children:[t,un(e.getIsSorted())]})}),$l=(t,e)=>({accessorKey:`item${e}`,accessorFn:r=>r.items[e],header:({column:r})=>n.jsxs($,{variant:"ghost",onClick:()=>r.toggleSorting(void 0),children:[t,un(r.getIsSorted())]})}),Vl=t=>({accessorKey:"count",header:({column:e})=>n.jsx("div",{className:"tw-flex tw-justify-end tw-tabular-nums",children:n.jsxs($,{variant:"ghost",onClick:()=>e.toggleSorting(void 0),children:[t,un(e.getIsSorted())]})}),cell:({row:e})=>n.jsx("div",{className:"tw-flex tw-justify-end",children:e.getValue("count")})}),kn=(t,e,r,o,s,a)=>{let l=[...r];t.forEach(d=>{e==="approved"?l.includes(d)||l.push(d):l=l.filter(w=>w!==d)}),o(l);let c=[...s];t.forEach(d=>{e==="unapproved"?c.includes(d)||c.push(d):c=c.filter(w=>w!==d)}),a(c)},Fl=(t,e,r,o,s)=>({accessorKey:"status",header:({column:a})=>n.jsx("div",{className:"tw-flex tw-justify-center",children:n.jsxs($,{variant:"ghost",onClick:()=>a.toggleSorting(void 0),children:[t,un(a.getIsSorted())]})}),cell:({row:a})=>{const l=a.getValue("status"),c=a.getValue("item");return n.jsxs(dn,{value:l,variant:"outline",type:"single",children:[n.jsx(ve,{onClick:d=>{d.stopPropagation(),kn([c],"approved",e,r,o,s)},value:"approved",children:n.jsx(k.CircleCheckIcon,{})}),n.jsx(ve,{onClick:d=>{d.stopPropagation(),kn([c],"unapproved",e,r,o,s)},value:"unapproved",children:n.jsx(k.CircleXIcon,{})}),n.jsx(ve,{onClick:d=>{d.stopPropagation(),kn([c],"unknown",e,r,o,s)},value:"unknown",children:n.jsx(k.CircleHelpIcon,{})})]})}}),zl=t=>t.split(/(?:\r?\n|\r)|(?=(?:\\(?:v|c|id)))/g),Gl=t=>{const e=/^\\[vc]\s+(\d+)/,r=t.match(e);if(r)return+r[1]},Bl=t=>{const e=t.match(/^\\id\s+([A-Za-z]+)/);return e?e[1]:""},ts=(t,e,r)=>r.includes(t)?"unapproved":e.includes(t)?"approved":"unknown",Kl=Object.freeze(["%webView_inventory_all%","%webView_inventory_approved%","%webView_inventory_unapproved%","%webView_inventory_unknown%","%webView_inventory_scope_currentBook%","%webView_inventory_scope_chapter%","%webView_inventory_scope_verse%","%webView_inventory_filter_text%","%webView_inventory_show_additional_items%","%webView_inventory_occurrences_table_header_reference%","%webView_inventory_occurrences_table_header_occurrence%","%webView_inventory_no_results%"]),ql=(t,e,r)=>{let o=t;return e!=="all"&&(o=o.filter(s=>e==="approved"&&s.status==="approved"||e==="unapproved"&&s.status==="unapproved"||e==="unknown"&&s.status==="unknown")),r!==""&&(o=o.filter(s=>s.items[0].includes(r))),o},Ul=(t,e,r)=>t.map(o=>{const s=N.isString(o.key)?o.key:o.key[0];return{items:N.isString(o.key)?[o.key]:o.key,count:o.count,status:o.status||ts(s,e,r),occurrences:o.occurrences||[]}}),Rt=(t,e)=>t[e]??e;function Hl({inventoryItems:t,setVerseRef:e,localizedStrings:r,additionalItemsLabels:o,approvedItems:s,unapprovedItems:a,scope:l,onScopeChange:c,columns:d,id:w,areInventoryItemsLoading:p=!1,classNameForVerseText:u,onItemSelected:h}){const f=Rt(r,"%webView_inventory_all%"),g=Rt(r,"%webView_inventory_approved%"),v=Rt(r,"%webView_inventory_unapproved%"),b=Rt(r,"%webView_inventory_unknown%"),y=Rt(r,"%webView_inventory_scope_currentBook%"),j=Rt(r,"%webView_inventory_scope_chapter%"),C=Rt(r,"%webView_inventory_scope_verse%"),M=Rt(r,"%webView_inventory_filter_text%"),F=Rt(r,"%webView_inventory_show_additional_items%"),V=Rt(r,"%webView_inventory_no_results%"),[_,T]=i.useState(!1),[S,R]=i.useState("all"),[P,L]=i.useState(""),[I,A]=i.useState([]),z=i.useMemo(()=>{const G=t??[];return G.length===0?[]:Ul(G,s,a)},[t,s,a]),E=i.useMemo(()=>{if(_)return z;const G=[];return z.forEach(tt=>{const wt=tt.items[0],ot=G.find(xt=>xt.items[0]===wt);ot?(ot.count+=tt.count,ot.occurrences=ot.occurrences.concat(tt.occurrences)):G.push({items:[wt],count:tt.count,occurrences:tt.occurrences,status:tt.status})}),G},[_,z]),B=i.useMemo(()=>E.length===0?[]:ql(E,S,P),[E,S,P]),at=i.useMemo(()=>{var wt,ot;if(!_)return d;const G=(wt=o==null?void 0:o.tableHeaders)==null?void 0:wt.length;if(!G)return d;const tt=[];for(let xt=0;xt{B.length===0?A([]):B.length===1&&A(B[0].items)},[B]);const lt=(G,tt)=>{tt.setRowSelection(()=>{const ot={};return ot[G.index]=!0,ot});const wt=G.original.items;A(wt),h&&wt.length>0&&h(wt[0])},Vt=G=>{if(G==="book"||G==="chapter"||G==="verse")c(G);else throw new Error(`Invalid scope value: ${G}`)},ct=G=>{if(G==="all"||G==="approved"||G==="unapproved"||G==="unknown")R(G);else throw new Error(`Invalid status filter value: ${G}`)},rt=i.useMemo(()=>{if(E.length===0||I.length===0)return[];const G=E.filter(tt=>N.deepEqual(_?tt.items:[tt.items[0]],I));if(G.length>1)throw new Error("Selected item is not unique");return G.length===0?[]:G[0].occurrences},[I,_,E]);return n.jsxs("div",{id:w,className:"pr-twp tw-flex tw-h-full tw-flex-col",children:[n.jsxs("div",{className:"tw-flex tw-items-stretch",children:[n.jsxs(we,{onValueChange:G=>ct(G),defaultValue:S,children:[n.jsx(Wt,{className:"tw-m-1",children:n.jsx(de,{placeholder:"Select filter"})}),n.jsxs(Jt,{children:[n.jsx(gt,{value:"all",children:f}),n.jsx(gt,{value:"approved",children:g}),n.jsx(gt,{value:"unapproved",children:v}),n.jsx(gt,{value:"unknown",children:b})]})]}),n.jsxs(we,{onValueChange:G=>Vt(G),defaultValue:l,children:[n.jsx(Wt,{className:"tw-m-1",children:n.jsx(de,{placeholder:"Select scope"})}),n.jsxs(Jt,{children:[n.jsx(gt,{value:"book",children:y}),n.jsx(gt,{value:"chapter",children:j}),n.jsx(gt,{value:"verse",children:C})]})]}),n.jsx(fe,{className:"tw-m-1 tw-rounded-md tw-border",placeholder:M,value:P,onChange:G=>{L(G.target.value)}}),o&&n.jsxs("div",{className:"tw-m-1 tw-flex tw-items-center tw-rounded-md tw-border",children:[n.jsx(pn,{className:"tw-m-1",checked:_,onCheckedChange:G=>{T(G)}}),n.jsx(et,{className:"tw-m-1 tw-flex-shrink-0 tw-whitespace-nowrap",children:(o==null?void 0:o.checkboxText)??F})]})]}),n.jsx("div",{className:"tw-m-1 tw-flex-1 tw-overflow-auto tw-rounded-md tw-border",children:n.jsx(Ho,{columns:at,data:B,onRowClickHandler:lt,stickyHeader:!0,isLoading:p,noResultsMessage:V})}),rt.length>0&&n.jsx("div",{className:"tw-m-1 tw-flex-1 tw-overflow-auto tw-rounded-md tw-border",children:n.jsx(Pl,{classNameForText:u,occurrenceData:rt,setScriptureReference:e,localizedStrings:r})})]})}const Yl=Object.freeze(["%markerMenu_deprecated_label%","%markerMenu_disallowed_label%","%markerMenu_noResults%","%markerMenu_searchPlaceholder%"]);function Xl({icon:t,className:e}){const r=t??k.Ban;return n.jsx(r,{className:e,size:16})}function Wl({localizedStrings:t,markerMenuItems:e}){const[r,o]=i.useState(""),s=i.useMemo(()=>{const a=r.trim().toLowerCase();return a?e.filter(l=>{var c;return((c=l.marker)==null?void 0:c.toLowerCase().includes(a))||l.title.toLowerCase().includes(a)}):e},[r,e]);return n.jsxs(At,{className:"tw-p-1",shouldFilter:!1,loop:!0,children:[n.jsx(pe,{value:r,onValueChange:a=>o(a),placeholder:t["%markerMenu_searchPlaceholder%"],autoFocus:!1}),n.jsxs(Pt,{children:[n.jsx(Ce,{children:t["%markerMenu_noResults%"]}),n.jsx(Ot,{children:s.map(a=>{var l;return n.jsxs(Ct,{className:"tw-flex tw-gap-2 hover:tw-bg-accent",disabled:a.isDisallowed||a.isDeprecated,onSelect:a.action,children:[n.jsx("div",{className:"tw-w-6",children:a.marker?n.jsx("span",{className:"tw-text-xs",children:a.marker}):n.jsx("div",{children:n.jsx(Xl,{icon:a.icon})})}),n.jsxs("div",{children:[n.jsx("p",{className:"tw-text-sm",children:a.title}),a.subtitle&&n.jsx("p",{className:"tw-text-xs tw-text-muted-foreground",children:a.subtitle})]}),(a.isDisallowed||a.isDeprecated)&&n.jsx(Wr,{className:"tw-font-sans",children:a.isDisallowed?t["%markerMenu_disallowed_label%"]:t["%markerMenu_deprecated_label%"]})]},`item-${a.marker??((l=a.icon)==null?void 0:l.displayName)}-${a.title.replaceAll(" ","")}`)})})]})]})}const Jl="16rem",Zl="3rem",es=i.createContext(void 0);function Be(){const t=i.useContext(es);if(!t)throw new Error("useSidebar must be used within a SidebarProvider.");return t}const rr=i.forwardRef(({defaultOpen:t=!0,open:e,onOpenChange:r,className:o,style:s,children:a,side:l="primary",...c},d)=>{const[w,p]=i.useState(t),u=e??w,h=i.useCallback(C=>{const M=typeof C=="function"?C(u):C;r?r(M):p(M)},[r,u]),f=i.useCallback(()=>h(C=>!C),[h]),g=u?"expanded":"collapsed",y=st()==="ltr"?l:l==="primary"?"secondary":"primary",j=i.useMemo(()=>({state:g,open:u,setOpen:h,toggleSidebar:f,side:y}),[g,u,h,f,y]);return n.jsx(es.Provider,{value:j,children:n.jsx(vt,{delayDuration:0,children:n.jsx("div",{style:{"--sidebar-width":Jl,"--sidebar-width-icon":Zl,...s},className:m("tw-group/sidebar-wrapper pr-twp tw-flex tw-w-full has-[[data-variant=inset]]:tw-bg-sidebar",o),ref:d,...c,children:a})})})});rr.displayName="SidebarProvider";const or=i.forwardRef(({variant:t="sidebar",collapsible:e="offcanvas",className:r,children:o,...s},a)=>{const l=Be();return e==="none"?n.jsx("div",{className:m("tw-flex tw-h-full tw-w-[--sidebar-width] tw-flex-col tw-bg-sidebar tw-text-sidebar-foreground",r),ref:a,...s,children:o}):n.jsxs("div",{ref:a,className:"tw-group tw-peer tw-hidden tw-text-sidebar-foreground md:tw-block","data-state":l.state,"data-collapsible":l.state==="collapsed"?e:"","data-variant":t,"data-side":l.side,children:[n.jsx("div",{className:m("tw-relative tw-h-svh tw-w-[--sidebar-width] tw-bg-transparent tw-transition-[width] tw-duration-200 tw-ease-linear","group-data-[collapsible=offcanvas]:tw-w-0","group-data-[side=secondary]:tw-rotate-180",t==="floating"||t==="inset"?"group-data-[collapsible=icon]:tw-w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]":"group-data-[collapsible=icon]:tw-w-[--sidebar-width-icon]")}),n.jsx("div",{className:m("tw-absolute tw-inset-y-0 tw-z-10 tw-hidden tw-h-svh tw-w-[--sidebar-width] tw-transition-[left,right,width] tw-duration-200 tw-ease-linear md:tw-flex",l.side==="primary"?"tw-left-0 group-data-[collapsible=offcanvas]:tw-left-[calc(var(--sidebar-width)*-1)]":"tw-right-0 group-data-[collapsible=offcanvas]:tw-right-[calc(var(--sidebar-width)*-1)]",t==="floating"||t==="inset"?"tw-p-2 group-data-[collapsible=icon]:tw-w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]":"group-data-[collapsible=icon]:tw-w-[--sidebar-width-icon] group-data-[side=primary]:tw-border-r group-data-[side=secondary]:tw-border-l",r),...s,children:n.jsx("div",{"data-sidebar":"sidebar",className:"tw-flex tw-h-full tw-w-full tw-flex-col tw-bg-sidebar group-data-[variant=floating]:tw-rounded-lg group-data-[variant=floating]:tw-border group-data-[variant=floating]:tw-border-sidebar-border group-data-[variant=floating]:tw-shadow",children:o})})]})});or.displayName="Sidebar";const ns=i.forwardRef(({className:t,onClick:e,...r},o)=>{const s=Be();return n.jsxs($,{ref:o,"data-sidebar":"trigger",variant:"ghost",size:"icon",className:m("tw-h-7 tw-w-7",t),onClick:a=>{e==null||e(a),s.toggleSidebar()},...r,children:[s.side==="primary"?n.jsx(k.PanelLeft,{}):n.jsx(k.PanelRight,{}),n.jsx("span",{className:"tw-sr-only",children:"Toggle Sidebar"})]})});ns.displayName="SidebarTrigger";const rs=i.forwardRef(({className:t,...e},r)=>{const{toggleSidebar:o}=Be();return n.jsx("button",{type:"button",ref:r,"data-sidebar":"rail","aria-label":"Toggle Sidebar",tabIndex:-1,onClick:o,title:"Toggle Sidebar",className:m("tw-absolute tw-inset-y-0 tw-z-20 tw-hidden tw-w-4 tw--translate-x-1/2 tw-transition-all tw-ease-linear after:tw-absolute after:tw-inset-y-0 after:tw-left-1/2 after:tw-w-[2px] hover:after:tw-bg-sidebar-border group-data-[side=primary]:tw--right-4 group-data-[side=secondary]:tw-left-0 sm:tw-flex","[[data-side=secondary]_&]:tw-cursor-e-resize [[data-side=secondary]_&]:tw-cursor-w-resize","[[data-side=primary][data-state=collapsed]_&]:tw-cursor-e-resize [[data-side=secondary][data-state=collapsed]_&]:tw-cursor-w-resize","group-data-[collapsible=offcanvas]:tw-translate-x-0 group-data-[collapsible=offcanvas]:after:tw-left-full group-data-[collapsible=offcanvas]:hover:tw-bg-sidebar","[[data-side=primary][data-collapsible=offcanvas]_&]:tw--right-2","[[data-side=secondary][data-collapsible=offcanvas]_&]:tw--left-2",t),...e})});rs.displayName="SidebarRail";const sr=i.forwardRef(({className:t,...e},r)=>n.jsx("main",{ref:r,className:m("tw-relative tw-flex tw-flex-1 tw-flex-col tw-bg-background","peer-data-[variant=inset]:tw-min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:tw-m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:tw-ml-2 md:peer-data-[variant=inset]:tw-ml-0 md:peer-data-[variant=inset]:tw-rounded-xl md:peer-data-[variant=inset]:tw-shadow",t),...e}));sr.displayName="SidebarInset";const os=i.forwardRef(({className:t,...e},r)=>n.jsx(fe,{ref:r,"data-sidebar":"input",className:m("tw-h-8 tw-w-full tw-bg-background tw-shadow-none focus-visible:tw-ring-2 focus-visible:tw-ring-sidebar-ring",t),...e}));os.displayName="SidebarInput";const ss=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,"data-sidebar":"header",className:m("tw-flex tw-flex-col tw-gap-2 tw-p-2",t),...e}));ss.displayName="SidebarHeader";const as=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,"data-sidebar":"footer",className:m("tw-flex tw-flex-col tw-gap-2 tw-p-2",t),...e}));as.displayName="SidebarFooter";const is=i.forwardRef(({className:t,...e},r)=>n.jsx(ce,{ref:r,"data-sidebar":"separator",className:m("tw-mx-2 tw-w-auto tw-bg-sidebar-border",t),...e}));is.displayName="SidebarSeparator";const ar=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,"data-sidebar":"content",className:m("tw-flex tw-min-h-0 tw-flex-1 tw-flex-col tw-gap-2 tw-overflow-auto group-data-[collapsible=icon]:tw-overflow-hidden",t),...e}));ar.displayName="SidebarContent";const en=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,"data-sidebar":"group",className:m("tw-relative tw-flex tw-w-full tw-min-w-0 tw-flex-col tw-p-2",t),...e}));en.displayName="SidebarGroup";const nn=i.forwardRef(({className:t,asChild:e=!1,...r},o)=>{const s=e?ke.Slot:"div";return n.jsx(s,{ref:o,"data-sidebar":"group-label",className:m("tw-flex tw-h-8 tw-shrink-0 tw-items-center tw-rounded-md tw-px-2 tw-text-xs tw-font-medium tw-text-sidebar-foreground/70 tw-outline-none tw-ring-sidebar-ring tw-transition-[margin,opa] tw-duration-200 tw-ease-linear focus-visible:tw-ring-2 [&>svg]:tw-size-4 [&>svg]:tw-shrink-0","group-data-[collapsible=icon]:tw--mt-8 group-data-[collapsible=icon]:tw-opacity-0",t),...r})});nn.displayName="SidebarGroupLabel";const ls=i.forwardRef(({className:t,asChild:e=!1,...r},o)=>{const s=e?ke.Slot:"button";return n.jsx(s,{ref:o,"data-sidebar":"group-action",className:m("tw-absolute tw-right-3 tw-top-3.5 tw-flex tw-aspect-square tw-w-5 tw-items-center tw-justify-center tw-rounded-md tw-p-0 tw-text-sidebar-foreground tw-outline-none tw-ring-sidebar-ring tw-transition-transform hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground focus-visible:tw-ring-2 [&>svg]:tw-size-4 [&>svg]:tw-shrink-0","after:tw-absolute after:tw--inset-2 after:md:tw-hidden","group-data-[collapsible=icon]:tw-hidden",t),...r})});ls.displayName="SidebarGroupAction";const rn=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,"data-sidebar":"group-content",className:m("tw-w-full tw-text-sm",t),...e}));rn.displayName="SidebarGroupContent";const ir=i.forwardRef(({className:t,...e},r)=>n.jsx("ul",{ref:r,"data-sidebar":"menu",className:m("tw-flex tw-w-full tw-min-w-0 tw-flex-col tw-gap-1",t),...e}));ir.displayName="SidebarMenu";const lr=i.forwardRef(({className:t,...e},r)=>n.jsx("li",{ref:r,"data-sidebar":"menu-item",className:m("tw-group/menu-item tw-relative",t),...e}));lr.displayName="SidebarMenuItem";const Ql=Zt.cva("tw-peer/menu-button tw-flex tw-w-full tw-items-center tw-gap-2 tw-overflow-hidden tw-rounded-md tw-p-2 tw-text-left tw-text-sm tw-outline-none tw-ring-sidebar-ring tw-transition-[width,height,padding] hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground focus-visible:tw-ring-2 active:tw-bg-sidebar-accent active:tw-text-sidebar-accent-foreground disabled:tw-pointer-events-none disabled:tw-opacity-50 tw-group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:tw-pointer-events-none aria-disabled:tw-opacity-50 data-[active=true]:tw-font-medium data-[active=true]:tw-text-sidebar-accent-foreground data-[active=true]:tw-bg-sidebar-accent data-[state=open]:hover:tw-bg-sidebar-accent data-[state=open]:hover:tw-text-sidebar-accent-foreground group-data-[collapsible=icon]:tw-!size-8 group-data-[collapsible=icon]:tw-!p-2 [&>span:last-child]:tw-truncate [&>svg]:tw-size-4 [&>svg]:tw-shrink-0",{variants:{variant:{default:"hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground",outline:"tw-bg-background tw-shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground hover:tw-shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]"},size:{default:"tw-h-8 tw-text-sm",sm:"tw-h-7 tw-text-xs",lg:"tw-h-12 tw-text-sm group-data-[collapsible=icon]:tw-!p-0"}},defaultVariants:{variant:"default",size:"default"}}),cr=i.forwardRef(({asChild:t=!1,isActive:e=!1,variant:r="default",size:o="default",tooltip:s,className:a,...l},c)=>{const d=t?ke.Slot:"button",{state:w}=Be(),p=n.jsx(d,{ref:c,"data-sidebar":"menu-button","data-size":o,"data-active":e,className:m(Ql({variant:r,size:o}),a),...l});return s?(typeof s=="string"&&(s={children:s}),n.jsxs(Nt,{children:[n.jsx(Dt,{asChild:!0,children:p}),n.jsx(yt,{side:"right",align:"center",hidden:w!=="collapsed",...s})]})):p});cr.displayName="SidebarMenuButton";const cs=i.forwardRef(({className:t,asChild:e=!1,showOnHover:r=!1,...o},s)=>{const a=e?ke.Slot:"button";return n.jsx(a,{ref:s,"data-sidebar":"menu-action",className:m("tw-peer-hover/menu-button:text-sidebar-accent-foreground tw-absolute tw-right-1 tw-top-1.5 tw-flex tw-aspect-square tw-w-5 tw-items-center tw-justify-center tw-rounded-md tw-p-0 tw-text-sidebar-foreground tw-outline-none tw-ring-sidebar-ring tw-transition-transform hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground focus-visible:tw-ring-2 [&>svg]:tw-size-4 [&>svg]:tw-shrink-0","after:tw-absolute after:tw--inset-2 after:md:tw-hidden","tw-peer-data-[size=sm]/menu-button:top-1","tw-peer-data-[size=default]/menu-button:top-1.5","tw-peer-data-[size=lg]/menu-button:top-2.5","group-data-[collapsible=icon]:tw-hidden",r&&"tw-group-focus-within/menu-item:opacity-100 tw-group-hover/menu-item:opacity-100 tw-peer-data-[active=true]/menu-button:text-sidebar-accent-foreground data-[state=open]:tw-opacity-100 md:tw-opacity-0",t),...o})});cs.displayName="SidebarMenuAction";const ws=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,"data-sidebar":"menu-badge",className:m("tw-pointer-events-none tw-absolute tw-right-1 tw-flex tw-h-5 tw-min-w-5 tw-select-none tw-items-center tw-justify-center tw-rounded-md tw-px-1 tw-text-xs tw-font-medium tw-tabular-nums tw-text-sidebar-foreground","tw-peer-hover/menu-button:text-sidebar-accent-foreground tw-peer-data-[active=true]/menu-button:text-sidebar-accent-foreground","tw-peer-data-[size=sm]/menu-button:top-1","tw-peer-data-[size=default]/menu-button:top-1.5","tw-peer-data-[size=lg]/menu-button:top-2.5","group-data-[collapsible=icon]:tw-hidden",t),...e}));ws.displayName="SidebarMenuBadge";const ds=i.forwardRef(({className:t,showIcon:e=!1,...r},o)=>{const s=i.useMemo(()=>`${Math.floor(Math.random()*40)+50}%`,[]);return n.jsxs("div",{ref:o,"data-sidebar":"menu-skeleton",className:m("tw-flex tw-h-8 tw-items-center tw-gap-2 tw-rounded-md tw-px-2",t),...r,children:[e&&n.jsx(tn,{className:"tw-size-4 tw-rounded-md","data-sidebar":"menu-skeleton-icon"}),n.jsx(tn,{className:"tw-h-4 tw-max-w-[--skeleton-width] tw-flex-1","data-sidebar":"menu-skeleton-text",style:{"--skeleton-width":s}})]})});ds.displayName="SidebarMenuSkeleton";const ps=i.forwardRef(({className:t,...e},r)=>n.jsx("ul",{ref:r,"data-sidebar":"menu-sub",className:m("tw-mx-3.5 tw-flex tw-min-w-0 tw-translate-x-px tw-flex-col tw-gap-1 tw-border-l tw-border-sidebar-border tw-px-2.5 tw-py-0.5","group-data-[collapsible=icon]:tw-hidden",t),...e}));ps.displayName="SidebarMenuSub";const us=i.forwardRef(({...t},e)=>n.jsx("li",{ref:e,...t}));us.displayName="SidebarMenuSubItem";const ms=i.forwardRef(({asChild:t=!1,size:e="md",isActive:r,className:o,...s},a)=>{const l=t?ke.Slot:"a";return n.jsx(l,{ref:a,"data-sidebar":"menu-sub-button","data-size":e,"data-active":r,className:m("tw-flex tw-h-7 tw-min-w-0 tw--translate-x-px tw-items-center tw-gap-2 tw-overflow-hidden tw-rounded-md tw-px-2 tw-text-sidebar-foreground tw-outline-none tw-ring-sidebar-ring hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground focus-visible:tw-ring-2 active:tw-bg-sidebar-accent active:tw-text-sidebar-accent-foreground disabled:tw-pointer-events-none disabled:tw-opacity-50 aria-disabled:tw-pointer-events-none aria-disabled:tw-opacity-50 [&>span:last-child]:tw-truncate [&>svg]:tw-size-4 [&>svg]:tw-shrink-0 [&>svg]:tw-text-sidebar-accent-foreground","data-[active=true]:tw-bg-sidebar-accent data-[active=true]:tw-text-sidebar-accent-foreground",e==="sm"&&"tw-text-xs",e==="md"&&"tw-text-sm","group-data-[collapsible=icon]:tw-hidden",o),...s})});ms.displayName="SidebarMenuSubButton";function fs({id:t,extensionLabels:e,projectInfo:r,handleSelectSidebarItem:o,selectedSidebarItem:s,extensionsSidebarGroupLabel:a,projectsSidebarGroupLabel:l,buttonPlaceholderText:c,className:d}){const w=i.useCallback((h,f)=>{o(h,f)},[o]),p=i.useCallback(h=>{const f=r.find(g=>g.projectId===h);return f?f.projectName:h},[r]),u=i.useCallback(h=>!s.projectId&&h===s.label,[s]);return n.jsx(or,{id:t,collapsible:"none",variant:"inset",className:m("tw-w-96 tw-gap-2 tw-overflow-y-auto",d),children:n.jsxs(ar,{children:[n.jsxs(en,{children:[n.jsx(nn,{className:"tw-text-sm",children:a}),n.jsx(rn,{children:n.jsx(ir,{children:Object.entries(e).map(([h,f])=>n.jsx(lr,{children:n.jsx(cr,{onClick:()=>w(h),isActive:u(h),children:n.jsx("span",{className:"tw-pl-3",children:f})})},h))})})]}),n.jsxs(en,{children:[n.jsx(nn,{className:"tw-text-sm",children:l}),n.jsx(rn,{className:"tw-pl-3",children:n.jsx(Je,{buttonVariant:"ghost",buttonClassName:m("tw-w-full",{"tw-bg-sidebar-accent tw-text-sidebar-accent-foreground":s==null?void 0:s.projectId}),popoverContentClassName:"tw-z-[1000]",options:r.flatMap(h=>h.projectId),getOptionLabel:p,buttonPlaceholder:c,onChange:h=>{const f=p(h);w(f,h)},value:(s==null?void 0:s.projectId)??void 0,icon:n.jsx(k.ScrollText,{})})})]})]})})}const mn=i.forwardRef(({value:t,onSearch:e,placeholder:r,isFullWidth:o,className:s,isDisabled:a=!1,id:l},c)=>{const d=st();return n.jsxs("div",{id:l,className:m("tw-relative",{"tw-w-full":o},s),children:[n.jsx(k.Search,{className:m("tw-absolute tw-top-1/2 tw-h-4 tw-w-4 tw--translate-y-1/2 tw-transform tw-opacity-50",{"tw-right-3":d==="rtl"},{"tw-left-3":d==="ltr"})}),n.jsx(fe,{ref:c,className:"tw-w-full tw-text-ellipsis tw-pe-9 tw-ps-9",placeholder:r,value:t,onChange:w=>e(w.target.value),disabled:a}),t&&n.jsxs($,{variant:"ghost",size:"icon",className:m("tw-absolute tw-top-1/2 tw-h-7 tw--translate-y-1/2 tw-transform hover:tw-bg-transparent",{"tw-left-0":d==="rtl"},{"tw-right-0":d==="ltr"}),onClick:()=>{e("")},children:[n.jsx(k.X,{className:"tw-h-4 tw-w-4"}),n.jsx("span",{className:"tw-sr-only",children:"Clear"})]})]})});mn.displayName="SearchBar";function tc({id:t,extensionLabels:e,projectInfo:r,children:o,handleSelectSidebarItem:s,selectedSidebarItem:a,searchValue:l,onSearch:c,extensionsSidebarGroupLabel:d,projectsSidebarGroupLabel:w,buttonPlaceholderText:p}){return n.jsxs("div",{className:"tw-box-border tw-flex tw-h-full tw-flex-col",children:[n.jsx("div",{className:"tw-box-border tw-flex tw-items-center tw-justify-center tw-py-4",children:n.jsx(mn,{className:"tw-w-9/12",value:l,onSearch:c,placeholder:"Search app settings, extension settings, and project settings"})}),n.jsxs(rr,{id:t,className:"tw-h-full tw-flex-1 tw-gap-4 tw-overflow-auto tw-border-t",children:[n.jsx(fs,{className:"tw-w-1/2 tw-min-w-[140px] tw-max-w-[220px] tw-border-e",extensionLabels:e,projectInfo:r,handleSelectSidebarItem:s,selectedSidebarItem:a,extensionsSidebarGroupLabel:d,projectsSidebarGroupLabel:w,buttonPlaceholderText:p}),n.jsx(sr,{className:"tw-min-w-[215px]",children:o})]})]})}const Ut="scrBook",ec="scrRef",ae="source",nc="details",rc="Scripture Reference",oc="Scripture Book",hs="Type",sc="Details";function ac(t,e){const r=e??!1;return[{accessorFn:o=>`${o.start.book} ${o.start.chapterNum}:${o.start.verseNum}`,id:Ut,header:(t==null?void 0:t.scriptureReferenceColumnName)??rc,cell:o=>{const s=o.row.original;return o.row.getIsGrouped()?Q.Canon.bookIdToEnglishName(s.start.book):o.row.groupingColumnId===Ut?N.formatScrRef(s.start):void 0},getGroupingValue:o=>Q.Canon.bookIdToNumber(o.start.book),sortingFn:(o,s)=>N.compareScrRefs(o.original.start,s.original.start),enableGrouping:!0},{accessorFn:o=>N.formatScrRef(o.start),id:ec,header:void 0,cell:o=>{const s=o.row.original;return o.row.getIsGrouped()?void 0:N.formatScrRef(s.start)},sortingFn:(o,s)=>N.compareScrRefs(o.original.start,s.original.start),enableGrouping:!1},{accessorFn:o=>o.source.displayName,id:ae,header:r?(t==null?void 0:t.typeColumnName)??hs:void 0,cell:o=>r||o.row.getIsGrouped()?o.getValue():void 0,getGroupingValue:o=>o.source.id,sortingFn:(o,s)=>o.original.source.displayName.localeCompare(s.original.source.displayName),enableGrouping:!0},{accessorFn:o=>o.detail,id:nc,header:(t==null?void 0:t.detailsColumnName)??sc,cell:o=>o.getValue(),enableGrouping:!1}]}const ic=t=>{if(!("offset"in t.start))throw new Error("No offset available in range start");if(t.end&&!("offset"in t.end))throw new Error("No offset available in range end");const{offset:e}=t.start;let r=0;return t.end&&({offset:r}=t.end),!t.end||N.compareScrRefs(t.start,t.end)===0?`${N.scrRefToBBBCCCVVV(t.start)}+${e}`:`${N.scrRefToBBBCCCVVV(t.start)}+${e}-${N.scrRefToBBBCCCVVV(t.end)}+${r}`},Pr=t=>`${ic({start:t.start,end:t.end})} ${t.source.displayName} ${t.detail}`;function lc({sources:t,showColumnHeaders:e=!1,showSourceColumn:r=!1,scriptureReferenceColumnName:o,scriptureBookGroupName:s,typeColumnName:a,detailsColumnName:l,onRowSelected:c,id:d}){const[w,p]=i.useState([]),[u,h]=i.useState([{id:Ut,desc:!1}]),[f,g]=i.useState({}),v=i.useMemo(()=>t.flatMap(S=>S.data.map(R=>({...R,source:S.source}))),[t]),b=i.useMemo(()=>ac({scriptureReferenceColumnName:o,typeColumnName:a,detailsColumnName:l},r),[o,a,l,r]);i.useEffect(()=>{w.includes(ae)?h([{id:ae,desc:!1},{id:Ut,desc:!1}]):h([{id:Ut,desc:!1}])},[w]);const y=ut.useReactTable({data:v,columns:b,state:{grouping:w,sorting:u,rowSelection:f},onGroupingChange:p,onSortingChange:h,onRowSelectionChange:g,getExpandedRowModel:ut.getExpandedRowModel(),getGroupedRowModel:ut.getGroupedRowModel(),getCoreRowModel:ut.getCoreRowModel(),getSortedRowModel:ut.getSortedRowModel(),getRowId:Pr,autoResetExpanded:!1,enableMultiRowSelection:!1,enableSubRowSelection:!1});i.useEffect(()=>{if(c){const S=y.getSelectedRowModel().rowsById,R=Object.keys(S);if(R.length===1){const P=v.find(L=>Pr(L)===R[0])||void 0;P&&c(P)}}},[f,v,c,y]);const j=s??oc,C=a??hs,M=[{label:"No Grouping",value:[]},{label:`Group by ${j}`,value:[Ut]},{label:`Group by ${C}`,value:[ae]},{label:`Group by ${j} and ${C}`,value:[Ut,ae]},{label:`Group by ${C} and ${j}`,value:[ae,Ut]}],F=S=>{p(JSON.parse(S))},V=(S,R)=>{!S.getIsGrouped()&&!S.getIsSelected()&&S.getToggleSelectedHandler()(R)},_=(S,R)=>S.getIsGrouped()?"":m("banded-row",R%2===0?"even":"odd"),T=(S,R,P)=>{if(!((S==null?void 0:S.length)===0||R.depth{F(S)},children:[n.jsx(Wt,{className:"tw-mb-1 tw-mt-2",children:n.jsx(de,{})}),n.jsx(Jt,{position:"item-aligned",children:n.jsx(zo,{children:M.map(S=>n.jsx(gt,{value:JSON.stringify(S.value),children:S.label},S.label))})})]}),n.jsxs(Fe,{className:"tw-relative tw-flex tw-flex-col tw-overflow-y-auto tw-p-0",children:[e&&n.jsx(ze,{children:y.getHeaderGroups().map(S=>n.jsx(Mt,{children:S.headers.filter(R=>R.column.columnDef.header).map(R=>n.jsx(Ne,{colSpan:R.colSpan,className:"top-0 tw-sticky",children:R.isPlaceholder?void 0:n.jsxs("div",{children:[R.column.getCanGroup()?n.jsx($,{variant:"ghost",title:`Toggle grouping by ${R.column.columnDef.header}`,onClick:R.column.getToggleGroupingHandler(),type:"button",children:R.column.getIsGrouped()?"๐Ÿ›‘":"๐Ÿ‘Š "}):void 0," ",ut.flexRender(R.column.columnDef.header,R.getContext())]})},R.id))},S.id))}),n.jsx(Ge,{children:y.getRowModel().rows.map((S,R)=>{const P=st();return n.jsx(Mt,{"data-state":S.getIsSelected()?"selected":"",className:m(_(S,R)),onClick:L=>V(S,L),children:S.getVisibleCells().map(L=>{if(!(L.getIsPlaceholder()||L.column.columnDef.enableGrouping&&!L.getIsGrouped()&&(L.column.columnDef.id!==ae||!r)))return n.jsx(Yt,{className:m(L.column.columnDef.id,"tw-p-[1px]",T(w,S,L)),children:L.getIsGrouped()?n.jsxs($,{variant:"link",onClick:S.getToggleExpandedHandler(),type:"button",children:[S.getIsExpanded()&&n.jsx(k.ChevronDown,{}),!S.getIsExpanded()&&(P==="ltr"?n.jsx(k.ChevronRight,{}):n.jsx(k.ChevronLeft,{}))," ",ut.flexRender(L.column.columnDef.cell,L.getContext())," (",S.subRows.length,")"]}):ut.flexRender(L.column.columnDef.cell,L.getContext())},L.id)})},S.id)})})]})]})}const wr=(t,e)=>t.filter(r=>{try{return N.getSectionForBook(r)===e}catch{return!1}}),gs=(t,e,r)=>wr(t,e).every(o=>r.includes(o));function cc({section:t,availableBookIds:e,selectedBookIds:r,onToggle:o,localizedStrings:s}){const a=wr(e,t).length===0,l=s["%scripture_section_ot_short%"],c=s["%scripture_section_nt_short%"],d=s["%scripture_section_dc_short%"],w=s["%scripture_section_extra_short%"];return n.jsx($,{variant:"outline",size:"sm",onClick:()=>o(t),className:m(gs(e,t,r)&&!a&&"tw-bg-primary tw-text-primary-foreground hover:tw-bg-primary/70 hover:tw-text-primary-foreground"),disabled:a,children:Aa(t,l,c,d,w)})}const Lr=5,_n=6;function wc({availableBookInfo:t,selectedBookIds:e,onChangeSelectedBookIds:r,localizedStrings:o,localizedBookNames:s}){const a=o["%webView_book_selector_books_selected%"],l=o["%webView_book_selector_select_books%"],c=o["%webView_book_selector_search_books%"],d=o["%webView_book_selector_select_all%"],w=o["%webView_book_selector_clear_all%"],p=o["%webView_book_selector_no_book_found%"],u=o["%webView_book_selector_more%"],{otLong:h,ntLong:f,dcLong:g,extraLong:v}={otLong:o==null?void 0:o["%scripture_section_ot_long%"],ntLong:o==null?void 0:o["%scripture_section_nt_long%"],dcLong:o==null?void 0:o["%scripture_section_dc_long%"],extraLong:o==null?void 0:o["%scripture_section_extra_long%"]},[b,y]=i.useState(!1),[j,C]=i.useState(""),M=i.useRef(void 0),F=i.useRef(!1);if(t.length!==Q.Canon.allBookIds.length)throw new Error("availableBookInfo length must match Canon.allBookIds length");const V=i.useMemo(()=>Q.Canon.allBookIds.filter((A,z)=>t[z]==="1"&&!Q.Canon.isObsolete(Q.Canon.bookIdToNumber(A))),[t]),_=i.useMemo(()=>{if(!j.trim()){const E={[N.Section.OT]:[],[N.Section.NT]:[],[N.Section.DC]:[],[N.Section.Extra]:[]};return V.forEach(B=>{const at=N.getSectionForBook(B);E[at].push(B)}),E}const A=V.filter(E=>$n(E,j,s)),z={[N.Section.OT]:[],[N.Section.NT]:[],[N.Section.DC]:[],[N.Section.Extra]:[]};return A.forEach(E=>{const B=N.getSectionForBook(E);z[B].push(E)}),z},[V,j,s]),T=i.useCallback((A,z=!1)=>{if(!z||!M.current){r(e.includes(A)?e.filter(ct=>ct!==A):[...e,A]),M.current=A;return}const E=V.findIndex(ct=>ct===M.current),B=V.findIndex(ct=>ct===A);if(E===-1||B===-1)return;const[at,lt]=[Math.min(E,B),Math.max(E,B)],Vt=V.slice(at,lt+1).map(ct=>ct);r(e.includes(A)?e.filter(ct=>!Vt.includes(ct)):[...new Set([...e,...Vt])])},[e,r,V]),S=A=>{T(A,F.current),F.current=!1},R=(A,z)=>{A.preventDefault(),T(z,A.shiftKey)},P=i.useCallback(A=>{const z=wr(V,A).map(E=>E);r(gs(V,A,e)?e.filter(E=>!z.includes(E)):[...new Set([...e,...z])])},[e,r,V]),L=()=>{r(V.map(A=>A))},I=()=>{r([])};return n.jsxs("div",{className:"tw-space-y-2",children:[n.jsx("div",{className:"tw-flex tw-flex-wrap tw-gap-2",children:Object.values(N.Section).map(A=>n.jsx(cc,{section:A,availableBookIds:V,selectedBookIds:e,onToggle:P,localizedStrings:o},A))}),n.jsxs(zt,{open:b,onOpenChange:A=>{y(A),A||C("")},children:[n.jsx(Gt,{asChild:!0,children:n.jsxs($,{variant:"outline",role:"combobox","aria-expanded":b,className:"tw-max-w-64 tw-justify-between",children:[e.length>0?`${a}: ${e.length}`:l,n.jsx(k.ChevronsUpDown,{className:"tw-ml-2 tw-h-4 tw-w-4 tw-shrink-0 tw-opacity-50"})]})}),n.jsx(Lt,{className:"tw-w-full tw-p-0",align:"start",children:n.jsxs(At,{shouldFilter:!1,onKeyDown:A=>{A.key==="Enter"&&(F.current=A.shiftKey)},children:[n.jsx(pe,{placeholder:c,value:j,onValueChange:C}),n.jsxs("div",{className:"tw-flex tw-justify-between tw-border-b tw-p-2",children:[n.jsx($,{variant:"ghost",size:"sm",onClick:L,children:d}),n.jsx($,{variant:"ghost",size:"sm",onClick:I,children:w})]}),n.jsxs(Pt,{children:[n.jsx(Ce,{children:p}),Object.values(N.Section).map((A,z)=>{const E=_[A];if(E.length!==0)return n.jsxs(i.Fragment,{children:[n.jsx(Ot,{heading:Jr(A,h,f,g,v),children:E.map(B=>n.jsx(Qr,{bookId:B,isSelected:e.includes(B),onSelect:()=>S(B),onMouseDown:at=>R(at,B),section:N.getSectionForBook(B),showCheck:!0,localizedBookNames:s,commandValue:Dn(B,s),className:"tw-flex tw-items-center"},B))}),z0&&n.jsxs("div",{className:"tw-mt-2 tw-flex tw-flex-wrap tw-gap-1",children:[e.slice(0,e.length===_n?_n:Lr).map(A=>n.jsx(le,{className:"hover:tw-bg-secondary",variant:"secondary",children:be(A,s)},A)),e.length>_n&&n.jsx(le,{className:"hover:tw-bg-secondary",variant:"secondary",children:`+${e.length-Lr} ${u}`})]})]})}const dc=Object.freeze(["%webView_scope_selector_selected_text%","%webView_scope_selector_current_verse%","%webView_scope_selector_current_chapter%","%webView_scope_selector_current_book%","%webView_scope_selector_choose_books%","%webView_scope_selector_scope%","%webView_scope_selector_select_books%","%webView_book_selector_books_selected%","%webView_book_selector_select_books%","%webView_book_selector_search_books%","%webView_book_selector_select_all%","%webView_book_selector_clear_all%","%webView_book_selector_no_book_found%","%webView_book_selector_more%","%scripture_section_ot_long%","%scripture_section_ot_short%","%scripture_section_nt_long%","%scripture_section_nt_short%","%scripture_section_dc_long%","%scripture_section_dc_short%","%scripture_section_extra_long%","%scripture_section_extra_short%"]),oe=(t,e)=>t[e]??e;function pc({scope:t,availableScopes:e,onScopeChange:r,availableBookInfo:o,selectedBookIds:s,onSelectedBookIdsChange:a,localizedStrings:l,localizedBookNames:c,id:d}){const w=oe(l,"%webView_scope_selector_selected_text%"),p=oe(l,"%webView_scope_selector_current_verse%"),u=oe(l,"%webView_scope_selector_current_chapter%"),h=oe(l,"%webView_scope_selector_current_book%"),f=oe(l,"%webView_scope_selector_choose_books%"),g=oe(l,"%webView_scope_selector_scope%"),v=oe(l,"%webView_scope_selector_select_books%"),b=[{value:"selectedText",label:w,id:"scope-selected-text"},{value:"verse",label:p,id:"scope-verse"},{value:"chapter",label:u,id:"scope-chapter"},{value:"book",label:h,id:"scope-book"},{value:"selectedBooks",label:f,id:"scope-selected"}],y=e?b.filter(j=>e.includes(j.value)):b;return n.jsxs("div",{id:d,className:"tw-grid tw-gap-4",children:[n.jsxs("div",{className:"tw-grid tw-gap-2",children:[n.jsx(et,{children:g}),n.jsx(ln,{value:t,onValueChange:r,className:"tw-flex tw-flex-col tw-space-y-1",children:y.map(({value:j,label:C,id:M})=>n.jsxs("div",{className:"tw-flex tw-items-center",children:[n.jsx(Ae,{className:"tw-me-2",value:j,id:M}),n.jsx(et,{htmlFor:M,children:C})]},M))})]}),t==="selectedBooks"&&n.jsxs("div",{className:"tw-grid tw-gap-2",children:[n.jsx(et,{children:v}),n.jsx(wc,{availableBookInfo:o,selectedBookIds:s,onChangeSelectedBookIds:a,localizedStrings:l,localizedBookNames:c})]})]})}const Cn={[N.getLocalizeKeyForScrollGroupId("undefined")]:"ร˜",[N.getLocalizeKeyForScrollGroupId(0)]:"A",[N.getLocalizeKeyForScrollGroupId(1)]:"B",[N.getLocalizeKeyForScrollGroupId(2)]:"C",[N.getLocalizeKeyForScrollGroupId(3)]:"D",[N.getLocalizeKeyForScrollGroupId(4)]:"E",[N.getLocalizeKeyForScrollGroupId(5)]:"F",[N.getLocalizeKeyForScrollGroupId(6)]:"G",[N.getLocalizeKeyForScrollGroupId(7)]:"H",[N.getLocalizeKeyForScrollGroupId(8)]:"I",[N.getLocalizeKeyForScrollGroupId(9)]:"J",[N.getLocalizeKeyForScrollGroupId(10)]:"K",[N.getLocalizeKeyForScrollGroupId(11)]:"L",[N.getLocalizeKeyForScrollGroupId(12)]:"M",[N.getLocalizeKeyForScrollGroupId(13)]:"N",[N.getLocalizeKeyForScrollGroupId(14)]:"O",[N.getLocalizeKeyForScrollGroupId(15)]:"P",[N.getLocalizeKeyForScrollGroupId(16)]:"Q",[N.getLocalizeKeyForScrollGroupId(17)]:"R",[N.getLocalizeKeyForScrollGroupId(18)]:"S",[N.getLocalizeKeyForScrollGroupId(19)]:"T",[N.getLocalizeKeyForScrollGroupId(20)]:"U",[N.getLocalizeKeyForScrollGroupId(21)]:"V",[N.getLocalizeKeyForScrollGroupId(22)]:"W",[N.getLocalizeKeyForScrollGroupId(23)]:"X",[N.getLocalizeKeyForScrollGroupId(24)]:"Y",[N.getLocalizeKeyForScrollGroupId(25)]:"Z"};function uc({availableScrollGroupIds:t,scrollGroupId:e,onChangeScrollGroupId:r,localizedStrings:o={},size:s="sm",className:a,id:l}){const c={...Cn,...Object.fromEntries(Object.entries(o).map(([w,p])=>[w,w===p&&w in Cn?Cn[w]:p]))},d=st();return n.jsxs(we,{value:`${e}`,onValueChange:w=>r(w==="undefined"?void 0:parseInt(w,10)),children:[n.jsx(Wt,{size:s,className:m("pr-twp tw-w-auto",a),children:n.jsx(de,{placeholder:c[N.getLocalizeKeyForScrollGroupId(e)]??e})}),n.jsx(Jt,{id:l,align:d==="rtl"?"end":"start",style:{zIndex:250},children:t.map(w=>n.jsx(gt,{value:`${w}`,children:c[N.getLocalizeKeyForScrollGroupId(w)]},`${w}`))})]})}function mc({children:t}){return n.jsx("div",{className:"pr-twp tw-grid",children:t})}function fc({primary:t,secondary:e,children:r,isLoading:o=!1,loadingMessage:s}){return n.jsxs("div",{className:"tw-flex tw-items-center tw-justify-between tw-space-x-4 tw-py-2",children:[n.jsxs("div",{children:[n.jsx("p",{className:"tw-text-sm tw-font-medium tw-leading-none",children:t}),n.jsx("p",{className:"tw-whitespace-normal tw-break-words tw-text-sm tw-text-muted-foreground",children:e})]}),o?n.jsx("p",{className:"tw-text-sm tw-text-muted-foreground",children:s}):n.jsx("div",{children:r})]})}function hc({primary:t,secondary:e,includeSeparator:r=!1}){return n.jsxs("div",{className:"tw-space-y-4 tw-py-2",children:[n.jsxs("div",{children:[n.jsx("h3",{className:"tw-text-lg tw-font-medium",children:t}),n.jsx("p",{className:"tw-text-sm tw-text-muted-foreground",children:e})]}),r?n.jsx(ce,{}):""]})}function xs(t,e){var r;return(r=Object.entries(t).find(([,o])=>"menuItem"in o&&o.menuItem===e))==null?void 0:r[0]}function on({icon:t,menuLabel:e,leading:r}){return t?n.jsx("img",{className:m("tw-max-h-5 tw-max-w-5",r?"tw-me-2":"tw-ms-2"),src:t,alt:`${r?"Leading":"Trailing"} icon for ${e}`}):void 0}const bs=(t,e,r,o)=>r?Object.entries(t).filter(([a,l])=>"column"in l&&l.column===r||a===r).sort(([,a],[,l])=>a.order-l.order).flatMap(([a])=>e.filter(c=>c.group===a).sort((c,d)=>c.order-d.order).map(c=>n.jsxs(Nt,{children:[n.jsx(Dt,{asChild:!0,children:"command"in c?n.jsxs(Le,{onClick:()=>{o(c)},children:[c.iconPathBefore&&n.jsx(on,{icon:c.iconPathBefore,menuLabel:c.label,leading:!0}),c.label,c.iconPathAfter&&n.jsx(on,{icon:c.iconPathAfter,menuLabel:c.label})]},`dropdown-menu-item-${c.label}-${c.command}`):n.jsxs($o,{children:[n.jsx(Jn,{children:c.label}),n.jsx(Lo,{children:n.jsx(Zn,{children:bs(t,e,xs(t,c.id),o)})})]},`dropdown-menu-sub-${c.label}-${c.id}`)}),c.tooltip&&n.jsx(yt,{children:c.tooltip})]},`tooltip-${c.label}-${"command"in c?c.command:c.id}`))):void 0;function sn({onSelectMenuItem:t,menuData:e,tabLabel:r,icon:o,className:s,variant:a,buttonVariant:l="ghost",id:c}){return n.jsxs(Qt,{variant:a,children:[n.jsx(ue,{"aria-label":r,className:s,asChild:!0,id:c,children:n.jsx($,{variant:l,size:"icon",children:o??n.jsx(k.MenuIcon,{})})}),n.jsx(Kt,{align:"start",className:"tw-z-[250]",children:Object.entries(e.columns).filter(([,d])=>typeof d=="object").sort(([,d],[,w])=>typeof d=="boolean"||typeof w=="boolean"?0:d.order-w.order).map(([d],w,p)=>n.jsxs(i.Fragment,{children:[n.jsx(Wn,{children:n.jsx(vt,{children:bs(e.groups,e.items,d,t)})}),wn.jsx("div",{ref:o,className:`tw-sticky tw-top-0 tw-box-border tw-flex tw-h-14 tw-flex-row tw-items-center tw-justify-between tw-gap-2 tw-overflow-clip tw-px-4 tw-py-2 tw-text-foreground tw-@container/toolbar ${e}`,id:t,children:r}));function gc({onSelectProjectMenuItem:t,onSelectViewInfoMenuItem:e,projectMenuData:r,tabViewMenuData:o,id:s,className:a,startAreaChildren:l,centerAreaChildren:c,endAreaChildren:d,menuButtonIcon:w}){return n.jsxs(vs,{className:`tw-w-full tw-border ${a}`,id:s,children:[r&&n.jsx(sn,{onSelectMenuItem:t,menuData:r,tabLabel:"Project",icon:w??n.jsx(k.Menu,{}),buttonVariant:"ghost"}),l&&n.jsx("div",{className:"tw-flex tw-h-full tw-shrink tw-grow-[2] tw-flex-row tw-flex-wrap tw-items-start tw-gap-2 tw-overflow-clip tw-@container/tab-toolbar-start",children:l}),c&&n.jsx("div",{className:"tw-flex tw-h-full tw-shrink tw-basis-0 tw-flex-row tw-flex-wrap tw-items-start tw-justify-center tw-gap-2 tw-overflow-clip tw-@container/tab-toolbar-center @sm:tw-grow @sm:tw-basis-auto",children:c}),n.jsxs("div",{className:"tw-flex tw-h-full tw-shrink tw-grow-[2] tw-flex-row-reverse tw-flex-wrap tw-items-start tw-gap-2 tw-overflow-clip tw-@container/tab-toolbar-end",children:[o&&n.jsx(sn,{onSelectMenuItem:e,menuData:o,tabLabel:"View Info",icon:n.jsx(k.EllipsisVertical,{}),className:"tw-h-full"}),d]})]})}function xc({onSelectProjectMenuItem:t,projectMenuData:e,id:r,className:o,menuButtonIcon:s}){return n.jsx(vs,{className:"tw-pointer-events-none",id:r,children:e&&n.jsx(sn,{onSelectMenuItem:t,menuData:e,tabLabel:"Project",icon:s,className:`tw-pointer-events-auto tw-shadow-lg ${o}`,buttonVariant:"outline"})})}const dr=i.forwardRef(({className:t,...e},r)=>{const o=st();return n.jsx(ht.Root,{orientation:"vertical",ref:r,className:m("tw-flex tw-gap-1 tw-rounded-md tw-text-muted-foreground",t),...e,dir:o})});dr.displayName=ht.List.displayName;const pr=i.forwardRef(({className:t,...e},r)=>n.jsx(ht.List,{ref:r,className:m("tw-flex-fit tw-mlk-items-center tw-w-[124px] tw-justify-center tw-rounded-md tw-bg-muted tw-p-1 tw-text-muted-foreground",t),...e}));pr.displayName=ht.List.displayName;const ys=i.forwardRef(({className:t,...e},r)=>n.jsx(ht.Trigger,{ref:r,...e,className:m("overflow-clip tw-inline-flex tw-w-[116px] tw-cursor-pointer tw-items-center tw-justify-center tw-break-words tw-rounded-sm tw-border-0 tw-bg-muted tw-px-3 tw-py-1.5 tw-text-sm tw-font-medium tw-text-inherit tw-ring-offset-background tw-transition-all hover:tw-text-foreground focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-pointer-events-none disabled:tw-opacity-50 data-[state=active]:tw-bg-background data-[state=active]:tw-text-foreground data-[state=active]:tw-shadow-sm",t)})),ur=i.forwardRef(({className:t,...e},r)=>n.jsx(ht.Content,{ref:r,className:m("tw-ms-5 tw-flex-grow tw-text-foreground tw-ring-offset-background focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2",t),...e}));ur.displayName=ht.Content.displayName;function bc({tabList:t,searchValue:e,onSearch:r,searchPlaceholder:o,headerTitle:s,searchClassName:a,id:l}){return n.jsxs("div",{id:l,className:"pr-twp",children:[n.jsxs("div",{className:"tw-sticky tw-top-0 tw-space-y-2 tw-pb-2",children:[s?n.jsx("h1",{children:s}):"",n.jsx(mn,{className:a,value:e,onSearch:r,placeholder:o})]}),n.jsxs(dr,{children:[n.jsx(pr,{children:t.map(c=>n.jsx(ys,{value:c.value,children:c.value},c.key))}),t.map(c=>n.jsx(ur,{value:c.value,children:c.content},c.key))]})]})}function vc({...t}){return n.jsx(W.Menu,{...t})}function yc({...t}){return n.jsx(W.Sub,{"data-slot":"menubar-sub",...t})}const js=i.forwardRef(({className:t,variant:e="default",...r},o)=>{const s=i.useMemo(()=>({variant:e}),[e]);return n.jsx(Xn.Provider,{value:s,children:n.jsx(W.Root,{ref:o,className:m("tw-flex tw-h-10 tw-items-center tw-space-x-1 tw-rounded-md tw-border tw-bg-background tw-p-1",t),...r})})});js.displayName=W.Root.displayName;const Ns=i.forwardRef(({className:t,...e},r)=>{const o=St();return n.jsx(W.Trigger,{ref:r,className:m("tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-3 tw-py-1.5 tw-text-sm tw-font-medium tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[state=open]:tw-bg-accent data-[state=open]:tw-text-accent-foreground","pr-twp",Bt({variant:o.variant,className:t})),...e})});Ns.displayName=W.Trigger.displayName;const ks=i.forwardRef(({className:t,inset:e,children:r,...o},s)=>{const a=St();return n.jsxs(W.SubTrigger,{ref:s,className:m("tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[state=open]:tw-bg-accent data-[state=open]:tw-text-accent-foreground",e&&"tw-pl-8",Bt({variant:a.variant,className:t}),t),...o,children:[r,n.jsx(k.ChevronRight,{className:"tw-ml-auto tw-h-4 tw-w-4"})]})});ks.displayName=W.SubTrigger.displayName;const _s=i.forwardRef(({className:t,...e},r)=>{const o=St();return n.jsx(W.SubContent,{ref:r,className:m("tw-z-50 tw-min-w-[8rem] tw-overflow-hidden tw-rounded-md tw-border tw-bg-popover tw-p-1 tw-text-popover-foreground data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2",{"tw-bg-popover":o.variant==="muted"},t),...e})});_s.displayName=W.SubContent.displayName;const Cs=i.forwardRef(({className:t,align:e="start",alignOffset:r=-4,sideOffset:o=8,...s},a)=>{const l=St();return n.jsx(W.Portal,{children:n.jsx(W.Content,{ref:a,align:e,alignOffset:r,sideOffset:o,className:m("tw-z-50 tw-min-w-[12rem] tw-overflow-hidden tw-rounded-md tw-border tw-bg-popover tw-p-1 tw-text-popover-foreground tw-shadow-md data-[state=open]:tw-animate-in data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2","pr-twp",{"tw-bg-popover":l.variant==="muted"},t),...s})})});Cs.displayName=W.Content.displayName;const Ss=i.forwardRef(({className:t,inset:e,...r},o)=>{const s=St();return n.jsx(W.Item,{ref:o,className:m("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",e&&"tw-pl-8",Bt({variant:s.variant,className:t}),t),...r})});Ss.displayName=W.Item.displayName;const jc=i.forwardRef(({className:t,children:e,checked:r,...o},s)=>{const a=St();return n.jsxs(W.CheckboxItem,{ref:s,className:m("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pl-8 tw-pr-2 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",Bt({variant:a.variant,className:t}),t),checked:r,...o,children:[n.jsx("span",{className:"tw-absolute tw-left-2 tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center",children:n.jsx(W.ItemIndicator,{children:n.jsx(k.Check,{className:"tw-h-4 tw-w-4"})})}),e]})});jc.displayName=W.CheckboxItem.displayName;const Nc=i.forwardRef(({className:t,children:e,...r},o)=>{const s=St();return n.jsxs(W.RadioItem,{ref:o,className:m("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pl-8 tw-pr-2 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",Bt({variant:s.variant,className:t}),t),...r,children:[n.jsx("span",{className:"tw-absolute tw-left-2 tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center",children:n.jsx(W.ItemIndicator,{children:n.jsx(k.Circle,{className:"tw-h-2 tw-w-2 tw-fill-current"})})}),e]})});Nc.displayName=W.RadioItem.displayName;const kc=i.forwardRef(({className:t,inset:e,...r},o)=>n.jsx(W.Label,{ref:o,className:m("tw-px-2 tw-py-1.5 tw-text-sm tw-font-semibold",e&&"tw-pl-8",t),...r}));kc.displayName=W.Label.displayName;const Es=i.forwardRef(({className:t,...e},r)=>n.jsx(W.Separator,{ref:r,className:m("tw--mx-1 tw-my-1 tw-h-px tw-bg-muted",t),...e}));Es.displayName=W.Separator.displayName;const Te=(t,e)=>{setTimeout(()=>{e.forEach(r=>{var o;(o=t.current)==null||o.dispatchEvent(new KeyboardEvent("keydown",r))})},0)},Rs=(t,e,r,o)=>{if(!r)return;const s=Object.entries(t).filter(([a,l])=>"column"in l&&l.column===r||a===r).sort(([,a],[,l])=>a.order-l.order);return s.flatMap(([a],l)=>{const c=e.filter(w=>w.group===a).sort((w,p)=>w.order-p.order).map(w=>n.jsxs(Nt,{children:[n.jsx(Dt,{asChild:!0,children:"command"in w?n.jsxs(Ss,{onClick:()=>{o(w)},children:[w.iconPathBefore&&n.jsx(on,{icon:w.iconPathBefore,menuLabel:w.label,leading:!0}),w.label,w.iconPathAfter&&n.jsx(on,{icon:w.iconPathAfter,menuLabel:w.label})]},`menubar-item-${w.label}-${w.command}`):n.jsxs(yc,{children:[n.jsx(ks,{children:w.label}),n.jsx(_s,{children:Rs(t,e,xs(t,w.id),o)})]},`menubar-sub-${w.label}-${w.id}`)}),w.tooltip&&n.jsx(yt,{children:w.tooltip})]},`tooltip-${w.label}-${"command"in w?w.command:w.id}`)),d=[...c];return c.length>0&&l{switch(p){case"platform.app":return a;case"platform.window":return l;case"platform.layout":return c;case"platform.help":return d;default:return}};if(ka.useHotkeys(["alt","alt+p","alt+l","alt+n","alt+h"],(p,u)=>{var g,v,b,y;p.preventDefault();const h={key:"Escape",code:"Escape",keyCode:27,bubbles:!0},f={key:" ",code:"Space",keyCode:32,bubbles:!0};switch(u.hotkey){case"alt":Te(a,[h]);break;case"alt+p":(g=a.current)==null||g.focus(),Te(a,[h,f]);break;case"alt+l":(v=l.current)==null||v.focus(),Te(l,[h,f]);break;case"alt+n":(b=c.current)==null||b.focus(),Te(c,[h,f]);break;case"alt+h":(y=d.current)==null||y.focus(),Te(d,[h,f]);break}}),i.useEffect(()=>{if(!r||!s.current)return;const p=new MutationObserver(f=>{f.forEach(g=>{if(g.attributeName==="data-state"&&g.target instanceof HTMLElement){const v=g.target.getAttribute("data-state");r(v==="open")}})});return s.current.querySelectorAll("[data-state]").forEach(f=>{p.observe(f,{attributes:!0})}),()=>p.disconnect()},[r]),!!t)return n.jsx(js,{ref:s,className:"pr-twp tw-border-0 tw-bg-transparent",variant:o,children:Object.entries(t.columns).filter(([,p])=>typeof p=="object").sort(([,p],[,u])=>typeof p=="boolean"||typeof u=="boolean"?0:p.order-u.order).map(([p,u])=>n.jsxs(vc,{children:[n.jsx(Ns,{ref:w(p),children:typeof u=="object"&&"label"in u&&u.label}),n.jsx(Cs,{className:"tw-z-[250]",children:n.jsx(vt,{children:Rs(t.groups,t.items,p,e)})})]},p))})}function Cc(t){switch(t){case void 0:return;case"darwin":return"tw-ps-[85px]";default:return"tw-pe-[calc(138px+1rem)]"}}function Sc({menuData:t,onOpenChange:e,onSelectMenuItem:r,className:o,id:s,children:a,appMenuAreaChildren:l,configAreaChildren:c,shouldUseAsAppDragArea:d,menubarVariant:w="default"}){const p=i.useRef(void 0);return n.jsx("div",{className:m("tw-border tw-px-4 tw-text-foreground",o),ref:p,style:{position:"relative"},id:s,children:n.jsxs("div",{className:"tw-flex tw-h-full tw-w-full tw-justify-between tw-overflow-hidden",style:d?{WebkitAppRegion:"drag"}:void 0,children:[n.jsx("div",{className:"tw-flex tw-grow tw-basis-0",children:n.jsxs("div",{className:"tw-flex tw-items-center tw-gap-2",style:d?{WebkitAppRegion:"no-drag"}:void 0,children:[l,t&&n.jsx(_c,{menuData:t,onOpenChange:e,onSelectMenuItem:r,variant:w})]})}),n.jsx("div",{className:"tw-flex tw-items-center tw-gap-2 tw-px-2",style:d?{WebkitAppRegion:"no-drag"}:void 0,children:a}),n.jsx("div",{className:"tw-flex tw-min-w-0 tw-grow tw-basis-0 tw-justify-end",children:n.jsx("div",{className:"tw-flex tw-min-w-0 tw-items-center tw-gap-2 tw-pe-1",style:d?{WebkitAppRegion:"no-drag"}:void 0,children:c})})]})})}const Ec=(t,e)=>t[e]??e;function Rc({knownUiLanguages:t,primaryLanguage:e="en",fallbackLanguages:r=[],onLanguagesChange:o,onPrimaryLanguageChange:s,onFallbackLanguagesChange:a,localizedStrings:l,className:c,id:d}){const w=Ec(l,"%settings_uiLanguageSelector_fallbackLanguages%"),[p,u]=i.useState(!1),h=g=>{s&&s(g),o&&o([g,...r.filter(v=>v!==g)]),a&&r.find(v=>v===g)&&a([...r.filter(v=>v!==g)]),u(!1)},f=(g,v)=>{var y,j,C,M,F,V;const b=v!==g?((j=(y=t[g])==null?void 0:y.uiNames)==null?void 0:j[v])??((M=(C=t[g])==null?void 0:C.uiNames)==null?void 0:M.en):void 0;return b?`${(F=t[g])==null?void 0:F.autonym} (${b})`:(V=t[g])==null?void 0:V.autonym};return n.jsxs("div",{id:d,className:m("pr-twp tw-max-w-sm",c),children:[n.jsxs(we,{name:"uiLanguage",value:e,onValueChange:h,open:p,onOpenChange:g=>u(g),children:[n.jsx(Wt,{children:n.jsx(de,{})}),n.jsx(Jt,{className:"tw-z-[250]",children:Object.keys(t).map(g=>n.jsx(gt,{value:g,children:f(g,e)},g))})]}),e!=="en"&&n.jsx("div",{className:"tw-pt-3",children:n.jsx(et,{className:"tw-font-normal tw-text-muted-foreground",children:N.formatReplacementString(w,{fallbackLanguages:(r==null?void 0:r.length)>0?r.map(g=>f(g,e)).join(", "):t.en.autonym})})})]})}function Tc({item:t,createLabel:e,createComplexLabel:r}){return e?n.jsx(et,{children:e(t)}):r?n.jsx(et,{children:r(t)}):n.jsx(et,{children:t})}function Mc({id:t,className:e,listItems:r,selectedListItems:o,handleSelectListItem:s,createLabel:a,createComplexLabel:l}){return n.jsx("div",{id:t,className:e,children:r.map(c=>n.jsxs("div",{className:"tw-m-2 tw-flex tw-items-center",children:[n.jsx(pn,{className:"tw-me-2 tw-align-middle",checked:o.includes(c),onCheckedChange:d=>s(c,d)}),n.jsx(Tc,{item:c,createLabel:a,createComplexLabel:l})]},c))})}function Dc({cardKey:t,isSelected:e,onSelect:r,isDenied:o,isHidden:s=!1,className:a,children:l,dropdownContent:c,additionalSelectedContent:d,accentColor:w}){const p=u=>{(u.key==="Enter"||u.key===" ")&&(u.preventDefault(),r())};return n.jsxs("div",{hidden:s,onClick:r,onKeyDown:p,role:"button",tabIndex:0,"aria-pressed":e,className:m("tw-relative tw-min-w-36 tw-rounded-xl tw-border tw-shadow-none hover:tw-bg-muted/50",{"tw-opacity-50 hover:tw-opacity-100":o&&!e},{"tw-bg-accent":e},{"tw-bg-transparent":!e},a),children:[n.jsxs("div",{className:"tw-flex tw-flex-col tw-gap-2 tw-p-4",children:[n.jsxs("div",{className:"tw-flex tw-justify-between tw-overflow-hidden",children:[n.jsx("div",{className:"tw-min-w-0 tw-flex-1",children:l}),e&&c&&n.jsxs(Qt,{children:[n.jsx(ue,{className:m(w&&"tw-me-1"),asChild:!0,children:n.jsx($,{className:"tw-m-1 tw-h-6 tw-w-6",variant:"ghost",size:"icon",children:n.jsx(k.MoreHorizontal,{})})}),n.jsx(Kt,{align:"end",children:c})]})]}),e&&d&&n.jsx("div",{className:"tw-w-fit tw-min-w-0 tw-max-w-full tw-overflow-hidden",children:d})]}),w&&n.jsx("div",{className:`tw-absolute tw-right-0 tw-top-0 tw-h-full tw-w-2 tw-rounded-r-xl ${w}`})]},t)}const Ts=i.forwardRef(({className:t,...e},r)=>n.jsx(k.LoaderCircle,{size:35,className:m("tw-animate-spin",t),...e,ref:r}));Ts.displayName="Spinner";function Ic({id:t,isDisabled:e=!1,hasError:r=!1,isFullWidth:o=!1,helperText:s,label:a,placeholder:l,isRequired:c=!1,className:d,defaultValue:w,value:p,onChange:u,onFocus:h,onBlur:f}){return n.jsxs("div",{className:m("tw-inline-grid tw-items-center tw-gap-1.5",{"tw-w-full":o}),children:[n.jsx(et,{htmlFor:t,className:m({"tw-text-red-600":r,"tw-hidden":!a}),children:`${a}${c?"*":""}`}),n.jsx(fe,{id:t,disabled:e,placeholder:l,required:c,className:m(d,{"tw-border-red-600":r}),defaultValue:w,value:p,onChange:u,onFocus:h,onBlur:f}),n.jsx("p",{className:m({"tw-hidden":!s}),children:s})]})}const Oc=Zt.cva("tw-relative tw-w-full tw-rounded-lg tw-border tw-p-4 [&>svg~*]:tw-pl-7 [&>svg+div]:tw-translate-y-[-3px] [&>svg]:tw-absolute [&>svg]:tw-left-4 [&>svg]:tw-top-4 [&>svg]:tw-text-foreground [&>img~*]:tw-pl-7 [&>img+div]:tw-translate-y-[-3px] [&>img]:tw-absolute [&>img]:tw-left-4 [&>img]:tw-top-4 [&>img]:tw-text-foreground",{variants:{variant:{default:"tw-bg-background tw-text-foreground",destructive:"tw-border-destructive/50 tw-text-destructive dark:tw-border-destructive [&>svg]:tw-text-destructive [&>img]:tw-text-destructive"}},defaultVariants:{variant:"default"}}),Ms=i.forwardRef(({className:t,variant:e,...r},o)=>n.jsx("div",{ref:o,role:"alert",className:m("pr-twp",Oc({variant:e}),t),...r}));Ms.displayName="Alert";const Ds=i.forwardRef(({className:t,...e},r)=>n.jsxs("h5",{ref:r,className:m("tw-mb-1 tw-font-medium tw-leading-none tw-tracking-tight",t),...e,children:[e.children," "]}));Ds.displayName="AlertTitle";const Is=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,className:m("tw-text-sm [&_p]:tw-leading-relaxed",t),...e}));Is.displayName="AlertDescription";const Ac=J.Root,Pc=J.Trigger,Lc=J.Group,$c=J.Portal,Vc=J.Sub,Fc=J.RadioGroup,Os=i.forwardRef(({className:t,inset:e,children:r,...o},s)=>n.jsxs(J.SubTrigger,{ref:s,className:m("pr-twp tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[state=open]:tw-bg-accent data-[state=open]:tw-text-accent-foreground",e&&"tw-pl-8",t),...o,children:[r,n.jsx(k.ChevronRight,{className:"tw-ml-auto tw-h-4 tw-w-4"})]}));Os.displayName=J.SubTrigger.displayName;const As=i.forwardRef(({className:t,...e},r)=>n.jsx(J.SubContent,{ref:r,className:m("pr-twp tw-z-50 tw-min-w-[8rem] tw-origin-[--radix-context-menu-content-transform-origin] tw-overflow-hidden tw-rounded-md tw-border tw-bg-popover tw-p-1 tw-text-popover-foreground tw-shadow-md data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2",t),...e}));As.displayName=J.SubContent.displayName;const Ps=i.forwardRef(({className:t,...e},r)=>n.jsx(J.Portal,{children:n.jsx(J.Content,{ref:r,className:m("pr-twp tw-z-50 tw-max-h-[--radix-context-menu-content-available-height] tw-min-w-[8rem] tw-origin-[--radix-context-menu-content-transform-origin] tw-overflow-y-auto tw-overflow-x-hidden tw-rounded-md tw-border tw-bg-popover tw-p-1 tw-text-popover-foreground tw-shadow-md tw-animate-in tw-fade-in-80 data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2",t),...e})}));Ps.displayName=J.Content.displayName;const Ls=i.forwardRef(({className:t,inset:e,...r},o)=>n.jsx(J.Item,{ref:o,className:m("pr-twp tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",e&&"tw-pl-8",t),...r}));Ls.displayName=J.Item.displayName;const $s=i.forwardRef(({className:t,children:e,checked:r,...o},s)=>n.jsxs(J.CheckboxItem,{ref:s,className:m("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pl-8 tw-pr-2 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",t),checked:r,...o,children:[n.jsx("span",{className:"tw-absolute tw-left-2 tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center",children:n.jsx(J.ItemIndicator,{children:n.jsx(k.Check,{className:"tw-h-4 tw-w-4"})})}),e]}));$s.displayName=J.CheckboxItem.displayName;const Vs=i.forwardRef(({className:t,children:e,...r},o)=>n.jsxs(J.RadioItem,{ref:o,className:m("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pl-8 tw-pr-2 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",t),...r,children:[n.jsx("span",{className:"tw-absolute tw-left-2 tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center",children:n.jsx(J.ItemIndicator,{children:n.jsx(k.Circle,{className:"tw-h-2 tw-w-2 tw-fill-current"})})}),e]}));Vs.displayName=J.RadioItem.displayName;const Fs=i.forwardRef(({className:t,inset:e,...r},o)=>n.jsx(J.Label,{ref:o,className:m("tw-px-2 tw-py-1.5 tw-text-sm tw-font-semibold tw-text-foreground",e&&"tw-pl-8",t),...r}));Fs.displayName=J.Label.displayName;const zs=i.forwardRef(({className:t,...e},r)=>n.jsx(J.Separator,{ref:r,className:m("tw--mx-1 tw-my-1 tw-h-px tw-bg-border",t),...e}));zs.displayName=J.Separator.displayName;function Gs({className:t,...e}){return n.jsx("span",{className:m("tw-ml-auto tw-text-xs tw-tracking-widest tw-text-muted-foreground",t),...e})}Gs.displayName="ContextMenuShortcut";const Bs=i.createContext({direction:"bottom"});function Ks({shouldScaleBackground:t=!0,direction:e="bottom",...r}){const o=i.useMemo(()=>({direction:e}),[e]);return n.jsx(Bs.Provider,{value:o,children:n.jsx(_t.Drawer.Root,{shouldScaleBackground:t,direction:e,...r})})}Ks.displayName="Drawer";const zc=_t.Drawer.Trigger,qs=_t.Drawer.Portal,Gc=_t.Drawer.Close,mr=i.forwardRef(({className:t,...e},r)=>n.jsx(_t.Drawer.Overlay,{ref:r,className:m("tw-fixed tw-inset-0 tw-z-50 tw-bg-black/80",t),...e}));mr.displayName=_t.Drawer.Overlay.displayName;const Us=i.forwardRef(({className:t,children:e,hideDrawerHandle:r=!1,...o},s)=>{const{direction:a="bottom"}=i.useContext(Bs),l={bottom:"tw-inset-x-0 tw-bottom-0 tw-mt-24 tw-rounded-t-[10px]",top:"tw-inset-x-0 tw-top-0 tw-mb-24 tw-rounded-b-[10px]",left:"tw-inset-y-0 tw-left-0 tw-mr-24 tw-rounded-r-[10px] tw-w-auto tw-max-w-sm",right:"tw-inset-y-0 tw-right-0 tw-ml-24 tw-rounded-l-[10px] tw-w-auto tw-max-w-sm"},c={bottom:"tw-mx-auto tw-mt-4 tw-h-2 tw-w-[100px] tw-rounded-full tw-bg-muted",top:"tw-mx-auto tw-mb-4 tw-h-2 tw-w-[100px] tw-rounded-full tw-bg-muted",left:"tw-my-auto tw-mr-4 tw-w-2 tw-h-[100px] tw-rounded-full tw-bg-muted",right:"tw-my-auto tw-ml-4 tw-w-2 tw-h-[100px] tw-rounded-full tw-bg-muted"};return n.jsxs(qs,{children:[n.jsx(mr,{}),n.jsxs(_t.Drawer.Content,{ref:s,className:m("pr-twp tw-fixed tw-z-50 tw-flex tw-h-auto tw-border tw-bg-background",a==="bottom"||a==="top"?"tw-flex-col":"tw-flex-row",l[a],t),...o,children:[!r&&(a==="bottom"||a==="right")&&n.jsx("div",{className:c[a]}),n.jsx("div",{className:"tw-flex tw-flex-col",children:e}),!r&&(a==="top"||a==="left")&&n.jsx("div",{className:c[a]})]})]})});Us.displayName="DrawerContent";function Hs({className:t,...e}){return n.jsx("div",{className:m("tw-grid tw-gap-1.5 tw-p-4 tw-text-center sm:tw-text-left",t),...e})}Hs.displayName="DrawerHeader";function Ys({className:t,...e}){return n.jsx("div",{className:m("tw-mt-auto tw-flex tw-flex-col tw-gap-2 tw-p-4",t),...e})}Ys.displayName="DrawerFooter";const Xs=i.forwardRef(({className:t,...e},r)=>n.jsx(_t.Drawer.Title,{ref:r,className:m("tw-text-lg tw-font-semibold tw-leading-none tw-tracking-tight",t),...e}));Xs.displayName=_t.Drawer.Title.displayName;const Ws=i.forwardRef(({className:t,...e},r)=>n.jsx(_t.Drawer.Description,{ref:r,className:m("tw-text-sm tw-text-muted-foreground",t),...e}));Ws.displayName=_t.Drawer.Description.displayName;const Js=i.forwardRef(({className:t,value:e,...r},o)=>n.jsx(Tn.Root,{ref:o,className:m("pr-twp tw-relative tw-h-4 tw-w-full tw-overflow-hidden tw-rounded-full tw-bg-secondary",t),...r,children:n.jsx(Tn.Indicator,{className:"tw-h-full tw-w-full tw-flex-1 tw-bg-primary tw-transition-all",style:{transform:`translateX(-${100-(e||0)}%)`}})}));Js.displayName=Tn.Root.displayName;function Bc({className:t,...e}){return n.jsx(Pn.PanelGroup,{className:m("tw-flex tw-h-full tw-w-full data-[panel-group-direction=vertical]:tw-flex-col",t),...e})}const Kc=Pn.Panel;function qc({withHandle:t,className:e,...r}){return n.jsx(Pn.PanelResizeHandle,{className:m("tw-relative tw-flex tw-w-px tw-items-center tw-justify-center tw-bg-border after:tw-absolute after:tw-inset-y-0 after:tw-left-1/2 after:tw-w-1 after:tw--translate-x-1/2 focus-visible:tw-outline-none focus-visible:tw-ring-1 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-1 data-[panel-group-direction=vertical]:tw-h-px data-[panel-group-direction=vertical]:tw-w-full data-[panel-group-direction=vertical]:after:tw-left-0 data-[panel-group-direction=vertical]:after:tw-h-1 data-[panel-group-direction=vertical]:after:tw-w-full data-[panel-group-direction=vertical]:after:tw--translate-y-1/2 data-[panel-group-direction=vertical]:after:tw-translate-x-0 [&[data-panel-group-direction=vertical]>div]:tw-rotate-90",e),...r,children:t&&n.jsx("div",{className:"tw-z-10 tw-flex tw-h-4 tw-w-3 tw-items-center tw-justify-center tw-rounded-sm tw-border tw-bg-border",children:n.jsx(k.GripVertical,{className:"tw-h-2.5 tw-w-2.5"})})})}function Uc({...t}){return n.jsx(zr.Toaster,{className:"tw-toaster tw-group",toastOptions:{classNames:{toast:"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",description:"group-[.toast]:text-muted-foreground",actionButton:"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",cancelButton:"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground"}},...t})}const Zs=i.forwardRef(({className:t,...e},r)=>{const o=st();return n.jsxs(Me.Root,{ref:r,className:m("pr-twp tw-relative tw-flex tw-w-full tw-touch-none tw-select-none tw-items-center",t),...e,dir:o,children:[n.jsx(Me.Track,{className:"tw-relative tw-h-2 tw-w-full tw-grow tw-overflow-hidden tw-rounded-full tw-bg-secondary",children:n.jsx(Me.Range,{className:"tw-absolute tw-h-full tw-bg-primary"})}),n.jsx(Me.Thumb,{className:"tw-block tw-h-5 tw-w-5 tw-rounded-full tw-border-2 tw-border-primary tw-bg-background tw-ring-offset-background tw-transition-colors focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-pointer-events-none disabled:tw-opacity-50"})]})});Zs.displayName=Me.Root.displayName;const Qs=i.forwardRef(({className:t,...e},r)=>{const o=st();return n.jsx(Mn.Root,{className:m("tw-peer pr-twp tw-inline-flex tw-h-6 tw-w-11 tw-shrink-0 tw-cursor-pointer tw-items-center tw-rounded-full tw-border-2 tw-border-transparent tw-transition-colors focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 focus-visible:tw-ring-offset-background disabled:tw-cursor-not-allowed disabled:tw-opacity-50 data-[state=checked]:tw-bg-primary data-[state=unchecked]:tw-bg-input",t),...e,ref:r,children:n.jsx(Mn.Thumb,{className:m("pr-twp tw-pointer-events-none tw-block tw-h-5 tw-w-5 tw-rounded-full tw-bg-background tw-shadow-lg tw-ring-0 tw-transition-transform",{"data-[state=checked]:tw-translate-x-5 data-[state=unchecked]:tw-translate-x-0":o==="ltr"},{"data-[state=checked]:tw-translate-x-[-20px] data-[state=unchecked]:tw-translate-x-0":o==="rtl"})})})});Qs.displayName=Mn.Root.displayName;const Hc=ht.Root,ta=i.forwardRef(({className:t,...e},r)=>{const o=st();return n.jsx(ht.List,{ref:r,className:m("pr-twp tw-inline-flex tw-h-10 tw-items-center tw-justify-center tw-rounded-md tw-bg-muted tw-p-1 tw-text-muted-foreground",t),...e,dir:o})});ta.displayName=ht.List.displayName;const ea=i.forwardRef(({className:t,...e},r)=>n.jsx(ht.Trigger,{ref:r,className:m("pr-twp tw-inline-flex tw-items-center tw-justify-center tw-whitespace-nowrap tw-rounded-sm tw-px-3 tw-py-1.5 tw-text-sm tw-font-medium tw-ring-offset-background tw-transition-all hover:tw-text-foreground focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-pointer-events-none disabled:tw-opacity-50 data-[state=active]:tw-bg-background data-[state=active]:tw-text-foreground data-[state=active]:tw-shadow-sm",t),...e}));ea.displayName=ht.Trigger.displayName;const na=i.forwardRef(({className:t,...e},r)=>n.jsx(ht.Content,{ref:r,className:m("pr-twp tw-mt-2 tw-ring-offset-background focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2",t),...e}));na.displayName=ht.Content.displayName;const ra=i.forwardRef(({className:t,...e},r)=>n.jsx("textarea",{className:m("pr-twp tw-flex tw-min-h-[80px] tw-w-full tw-rounded-md tw-border tw-border-input tw-bg-background tw-px-3 tw-py-2 tw-text-base tw-ring-offset-background placeholder:tw-text-muted-foreground focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-cursor-not-allowed disabled:tw-opacity-50 md:tw-text-sm",t),ref:r,...e}));ra.displayName="Textarea";const Yc=(t,e)=>{i.useEffect(()=>{if(!t)return()=>{};const r=t(e);return()=>{r()}},[t,e])};function Xc(t){return{preserveValue:!0,...t}}const oa=(t,e,r={})=>{const o=i.useRef(e);o.current=e;const s=i.useRef(r);s.current=Xc(s.current);const[a,l]=i.useState(()=>o.current),[c,d]=i.useState(!0);return i.useEffect(()=>{let w=!0;return d(!!t),(async()=>{if(t){const p=await t();w&&(l(()=>p),d(!1))}})(),()=>{w=!1,s.current.preserveValue||l(()=>o.current)}},[t]),[a,c]},Sn=()=>!1,Wc=(t,e)=>{const[r]=oa(i.useCallback(async()=>{if(!t)return Sn;const o=await Promise.resolve(t(e));return async()=>o()},[e,t]),Sn,{preserveValue:!1});i.useEffect(()=>()=>{r!==Sn&&r()},[r])};function Jc(t){i.useEffect(()=>{let e;return t&&(e=document.createElement("style"),e.appendChild(document.createTextNode(t)),document.head.appendChild(e)),()=>{e&&document.head.removeChild(e)}},[t])}Object.defineProperty(exports,"sonner",{enumerable:!0,get:()=>zr.toast});exports.Alert=Ms;exports.AlertDescription=Is;exports.AlertTitle=Ds;exports.Avatar=Hn;exports.AvatarFallback=Yn;exports.AvatarImage=Po;exports.BOOK_CHAPTER_CONTROL_STRING_KEYS=Ga;exports.BOOK_SELECTOR_STRING_KEYS=qa;exports.Badge=le;exports.BookChapterControl=za;exports.BookSelectionMode=ro;exports.BookSelector=Ua;exports.Button=$;exports.COMMENT_EDITOR_STRING_KEYS=nl;exports.COMMENT_LIST_STRING_KEYS=rl;exports.Card=qn;exports.CardContent=Un;exports.CardDescription=Oo;exports.CardFooter=Ao;exports.CardHeader=Do;exports.CardTitle=Io;exports.ChapterRangeSelector=no;exports.Checkbox=pn;exports.Checklist=Mc;exports.ComboBox=Je;exports.Command=At;exports.CommandEmpty=Ce;exports.CommandGroup=Ot;exports.CommandInput=pe;exports.CommandItem=Ct;exports.CommandList=Pt;exports.CommentEditor=el;exports.CommentList=il;exports.ContextMenu=Ac;exports.ContextMenuCheckboxItem=$s;exports.ContextMenuContent=Ps;exports.ContextMenuGroup=Lc;exports.ContextMenuItem=Ls;exports.ContextMenuLabel=Fs;exports.ContextMenuPortal=$c;exports.ContextMenuRadioGroup=Fc;exports.ContextMenuRadioItem=Vs;exports.ContextMenuSeparator=zs;exports.ContextMenuShortcut=Gs;exports.ContextMenuSub=Vc;exports.ContextMenuSubContent=As;exports.ContextMenuSubTrigger=Os;exports.ContextMenuTrigger=Pc;exports.DataTable=Ho;exports.Drawer=Ks;exports.DrawerClose=Gc;exports.DrawerContent=Us;exports.DrawerDescription=Ws;exports.DrawerFooter=Ys;exports.DrawerHeader=Hs;exports.DrawerOverlay=mr;exports.DrawerPortal=qs;exports.DrawerTitle=Xs;exports.DrawerTrigger=zc;exports.DropdownMenu=Qt;exports.DropdownMenuCheckboxItem=It;exports.DropdownMenuContent=Kt;exports.DropdownMenuGroup=Wn;exports.DropdownMenuItem=Le;exports.DropdownMenuItemType=Wo;exports.DropdownMenuLabel=Se;exports.DropdownMenuPortal=Lo;exports.DropdownMenuRadioGroup=Vo;exports.DropdownMenuRadioItem=Qn;exports.DropdownMenuSeparator=me;exports.DropdownMenuShortcut=Fo;exports.DropdownMenuSub=$o;exports.DropdownMenuSubContent=Zn;exports.DropdownMenuSubTrigger=Jn;exports.DropdownMenuTrigger=ue;exports.ERROR_DUMP_STRING_KEYS=Yo;exports.ERROR_POPOVER_STRING_KEYS=fl;exports.ErrorDump=Xo;exports.ErrorPopover=hl;exports.FOOTNOTE_EDITOR_STRING_KEYS=Ml;exports.Filter=yl;exports.FilterDropdown=gl;exports.Footer=vl;exports.FootnoteEditor=Tl;exports.FootnoteItem=Qo;exports.FootnoteList=Ol;exports.INVENTORY_STRING_KEYS=Kl;exports.Input=fe;exports.Inventory=Hl;exports.Label=et;exports.MARKER_MENU_STRING_KEYS=Yl;exports.MarkdownRenderer=ml;exports.MarkerMenu=Wl;exports.MoreInfo=xl;exports.MultiSelectComboBox=Jo;exports.NavigationContentSearch=bc;exports.Popover=zt;exports.PopoverAnchor=Pa;exports.PopoverContent=Lt;exports.PopoverTrigger=Gt;exports.Progress=Js;exports.RadioGroup=ln;exports.RadioGroupItem=Ae;exports.RecentSearches=eo;exports.ResizableHandle=qc;exports.ResizablePanel=Kc;exports.ResizablePanelGroup=Bc;exports.ResultsCard=Dc;exports.SCOPE_SELECTOR_STRING_KEYS=dc;exports.ScopeSelector=pc;exports.ScriptureResultsViewer=lc;exports.ScrollGroupSelector=uc;exports.SearchBar=mn;exports.Select=we;exports.SelectContent=Jt;exports.SelectGroup=zo;exports.SelectItem=gt;exports.SelectLabel=Bo;exports.SelectScrollDownButton=er;exports.SelectScrollUpButton=tr;exports.SelectSeparator=Ko;exports.SelectTrigger=Wt;exports.SelectValue=de;exports.Separator=ce;exports.SettingsList=mc;exports.SettingsListHeader=hc;exports.SettingsListItem=fc;exports.SettingsSidebar=fs;exports.SettingsSidebarContentSearch=tc;exports.Sidebar=or;exports.SidebarContent=ar;exports.SidebarFooter=as;exports.SidebarGroup=en;exports.SidebarGroupAction=ls;exports.SidebarGroupContent=rn;exports.SidebarGroupLabel=nn;exports.SidebarHeader=ss;exports.SidebarInput=os;exports.SidebarInset=sr;exports.SidebarMenu=ir;exports.SidebarMenuAction=cs;exports.SidebarMenuBadge=ws;exports.SidebarMenuButton=cr;exports.SidebarMenuItem=lr;exports.SidebarMenuSkeleton=ds;exports.SidebarMenuSub=ps;exports.SidebarMenuSubButton=ms;exports.SidebarMenuSubItem=us;exports.SidebarProvider=rr;exports.SidebarRail=rs;exports.SidebarSeparator=is;exports.SidebarTrigger=ns;exports.Skeleton=tn;exports.Slider=Zs;exports.Sonner=Uc;exports.Spinner=Ts;exports.Switch=Qs;exports.TabDropdownMenu=sn;exports.TabFloatingMenu=xc;exports.TabToolbar=gc;exports.Table=Fe;exports.TableBody=Ge;exports.TableCaption=Uo;exports.TableCell=Yt;exports.TableFooter=qo;exports.TableHead=Ne;exports.TableHeader=ze;exports.TableRow=Mt;exports.Tabs=Hc;exports.TabsContent=na;exports.TabsList=ta;exports.TabsTrigger=ea;exports.TextField=Ic;exports.Textarea=ra;exports.ToggleGroup=dn;exports.ToggleGroupItem=ve;exports.Toolbar=Sc;exports.Tooltip=Nt;exports.TooltipContent=yt;exports.TooltipProvider=vt;exports.TooltipTrigger=Dt;exports.UiLanguageSelector=Rc;exports.VerticalTabs=dr;exports.VerticalTabsContent=ur;exports.VerticalTabsList=pr;exports.VerticalTabsTrigger=ys;exports.badgeVariants=Mo;exports.buttonVariants=to;exports.cn=m;exports.getBookIdFromUSFM=Bl;exports.getLinesFromUSFM=zl;exports.getNumberFromUSFM=Gl;exports.getStatusForItem=ts;exports.getToolbarOSReservedSpaceClassName=Cc;exports.inventoryCountColumn=Vl;exports.inventoryItemColumn=Ll;exports.inventoryStatusColumn=Fl;exports.selectTriggerVariants=Go;exports.useEvent=Yc;exports.useEventAsync=Wc;exports.useListbox=To;exports.usePromise=oa;exports.useRecentSearches=La;exports.useSidebar=Be;exports.useStylesheet=Jc;function Zc(t,e="top"){if(!t||typeof document>"u")return;const r=document.head||document.querySelector("head"),o=r.querySelector(":first-child"),s=document.createElement("style");s.appendChild(document.createTextNode(t)),e==="top"&&o?r.insertBefore(s,o):r.appendChild(s)}Zc(`/* By default the editor is too tall for the footnote editor, even while empty, so this makes it - shorter. */ -.footnote-editor .editor-input { - min-height: 75px; -} - -.footnote-editor .typeahead-popover { - z-index: 300; -} - -.footnote-editor .immutable-note-caller { - display: none; -} - -/* Need to be able to override the styles for the editor that happens to have an underscore */ -/* stylelint-disable selector-class-pattern */ -.footnote-editor .text-spacing .usfm_p { - text-indent: 0; -} -.banded-row:hover { - cursor: pointer; -} - -.banded-row[data-state='selected']:hover { - cursor: default; -} -*, ::before, ::after { +`;function wl(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)}function Ve(t,e){const r=e?`${Ar}, ${e}`:Ar;return Array.from(t.querySelectorAll(r)).filter(o=>!o.hasAttribute("disabled")&&!o.getAttribute("aria-hidden")&&wl(o))}const Fe=i.forwardRef(({className:t,stickyHeader:e,...r},o)=>{const s=i.useRef(null);i.useEffect(()=>{typeof o=="function"?o(s.current):o&&"current"in o&&(o.current=s.current)},[o]),i.useEffect(()=>{const l=s.current;if(!l)return;const c=()=>{requestAnimationFrame(()=>{Ve(l,'[tabindex]:not([tabindex="-1"])').forEach(p=>{p.setAttribute("tabindex","-1")})})};c();const d=new MutationObserver(()=>{c()});return d.observe(l,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["tabindex"]}),()=>{d.disconnect()}},[]);const a=l=>{const{current:c}=s;if(c){if(l.key==="ArrowDown"){l.preventDefault(),Ve(c)[0].focus();return}l.key===" "&&document.activeElement===c&&l.preventDefault()}};return n.jsx("div",{className:m("pr-twp tw-relative tw-w-full",{"tw-p-1":e}),children:n.jsx("table",{tabIndex:0,onKeyDown:a,ref:s,className:m("tw-w-full tw-caption-bottom tw-text-sm tw-outline-none","focus:tw-relative focus:tw-z-10 focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-1 focus:tw-ring-offset-background",t),"aria-label":"Table","aria-labelledby":"table-label",...r})})});Fe.displayName="Table";const ze=i.forwardRef(({className:t,stickyHeader:e,...r},o)=>n.jsx("thead",{ref:o,className:m({"tw-sticky tw-top-[-1px] tw-z-20 tw-bg-background tw-drop-shadow-sm":e},"[&_tr]:tw-border-b",t),...r}));ze.displayName="TableHeader";const Ge=i.forwardRef(({className:t,...e},r)=>n.jsx("tbody",{ref:r,className:m("[&_tr:last-child]:tw-border-0",t),...e}));Ge.displayName="TableBody";const qo=i.forwardRef(({className:t,...e},r)=>n.jsx("tfoot",{ref:r,className:m("tw-border-t tw-bg-muted/50 tw-font-medium [&>tr]:last:tw-border-b-0",t),...e}));qo.displayName="TableFooter";function dl(t){i.useEffect(()=>{const e=t.current;if(!e)return;const r=o=>{if(e.contains(document.activeElement)){if(o.key==="ArrowRight"||o.key==="ArrowLeft"){o.preventDefault(),o.stopPropagation();const s=t.current?Ve(t.current):[],a=s.indexOf(document.activeElement),l=o.key==="ArrowRight"?a+1:a-1;l>=0&&l{e.removeEventListener("keydown",r)}},[t])}function pl(t,e,r){let o;return r==="ArrowLeft"&&e>0?o=t[e-1]:r==="ArrowRight"&&eo.focus()),!0):!1}function ul(t,e,r){let o;return r==="ArrowDown"&&e0&&(o=t[e-1]),o?(requestAnimationFrame(()=>o.focus()),!0):!1}const Dt=i.forwardRef(({className:t,onKeyDown:e,onSelect:r,setFocusAlsoRunsSelect:o=!1,...s},a)=>{const l=i.useRef(null);i.useEffect(()=>{typeof a=="function"?a(l.current):a&&"current"in a&&(a.current=l.current)},[a]),dl(l);const c=i.useMemo(()=>l.current?Ve(l.current):[],[l]),d=i.useCallback(p=>{const{current:u}=l;if(!u||!u.parentElement)return;const h=u.closest("table"),f=h?Ve(h).filter(b=>b.tagName==="TR"):[],g=f.indexOf(u),v=c.indexOf(document.activeElement);if(p.key==="ArrowDown"||p.key==="ArrowUp")p.preventDefault(),ul(f,g,p.key);else if(p.key==="ArrowLeft"||p.key==="ArrowRight")p.preventDefault(),pl(c,v,p.key);else if(p.key==="Escape"){p.preventDefault();const b=u.closest("table");b&&b.focus()}e==null||e(p)},[l,c,e]),w=i.useCallback(p=>{o&&(r==null||r(p))},[o,r]);return n.jsx("tr",{ref:l,tabIndex:-1,onKeyDown:d,onFocus:w,className:m("tw-border-b tw-outline-none tw-transition-colors hover:tw-bg-muted/50","focus:tw-relative focus:tw-z-10 focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-1 focus:tw-ring-offset-background","data-[state=selected]:tw-bg-muted",t),...s})});Dt.displayName="TableRow";const Ne=i.forwardRef(({className:t,...e},r)=>n.jsx("th",{ref:r,className:m("tw-h-12 tw-px-4 tw-text-start tw-align-middle tw-font-medium tw-text-muted-foreground [&:has([role=checkbox])]:tw-pe-0",t),...e}));Ne.displayName="TableHead";const Yt=i.forwardRef(({className:t,...e},r)=>n.jsx("td",{ref:r,className:m("tw-p-4 tw-align-middle [&:has([role=checkbox])]:tw-pe-0",t),...e}));Yt.displayName="TableCell";const Uo=i.forwardRef(({className:t,...e},r)=>n.jsx("caption",{ref:r,className:m("tw-mt-4 tw-text-sm tw-text-muted-foreground",t),...e}));Uo.displayName="TableCaption";function en({className:t,...e}){return n.jsx("div",{className:m("pr-twp tw-animate-pulse tw-rounded-md tw-bg-muted",t),...e})}function Ho({columns:t,data:e,enablePagination:r=!1,showPaginationControls:o=!1,showColumnVisibilityControls:s=!1,stickyHeader:a=!1,onRowClickHandler:l=()=>{},id:c,isLoading:d=!1,noResultsMessage:w}){var $;const[p,u]=i.useState([]),[h,f]=i.useState([]),[g,v]=i.useState({}),[b,y]=i.useState({}),j=i.useMemo(()=>e??[],[e]),C=ut.useReactTable({data:j,columns:t,getCoreRowModel:ut.getCoreRowModel(),...r&&{getPaginationRowModel:ut.getPaginationRowModel()},onSortingChange:u,getSortedRowModel:ut.getSortedRowModel(),onColumnFiltersChange:f,getFilteredRowModel:ut.getFilteredRowModel(),onColumnVisibilityChange:v,onRowSelectionChange:y,state:{sorting:p,columnFilters:h,columnVisibility:g,rowSelection:b}}),M=C.getVisibleFlatColumns();let F;return d?F=Array.from({length:10}).map((E,R)=>`skeleton-row-${R}`).map(E=>n.jsx(Dt,{className:"hover:tw-bg-transparent",children:n.jsx(Yt,{colSpan:M.length??t.length,className:"tw-border-0 tw-p-0",children:n.jsx("div",{className:"tw-w-full tw-py-2",children:n.jsx(en,{className:"tw-h-14 tw-w-full tw-rounded-md"})})})},E)):(($=C.getRowModel().rows)==null?void 0:$.length)>0?F=C.getRowModel().rows.map(_=>n.jsx(Dt,{onClick:()=>l(_,C),"data-state":_.getIsSelected()&&"selected",children:_.getVisibleCells().map(T=>n.jsx(Yt,{children:ut.flexRender(T.column.columnDef.cell,T.getContext())},T.id))},_.id)):F=n.jsx(Dt,{children:n.jsx(Yt,{colSpan:t.length,className:"tw-h-24 tw-text-center",children:w})}),n.jsxs("div",{className:"pr-twp",id:c,children:[s&&n.jsx(ll,{table:C}),n.jsxs(Fe,{stickyHeader:a,children:[n.jsx(ze,{stickyHeader:a,children:C.getHeaderGroups().map(_=>n.jsx(Dt,{children:_.headers.map(T=>n.jsx(Ne,{className:"tw-p-0",children:T.isPlaceholder?void 0:ut.flexRender(T.column.columnDef.header,T.getContext())},T.id))},_.id))}),n.jsx(Ge,{children:F})]}),r&&n.jsxs("div",{className:"tw-flex tw-items-center tw-justify-end tw-space-x-2 tw-py-4",children:[n.jsx(V,{variant:"outline",size:"sm",onClick:()=>C.previousPage(),disabled:!C.getCanPreviousPage(),children:"Previous"}),n.jsx(V,{variant:"outline",size:"sm",onClick:()=>C.nextPage(),disabled:!C.getCanNextPage(),children:"Next"})]}),r&&o&&n.jsx(cl,{table:C})]})}function ml({id:t,markdown:e,className:r,anchorTarget:o,truncate:s}){const a=i.useMemo(()=>({overrides:{a:{props:{target:o}}}}),[o]);return n.jsx("div",{id:t,className:m("pr-twp tw-prose",{"tw-line-clamp-3 tw-max-h-10 tw-overflow-hidden tw-text-ellipsis tw-break-words":s},r),children:n.jsx(va,{options:a,children:e})})}const Yo=Object.freeze(["%webView_error_dump_header%","%webView_error_dump_info_message%"]),Pr=(t,e)=>t[e]??e;function Xo({errorDetails:t,handleCopyNotify:e,localizedStrings:r,id:o}){const s=Pr(r,"%webView_error_dump_header%"),a=Pr(r,"%webView_error_dump_info_message%");function l(){navigator.clipboard.writeText(t),e&&e()}return n.jsxs("div",{id:o,className:"tw-inline-flex tw-w-full tw-flex-col tw-items-start tw-justify-start tw-gap-4",children:[n.jsxs("div",{className:"tw-inline-flex tw-items-start tw-justify-start tw-gap-4 tw-self-stretch",children:[n.jsxs("div",{className:"tw-inline-flex tw-flex-1 tw-flex-col tw-items-start tw-justify-start",children:[n.jsx("div",{className:"tw-text-color-text tw-justify-center tw-text-center tw-text-lg tw-font-semibold tw-leading-loose",children:s}),n.jsx("div",{className:"tw-justify-center tw-self-stretch tw-text-sm tw-font-normal tw-leading-tight tw-text-muted-foreground",children:a})]}),n.jsx(V,{variant:"secondary",size:"icon",className:"size-8",onClick:()=>l(),children:n.jsx(k.Copy,{})})]}),n.jsx("div",{className:"tw-prose tw-w-full",children:n.jsx("pre",{className:"tw-text-xs",children:t})})]})}const fl=Object.freeze([...Yo,"%webView_error_dump_copied_message%"]);function hl({errorDetails:t,handleCopyNotify:e,localizedStrings:r,children:o,className:s,id:a}){const[l,c]=i.useState(!1),d=()=>{c(!0),e&&e()},w=p=>{p||c(!1)};return n.jsxs(zt,{onOpenChange:w,children:[n.jsx(Gt,{asChild:!0,children:o}),n.jsxs(Lt,{id:a,className:m("tw-min-w-80 tw-max-w-96",s),children:[l&&r["%webView_error_dump_copied_message%"]&&n.jsx(et,{children:r["%webView_error_dump_copied_message%"]}),n.jsx(Xo,{errorDetails:t,handleCopyNotify:d,localizedStrings:r})]})]})}var Wo=(t=>(t[t.Check=0]="Check",t[t.Radio=1]="Radio",t))(Wo||{});function gl({id:t,label:e,groups:r}){const[o,s]=i.useState(Object.fromEntries(r.map((w,p)=>w.itemType===0?[p,[]]:void 0).filter(w=>!!w))),[a,l]=i.useState({}),c=(w,p)=>{const u=!o[w][p];s(f=>(f[w][p]=u,{...f}));const h=r[w].items[p];h.onUpdate(h.id,u)},d=(w,p)=>{l(h=>(h[w]=p,{...h}));const u=r[w].items.find(h=>h.id===p);u?u.onUpdate(p):console.error(`Could not find dropdown radio item with id '${p}'!`)};return n.jsx("div",{id:t,children:n.jsxs(Qt,{children:[n.jsx(ue,{asChild:!0,children:n.jsxs(V,{variant:"default",children:[n.jsx(k.Filter,{size:16,className:"tw-mr-2 tw-h-4 tw-w-4"}),e,n.jsx(k.ChevronDown,{size:16,className:"tw-ml-2 tw-h-4 tw-w-4"})]})}),n.jsx(Kt,{children:r.map((w,p)=>n.jsxs("div",{children:[n.jsx(Se,{children:w.label}),n.jsx(Jn,{children:w.itemType===0?n.jsx(n.Fragment,{children:w.items.map((u,h)=>n.jsx("div",{children:n.jsx(It,{checked:o[p][h],onCheckedChange:()=>c(p,h),children:u.label})},u.id))}):n.jsx(Vo,{value:a[p],onValueChange:u=>d(p,u),children:w.items.map(u=>n.jsx("div",{children:n.jsx(tr,{value:u.id,children:u.label})},u.id))})}),n.jsx(me,{})]},w.label))})]})})}function xl({id:t,category:e,downloads:r,languages:o,moreInfoUrl:s,handleMoreInfoLinkClick:a,supportUrl:l,handleSupportLinkClick:c}){const d=new N.NumberFormat("en",{notation:"compact",compactDisplay:"short"}).format(Object.values(r).reduce((p,u)=>p+u,0)),w=()=>{window.scrollTo(0,document.body.scrollHeight)};return n.jsxs("div",{id:t,className:"pr-twp tw-flex tw-items-center tw-justify-center tw-gap-4 tw-divide-x tw-border-b tw-border-t tw-py-2 tw-text-center",children:[e&&n.jsxs("div",{className:"tw-flex tw-flex-col tw-items-center tw-gap-1",children:[n.jsx("div",{className:"tw-flex",children:n.jsx("span",{className:"tw-text-xs tw-font-semibold tw-text-foreground",children:e})}),n.jsx("span",{className:"tw-text-xs tw-text-foreground",children:"CATEGORY"})]}),n.jsxs("div",{className:"tw-flex tw-flex-col tw-items-center tw-gap-1 tw-ps-4",children:[n.jsxs("div",{className:"tw-flex tw-gap-1",children:[n.jsx(k.User,{className:"tw-h-4 tw-w-4"}),n.jsx("span",{className:"tw-text-xs tw-font-semibold tw-text-foreground",children:d})]}),n.jsx("span",{className:"tw-text-xs tw-text-foreground",children:"USERS"})]}),n.jsxs("div",{className:"tw-flex tw-flex-col tw-items-center tw-gap-1 tw-ps-4",children:[n.jsx("div",{className:"tw-flex tw-gap-2",children:o.slice(0,3).map(p=>n.jsx("span",{className:"tw-text-xs tw-font-semibold tw-text-foreground",children:p.toUpperCase()},p))}),o.length>3&&n.jsxs("button",{type:"button",onClick:()=>w(),className:"tw-text-xs tw-text-foreground tw-underline",children:["+",o.length-3," more languages"]})]}),(s||l)&&n.jsxs("div",{className:"tw-flex tw-flex-col tw-gap-1 tw-ps-4",children:[s&&n.jsx("div",{className:"tw-flex tw-gap-1",children:n.jsxs(V,{onClick:()=>a(),variant:"link",className:"tw-flex tw-h-auto tw-gap-1 tw-py-0 tw-text-xs tw-font-semibold tw-text-foreground",children:["Website",n.jsx(k.Link,{className:"tw-h-4 tw-w-4"})]})}),l&&n.jsx("div",{className:"tw-flex tw-gap-1",children:n.jsxs(V,{onClick:()=>c(),variant:"link",className:"tw-flex tw-h-auto tw-gap-1 tw-py-0 tw-text-xs tw-font-semibold tw-text-foreground",children:["Support",n.jsx(k.CircleHelp,{className:"tw-h-4 tw-w-4"})]})})]})]})}function bl({id:t,versionHistory:e}){const[r,o]=i.useState(!1),s=new Date;function a(c){const d=new Date(c),w=new Date(s.getTime()-d.getTime()),p=w.getUTCFullYear()-1970,u=w.getUTCMonth(),h=w.getUTCDate()-1;let f="";return p>0?f=`${p.toString()} year${p===1?"":"s"} ago`:u>0?f=`${u.toString()} month${u===1?"":"s"} ago`:h===0?f="today":f=`${h.toString()} day${h===1?"":"s"} ago`,f}const l=Object.entries(e).sort((c,d)=>d[0].localeCompare(c[0]));return n.jsxs("div",{className:"pr-twp",id:t,children:[n.jsx("h3",{className:"tw-text-md tw-font-semibold",children:"What`s New"}),n.jsx("ul",{className:"tw-list-disc tw-pl-5 tw-pr-4 tw-text-xs tw-text-foreground",children:(r?l:l.slice(0,5)).map(c=>n.jsxs("div",{className:"tw-mt-3 tw-flex tw-justify-between",children:[n.jsx("div",{className:"tw-text-foreground",children:n.jsx("li",{className:"tw-prose tw-text-xs",children:n.jsx("span",{children:c[1].description})})}),n.jsxs("div",{className:"tw-justify-end tw-text-right",children:[n.jsxs("div",{children:["Version ",c[0]]}),n.jsx("div",{children:a(c[1].date)})]})]},c[0]))}),l.length>5&&n.jsx("button",{type:"button",onClick:()=>o(!r),className:"tw-text-xs tw-text-foreground tw-underline",children:r?"Show Less Version History":"Show All Version History"})]})}function vl({id:t,publisherDisplayName:e,fileSize:r,locales:o,versionHistory:s,currentVersion:a}){const l=i.useMemo(()=>N.formatBytes(r),[r]),d=(w=>{const p=new Intl.DisplayNames(N.getCurrentLocale(),{type:"language"});return w.map(u=>p.of(u))})(o);return n.jsx("div",{id:t,className:"pr-twp tw-border-t tw-py-2",children:n.jsxs("div",{className:"tw-flex tw-flex-col tw-gap-2 tw-divide-y",children:[Object.entries(s).length>0&&n.jsx(bl,{versionHistory:s}),n.jsxs("div",{className:"tw-flex tw-flex-col tw-gap-2 tw-py-2",children:[n.jsx("h2",{className:"tw-text-md tw-font-semibold",children:"Information"}),n.jsxs("div",{className:"tw-flex tw-items-start tw-justify-between tw-text-xs tw-text-foreground",children:[n.jsxs("p",{className:"tw-flex tw-flex-col tw-justify-start tw-gap-1",children:[n.jsx("span",{children:"Publisher"}),n.jsx("span",{className:"tw-font-semibold",children:e}),n.jsx("span",{children:"Size"}),n.jsx("span",{className:"tw-font-semibold",children:l})]}),n.jsx("div",{className:"tw-flex tw-w-3/4 tw-items-center tw-justify-between tw-text-xs tw-text-foreground",children:n.jsxs("p",{className:"tw-flex tw-flex-col tw-justify-start tw-gap-1",children:[n.jsx("span",{children:"Version"}),n.jsx("span",{className:"tw-font-semibold",children:a}),n.jsx("span",{children:"Languages"}),n.jsx("span",{className:"tw-font-semibold",children:d.join(", ")})]})})]})]})]})})}function Jo({entries:t,selected:e,onChange:r,placeholder:o,hasToggleAllFeature:s=!1,selectAllText:a="Select All",clearAllText:l="Clear All",commandEmptyMessage:c="No entries found",customSelectedText:d,isOpen:w=void 0,onOpenChange:p=void 0,isDisabled:u=!1,sortSelected:h=!1,icon:f=void 0,className:g=void 0,variant:v="ghost",id:b}){const[y,j]=i.useState(!1),C=i.useCallback(R=>{var L;const P=(L=t.find(I=>I.label===R))==null?void 0:L.value;P&&r(e.includes(P)?e.filter(I=>I!==P):[...e,P])},[t,e,r]),M=()=>d||o,F=i.useMemo(()=>{if(!h)return t;const R=t.filter(L=>L.starred).sort((L,I)=>L.label.localeCompare(I.label)),P=t.filter(L=>!L.starred).sort((L,I)=>{const A=e.includes(L.value),z=e.includes(I.value);return A&&!z?-1:!A&&z?1:L.label.localeCompare(I.label)});return[...R,...P]},[t,e,h]),$=()=>{r(t.map(R=>R.value))},_=()=>{r([])},T=w??y,E=p??j;return n.jsx("div",{id:b,className:g,children:n.jsxs(zt,{open:T,onOpenChange:E,children:[n.jsx(Gt,{asChild:!0,children:n.jsxs(V,{variant:v,role:"combobox","aria-expanded":T,className:"tw-group tw-w-full tw-justify-between",disabled:u,children:[n.jsxs("div",{className:"tw-flex tw-min-w-0 tw-flex-1 tw-items-center tw-gap-2",children:[f&&n.jsx("div",{className:"tw-ml-2 tw-h-4 tw-w-4 tw-shrink-0 tw-opacity-50",children:n.jsx("span",{className:"tw-flex tw-h-full tw-w-full tw-items-center tw-justify-center",children:f})}),n.jsx("span",{className:m("tw-min-w-0 tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-text-start tw-font-normal"),children:M()})]}),n.jsx(k.ChevronsUpDown,{className:"tw-ml-2 tw-h-4 tw-w-4 tw-shrink-0 tw-opacity-50"})]})}),n.jsx(Lt,{align:"start",className:"tw-w-full tw-p-0",children:n.jsxs(At,{children:[n.jsx(pe,{placeholder:`Search ${o.toLowerCase()}...`}),s&&n.jsxs("div",{className:"tw-flex tw-justify-between tw-border-b tw-p-2",children:[n.jsx(V,{variant:"ghost",size:"sm",onClick:$,children:a}),n.jsx(V,{variant:"ghost",size:"sm",onClick:_,children:l})]}),n.jsxs(Pt,{children:[n.jsx(Ee,{children:c}),n.jsx(Ot,{children:F.map(R=>n.jsxs(Et,{value:R.label,onSelect:C,className:"tw-flex tw-items-center tw-gap-2",children:[n.jsx("div",{className:"w-4",children:n.jsx(k.Check,{className:m("tw-h-4 tw-w-4",e.includes(R.value)?"tw-opacity-100":"tw-opacity-0")})}),R.starred&&n.jsx(k.Star,{className:"tw-h-4 tw-w-4"}),n.jsx("div",{className:"tw-flex-grow",children:R.label}),R.secondaryLabel&&n.jsx("div",{className:"tw-text-end tw-text-muted-foreground",children:R.secondaryLabel})]},R.label))})]})]})})]})})}function yl({entries:t,selected:e,onChange:r,placeholder:o,commandEmptyMessage:s,customSelectedText:a,isDisabled:l,sortSelected:c,icon:d,className:w,badgesPlaceholder:p,id:u}){return n.jsxs("div",{id:u,className:"tw-flex tw-items-center tw-gap-2",children:[n.jsx(Jo,{entries:t,selected:e,onChange:r,placeholder:o,commandEmptyMessage:s,customSelectedText:a,isDisabled:l,sortSelected:c,icon:d,className:w}),e.length>0?n.jsx("div",{className:"tw-flex tw-flex-wrap tw-items-center tw-gap-2",children:e.map(h=>{var f;return n.jsxs(le,{variant:"muted",className:"tw-flex tw-items-center tw-gap-1",children:[n.jsx(V,{variant:"ghost",size:"icon",className:"tw-h-4 tw-w-4 tw-p-0 hover:tw-bg-transparent",onClick:()=>r(e.filter(g=>g!==h)),children:n.jsx(k.X,{className:"tw-h-3 tw-w-3"})}),(f=t.find(g=>g.value===h))==null?void 0:f.label]},h)})}):n.jsx(et,{children:p})]})}const fe=i.forwardRef(({className:t,type:e,...r},o)=>n.jsx("input",{type:e,className:m("pr-twp tw-flex tw-h-10 tw-rounded-md tw-border tw-border-input tw-bg-background tw-px-3 tw-py-2 tw-text-sm tw-ring-offset-background file:tw-border-0 file:tw-bg-transparent file:tw-text-sm file:tw-font-medium file:tw-text-foreground placeholder:tw-text-muted-foreground focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-cursor-not-allowed disabled:tw-opacity-50",t),ref:o,...r}));fe.displayName="Input";const jl=(t,e,r)=>t==="generated"?n.jsxs(n.Fragment,{children:[n.jsx("p",{children:"+"})," ",e["%footnoteEditor_callerDropdown_item_generated%"]]}):t==="hidden"?n.jsxs(n.Fragment,{children:[n.jsx("p",{children:"-"})," ",e["%footnoteEditor_callerDropdown_item_hidden%"]]}):n.jsxs(n.Fragment,{children:[n.jsx("p",{children:r})," ",e["%footnoteEditor_callerDropdown_item_custom%"]]});function Nl({callerType:t,updateCallerType:e,customCaller:r,updateCustomCaller:o,localizedStrings:s}){const a=i.useRef(null),l=i.useRef(null),c=i.useRef(!1),[d,w]=i.useState(t),[p,u]=i.useState(r),[h,f]=i.useState(!1);i.useEffect(()=>{w(t)},[t]),i.useEffect(()=>{p!==r&&u(r)},[r]);const g=b=>{c.current=!1,f(b),b||(d!=="custom"||p?(e(d),o(p)):(w(t),u(r)))},v=b=>{var y,j,C,M;b.stopPropagation(),document.activeElement===l.current&&b.key==="ArrowDown"||b.key==="ArrowRight"?((y=a.current)==null||y.focus(),c.current=!0):document.activeElement===a.current&&b.key==="ArrowUp"?((j=l.current)==null||j.focus(),c.current=!1):document.activeElement===a.current&&b.key==="ArrowLeft"&&((C=a.current)==null?void 0:C.selectionStart)===0&&((M=l.current)==null||M.focus(),c.current=!1),d==="custom"&&b.key==="Enter"&&(document.activeElement===l.current||document.activeElement===a.current)&&g(!1)};return n.jsxs(Qt,{open:h,onOpenChange:g,children:[n.jsx(xt,{children:n.jsxs(jt,{children:[n.jsx(Nt,{asChild:!0,children:n.jsx(ue,{asChild:!0,children:n.jsx(V,{variant:"outline",className:"tw-h-6",children:jl(t,s,r)})})}),n.jsx(bt,{children:s["%footnoteEditor_callerDropdown_tooltip%"]})]})}),n.jsxs(Kt,{className:"tw-z-[300]",onClick:()=>{c.current&&(c.current=!1)},onKeyDown:v,onMouseMove:()=>{var b;c.current&&((b=a.current)==null||b.focus())},children:[n.jsx(Se,{children:s["%footnoteEditor_callerDropdown_label%"]}),n.jsx(me,{}),n.jsx(It,{checked:d==="generated",onCheckedChange:()=>w("generated"),children:n.jsxs("div",{className:"tw-flex tw-w-full tw-justify-between",children:[n.jsx("span",{children:s["%footnoteEditor_callerDropdown_item_generated%"]}),n.jsx("span",{className:"tw-w-10 tw-text-center",children:yt.GENERATOR_NOTE_CALLER})]})}),n.jsx(It,{checked:d==="hidden",onCheckedChange:()=>w("hidden"),children:n.jsxs("div",{className:"tw-flex tw-w-full tw-justify-between",children:[n.jsx("span",{children:s["%footnoteEditor_callerDropdown_item_hidden%"]}),n.jsx("span",{className:"tw-w-10 tw-text-center",children:yt.HIDDEN_NOTE_CALLER})]})}),n.jsx(It,{ref:l,checked:d==="custom",onCheckedChange:()=>w("custom"),onClick:b=>{var y;b.stopPropagation(),c.current=!0,(y=a.current)==null||y.focus()},onSelect:b=>b.preventDefault(),children:n.jsxs("div",{className:"tw-flex tw-w-full tw-justify-between",children:[n.jsx("span",{children:s["%footnoteEditor_callerDropdown_item_custom%"]}),n.jsx(fe,{tabIndex:0,onMouseDown:b=>{b.stopPropagation(),w("custom"),c.current=!0},ref:a,className:"tw-h-auto tw-w-10 tw-p-0 tw-text-center",value:p,onKeyDown:b=>{b.key==="Enter"||b.key==="ArrowUp"||b.key==="ArrowDown"||b.key==="ArrowLeft"||b.key==="ArrowRight"||b.stopPropagation()},maxLength:1,onChange:b=>u(b.target.value)})]})})]})]})}const kl=(t,e)=>t==="f"?n.jsxs(n.Fragment,{children:[n.jsx(k.FunctionSquare,{})," ",e["%footnoteEditor_noteType_footnote_label%"]]}):t==="fe"?n.jsxs(n.Fragment,{children:[n.jsx(k.SquareSigma,{})," ",e["%footnoteEditor_noteType_endNote_label%"]]}):n.jsxs(n.Fragment,{children:[n.jsx(k.SquareX,{})," ",e["%footnoteEditor_noteType_crossReference_label%"]]}),_l=(t,e)=>{if(t==="x")return e["%footnoteEditor_noteType_crossReference_label%"];let r=e["%footnoteEditor_noteType_endNote_label%"];return t==="f"&&(r=e["%footnoteEditor_noteType_footnote_label%"]),N.formatReplacementString(e["%footnoteEditor_noteType_tooltip%"]??"",{noteType:r})};function Cl({noteType:t,handleNoteTypeChange:e,localizedStrings:r,isTypeSwitchable:o}){return n.jsxs(Qt,{children:[n.jsx(xt,{children:n.jsxs(jt,{children:[n.jsx(Vr.TooltipTrigger,{asChild:!0,children:n.jsx(ue,{asChild:!0,children:n.jsx(V,{variant:"outline",className:"tw-h-6",children:kl(t,r)})})}),n.jsx(bt,{children:n.jsx("p",{children:_l(t,r)})})]})}),n.jsxs(Kt,{className:"tw-z-[300]",children:[n.jsx(Se,{children:r["%footnoteEditor_noteTypeDropdown_label%"]}),n.jsx(me,{}),n.jsxs(It,{disabled:t!=="x"&&!o,checked:t==="x",onCheckedChange:()=>e("x"),className:"tw-gap-2",children:[n.jsx(k.SquareX,{}),n.jsx("span",{children:r["%footnoteEditor_noteType_crossReference_label%"]})]}),n.jsxs(It,{disabled:t==="x"&&!o,checked:t==="f",onCheckedChange:()=>e("f"),className:"tw-gap-2",children:[n.jsx(k.FunctionSquare,{}),n.jsx("span",{children:r["%footnoteEditor_noteType_footnote_label%"]})]}),n.jsxs(It,{disabled:t==="x"&&!o,checked:t==="fe",onCheckedChange:()=>e("fe"),className:"tw-gap-2",children:[n.jsx(k.SquareSigma,{}),n.jsx("span",{children:r["%footnoteEditor_noteType_endNote_label%"]})]})]})]})}function El(t){var r;const e=(r=t.attributes)==null?void 0:r.char;e.style&&(e.style==="ft"&&(e.style="xt"),e.style==="fr"&&(e.style="xo"),e.style==="fq"&&(e.style="xq"))}function Sl(t){var r;const e=(r=t.attributes)==null?void 0:r.char;e.style&&(e.style==="xt"&&(e.style="ft"),e.style==="xo"&&(e.style="fr"),e.style==="xq"&&(e.style="fq"))}const Rl={type:"USJ",version:"3.1",content:[{type:"para"}]};function Tl({classNameForEditor:t,noteOps:e,onSave:r,onClose:o,scrRef:s,noteKey:a,editorOptions:l,localizedStrings:c}){const d=i.useRef(null),w=i.createRef(),[p,u]=i.useState("generated"),[h,f]=i.useState("*"),[g,v]=i.useState("f"),[b,y]=i.useState(!1),j=i.useMemo(()=>({...l,markerMenuTrigger:l.markerMenuTrigger??"\\",hasExternalUI:!0,view:{...l.view??yt.getDefaultViewOptions(),noteMode:"expanded"}}),[l]);i.useEffect(()=>{var _;(_=d.current)==null||_.focus()}),i.useEffect(()=>{var E,R;let _;const T=e==null?void 0:e.at(0);if(T&&yt.isInsertEmbedOpOfType("note",T)){const P=(E=T.insert.note)==null?void 0:E.caller;let L="custom";P===yt.GENERATOR_NOTE_CALLER?L="generated":P===yt.HIDDEN_NOTE_CALLER?L="hidden":P&&f(P),u(L),v(((R=T.insert.note)==null?void 0:R.style)??"f"),_=setTimeout(()=>{var I;(I=d.current)==null||I.applyUpdate([T])},0)}return()=>{_&&clearTimeout(_)}},[e,a]);const C=i.useCallback(()=>{var T,E;const _=(E=(T=d.current)==null?void 0:T.getNoteOps(0))==null?void 0:E.at(0);_&&yt.isInsertEmbedOpOfType("note",_)&&(_.insert.note&&(p==="custom"?_.insert.note.caller=h:_.insert.note.caller=p==="generated"?yt.GENERATOR_NOTE_CALLER:yt.HIDDEN_NOTE_CALLER),r([_]))},[p,h,r]),M=()=>{var T;const _=(T=w.current)==null?void 0:T.getElementsByClassName("editor-input")[0];_!=null&&_.textContent&&navigator.clipboard.writeText(_.textContent)},F=_=>{var E,R,P,L,I;v(_);const T=(R=(E=d.current)==null?void 0:E.getNoteOps(0))==null?void 0:R.at(0);if(T&&yt.isInsertEmbedOpOfType("note",T)){T.insert.note&&(T.insert.note.style=_);const A=(L=(P=T.insert.note)==null?void 0:P.contents)==null?void 0:L.ops;g!=="x"&&_==="x"?A==null||A.forEach(z=>El(z)):g==="x"&&_!=="x"&&(A==null||A.forEach(z=>Sl(z))),(I=d.current)==null||I.applyUpdate([T,{delete:1}])}},$=_=>{var E,R,P,L,I;const T=(R=(E=d.current)==null?void 0:E.getNoteOps(0))==null?void 0:R.at(0);if(T&&yt.isInsertEmbedOpOfType("note",T)){_.content.length>1&&setTimeout(()=>{var S;(S=d.current)==null||S.applyUpdate([{retain:2},{delete:1}])},0);const A=(P=T.insert.note)==null?void 0:P.style,z=(I=(L=T.insert.note)==null?void 0:L.contents)==null?void 0:I.ops;A||y(!1),y(A==="x"?!!(z!=null&&z.every(S=>{var at,lt;if(!((at=S.attributes)!=null&&at.char))return!0;const B=((lt=S.attributes)==null?void 0:lt.char).style;return B==="xt"||B==="xo"||B==="xq"})):!!(z!=null&&z.every(S=>{var at,lt;if(!((at=S.attributes)!=null&&at.char))return!0;const B=((lt=S.attributes)==null?void 0:lt.char).style;return B==="ft"||B==="fr"||B==="fq"})))}else y(!1)};return n.jsxs("div",{className:"footnote-editor tw-grid tw-gap-[12px]",children:[n.jsxs("div",{className:"tw-flex",children:[n.jsxs("div",{className:"tw-flex tw-gap-4",children:[n.jsx(Cl,{isTypeSwitchable:b,noteType:g,handleNoteTypeChange:F,localizedStrings:c}),n.jsx(Nl,{callerType:p,updateCallerType:u,customCaller:h,updateCustomCaller:f,localizedStrings:c})]}),n.jsxs("div",{className:"tw-flex tw-w-full tw-justify-end tw-gap-4",children:[n.jsx(xt,{children:n.jsxs(jt,{children:[n.jsx(Nt,{asChild:!0,children:n.jsx(V,{onClick:o,className:"tw-h-6 tw-w-6",size:"icon",variant:"secondary",children:n.jsx(k.X,{})})}),n.jsx(bt,{children:n.jsx("p",{children:c["%footnoteEditor_cancelButton_tooltip%"]})})]})}),n.jsx(xt,{children:n.jsxs(jt,{children:[n.jsx(Nt,{asChild:!0,children:n.jsx(V,{onClick:C,className:"tw-h-6 tw-w-6",size:"icon",variant:"default",children:n.jsx(k.Check,{})})}),n.jsx(bt,{children:c["%footnoteEditor_saveButton_tooltip%"]})]})})]})]}),n.jsxs("div",{ref:w,className:"tw-relative tw-rounded-[6px] tw-border-2 tw-border-ring",children:[n.jsx("div",{className:t,children:n.jsx(yt.Editorial,{options:j,onUsjChange:$,defaultUsj:Rl,onScrRefChange:()=>{},scrRef:s,ref:d})}),n.jsx("div",{className:"tw-absolute tw-bottom-0 tw-right-0",children:n.jsx(xt,{children:n.jsxs(jt,{children:[n.jsx(Nt,{asChild:!0,children:n.jsx(V,{onClick:M,className:"tw-h-6 tw-w-6",variant:"ghost",size:"icon",children:n.jsx(k.Copy,{})})}),n.jsx(bt,{children:n.jsx("p",{children:c["%footnoteEditor_copyButton_tooltip%"]})})]})})})]})]})}const Ml=Object.freeze(["%footnoteEditor_callerDropdown_label%","%footnoteEditor_callerDropdown_item_generated%","%footnoteEditor_callerDropdown_item_hidden%","%footnoteEditor_callerDropdown_item_custom%","%footnoteEditor_callerDropdown_tooltip%","%footnoteEditor_cancelButton_tooltip%","%footnoteEditor_copyButton_tooltip%","%footnoteEditor_noteType_crossReference_label%","%footnoteEditor_noteType_endNote_label%","%footnoteEditor_noteType_footnote_label%","%footnoteEditor_noteType_tooltip%","%footnoteEditor_noteTypeDropdown_label%","%footnoteEditor_saveButton_tooltip%"]);function Zo(t,e){if(!e||e.length===0)return t??"empty";const r=e.find(s=>typeof s=="string");if(r)return`key-${t??"unknown"}-${r.slice(0,10)}`;const o=typeof e[0]=="string"?"impossible":e[0].marker??"unknown";return`key-${t??"unknown"}-${o}`}function Dl(t,e,r=!0,o=void 0){if(!e||e.length===0)return;const s=[],a=[];let l=[];return e.forEach(c=>{typeof c!="string"&&c.marker==="fp"?(l.length>0&&a.push(l),l=[c]):l.push(c)}),l.length>0&&a.push(l),a.map((c,d)=>{const w=d===a.length-1;return n.jsxs("p",{children:[rr(t,c,r,!0,s),w&&o]},Zo(t,c))})}function rr(t,e,r=!0,o=!0,s=[]){if(!(!e||e.length===0))return e.map(a=>{if(typeof a=="string"){const l=`${t}-text-${a.slice(0,10)}`;if(o){const c=m(`usfm_${t}`);return n.jsx("span",{className:c,children:a},l)}return n.jsxs("span",{className:"tw-inline-flex tw-items-center tw-gap-1 tw-underline tw-decoration-destructive",children:[n.jsx(k.AlertCircle,{className:"tw-h-4 tw-w-4 tw-fill-destructive"}),n.jsx("span",{children:a}),n.jsx(k.AlertCircle,{className:"tw-h-4 tw-w-4 tw-fill-destructive"})]},l)}return Il(a,Zo(`${t}\\${a.marker}`,[a]),r,[...s,t??"unknown"])})}function Il(t,e,r,o=[]){const{marker:s}=t;return n.jsxs("span",{children:[s?r&&n.jsx("span",{className:"marker",children:`\\${s} `}):n.jsx(k.AlertCircle,{className:"tw-text-error tw-mr-1 tw-inline-block tw-h-4 tw-w-4","aria-label":"Missing marker"}),rr(s,t.content,r,!0,[...o,s??"unknown"])]},e)}function Qo({footnote:t,layout:e="horizontal",formatCaller:r,showMarkers:o=!0}){const s=r?r(t.caller):t.caller,a=s!==t.caller;let l,c=t.content;Array.isArray(t.content)&&t.content.length>0&&typeof t.content[0]!="string"&&(t.content[0].marker==="fr"||t.content[0].marker==="xo")&&([l,...c]=t.content);const d=o?n.jsx("span",{className:"marker",children:`\\${t.marker} `}):void 0,w=o?n.jsx("span",{className:"marker",children:` \\${t.marker}*`}):void 0,p=s&&n.jsxs("span",{className:m("note-caller tw-inline-block",{formatted:a}),children:[s," "]}),u=l&&n.jsxs(n.Fragment,{children:[rr(t.marker,[l],o,!1)," "]}),h=e==="horizontal"?"horizontal":"vertical",f=o?"marker-visible":"",g=e==="horizontal"?"tw-col-span-1":"tw-col-span-2 tw-col-start-1 tw-row-start-2",v=m(h,f);return n.jsxs(n.Fragment,{children:[n.jsxs("div",{className:m("textual-note-header tw-col-span-1 tw-w-fit tw-text-nowrap",v),children:[d,p]}),n.jsx("div",{className:m("textual-note-header tw-col-span-1 tw-w-fit tw-text-nowrap",v),children:u}),n.jsx("div",{className:m("textual-note-body tw-flex tw-flex-col tw-gap-1",g,v),children:c&&c.length>0&&n.jsx(n.Fragment,{children:Dl(t.marker,c,o,w)})})]})}function Ol({className:t,classNameForItems:e,footnotes:r,layout:o="horizontal",listId:s,selectedFootnote:a,showMarkers:l=!0,suppressFormatting:c=!1,formatCaller:d,onFootnoteSelected:w}){const p=d??N.getFormatCallerFunction(r,void 0),u=(j,C)=>{w==null||w(j,C,s)},h=a?r.findIndex(j=>j===a):-1,[f,g]=i.useState(h),v=(j,C,M)=>{if(r.length)switch(j.key){case"Enter":case" ":j.preventDefault(),w==null||w(C,M,s);break}},b=j=>{if(r.length)switch(j.key){case"ArrowDown":j.preventDefault(),g(C=>Math.min(C+1,r.length-1));break;case"ArrowUp":j.preventDefault(),g(C=>Math.max(C-1,0));break}},y=i.useRef([]);return i.useEffect(()=>{var j;f>=0&&f{const M=j===a,F=`${s}-${C}`;return n.jsxs(n.Fragment,{children:[n.jsx("li",{ref:$=>{y.current[C]=$},role:"option","aria-selected":M,"data-marker":j.marker,"data-state":M?"selected":void 0,tabIndex:C===f?0:-1,className:m("tw-gap-x-3 tw-gap-y-1 tw-p-2 data-[state=selected]:tw-bg-muted",w&&"hover:tw-bg-muted/50","tw-w-full tw-rounded-sm tw-border-0 tw-bg-transparent tw-shadow-none","focus:tw-outline-none focus-visible:tw-outline-none","focus-visible:tw-ring-offset-0.5 focus-visible:tw-relative focus-visible:tw-z-10 focus-visible:tw-ring-2 focus-visible:tw-ring-ring","tw-grid tw-grid-flow-col tw-grid-cols-subgrid",o==="horizontal"?"tw-col-span-3":"tw-col-span-2 tw-row-span-2",e),onClick:()=>u(j,C),onKeyDown:$=>v($,j,C),children:n.jsx(Qo,{footnote:j,layout:o,formatCaller:()=>p(j.caller,C),showMarkers:l})},F),Cr&&e.push(t.substring(r,s.index)),e.push(n.jsx("strong",{children:s[1]},s.index)),r=o.lastIndex;return r0?e:[t]}function Pl({occurrenceData:t,setScriptureReference:e,localizedStrings:r,classNameForText:o}){const s=r["%webView_inventory_occurrences_table_header_reference%"],a=r["%webView_inventory_occurrences_table_header_occurrence%"],l=i.useMemo(()=>{const c=[],d=new Set;return t.forEach(w=>{const p=`${w.reference.book}:${w.reference.chapterNum}:${w.reference.verseNum}:${w.text}`;d.has(p)||(d.add(p),c.push(w))}),c},[t]);return n.jsxs(Fe,{stickyHeader:!0,children:[n.jsx(ze,{stickyHeader:!0,children:n.jsxs(Dt,{children:[n.jsx(Ne,{children:s}),n.jsx(Ne,{children:a})]})}),n.jsx(Ge,{children:l.length>0&&l.map(c=>n.jsxs(Dt,{onClick:()=>{e(c.reference)},children:[n.jsx(Yt,{children:N.formatScrRef(c.reference,"English")}),n.jsx(Yt,{className:o,children:Al(c.text)})]},`${c.reference.book} ${c.reference.chapterNum}:${c.reference.verseNum}-${c.text}`))})]})}const un=i.forwardRef(({className:t,...e},r)=>n.jsx(Rn.Root,{ref:r,className:m("tw-peer pr-twp tw-h-4 tw-w-4 tw-shrink-0 tw-rounded-sm tw-border tw-border-primary tw-ring-offset-background focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-cursor-not-allowed disabled:tw-opacity-50 data-[state=checked]:tw-bg-primary data-[state=checked]:tw-text-primary-foreground",t),...e,children:n.jsx(Rn.Indicator,{className:m("tw-flex tw-items-center tw-justify-center tw-text-current"),children:n.jsx(k.Check,{className:"tw-h-4 tw-w-4"})})}));un.displayName=Rn.Root.displayName;const Ll=t=>{if(t==="asc")return n.jsx(k.ArrowUpIcon,{className:"tw-h-4 tw-w-4"});if(t==="desc")return n.jsx(k.ArrowDownIcon,{className:"tw-h-4 tw-w-4"})},Be=(t,e,r)=>n.jsx(xt,{children:n.jsxs(jt,{children:[n.jsxs(Nt,{className:m("tw-flex tw-w-full tw-justify-start",r),variant:"ghost",onClick:()=>t.toggleSorting(void 0),children:[n.jsx("span",{className:"tw-w-6 tw-max-w-fit tw-flex-1 tw-overflow-hidden tw-text-ellipsis",children:e}),Ll(t.getIsSorted())]}),n.jsx(bt,{side:"bottom",children:e})]})}),$l=t=>({accessorKey:"item",accessorFn:e=>e.items[0],header:({column:e})=>Be(e,t)}),Vl=(t,e)=>({accessorKey:`item${e}`,accessorFn:r=>r.items[e],header:({column:r})=>Be(r,t)}),Fl=t=>({accessorKey:"count",header:({column:e})=>Be(e,t,"tw-justify-end"),cell:({row:e})=>n.jsx("div",{className:"tw-flex tw-justify-end tw-tabular-nums",children:e.getValue("count")})}),kn=(t,e,r,o,s,a)=>{let l=[...r];t.forEach(d=>{e==="approved"?l.includes(d)||l.push(d):l=l.filter(w=>w!==d)}),o(l);let c=[...s];t.forEach(d=>{e==="unapproved"?c.includes(d)||c.push(d):c=c.filter(w=>w!==d)}),a(c)},zl=(t,e,r,o,s)=>({accessorKey:"status",header:({column:a})=>Be(a,t,"tw-justify-center"),cell:({row:a})=>{const l=a.getValue("status"),c=a.getValue("item");return n.jsxs(pn,{value:l,variant:"outline",type:"single",className:"tw-gap-0",children:[n.jsx(ve,{onClick:d=>{d.stopPropagation(),kn([c],"approved",e,r,o,s)},value:"approved",className:"tw-rounded-e-none tw-border-e-0",children:n.jsx(k.CircleCheckIcon,{})}),n.jsx(ve,{onClick:d=>{d.stopPropagation(),kn([c],"unapproved",e,r,o,s)},value:"unapproved",className:"tw-rounded-none",children:n.jsx(k.CircleXIcon,{})}),n.jsx(ve,{onClick:d=>{d.stopPropagation(),kn([c],"unknown",e,r,o,s)},value:"unknown",className:"tw-rounded-s-none tw-border-s-0",children:n.jsx(k.CircleHelpIcon,{})})]})}}),Gl=t=>t.split(/(?:\r?\n|\r)|(?=(?:\\(?:v|c|id)))/g),Bl=t=>{const e=/^\\[vc]\s+(\d+)/,r=t.match(e);if(r)return+r[1]},Kl=t=>{const e=t.match(/^\\id\s+([A-Za-z]+)/);return e?e[1]:""},ts=(t,e,r)=>r.includes(t)?"unapproved":e.includes(t)?"approved":"unknown",ql=Object.freeze(["%webView_inventory_all%","%webView_inventory_approved%","%webView_inventory_unapproved%","%webView_inventory_unknown%","%webView_inventory_scope_currentBook%","%webView_inventory_scope_chapter%","%webView_inventory_scope_verse%","%webView_inventory_filter_text%","%webView_inventory_show_additional_items%","%webView_inventory_occurrences_table_header_reference%","%webView_inventory_occurrences_table_header_occurrence%","%webView_inventory_no_results%"]),Ul=(t,e,r)=>{let o=t;return e!=="all"&&(o=o.filter(s=>e==="approved"&&s.status==="approved"||e==="unapproved"&&s.status==="unapproved"||e==="unknown"&&s.status==="unknown")),r!==""&&(o=o.filter(s=>s.items[0].includes(r))),o},Hl=(t,e,r)=>t.map(o=>{const s=N.isString(o.key)?o.key:o.key[0];return{items:N.isString(o.key)?[o.key]:o.key,count:o.count,status:o.status||ts(s,e,r),occurrences:o.occurrences||[]}}),Tt=(t,e)=>t[e]??e;function Yl({inventoryItems:t,setVerseRef:e,localizedStrings:r,additionalItemsLabels:o,approvedItems:s,unapprovedItems:a,scope:l,onScopeChange:c,columns:d,id:w,areInventoryItemsLoading:p=!1,classNameForVerseText:u,onItemSelected:h}){const f=Tt(r,"%webView_inventory_all%"),g=Tt(r,"%webView_inventory_approved%"),v=Tt(r,"%webView_inventory_unapproved%"),b=Tt(r,"%webView_inventory_unknown%"),y=Tt(r,"%webView_inventory_scope_currentBook%"),j=Tt(r,"%webView_inventory_scope_chapter%"),C=Tt(r,"%webView_inventory_scope_verse%"),M=Tt(r,"%webView_inventory_filter_text%"),F=Tt(r,"%webView_inventory_show_additional_items%"),$=Tt(r,"%webView_inventory_no_results%"),[_,T]=i.useState(!1),[E,R]=i.useState("all"),[P,L]=i.useState(""),[I,A]=i.useState([]),z=i.useMemo(()=>{const G=t??[];return G.length===0?[]:Hl(G,s,a)},[t,s,a]),S=i.useMemo(()=>{if(_)return z;const G=[];return z.forEach(tt=>{const wt=tt.items[0],ot=G.find(vt=>vt.items[0]===wt);ot?(ot.count+=tt.count,ot.occurrences=ot.occurrences.concat(tt.occurrences)):G.push({items:[wt],count:tt.count,occurrences:tt.occurrences,status:tt.status})}),G},[_,z]),B=i.useMemo(()=>S.length===0?[]:Ul(S,E,P),[S,E,P]),at=i.useMemo(()=>{var wt,ot;if(!_)return d;const G=(wt=o==null?void 0:o.tableHeaders)==null?void 0:wt.length;if(!G)return d;const tt=[];for(let vt=0;vt{B.length===0?A([]):B.length===1&&A(B[0].items)},[B]);const lt=(G,tt)=>{tt.setRowSelection(()=>{const ot={};return ot[G.index]=!0,ot});const wt=G.original.items;A(wt),h&&wt.length>0&&h(wt[0])},Vt=G=>{if(G==="book"||G==="chapter"||G==="verse")c(G);else throw new Error(`Invalid scope value: ${G}`)},ct=G=>{if(G==="all"||G==="approved"||G==="unapproved"||G==="unknown")R(G);else throw new Error(`Invalid status filter value: ${G}`)},rt=i.useMemo(()=>{if(S.length===0||I.length===0)return[];const G=S.filter(tt=>N.deepEqual(_?tt.items:[tt.items[0]],I));if(G.length>1)throw new Error("Selected item is not unique");return G.length===0?[]:G[0].occurrences},[I,_,S]);return n.jsxs("div",{id:w,className:"pr-twp tw-flex tw-h-full tw-flex-col",children:[n.jsxs("div",{className:"tw-flex tw-items-stretch",children:[n.jsxs(we,{onValueChange:G=>ct(G),defaultValue:E,children:[n.jsx(Wt,{className:"tw-m-1",children:n.jsx(de,{placeholder:"Select filter"})}),n.jsxs(Jt,{children:[n.jsx(gt,{value:"all",children:f}),n.jsx(gt,{value:"approved",children:g}),n.jsx(gt,{value:"unapproved",children:v}),n.jsx(gt,{value:"unknown",children:b})]})]}),n.jsxs(we,{onValueChange:G=>Vt(G),defaultValue:l,children:[n.jsx(Wt,{className:"tw-m-1",children:n.jsx(de,{placeholder:"Select scope"})}),n.jsxs(Jt,{children:[n.jsx(gt,{value:"book",children:y}),n.jsx(gt,{value:"chapter",children:j}),n.jsx(gt,{value:"verse",children:C})]})]}),n.jsx(fe,{className:"tw-m-1 tw-rounded-md tw-border",placeholder:M,value:P,onChange:G=>{L(G.target.value)}}),o&&n.jsxs("div",{className:"tw-m-1 tw-flex tw-items-center tw-rounded-md tw-border",children:[n.jsx(un,{className:"tw-m-1",checked:_,onCheckedChange:G=>{T(G)}}),n.jsx(et,{className:"tw-m-1 tw-flex-shrink-0 tw-whitespace-nowrap",children:(o==null?void 0:o.checkboxText)??F})]})]}),n.jsx("div",{className:"tw-m-1 tw-flex-1 tw-overflow-auto tw-rounded-md tw-border",children:n.jsx(Ho,{columns:at,data:B,onRowClickHandler:lt,stickyHeader:!0,isLoading:p,noResultsMessage:$})}),rt.length>0&&n.jsx("div",{className:"tw-m-1 tw-flex-1 tw-overflow-auto tw-rounded-md tw-border",children:n.jsx(Pl,{classNameForText:u,occurrenceData:rt,setScriptureReference:e,localizedStrings:r})})]})}const Xl=Object.freeze(["%markerMenu_deprecated_label%","%markerMenu_disallowed_label%","%markerMenu_noResults%","%markerMenu_searchPlaceholder%"]);function Wl({icon:t,className:e}){const r=t??k.Ban;return n.jsx(r,{className:e,size:16})}function Jl({localizedStrings:t,markerMenuItems:e}){const[r,o]=i.useState(""),s=i.useMemo(()=>{const a=r.trim().toLowerCase();return a?e.filter(l=>{var c;return((c=l.marker)==null?void 0:c.toLowerCase().includes(a))||l.title.toLowerCase().includes(a)}):e},[r,e]);return n.jsxs(At,{className:"tw-p-1",shouldFilter:!1,loop:!0,children:[n.jsx(pe,{value:r,onValueChange:a=>o(a),placeholder:t["%markerMenu_searchPlaceholder%"],autoFocus:!1}),n.jsxs(Pt,{children:[n.jsx(Ee,{children:t["%markerMenu_noResults%"]}),n.jsx(Ot,{children:s.map(a=>{var l;return n.jsxs(Et,{className:"tw-flex tw-gap-2 hover:tw-bg-accent",disabled:a.isDisallowed||a.isDeprecated,onSelect:a.action,children:[n.jsx("div",{className:"tw-w-6",children:a.marker?n.jsx("span",{className:"tw-text-xs",children:a.marker}):n.jsx("div",{children:n.jsx(Wl,{icon:a.icon})})}),n.jsxs("div",{children:[n.jsx("p",{className:"tw-text-sm",children:a.title}),a.subtitle&&n.jsx("p",{className:"tw-text-xs tw-text-muted-foreground",children:a.subtitle})]}),(a.isDisallowed||a.isDeprecated)&&n.jsx(Jr,{className:"tw-font-sans",children:a.isDisallowed?t["%markerMenu_disallowed_label%"]:t["%markerMenu_deprecated_label%"]})]},`item-${a.marker??((l=a.icon)==null?void 0:l.displayName)}-${a.title.replaceAll(" ","")}`)})})]})]})}const Zl="16rem",Ql="3rem",es=i.createContext(void 0);function Ke(){const t=i.useContext(es);if(!t)throw new Error("useSidebar must be used within a SidebarProvider.");return t}const or=i.forwardRef(({defaultOpen:t=!0,open:e,onOpenChange:r,className:o,style:s,children:a,side:l="primary",...c},d)=>{const[w,p]=i.useState(t),u=e??w,h=i.useCallback(C=>{const M=typeof C=="function"?C(u):C;r?r(M):p(M)},[r,u]),f=i.useCallback(()=>h(C=>!C),[h]),g=u?"expanded":"collapsed",y=st()==="ltr"?l:l==="primary"?"secondary":"primary",j=i.useMemo(()=>({state:g,open:u,setOpen:h,toggleSidebar:f,side:y}),[g,u,h,f,y]);return n.jsx(es.Provider,{value:j,children:n.jsx(xt,{delayDuration:0,children:n.jsx("div",{style:{"--sidebar-width":Zl,"--sidebar-width-icon":Ql,...s},className:m("tw-group/sidebar-wrapper pr-twp tw-flex tw-w-full has-[[data-variant=inset]]:tw-bg-sidebar",o),ref:d,...c,children:a})})})});or.displayName="SidebarProvider";const sr=i.forwardRef(({variant:t="sidebar",collapsible:e="offcanvas",className:r,children:o,...s},a)=>{const l=Ke();return e==="none"?n.jsx("div",{className:m("tw-flex tw-h-full tw-w-[--sidebar-width] tw-flex-col tw-bg-sidebar tw-text-sidebar-foreground",r),ref:a,...s,children:o}):n.jsxs("div",{ref:a,className:"tw-group tw-peer tw-hidden tw-text-sidebar-foreground md:tw-block","data-state":l.state,"data-collapsible":l.state==="collapsed"?e:"","data-variant":t,"data-side":l.side,children:[n.jsx("div",{className:m("tw-relative tw-h-svh tw-w-[--sidebar-width] tw-bg-transparent tw-transition-[width] tw-duration-200 tw-ease-linear","group-data-[collapsible=offcanvas]:tw-w-0","group-data-[side=secondary]:tw-rotate-180",t==="floating"||t==="inset"?"group-data-[collapsible=icon]:tw-w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]":"group-data-[collapsible=icon]:tw-w-[--sidebar-width-icon]")}),n.jsx("div",{className:m("tw-absolute tw-inset-y-0 tw-z-10 tw-hidden tw-h-svh tw-w-[--sidebar-width] tw-transition-[left,right,width] tw-duration-200 tw-ease-linear md:tw-flex",l.side==="primary"?"tw-left-0 group-data-[collapsible=offcanvas]:tw-left-[calc(var(--sidebar-width)*-1)]":"tw-right-0 group-data-[collapsible=offcanvas]:tw-right-[calc(var(--sidebar-width)*-1)]",t==="floating"||t==="inset"?"tw-p-2 group-data-[collapsible=icon]:tw-w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]":"group-data-[collapsible=icon]:tw-w-[--sidebar-width-icon] group-data-[side=primary]:tw-border-r group-data-[side=secondary]:tw-border-l",r),...s,children:n.jsx("div",{"data-sidebar":"sidebar",className:"tw-flex tw-h-full tw-w-full tw-flex-col tw-bg-sidebar group-data-[variant=floating]:tw-rounded-lg group-data-[variant=floating]:tw-border group-data-[variant=floating]:tw-border-sidebar-border group-data-[variant=floating]:tw-shadow",children:o})})]})});sr.displayName="Sidebar";const ns=i.forwardRef(({className:t,onClick:e,...r},o)=>{const s=Ke();return n.jsxs(V,{ref:o,"data-sidebar":"trigger",variant:"ghost",size:"icon",className:m("tw-h-7 tw-w-7",t),onClick:a=>{e==null||e(a),s.toggleSidebar()},...r,children:[s.side==="primary"?n.jsx(k.PanelLeft,{}):n.jsx(k.PanelRight,{}),n.jsx("span",{className:"tw-sr-only",children:"Toggle Sidebar"})]})});ns.displayName="SidebarTrigger";const rs=i.forwardRef(({className:t,...e},r)=>{const{toggleSidebar:o}=Ke();return n.jsx("button",{type:"button",ref:r,"data-sidebar":"rail","aria-label":"Toggle Sidebar",tabIndex:-1,onClick:o,title:"Toggle Sidebar",className:m("tw-absolute tw-inset-y-0 tw-z-20 tw-hidden tw-w-4 tw--translate-x-1/2 tw-transition-all tw-ease-linear after:tw-absolute after:tw-inset-y-0 after:tw-left-1/2 after:tw-w-[2px] hover:after:tw-bg-sidebar-border group-data-[side=primary]:tw--right-4 group-data-[side=secondary]:tw-left-0 sm:tw-flex","[[data-side=secondary]_&]:tw-cursor-e-resize [[data-side=secondary]_&]:tw-cursor-w-resize","[[data-side=primary][data-state=collapsed]_&]:tw-cursor-e-resize [[data-side=secondary][data-state=collapsed]_&]:tw-cursor-w-resize","group-data-[collapsible=offcanvas]:tw-translate-x-0 group-data-[collapsible=offcanvas]:after:tw-left-full group-data-[collapsible=offcanvas]:hover:tw-bg-sidebar","[[data-side=primary][data-collapsible=offcanvas]_&]:tw--right-2","[[data-side=secondary][data-collapsible=offcanvas]_&]:tw--left-2",t),...e})});rs.displayName="SidebarRail";const ar=i.forwardRef(({className:t,...e},r)=>n.jsx("main",{ref:r,className:m("tw-relative tw-flex tw-flex-1 tw-flex-col tw-bg-background","peer-data-[variant=inset]:tw-min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:tw-m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:tw-ml-2 md:peer-data-[variant=inset]:tw-ml-0 md:peer-data-[variant=inset]:tw-rounded-xl md:peer-data-[variant=inset]:tw-shadow",t),...e}));ar.displayName="SidebarInset";const os=i.forwardRef(({className:t,...e},r)=>n.jsx(fe,{ref:r,"data-sidebar":"input",className:m("tw-h-8 tw-w-full tw-bg-background tw-shadow-none focus-visible:tw-ring-2 focus-visible:tw-ring-sidebar-ring",t),...e}));os.displayName="SidebarInput";const ss=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,"data-sidebar":"header",className:m("tw-flex tw-flex-col tw-gap-2 tw-p-2",t),...e}));ss.displayName="SidebarHeader";const as=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,"data-sidebar":"footer",className:m("tw-flex tw-flex-col tw-gap-2 tw-p-2",t),...e}));as.displayName="SidebarFooter";const is=i.forwardRef(({className:t,...e},r)=>n.jsx(ce,{ref:r,"data-sidebar":"separator",className:m("tw-mx-2 tw-w-auto tw-bg-sidebar-border",t),...e}));is.displayName="SidebarSeparator";const ir=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,"data-sidebar":"content",className:m("tw-flex tw-min-h-0 tw-flex-1 tw-flex-col tw-gap-2 tw-overflow-auto group-data-[collapsible=icon]:tw-overflow-hidden",t),...e}));ir.displayName="SidebarContent";const nn=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,"data-sidebar":"group",className:m("tw-relative tw-flex tw-w-full tw-min-w-0 tw-flex-col tw-p-2",t),...e}));nn.displayName="SidebarGroup";const rn=i.forwardRef(({className:t,asChild:e=!1,...r},o)=>{const s=e?ke.Slot:"div";return n.jsx(s,{ref:o,"data-sidebar":"group-label",className:m("tw-flex tw-h-8 tw-shrink-0 tw-items-center tw-rounded-md tw-px-2 tw-text-xs tw-font-medium tw-text-sidebar-foreground/70 tw-outline-none tw-ring-sidebar-ring tw-transition-[margin,opa] tw-duration-200 tw-ease-linear focus-visible:tw-ring-2 [&>svg]:tw-size-4 [&>svg]:tw-shrink-0","group-data-[collapsible=icon]:tw--mt-8 group-data-[collapsible=icon]:tw-opacity-0",t),...r})});rn.displayName="SidebarGroupLabel";const ls=i.forwardRef(({className:t,asChild:e=!1,...r},o)=>{const s=e?ke.Slot:"button";return n.jsx(s,{ref:o,"data-sidebar":"group-action",className:m("tw-absolute tw-right-3 tw-top-3.5 tw-flex tw-aspect-square tw-w-5 tw-items-center tw-justify-center tw-rounded-md tw-p-0 tw-text-sidebar-foreground tw-outline-none tw-ring-sidebar-ring tw-transition-transform hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground focus-visible:tw-ring-2 [&>svg]:tw-size-4 [&>svg]:tw-shrink-0","after:tw-absolute after:tw--inset-2 after:md:tw-hidden","group-data-[collapsible=icon]:tw-hidden",t),...r})});ls.displayName="SidebarGroupAction";const on=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,"data-sidebar":"group-content",className:m("tw-w-full tw-text-sm",t),...e}));on.displayName="SidebarGroupContent";const lr=i.forwardRef(({className:t,...e},r)=>n.jsx("ul",{ref:r,"data-sidebar":"menu",className:m("tw-flex tw-w-full tw-min-w-0 tw-flex-col tw-gap-1",t),...e}));lr.displayName="SidebarMenu";const cr=i.forwardRef(({className:t,...e},r)=>n.jsx("li",{ref:r,"data-sidebar":"menu-item",className:m("tw-group/menu-item tw-relative",t),...e}));cr.displayName="SidebarMenuItem";const tc=Zt.cva("tw-peer/menu-button tw-flex tw-w-full tw-items-center tw-gap-2 tw-overflow-hidden tw-rounded-md tw-p-2 tw-text-left tw-text-sm tw-outline-none tw-ring-sidebar-ring tw-transition-[width,height,padding] hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground focus-visible:tw-ring-2 active:tw-bg-sidebar-accent active:tw-text-sidebar-accent-foreground disabled:tw-pointer-events-none disabled:tw-opacity-50 tw-group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:tw-pointer-events-none aria-disabled:tw-opacity-50 data-[active=true]:tw-font-medium data-[active=true]:tw-text-sidebar-accent-foreground data-[active=true]:tw-bg-sidebar-accent data-[state=open]:hover:tw-bg-sidebar-accent data-[state=open]:hover:tw-text-sidebar-accent-foreground group-data-[collapsible=icon]:tw-!size-8 group-data-[collapsible=icon]:tw-!p-2 [&>span:last-child]:tw-truncate [&>svg]:tw-size-4 [&>svg]:tw-shrink-0",{variants:{variant:{default:"hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground",outline:"tw-bg-background tw-shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground hover:tw-shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]"},size:{default:"tw-h-8 tw-text-sm",sm:"tw-h-7 tw-text-xs",lg:"tw-h-12 tw-text-sm group-data-[collapsible=icon]:tw-!p-0"}},defaultVariants:{variant:"default",size:"default"}}),wr=i.forwardRef(({asChild:t=!1,isActive:e=!1,variant:r="default",size:o="default",tooltip:s,className:a,...l},c)=>{const d=t?ke.Slot:"button",{state:w}=Ke(),p=n.jsx(d,{ref:c,"data-sidebar":"menu-button","data-size":o,"data-active":e,className:m(tc({variant:r,size:o}),a),...l});return s?(typeof s=="string"&&(s={children:s}),n.jsxs(jt,{children:[n.jsx(Nt,{asChild:!0,children:p}),n.jsx(bt,{side:"right",align:"center",hidden:w!=="collapsed",...s})]})):p});wr.displayName="SidebarMenuButton";const cs=i.forwardRef(({className:t,asChild:e=!1,showOnHover:r=!1,...o},s)=>{const a=e?ke.Slot:"button";return n.jsx(a,{ref:s,"data-sidebar":"menu-action",className:m("tw-peer-hover/menu-button:text-sidebar-accent-foreground tw-absolute tw-right-1 tw-top-1.5 tw-flex tw-aspect-square tw-w-5 tw-items-center tw-justify-center tw-rounded-md tw-p-0 tw-text-sidebar-foreground tw-outline-none tw-ring-sidebar-ring tw-transition-transform hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground focus-visible:tw-ring-2 [&>svg]:tw-size-4 [&>svg]:tw-shrink-0","after:tw-absolute after:tw--inset-2 after:md:tw-hidden","tw-peer-data-[size=sm]/menu-button:top-1","tw-peer-data-[size=default]/menu-button:top-1.5","tw-peer-data-[size=lg]/menu-button:top-2.5","group-data-[collapsible=icon]:tw-hidden",r&&"tw-group-focus-within/menu-item:opacity-100 tw-group-hover/menu-item:opacity-100 tw-peer-data-[active=true]/menu-button:text-sidebar-accent-foreground data-[state=open]:tw-opacity-100 md:tw-opacity-0",t),...o})});cs.displayName="SidebarMenuAction";const ws=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,"data-sidebar":"menu-badge",className:m("tw-pointer-events-none tw-absolute tw-right-1 tw-flex tw-h-5 tw-min-w-5 tw-select-none tw-items-center tw-justify-center tw-rounded-md tw-px-1 tw-text-xs tw-font-medium tw-tabular-nums tw-text-sidebar-foreground","tw-peer-hover/menu-button:text-sidebar-accent-foreground tw-peer-data-[active=true]/menu-button:text-sidebar-accent-foreground","tw-peer-data-[size=sm]/menu-button:top-1","tw-peer-data-[size=default]/menu-button:top-1.5","tw-peer-data-[size=lg]/menu-button:top-2.5","group-data-[collapsible=icon]:tw-hidden",t),...e}));ws.displayName="SidebarMenuBadge";const ds=i.forwardRef(({className:t,showIcon:e=!1,...r},o)=>{const s=i.useMemo(()=>`${Math.floor(Math.random()*40)+50}%`,[]);return n.jsxs("div",{ref:o,"data-sidebar":"menu-skeleton",className:m("tw-flex tw-h-8 tw-items-center tw-gap-2 tw-rounded-md tw-px-2",t),...r,children:[e&&n.jsx(en,{className:"tw-size-4 tw-rounded-md","data-sidebar":"menu-skeleton-icon"}),n.jsx(en,{className:"tw-h-4 tw-max-w-[--skeleton-width] tw-flex-1","data-sidebar":"menu-skeleton-text",style:{"--skeleton-width":s}})]})});ds.displayName="SidebarMenuSkeleton";const ps=i.forwardRef(({className:t,...e},r)=>n.jsx("ul",{ref:r,"data-sidebar":"menu-sub",className:m("tw-mx-3.5 tw-flex tw-min-w-0 tw-translate-x-px tw-flex-col tw-gap-1 tw-border-l tw-border-sidebar-border tw-px-2.5 tw-py-0.5","group-data-[collapsible=icon]:tw-hidden",t),...e}));ps.displayName="SidebarMenuSub";const us=i.forwardRef(({...t},e)=>n.jsx("li",{ref:e,...t}));us.displayName="SidebarMenuSubItem";const ms=i.forwardRef(({asChild:t=!1,size:e="md",isActive:r,className:o,...s},a)=>{const l=t?ke.Slot:"a";return n.jsx(l,{ref:a,"data-sidebar":"menu-sub-button","data-size":e,"data-active":r,className:m("tw-flex tw-h-7 tw-min-w-0 tw--translate-x-px tw-items-center tw-gap-2 tw-overflow-hidden tw-rounded-md tw-px-2 tw-text-sidebar-foreground tw-outline-none tw-ring-sidebar-ring hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground focus-visible:tw-ring-2 active:tw-bg-sidebar-accent active:tw-text-sidebar-accent-foreground disabled:tw-pointer-events-none disabled:tw-opacity-50 aria-disabled:tw-pointer-events-none aria-disabled:tw-opacity-50 [&>span:last-child]:tw-truncate [&>svg]:tw-size-4 [&>svg]:tw-shrink-0 [&>svg]:tw-text-sidebar-accent-foreground","data-[active=true]:tw-bg-sidebar-accent data-[active=true]:tw-text-sidebar-accent-foreground",e==="sm"&&"tw-text-xs",e==="md"&&"tw-text-sm","group-data-[collapsible=icon]:tw-hidden",o),...s})});ms.displayName="SidebarMenuSubButton";function fs({id:t,extensionLabels:e,projectInfo:r,handleSelectSidebarItem:o,selectedSidebarItem:s,extensionsSidebarGroupLabel:a,projectsSidebarGroupLabel:l,buttonPlaceholderText:c,className:d}){const w=i.useCallback((h,f)=>{o(h,f)},[o]),p=i.useCallback(h=>{const f=r.find(g=>g.projectId===h);return f?f.projectName:h},[r]),u=i.useCallback(h=>!s.projectId&&h===s.label,[s]);return n.jsx(sr,{id:t,collapsible:"none",variant:"inset",className:m("tw-w-96 tw-gap-2 tw-overflow-y-auto",d),children:n.jsxs(ir,{children:[n.jsxs(nn,{children:[n.jsx(rn,{className:"tw-text-sm",children:a}),n.jsx(on,{children:n.jsx(lr,{children:Object.entries(e).map(([h,f])=>n.jsx(cr,{children:n.jsx(wr,{onClick:()=>w(h),isActive:u(h),children:n.jsx("span",{className:"tw-pl-3",children:f})})},h))})})]}),n.jsxs(nn,{children:[n.jsx(rn,{className:"tw-text-sm",children:l}),n.jsx(on,{className:"tw-pl-3",children:n.jsx(Ze,{buttonVariant:"ghost",buttonClassName:m("tw-w-full",{"tw-bg-sidebar-accent tw-text-sidebar-accent-foreground":s==null?void 0:s.projectId}),popoverContentClassName:"tw-z-[1000]",options:r.flatMap(h=>h.projectId),getOptionLabel:p,buttonPlaceholder:c,onChange:h=>{const f=p(h);w(f,h)},value:(s==null?void 0:s.projectId)??void 0,icon:n.jsx(k.ScrollText,{})})})]})]})})}const mn=i.forwardRef(({value:t,onSearch:e,placeholder:r,isFullWidth:o,className:s,isDisabled:a=!1,id:l},c)=>{const d=st();return n.jsxs("div",{id:l,className:m("tw-relative",{"tw-w-full":o},s),children:[n.jsx(k.Search,{className:m("tw-absolute tw-top-1/2 tw-h-4 tw-w-4 tw--translate-y-1/2 tw-transform tw-opacity-50",{"tw-right-3":d==="rtl"},{"tw-left-3":d==="ltr"})}),n.jsx(fe,{ref:c,className:"tw-w-full tw-text-ellipsis tw-pe-9 tw-ps-9",placeholder:r,value:t,onChange:w=>e(w.target.value),disabled:a}),t&&n.jsxs(V,{variant:"ghost",size:"icon",className:m("tw-absolute tw-top-1/2 tw-h-7 tw--translate-y-1/2 tw-transform hover:tw-bg-transparent",{"tw-left-0":d==="rtl"},{"tw-right-0":d==="ltr"}),onClick:()=>{e("")},children:[n.jsx(k.X,{className:"tw-h-4 tw-w-4"}),n.jsx("span",{className:"tw-sr-only",children:"Clear"})]})]})});mn.displayName="SearchBar";function ec({id:t,extensionLabels:e,projectInfo:r,children:o,handleSelectSidebarItem:s,selectedSidebarItem:a,searchValue:l,onSearch:c,extensionsSidebarGroupLabel:d,projectsSidebarGroupLabel:w,buttonPlaceholderText:p}){return n.jsxs("div",{className:"tw-box-border tw-flex tw-h-full tw-flex-col",children:[n.jsx("div",{className:"tw-box-border tw-flex tw-items-center tw-justify-center tw-py-4",children:n.jsx(mn,{className:"tw-w-9/12",value:l,onSearch:c,placeholder:"Search app settings, extension settings, and project settings"})}),n.jsxs(or,{id:t,className:"tw-h-full tw-flex-1 tw-gap-4 tw-overflow-auto tw-border-t",children:[n.jsx(fs,{className:"tw-w-1/2 tw-min-w-[140px] tw-max-w-[220px] tw-border-e",extensionLabels:e,projectInfo:r,handleSelectSidebarItem:s,selectedSidebarItem:a,extensionsSidebarGroupLabel:d,projectsSidebarGroupLabel:w,buttonPlaceholderText:p}),n.jsx(ar,{className:"tw-min-w-[215px]",children:o})]})]})}const Ut="scrBook",nc="scrRef",ae="source",rc="details",oc="Scripture Reference",sc="Scripture Book",hs="Type",ac="Details";function ic(t,e){const r=e??!1;return[{accessorFn:o=>`${o.start.book} ${o.start.chapterNum}:${o.start.verseNum}`,id:Ut,header:(t==null?void 0:t.scriptureReferenceColumnName)??oc,cell:o=>{const s=o.row.original;return o.row.getIsGrouped()?Q.Canon.bookIdToEnglishName(s.start.book):o.row.groupingColumnId===Ut?N.formatScrRef(s.start):void 0},getGroupingValue:o=>Q.Canon.bookIdToNumber(o.start.book),sortingFn:(o,s)=>N.compareScrRefs(o.original.start,s.original.start),enableGrouping:!0},{accessorFn:o=>N.formatScrRef(o.start),id:nc,header:void 0,cell:o=>{const s=o.row.original;return o.row.getIsGrouped()?void 0:N.formatScrRef(s.start)},sortingFn:(o,s)=>N.compareScrRefs(o.original.start,s.original.start),enableGrouping:!1},{accessorFn:o=>o.source.displayName,id:ae,header:r?(t==null?void 0:t.typeColumnName)??hs:void 0,cell:o=>r||o.row.getIsGrouped()?o.getValue():void 0,getGroupingValue:o=>o.source.id,sortingFn:(o,s)=>o.original.source.displayName.localeCompare(s.original.source.displayName),enableGrouping:!0},{accessorFn:o=>o.detail,id:rc,header:(t==null?void 0:t.detailsColumnName)??ac,cell:o=>o.getValue(),enableGrouping:!1}]}const lc=t=>{if(!("offset"in t.start))throw new Error("No offset available in range start");if(t.end&&!("offset"in t.end))throw new Error("No offset available in range end");const{offset:e}=t.start;let r=0;return t.end&&({offset:r}=t.end),!t.end||N.compareScrRefs(t.start,t.end)===0?`${N.scrRefToBBBCCCVVV(t.start)}+${e}`:`${N.scrRefToBBBCCCVVV(t.start)}+${e}-${N.scrRefToBBBCCCVVV(t.end)}+${r}`},Lr=t=>`${lc({start:t.start,end:t.end})} ${t.source.displayName} ${t.detail}`;function cc({sources:t,showColumnHeaders:e=!1,showSourceColumn:r=!1,scriptureReferenceColumnName:o,scriptureBookGroupName:s,typeColumnName:a,detailsColumnName:l,onRowSelected:c,id:d}){const[w,p]=i.useState([]),[u,h]=i.useState([{id:Ut,desc:!1}]),[f,g]=i.useState({}),v=i.useMemo(()=>t.flatMap(E=>E.data.map(R=>({...R,source:E.source}))),[t]),b=i.useMemo(()=>ic({scriptureReferenceColumnName:o,typeColumnName:a,detailsColumnName:l},r),[o,a,l,r]);i.useEffect(()=>{w.includes(ae)?h([{id:ae,desc:!1},{id:Ut,desc:!1}]):h([{id:Ut,desc:!1}])},[w]);const y=ut.useReactTable({data:v,columns:b,state:{grouping:w,sorting:u,rowSelection:f},onGroupingChange:p,onSortingChange:h,onRowSelectionChange:g,getExpandedRowModel:ut.getExpandedRowModel(),getGroupedRowModel:ut.getGroupedRowModel(),getCoreRowModel:ut.getCoreRowModel(),getSortedRowModel:ut.getSortedRowModel(),getRowId:Lr,autoResetExpanded:!1,enableMultiRowSelection:!1,enableSubRowSelection:!1});i.useEffect(()=>{if(c){const E=y.getSelectedRowModel().rowsById,R=Object.keys(E);if(R.length===1){const P=v.find(L=>Lr(L)===R[0])||void 0;P&&c(P)}}},[f,v,c,y]);const j=s??sc,C=a??hs,M=[{label:"No Grouping",value:[]},{label:`Group by ${j}`,value:[Ut]},{label:`Group by ${C}`,value:[ae]},{label:`Group by ${j} and ${C}`,value:[Ut,ae]},{label:`Group by ${C} and ${j}`,value:[ae,Ut]}],F=E=>{p(JSON.parse(E))},$=(E,R)=>{!E.getIsGrouped()&&!E.getIsSelected()&&E.getToggleSelectedHandler()(R)},_=(E,R)=>E.getIsGrouped()?"":m("banded-row",R%2===0?"even":"odd"),T=(E,R,P)=>{if(!((E==null?void 0:E.length)===0||R.depth{F(E)},children:[n.jsx(Wt,{className:"tw-mb-1 tw-mt-2",children:n.jsx(de,{})}),n.jsx(Jt,{position:"item-aligned",children:n.jsx(zo,{children:M.map(E=>n.jsx(gt,{value:JSON.stringify(E.value),children:E.label},E.label))})})]}),n.jsxs(Fe,{className:"tw-relative tw-flex tw-flex-col tw-overflow-y-auto tw-p-0",children:[e&&n.jsx(ze,{children:y.getHeaderGroups().map(E=>n.jsx(Dt,{children:E.headers.filter(R=>R.column.columnDef.header).map(R=>n.jsx(Ne,{colSpan:R.colSpan,className:"top-0 tw-sticky",children:R.isPlaceholder?void 0:n.jsxs("div",{children:[R.column.getCanGroup()?n.jsx(V,{variant:"ghost",title:`Toggle grouping by ${R.column.columnDef.header}`,onClick:R.column.getToggleGroupingHandler(),type:"button",children:R.column.getIsGrouped()?"๐Ÿ›‘":"๐Ÿ‘Š "}):void 0," ",ut.flexRender(R.column.columnDef.header,R.getContext())]})},R.id))},E.id))}),n.jsx(Ge,{children:y.getRowModel().rows.map((E,R)=>{const P=st();return n.jsx(Dt,{"data-state":E.getIsSelected()?"selected":"",className:m(_(E,R)),onClick:L=>$(E,L),children:E.getVisibleCells().map(L=>{if(!(L.getIsPlaceholder()||L.column.columnDef.enableGrouping&&!L.getIsGrouped()&&(L.column.columnDef.id!==ae||!r)))return n.jsx(Yt,{className:m(L.column.columnDef.id,"tw-p-[1px]",T(w,E,L)),children:L.getIsGrouped()?n.jsxs(V,{variant:"link",onClick:E.getToggleExpandedHandler(),type:"button",children:[E.getIsExpanded()&&n.jsx(k.ChevronDown,{}),!E.getIsExpanded()&&(P==="ltr"?n.jsx(k.ChevronRight,{}):n.jsx(k.ChevronLeft,{}))," ",ut.flexRender(L.column.columnDef.cell,L.getContext())," (",E.subRows.length,")"]}):ut.flexRender(L.column.columnDef.cell,L.getContext())},L.id)})},E.id)})})]})]})}const dr=(t,e)=>t.filter(r=>{try{return N.getSectionForBook(r)===e}catch{return!1}}),gs=(t,e,r)=>dr(t,e).every(o=>r.includes(o));function wc({section:t,availableBookIds:e,selectedBookIds:r,onToggle:o,localizedStrings:s}){const a=dr(e,t).length===0,l=s["%scripture_section_ot_short%"],c=s["%scripture_section_nt_short%"],d=s["%scripture_section_dc_short%"],w=s["%scripture_section_extra_short%"];return n.jsx(V,{variant:"outline",size:"sm",onClick:()=>o(t),className:m(gs(e,t,r)&&!a&&"tw-bg-primary tw-text-primary-foreground hover:tw-bg-primary/70 hover:tw-text-primary-foreground"),disabled:a,children:Aa(t,l,c,d,w)})}const $r=5,_n=6;function dc({availableBookInfo:t,selectedBookIds:e,onChangeSelectedBookIds:r,localizedStrings:o,localizedBookNames:s}){const a=o["%webView_book_selector_books_selected%"],l=o["%webView_book_selector_select_books%"],c=o["%webView_book_selector_search_books%"],d=o["%webView_book_selector_select_all%"],w=o["%webView_book_selector_clear_all%"],p=o["%webView_book_selector_no_book_found%"],u=o["%webView_book_selector_more%"],{otLong:h,ntLong:f,dcLong:g,extraLong:v}={otLong:o==null?void 0:o["%scripture_section_ot_long%"],ntLong:o==null?void 0:o["%scripture_section_nt_long%"],dcLong:o==null?void 0:o["%scripture_section_dc_long%"],extraLong:o==null?void 0:o["%scripture_section_extra_long%"]},[b,y]=i.useState(!1),[j,C]=i.useState(""),M=i.useRef(void 0),F=i.useRef(!1);if(t.length!==Q.Canon.allBookIds.length)throw new Error("availableBookInfo length must match Canon.allBookIds length");const $=i.useMemo(()=>Q.Canon.allBookIds.filter((A,z)=>t[z]==="1"&&!Q.Canon.isObsolete(Q.Canon.bookIdToNumber(A))),[t]),_=i.useMemo(()=>{if(!j.trim()){const S={[N.Section.OT]:[],[N.Section.NT]:[],[N.Section.DC]:[],[N.Section.Extra]:[]};return $.forEach(B=>{const at=N.getSectionForBook(B);S[at].push(B)}),S}const A=$.filter(S=>$n(S,j,s)),z={[N.Section.OT]:[],[N.Section.NT]:[],[N.Section.DC]:[],[N.Section.Extra]:[]};return A.forEach(S=>{const B=N.getSectionForBook(S);z[B].push(S)}),z},[$,j,s]),T=i.useCallback((A,z=!1)=>{if(!z||!M.current){r(e.includes(A)?e.filter(ct=>ct!==A):[...e,A]),M.current=A;return}const S=$.findIndex(ct=>ct===M.current),B=$.findIndex(ct=>ct===A);if(S===-1||B===-1)return;const[at,lt]=[Math.min(S,B),Math.max(S,B)],Vt=$.slice(at,lt+1).map(ct=>ct);r(e.includes(A)?e.filter(ct=>!Vt.includes(ct)):[...new Set([...e,...Vt])])},[e,r,$]),E=A=>{T(A,F.current),F.current=!1},R=(A,z)=>{A.preventDefault(),T(z,A.shiftKey)},P=i.useCallback(A=>{const z=dr($,A).map(S=>S);r(gs($,A,e)?e.filter(S=>!z.includes(S)):[...new Set([...e,...z])])},[e,r,$]),L=()=>{r($.map(A=>A))},I=()=>{r([])};return n.jsxs("div",{className:"tw-space-y-2",children:[n.jsx("div",{className:"tw-flex tw-flex-wrap tw-gap-2",children:Object.values(N.Section).map(A=>n.jsx(wc,{section:A,availableBookIds:$,selectedBookIds:e,onToggle:P,localizedStrings:o},A))}),n.jsxs(zt,{open:b,onOpenChange:A=>{y(A),A||C("")},children:[n.jsx(Gt,{asChild:!0,children:n.jsxs(V,{variant:"outline",role:"combobox","aria-expanded":b,className:"tw-max-w-64 tw-justify-between",children:[e.length>0?`${a}: ${e.length}`:l,n.jsx(k.ChevronsUpDown,{className:"tw-ml-2 tw-h-4 tw-w-4 tw-shrink-0 tw-opacity-50"})]})}),n.jsx(Lt,{className:"tw-w-full tw-p-0",align:"start",children:n.jsxs(At,{shouldFilter:!1,onKeyDown:A=>{A.key==="Enter"&&(F.current=A.shiftKey)},children:[n.jsx(pe,{placeholder:c,value:j,onValueChange:C}),n.jsxs("div",{className:"tw-flex tw-justify-between tw-border-b tw-p-2",children:[n.jsx(V,{variant:"ghost",size:"sm",onClick:L,children:d}),n.jsx(V,{variant:"ghost",size:"sm",onClick:I,children:w})]}),n.jsxs(Pt,{children:[n.jsx(Ee,{children:p}),Object.values(N.Section).map((A,z)=>{const S=_[A];if(S.length!==0)return n.jsxs(i.Fragment,{children:[n.jsx(Ot,{heading:Zr(A,h,f,g,v),children:S.map(B=>n.jsx(to,{bookId:B,isSelected:e.includes(B),onSelect:()=>E(B),onMouseDown:at=>R(at,B),section:N.getSectionForBook(B),showCheck:!0,localizedBookNames:s,commandValue:Dn(B,s),className:"tw-flex tw-items-center"},B))}),z0&&n.jsxs("div",{className:"tw-mt-2 tw-flex tw-flex-wrap tw-gap-1",children:[e.slice(0,e.length===_n?_n:$r).map(A=>n.jsx(le,{className:"hover:tw-bg-secondary",variant:"secondary",children:be(A,s)},A)),e.length>_n&&n.jsx(le,{className:"hover:tw-bg-secondary",variant:"secondary",children:`+${e.length-$r} ${u}`})]})]})}const pc=Object.freeze(["%webView_scope_selector_selected_text%","%webView_scope_selector_current_verse%","%webView_scope_selector_current_chapter%","%webView_scope_selector_current_book%","%webView_scope_selector_choose_books%","%webView_scope_selector_scope%","%webView_scope_selector_select_books%","%webView_book_selector_books_selected%","%webView_book_selector_select_books%","%webView_book_selector_search_books%","%webView_book_selector_select_all%","%webView_book_selector_clear_all%","%webView_book_selector_no_book_found%","%webView_book_selector_more%","%scripture_section_ot_long%","%scripture_section_ot_short%","%scripture_section_nt_long%","%scripture_section_nt_short%","%scripture_section_dc_long%","%scripture_section_dc_short%","%scripture_section_extra_long%","%scripture_section_extra_short%"]),oe=(t,e)=>t[e]??e;function uc({scope:t,availableScopes:e,onScopeChange:r,availableBookInfo:o,selectedBookIds:s,onSelectedBookIdsChange:a,localizedStrings:l,localizedBookNames:c,id:d}){const w=oe(l,"%webView_scope_selector_selected_text%"),p=oe(l,"%webView_scope_selector_current_verse%"),u=oe(l,"%webView_scope_selector_current_chapter%"),h=oe(l,"%webView_scope_selector_current_book%"),f=oe(l,"%webView_scope_selector_choose_books%"),g=oe(l,"%webView_scope_selector_scope%"),v=oe(l,"%webView_scope_selector_select_books%"),b=[{value:"selectedText",label:w,id:"scope-selected-text"},{value:"verse",label:p,id:"scope-verse"},{value:"chapter",label:u,id:"scope-chapter"},{value:"book",label:h,id:"scope-book"},{value:"selectedBooks",label:f,id:"scope-selected"}],y=e?b.filter(j=>e.includes(j.value)):b;return n.jsxs("div",{id:d,className:"tw-grid tw-gap-4",children:[n.jsxs("div",{className:"tw-grid tw-gap-2",children:[n.jsx(et,{children:g}),n.jsx(cn,{value:t,onValueChange:r,className:"tw-flex tw-flex-col tw-space-y-1",children:y.map(({value:j,label:C,id:M})=>n.jsxs("div",{className:"tw-flex tw-items-center",children:[n.jsx(Pe,{className:"tw-me-2",value:j,id:M}),n.jsx(et,{htmlFor:M,children:C})]},M))})]}),t==="selectedBooks"&&n.jsxs("div",{className:"tw-grid tw-gap-2",children:[n.jsx(et,{children:v}),n.jsx(dc,{availableBookInfo:o,selectedBookIds:s,onChangeSelectedBookIds:a,localizedStrings:l,localizedBookNames:c})]})]})}const Cn={[N.getLocalizeKeyForScrollGroupId("undefined")]:"ร˜",[N.getLocalizeKeyForScrollGroupId(0)]:"A",[N.getLocalizeKeyForScrollGroupId(1)]:"B",[N.getLocalizeKeyForScrollGroupId(2)]:"C",[N.getLocalizeKeyForScrollGroupId(3)]:"D",[N.getLocalizeKeyForScrollGroupId(4)]:"E",[N.getLocalizeKeyForScrollGroupId(5)]:"F",[N.getLocalizeKeyForScrollGroupId(6)]:"G",[N.getLocalizeKeyForScrollGroupId(7)]:"H",[N.getLocalizeKeyForScrollGroupId(8)]:"I",[N.getLocalizeKeyForScrollGroupId(9)]:"J",[N.getLocalizeKeyForScrollGroupId(10)]:"K",[N.getLocalizeKeyForScrollGroupId(11)]:"L",[N.getLocalizeKeyForScrollGroupId(12)]:"M",[N.getLocalizeKeyForScrollGroupId(13)]:"N",[N.getLocalizeKeyForScrollGroupId(14)]:"O",[N.getLocalizeKeyForScrollGroupId(15)]:"P",[N.getLocalizeKeyForScrollGroupId(16)]:"Q",[N.getLocalizeKeyForScrollGroupId(17)]:"R",[N.getLocalizeKeyForScrollGroupId(18)]:"S",[N.getLocalizeKeyForScrollGroupId(19)]:"T",[N.getLocalizeKeyForScrollGroupId(20)]:"U",[N.getLocalizeKeyForScrollGroupId(21)]:"V",[N.getLocalizeKeyForScrollGroupId(22)]:"W",[N.getLocalizeKeyForScrollGroupId(23)]:"X",[N.getLocalizeKeyForScrollGroupId(24)]:"Y",[N.getLocalizeKeyForScrollGroupId(25)]:"Z"};function mc({availableScrollGroupIds:t,scrollGroupId:e,onChangeScrollGroupId:r,localizedStrings:o={},size:s="sm",className:a,id:l}){const c={...Cn,...Object.fromEntries(Object.entries(o).map(([w,p])=>[w,w===p&&w in Cn?Cn[w]:p]))},d=st();return n.jsxs(we,{value:`${e}`,onValueChange:w=>r(w==="undefined"?void 0:parseInt(w,10)),children:[n.jsx(Wt,{size:s,className:m("pr-twp tw-w-auto",a),children:n.jsx(de,{placeholder:c[N.getLocalizeKeyForScrollGroupId(e)]??e})}),n.jsx(Jt,{id:l,align:d==="rtl"?"end":"start",style:{zIndex:250},children:t.map(w=>n.jsx(gt,{value:`${w}`,children:c[N.getLocalizeKeyForScrollGroupId(w)]},`${w}`))})]})}function fc({children:t}){return n.jsx("div",{className:"pr-twp tw-grid",children:t})}function hc({primary:t,secondary:e,children:r,isLoading:o=!1,loadingMessage:s}){return n.jsxs("div",{className:"tw-flex tw-items-center tw-justify-between tw-space-x-4 tw-py-2",children:[n.jsxs("div",{children:[n.jsx("p",{className:"tw-text-sm tw-font-medium tw-leading-none",children:t}),n.jsx("p",{className:"tw-whitespace-normal tw-break-words tw-text-sm tw-text-muted-foreground",children:e})]}),o?n.jsx("p",{className:"tw-text-sm tw-text-muted-foreground",children:s}):n.jsx("div",{children:r})]})}function gc({primary:t,secondary:e,includeSeparator:r=!1}){return n.jsxs("div",{className:"tw-space-y-4 tw-py-2",children:[n.jsxs("div",{children:[n.jsx("h3",{className:"tw-text-lg tw-font-medium",children:t}),n.jsx("p",{className:"tw-text-sm tw-text-muted-foreground",children:e})]}),r?n.jsx(ce,{}):""]})}function xs(t,e){var r;return(r=Object.entries(t).find(([,o])=>"menuItem"in o&&o.menuItem===e))==null?void 0:r[0]}function sn({icon:t,menuLabel:e,leading:r}){return t?n.jsx("img",{className:m("tw-max-h-5 tw-max-w-5",r?"tw-me-2":"tw-ms-2"),src:t,alt:`${r?"Leading":"Trailing"} icon for ${e}`}):void 0}const bs=(t,e,r,o)=>r?Object.entries(t).filter(([a,l])=>"column"in l&&l.column===r||a===r).sort(([,a],[,l])=>a.order-l.order).flatMap(([a])=>e.filter(c=>c.group===a).sort((c,d)=>c.order-d.order).map(c=>n.jsxs(jt,{children:[n.jsx(Nt,{asChild:!0,children:"command"in c?n.jsxs($e,{onClick:()=>{o(c)},children:[c.iconPathBefore&&n.jsx(sn,{icon:c.iconPathBefore,menuLabel:c.label,leading:!0}),c.label,c.iconPathAfter&&n.jsx(sn,{icon:c.iconPathAfter,menuLabel:c.label})]},`dropdown-menu-item-${c.label}-${c.command}`):n.jsxs($o,{children:[n.jsx(Zn,{children:c.label}),n.jsx(Lo,{children:n.jsx(Qn,{children:bs(t,e,xs(t,c.id),o)})})]},`dropdown-menu-sub-${c.label}-${c.id}`)}),c.tooltip&&n.jsx(bt,{children:c.tooltip})]},`tooltip-${c.label}-${"command"in c?c.command:c.id}`))):void 0;function an({onSelectMenuItem:t,menuData:e,tabLabel:r,icon:o,className:s,variant:a,buttonVariant:l="ghost",id:c}){return n.jsxs(Qt,{variant:a,children:[n.jsx(ue,{"aria-label":r,className:s,asChild:!0,id:c,children:n.jsx(V,{variant:l,size:"icon",children:o??n.jsx(k.MenuIcon,{})})}),n.jsx(Kt,{align:"start",className:"tw-z-[250]",children:Object.entries(e.columns).filter(([,d])=>typeof d=="object").sort(([,d],[,w])=>typeof d=="boolean"||typeof w=="boolean"?0:d.order-w.order).map(([d],w,p)=>n.jsxs(i.Fragment,{children:[n.jsx(Jn,{children:n.jsx(xt,{children:bs(e.groups,e.items,d,t)})}),wn.jsx("div",{ref:o,className:`tw-sticky tw-top-0 tw-box-border tw-flex tw-h-14 tw-flex-row tw-items-center tw-justify-between tw-gap-2 tw-overflow-clip tw-px-4 tw-py-2 tw-text-foreground tw-@container/toolbar ${e}`,id:t,children:r}));function xc({onSelectProjectMenuItem:t,onSelectViewInfoMenuItem:e,projectMenuData:r,tabViewMenuData:o,id:s,className:a,startAreaChildren:l,centerAreaChildren:c,endAreaChildren:d,menuButtonIcon:w}){return n.jsxs(vs,{className:`tw-w-full tw-border ${a}`,id:s,children:[r&&n.jsx(an,{onSelectMenuItem:t,menuData:r,tabLabel:"Project",icon:w??n.jsx(k.Menu,{}),buttonVariant:"ghost"}),l&&n.jsx("div",{className:"tw-flex tw-h-full tw-shrink tw-grow-[2] tw-flex-row tw-flex-wrap tw-items-start tw-gap-2 tw-overflow-clip tw-@container/tab-toolbar-start",children:l}),c&&n.jsx("div",{className:"tw-flex tw-h-full tw-shrink tw-basis-0 tw-flex-row tw-flex-wrap tw-items-start tw-justify-center tw-gap-2 tw-overflow-clip tw-@container/tab-toolbar-center @sm:tw-grow @sm:tw-basis-auto",children:c}),n.jsxs("div",{className:"tw-flex tw-h-full tw-shrink tw-grow-[2] tw-flex-row-reverse tw-flex-wrap tw-items-start tw-gap-2 tw-overflow-clip tw-@container/tab-toolbar-end",children:[o&&n.jsx(an,{onSelectMenuItem:e,menuData:o,tabLabel:"View Info",icon:n.jsx(k.EllipsisVertical,{}),className:"tw-h-full"}),d]})]})}function bc({onSelectProjectMenuItem:t,projectMenuData:e,id:r,className:o,menuButtonIcon:s}){return n.jsx(vs,{className:"tw-pointer-events-none",id:r,children:e&&n.jsx(an,{onSelectMenuItem:t,menuData:e,tabLabel:"Project",icon:s,className:`tw-pointer-events-auto tw-shadow-lg ${o}`,buttonVariant:"outline"})})}const pr=i.forwardRef(({className:t,...e},r)=>{const o=st();return n.jsx(ht.Root,{orientation:"vertical",ref:r,className:m("tw-flex tw-gap-1 tw-rounded-md tw-text-muted-foreground",t),...e,dir:o})});pr.displayName=ht.List.displayName;const ur=i.forwardRef(({className:t,...e},r)=>n.jsx(ht.List,{ref:r,className:m("tw-flex-fit tw-mlk-items-center tw-w-[124px] tw-justify-center tw-rounded-md tw-bg-muted tw-p-1 tw-text-muted-foreground",t),...e}));ur.displayName=ht.List.displayName;const ys=i.forwardRef(({className:t,...e},r)=>n.jsx(ht.Trigger,{ref:r,...e,className:m("overflow-clip tw-inline-flex tw-w-[116px] tw-cursor-pointer tw-items-center tw-justify-center tw-break-words tw-rounded-sm tw-border-0 tw-bg-muted tw-px-3 tw-py-1.5 tw-text-sm tw-font-medium tw-text-inherit tw-ring-offset-background tw-transition-all hover:tw-text-foreground focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-pointer-events-none disabled:tw-opacity-50 data-[state=active]:tw-bg-background data-[state=active]:tw-text-foreground data-[state=active]:tw-shadow-sm",t)})),mr=i.forwardRef(({className:t,...e},r)=>n.jsx(ht.Content,{ref:r,className:m("tw-ms-5 tw-flex-grow tw-text-foreground tw-ring-offset-background focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2",t),...e}));mr.displayName=ht.Content.displayName;function vc({tabList:t,searchValue:e,onSearch:r,searchPlaceholder:o,headerTitle:s,searchClassName:a,id:l}){return n.jsxs("div",{id:l,className:"pr-twp",children:[n.jsxs("div",{className:"tw-sticky tw-top-0 tw-space-y-2 tw-pb-2",children:[s?n.jsx("h1",{children:s}):"",n.jsx(mn,{className:a,value:e,onSearch:r,placeholder:o})]}),n.jsxs(pr,{children:[n.jsx(ur,{children:t.map(c=>n.jsx(ys,{value:c.value,children:c.value},c.key))}),t.map(c=>n.jsx(mr,{value:c.value,children:c.content},c.key))]})]})}function yc({...t}){return n.jsx(W.Menu,{...t})}function jc({...t}){return n.jsx(W.Sub,{"data-slot":"menubar-sub",...t})}const js=i.forwardRef(({className:t,variant:e="default",...r},o)=>{const s=i.useMemo(()=>({variant:e}),[e]);return n.jsx(Wn.Provider,{value:s,children:n.jsx(W.Root,{ref:o,className:m("tw-flex tw-h-10 tw-items-center tw-space-x-1 tw-rounded-md tw-border tw-bg-background tw-p-1",t),...r})})});js.displayName=W.Root.displayName;const Ns=i.forwardRef(({className:t,...e},r)=>{const o=St();return n.jsx(W.Trigger,{ref:r,className:m("tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-3 tw-py-1.5 tw-text-sm tw-font-medium tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[state=open]:tw-bg-accent data-[state=open]:tw-text-accent-foreground","pr-twp",Bt({variant:o.variant,className:t})),...e})});Ns.displayName=W.Trigger.displayName;const ks=i.forwardRef(({className:t,inset:e,children:r,...o},s)=>{const a=St();return n.jsxs(W.SubTrigger,{ref:s,className:m("tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[state=open]:tw-bg-accent data-[state=open]:tw-text-accent-foreground",e&&"tw-pl-8",Bt({variant:a.variant,className:t}),t),...o,children:[r,n.jsx(k.ChevronRight,{className:"tw-ml-auto tw-h-4 tw-w-4"})]})});ks.displayName=W.SubTrigger.displayName;const _s=i.forwardRef(({className:t,...e},r)=>{const o=St();return n.jsx(W.SubContent,{ref:r,className:m("tw-z-50 tw-min-w-[8rem] tw-overflow-hidden tw-rounded-md tw-border tw-bg-popover tw-p-1 tw-text-popover-foreground data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2",{"tw-bg-popover":o.variant==="muted"},t),...e})});_s.displayName=W.SubContent.displayName;const Cs=i.forwardRef(({className:t,align:e="start",alignOffset:r=-4,sideOffset:o=8,...s},a)=>{const l=St();return n.jsx(W.Portal,{children:n.jsx(W.Content,{ref:a,align:e,alignOffset:r,sideOffset:o,className:m("tw-z-50 tw-min-w-[12rem] tw-overflow-hidden tw-rounded-md tw-border tw-bg-popover tw-p-1 tw-text-popover-foreground tw-shadow-md data-[state=open]:tw-animate-in data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2","pr-twp",{"tw-bg-popover":l.variant==="muted"},t),...s})})});Cs.displayName=W.Content.displayName;const Es=i.forwardRef(({className:t,inset:e,...r},o)=>{const s=St();return n.jsx(W.Item,{ref:o,className:m("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",e&&"tw-pl-8",Bt({variant:s.variant,className:t}),t),...r})});Es.displayName=W.Item.displayName;const Nc=i.forwardRef(({className:t,children:e,checked:r,...o},s)=>{const a=St();return n.jsxs(W.CheckboxItem,{ref:s,className:m("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pl-8 tw-pr-2 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",Bt({variant:a.variant,className:t}),t),checked:r,...o,children:[n.jsx("span",{className:"tw-absolute tw-left-2 tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center",children:n.jsx(W.ItemIndicator,{children:n.jsx(k.Check,{className:"tw-h-4 tw-w-4"})})}),e]})});Nc.displayName=W.CheckboxItem.displayName;const kc=i.forwardRef(({className:t,children:e,...r},o)=>{const s=St();return n.jsxs(W.RadioItem,{ref:o,className:m("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pl-8 tw-pr-2 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",Bt({variant:s.variant,className:t}),t),...r,children:[n.jsx("span",{className:"tw-absolute tw-left-2 tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center",children:n.jsx(W.ItemIndicator,{children:n.jsx(k.Circle,{className:"tw-h-2 tw-w-2 tw-fill-current"})})}),e]})});kc.displayName=W.RadioItem.displayName;const _c=i.forwardRef(({className:t,inset:e,...r},o)=>n.jsx(W.Label,{ref:o,className:m("tw-px-2 tw-py-1.5 tw-text-sm tw-font-semibold",e&&"tw-pl-8",t),...r}));_c.displayName=W.Label.displayName;const Ss=i.forwardRef(({className:t,...e},r)=>n.jsx(W.Separator,{ref:r,className:m("tw--mx-1 tw-my-1 tw-h-px tw-bg-muted",t),...e}));Ss.displayName=W.Separator.displayName;const Me=(t,e)=>{setTimeout(()=>{e.forEach(r=>{var o;(o=t.current)==null||o.dispatchEvent(new KeyboardEvent("keydown",r))})},0)},Rs=(t,e,r,o)=>{if(!r)return;const s=Object.entries(t).filter(([a,l])=>"column"in l&&l.column===r||a===r).sort(([,a],[,l])=>a.order-l.order);return s.flatMap(([a],l)=>{const c=e.filter(w=>w.group===a).sort((w,p)=>w.order-p.order).map(w=>n.jsxs(jt,{children:[n.jsx(Nt,{asChild:!0,children:"command"in w?n.jsxs(Es,{onClick:()=>{o(w)},children:[w.iconPathBefore&&n.jsx(sn,{icon:w.iconPathBefore,menuLabel:w.label,leading:!0}),w.label,w.iconPathAfter&&n.jsx(sn,{icon:w.iconPathAfter,menuLabel:w.label})]},`menubar-item-${w.label}-${w.command}`):n.jsxs(jc,{children:[n.jsx(ks,{children:w.label}),n.jsx(_s,{children:Rs(t,e,xs(t,w.id),o)})]},`menubar-sub-${w.label}-${w.id}`)}),w.tooltip&&n.jsx(bt,{children:w.tooltip})]},`tooltip-${w.label}-${"command"in w?w.command:w.id}`)),d=[...c];return c.length>0&&l{switch(p){case"platform.app":return a;case"platform.window":return l;case"platform.layout":return c;case"platform.help":return d;default:return}};if(ka.useHotkeys(["alt","alt+p","alt+l","alt+n","alt+h"],(p,u)=>{var g,v,b,y;p.preventDefault();const h={key:"Escape",code:"Escape",keyCode:27,bubbles:!0},f={key:" ",code:"Space",keyCode:32,bubbles:!0};switch(u.hotkey){case"alt":Me(a,[h]);break;case"alt+p":(g=a.current)==null||g.focus(),Me(a,[h,f]);break;case"alt+l":(v=l.current)==null||v.focus(),Me(l,[h,f]);break;case"alt+n":(b=c.current)==null||b.focus(),Me(c,[h,f]);break;case"alt+h":(y=d.current)==null||y.focus(),Me(d,[h,f]);break}}),i.useEffect(()=>{if(!r||!s.current)return;const p=new MutationObserver(f=>{f.forEach(g=>{if(g.attributeName==="data-state"&&g.target instanceof HTMLElement){const v=g.target.getAttribute("data-state");r(v==="open")}})});return s.current.querySelectorAll("[data-state]").forEach(f=>{p.observe(f,{attributes:!0})}),()=>p.disconnect()},[r]),!!t)return n.jsx(js,{ref:s,className:"pr-twp tw-border-0 tw-bg-transparent",variant:o,children:Object.entries(t.columns).filter(([,p])=>typeof p=="object").sort(([,p],[,u])=>typeof p=="boolean"||typeof u=="boolean"?0:p.order-u.order).map(([p,u])=>n.jsxs(yc,{children:[n.jsx(Ns,{ref:w(p),children:typeof u=="object"&&"label"in u&&u.label}),n.jsx(Cs,{className:"tw-z-[250]",children:n.jsx(xt,{children:Rs(t.groups,t.items,p,e)})})]},p))})}function Ec(t){switch(t){case void 0:return;case"darwin":return"tw-ps-[85px]";default:return"tw-pe-[calc(138px+1rem)]"}}function Sc({menuData:t,onOpenChange:e,onSelectMenuItem:r,className:o,id:s,children:a,appMenuAreaChildren:l,configAreaChildren:c,shouldUseAsAppDragArea:d,menubarVariant:w="default"}){const p=i.useRef(void 0);return n.jsx("div",{className:m("tw-border tw-px-4 tw-text-foreground",o),ref:p,style:{position:"relative"},id:s,children:n.jsxs("div",{className:"tw-flex tw-h-full tw-w-full tw-justify-between tw-overflow-hidden",style:d?{WebkitAppRegion:"drag"}:void 0,children:[n.jsx("div",{className:"tw-flex tw-grow tw-basis-0",children:n.jsxs("div",{className:"tw-flex tw-items-center tw-gap-2",style:d?{WebkitAppRegion:"no-drag"}:void 0,children:[l,t&&n.jsx(Cc,{menuData:t,onOpenChange:e,onSelectMenuItem:r,variant:w})]})}),n.jsx("div",{className:"tw-flex tw-items-center tw-gap-2 tw-px-2",style:d?{WebkitAppRegion:"no-drag"}:void 0,children:a}),n.jsx("div",{className:"tw-flex tw-min-w-0 tw-grow tw-basis-0 tw-justify-end",children:n.jsx("div",{className:"tw-flex tw-min-w-0 tw-items-center tw-gap-2 tw-pe-1",style:d?{WebkitAppRegion:"no-drag"}:void 0,children:c})})]})})}const Rc=(t,e)=>t[e]??e;function Tc({knownUiLanguages:t,primaryLanguage:e="en",fallbackLanguages:r=[],onLanguagesChange:o,onPrimaryLanguageChange:s,onFallbackLanguagesChange:a,localizedStrings:l,className:c,id:d}){const w=Rc(l,"%settings_uiLanguageSelector_fallbackLanguages%"),[p,u]=i.useState(!1),h=g=>{s&&s(g),o&&o([g,...r.filter(v=>v!==g)]),a&&r.find(v=>v===g)&&a([...r.filter(v=>v!==g)]),u(!1)},f=(g,v)=>{var y,j,C,M,F,$;const b=v!==g?((j=(y=t[g])==null?void 0:y.uiNames)==null?void 0:j[v])??((M=(C=t[g])==null?void 0:C.uiNames)==null?void 0:M.en):void 0;return b?`${(F=t[g])==null?void 0:F.autonym} (${b})`:($=t[g])==null?void 0:$.autonym};return n.jsxs("div",{id:d,className:m("pr-twp tw-max-w-sm",c),children:[n.jsxs(we,{name:"uiLanguage",value:e,onValueChange:h,open:p,onOpenChange:g=>u(g),children:[n.jsx(Wt,{children:n.jsx(de,{})}),n.jsx(Jt,{className:"tw-z-[250]",children:Object.keys(t).map(g=>n.jsx(gt,{value:g,children:f(g,e)},g))})]}),e!=="en"&&n.jsx("div",{className:"tw-pt-3",children:n.jsx(et,{className:"tw-font-normal tw-text-muted-foreground",children:N.formatReplacementString(w,{fallbackLanguages:(r==null?void 0:r.length)>0?r.map(g=>f(g,e)).join(", "):t.en.autonym})})})]})}function Mc({item:t,createLabel:e,createComplexLabel:r}){return e?n.jsx(et,{children:e(t)}):r?n.jsx(et,{children:r(t)}):n.jsx(et,{children:t})}function Dc({id:t,className:e,listItems:r,selectedListItems:o,handleSelectListItem:s,createLabel:a,createComplexLabel:l}){return n.jsx("div",{id:t,className:e,children:r.map(c=>n.jsxs("div",{className:"tw-m-2 tw-flex tw-items-center",children:[n.jsx(un,{className:"tw-me-2 tw-align-middle",checked:o.includes(c),onCheckedChange:d=>s(c,d)}),n.jsx(Mc,{item:c,createLabel:a,createComplexLabel:l})]},c))})}function Ic({cardKey:t,isSelected:e,onSelect:r,isDenied:o,isHidden:s=!1,className:a,children:l,dropdownContent:c,additionalSelectedContent:d,accentColor:w}){const p=u=>{(u.key==="Enter"||u.key===" ")&&(u.preventDefault(),r())};return n.jsxs("div",{hidden:s,onClick:r,onKeyDown:p,role:"button",tabIndex:0,"aria-pressed":e,className:m("tw-relative tw-min-w-36 tw-rounded-xl tw-border tw-shadow-none hover:tw-bg-muted/50",{"tw-opacity-50 hover:tw-opacity-100":o&&!e},{"tw-bg-accent":e},{"tw-bg-transparent":!e},a),children:[n.jsxs("div",{className:"tw-flex tw-flex-col tw-gap-2 tw-p-4",children:[n.jsxs("div",{className:"tw-flex tw-justify-between tw-overflow-hidden",children:[n.jsx("div",{className:"tw-min-w-0 tw-flex-1",children:l}),e&&c&&n.jsxs(Qt,{children:[n.jsx(ue,{className:m(w&&"tw-me-1"),asChild:!0,children:n.jsx(V,{className:"tw-m-1 tw-h-6 tw-w-6",variant:"ghost",size:"icon",children:n.jsx(k.MoreHorizontal,{})})}),n.jsx(Kt,{align:"end",children:c})]})]}),e&&d&&n.jsx("div",{className:"tw-w-fit tw-min-w-0 tw-max-w-full tw-overflow-hidden",children:d})]}),w&&n.jsx("div",{className:`tw-absolute tw-right-0 tw-top-0 tw-h-full tw-w-2 tw-rounded-r-xl ${w}`})]},t)}const Ts=i.forwardRef(({className:t,...e},r)=>n.jsx(k.LoaderCircle,{size:35,className:m("tw-animate-spin",t),...e,ref:r}));Ts.displayName="Spinner";function Oc({id:t,isDisabled:e=!1,hasError:r=!1,isFullWidth:o=!1,helperText:s,label:a,placeholder:l,isRequired:c=!1,className:d,defaultValue:w,value:p,onChange:u,onFocus:h,onBlur:f}){return n.jsxs("div",{className:m("tw-inline-grid tw-items-center tw-gap-1.5",{"tw-w-full":o}),children:[n.jsx(et,{htmlFor:t,className:m({"tw-text-red-600":r,"tw-hidden":!a}),children:`${a}${c?"*":""}`}),n.jsx(fe,{id:t,disabled:e,placeholder:l,required:c,className:m(d,{"tw-border-red-600":r}),defaultValue:w,value:p,onChange:u,onFocus:h,onBlur:f}),n.jsx("p",{className:m({"tw-hidden":!s}),children:s})]})}const Ac=Zt.cva("tw-relative tw-w-full tw-rounded-lg tw-border tw-p-4 [&>svg~*]:tw-pl-7 [&>svg+div]:tw-translate-y-[-3px] [&>svg]:tw-absolute [&>svg]:tw-left-4 [&>svg]:tw-top-4 [&>svg]:tw-text-foreground [&>img~*]:tw-pl-7 [&>img+div]:tw-translate-y-[-3px] [&>img]:tw-absolute [&>img]:tw-left-4 [&>img]:tw-top-4 [&>img]:tw-text-foreground",{variants:{variant:{default:"tw-bg-background tw-text-foreground",destructive:"tw-border-destructive/50 tw-text-destructive dark:tw-border-destructive [&>svg]:tw-text-destructive [&>img]:tw-text-destructive"}},defaultVariants:{variant:"default"}}),Ms=i.forwardRef(({className:t,variant:e,...r},o)=>n.jsx("div",{ref:o,role:"alert",className:m("pr-twp",Ac({variant:e}),t),...r}));Ms.displayName="Alert";const Ds=i.forwardRef(({className:t,...e},r)=>n.jsxs("h5",{ref:r,className:m("tw-mb-1 tw-font-medium tw-leading-none tw-tracking-tight",t),...e,children:[e.children," "]}));Ds.displayName="AlertTitle";const Is=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,className:m("tw-text-sm [&_p]:tw-leading-relaxed",t),...e}));Is.displayName="AlertDescription";const Pc=J.Root,Lc=J.Trigger,$c=J.Group,Vc=J.Portal,Fc=J.Sub,zc=J.RadioGroup,Os=i.forwardRef(({className:t,inset:e,children:r,...o},s)=>n.jsxs(J.SubTrigger,{ref:s,className:m("pr-twp tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[state=open]:tw-bg-accent data-[state=open]:tw-text-accent-foreground",e&&"tw-pl-8",t),...o,children:[r,n.jsx(k.ChevronRight,{className:"tw-ml-auto tw-h-4 tw-w-4"})]}));Os.displayName=J.SubTrigger.displayName;const As=i.forwardRef(({className:t,...e},r)=>n.jsx(J.SubContent,{ref:r,className:m("pr-twp tw-z-50 tw-min-w-[8rem] tw-origin-[--radix-context-menu-content-transform-origin] tw-overflow-hidden tw-rounded-md tw-border tw-bg-popover tw-p-1 tw-text-popover-foreground tw-shadow-md data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2",t),...e}));As.displayName=J.SubContent.displayName;const Ps=i.forwardRef(({className:t,...e},r)=>n.jsx(J.Portal,{children:n.jsx(J.Content,{ref:r,className:m("pr-twp tw-z-50 tw-max-h-[--radix-context-menu-content-available-height] tw-min-w-[8rem] tw-origin-[--radix-context-menu-content-transform-origin] tw-overflow-y-auto tw-overflow-x-hidden tw-rounded-md tw-border tw-bg-popover tw-p-1 tw-text-popover-foreground tw-shadow-md tw-animate-in tw-fade-in-80 data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2",t),...e})}));Ps.displayName=J.Content.displayName;const Ls=i.forwardRef(({className:t,inset:e,...r},o)=>n.jsx(J.Item,{ref:o,className:m("pr-twp tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",e&&"tw-pl-8",t),...r}));Ls.displayName=J.Item.displayName;const $s=i.forwardRef(({className:t,children:e,checked:r,...o},s)=>n.jsxs(J.CheckboxItem,{ref:s,className:m("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pl-8 tw-pr-2 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",t),checked:r,...o,children:[n.jsx("span",{className:"tw-absolute tw-left-2 tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center",children:n.jsx(J.ItemIndicator,{children:n.jsx(k.Check,{className:"tw-h-4 tw-w-4"})})}),e]}));$s.displayName=J.CheckboxItem.displayName;const Vs=i.forwardRef(({className:t,children:e,...r},o)=>n.jsxs(J.RadioItem,{ref:o,className:m("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pl-8 tw-pr-2 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",t),...r,children:[n.jsx("span",{className:"tw-absolute tw-left-2 tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center",children:n.jsx(J.ItemIndicator,{children:n.jsx(k.Circle,{className:"tw-h-2 tw-w-2 tw-fill-current"})})}),e]}));Vs.displayName=J.RadioItem.displayName;const Fs=i.forwardRef(({className:t,inset:e,...r},o)=>n.jsx(J.Label,{ref:o,className:m("tw-px-2 tw-py-1.5 tw-text-sm tw-font-semibold tw-text-foreground",e&&"tw-pl-8",t),...r}));Fs.displayName=J.Label.displayName;const zs=i.forwardRef(({className:t,...e},r)=>n.jsx(J.Separator,{ref:r,className:m("tw--mx-1 tw-my-1 tw-h-px tw-bg-border",t),...e}));zs.displayName=J.Separator.displayName;function Gs({className:t,...e}){return n.jsx("span",{className:m("tw-ml-auto tw-text-xs tw-tracking-widest tw-text-muted-foreground",t),...e})}Gs.displayName="ContextMenuShortcut";const Bs=i.createContext({direction:"bottom"});function Ks({shouldScaleBackground:t=!0,direction:e="bottom",...r}){const o=i.useMemo(()=>({direction:e}),[e]);return n.jsx(Bs.Provider,{value:o,children:n.jsx(Ct.Drawer.Root,{shouldScaleBackground:t,direction:e,...r})})}Ks.displayName="Drawer";const Gc=Ct.Drawer.Trigger,qs=Ct.Drawer.Portal,Bc=Ct.Drawer.Close,fr=i.forwardRef(({className:t,...e},r)=>n.jsx(Ct.Drawer.Overlay,{ref:r,className:m("tw-fixed tw-inset-0 tw-z-50 tw-bg-black/80",t),...e}));fr.displayName=Ct.Drawer.Overlay.displayName;const Us=i.forwardRef(({className:t,children:e,hideDrawerHandle:r=!1,...o},s)=>{const{direction:a="bottom"}=i.useContext(Bs),l={bottom:"tw-inset-x-0 tw-bottom-0 tw-mt-24 tw-rounded-t-[10px]",top:"tw-inset-x-0 tw-top-0 tw-mb-24 tw-rounded-b-[10px]",left:"tw-inset-y-0 tw-left-0 tw-mr-24 tw-rounded-r-[10px] tw-w-auto tw-max-w-sm",right:"tw-inset-y-0 tw-right-0 tw-ml-24 tw-rounded-l-[10px] tw-w-auto tw-max-w-sm"},c={bottom:"tw-mx-auto tw-mt-4 tw-h-2 tw-w-[100px] tw-rounded-full tw-bg-muted",top:"tw-mx-auto tw-mb-4 tw-h-2 tw-w-[100px] tw-rounded-full tw-bg-muted",left:"tw-my-auto tw-mr-4 tw-w-2 tw-h-[100px] tw-rounded-full tw-bg-muted",right:"tw-my-auto tw-ml-4 tw-w-2 tw-h-[100px] tw-rounded-full tw-bg-muted"};return n.jsxs(qs,{children:[n.jsx(fr,{}),n.jsxs(Ct.Drawer.Content,{ref:s,className:m("pr-twp tw-fixed tw-z-50 tw-flex tw-h-auto tw-border tw-bg-background",a==="bottom"||a==="top"?"tw-flex-col":"tw-flex-row",l[a],t),...o,children:[!r&&(a==="bottom"||a==="right")&&n.jsx("div",{className:c[a]}),n.jsx("div",{className:"tw-flex tw-flex-col",children:e}),!r&&(a==="top"||a==="left")&&n.jsx("div",{className:c[a]})]})]})});Us.displayName="DrawerContent";function Hs({className:t,...e}){return n.jsx("div",{className:m("tw-grid tw-gap-1.5 tw-p-4 tw-text-center sm:tw-text-left",t),...e})}Hs.displayName="DrawerHeader";function Ys({className:t,...e}){return n.jsx("div",{className:m("tw-mt-auto tw-flex tw-flex-col tw-gap-2 tw-p-4",t),...e})}Ys.displayName="DrawerFooter";const Xs=i.forwardRef(({className:t,...e},r)=>n.jsx(Ct.Drawer.Title,{ref:r,className:m("tw-text-lg tw-font-semibold tw-leading-none tw-tracking-tight",t),...e}));Xs.displayName=Ct.Drawer.Title.displayName;const Ws=i.forwardRef(({className:t,...e},r)=>n.jsx(Ct.Drawer.Description,{ref:r,className:m("tw-text-sm tw-text-muted-foreground",t),...e}));Ws.displayName=Ct.Drawer.Description.displayName;const Js=i.forwardRef(({className:t,value:e,...r},o)=>n.jsx(Tn.Root,{ref:o,className:m("pr-twp tw-relative tw-h-4 tw-w-full tw-overflow-hidden tw-rounded-full tw-bg-secondary",t),...r,children:n.jsx(Tn.Indicator,{className:"tw-h-full tw-w-full tw-flex-1 tw-bg-primary tw-transition-all",style:{transform:`translateX(-${100-(e||0)}%)`}})}));Js.displayName=Tn.Root.displayName;function Kc({className:t,...e}){return n.jsx(Pn.PanelGroup,{className:m("tw-flex tw-h-full tw-w-full data-[panel-group-direction=vertical]:tw-flex-col",t),...e})}const qc=Pn.Panel;function Uc({withHandle:t,className:e,...r}){return n.jsx(Pn.PanelResizeHandle,{className:m("tw-relative tw-flex tw-w-px tw-items-center tw-justify-center tw-bg-border after:tw-absolute after:tw-inset-y-0 after:tw-left-1/2 after:tw-w-1 after:tw--translate-x-1/2 focus-visible:tw-outline-none focus-visible:tw-ring-1 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-1 data-[panel-group-direction=vertical]:tw-h-px data-[panel-group-direction=vertical]:tw-w-full data-[panel-group-direction=vertical]:after:tw-left-0 data-[panel-group-direction=vertical]:after:tw-h-1 data-[panel-group-direction=vertical]:after:tw-w-full data-[panel-group-direction=vertical]:after:tw--translate-y-1/2 data-[panel-group-direction=vertical]:after:tw-translate-x-0 [&[data-panel-group-direction=vertical]>div]:tw-rotate-90",e),...r,children:t&&n.jsx("div",{className:"tw-z-10 tw-flex tw-h-4 tw-w-3 tw-items-center tw-justify-center tw-rounded-sm tw-border tw-bg-border",children:n.jsx(k.GripVertical,{className:"tw-h-2.5 tw-w-2.5"})})})}function Hc({...t}){return n.jsx(Gr.Toaster,{className:"tw-toaster tw-group",toastOptions:{classNames:{toast:"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",description:"group-[.toast]:text-muted-foreground",actionButton:"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",cancelButton:"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground"}},...t})}const Zs=i.forwardRef(({className:t,...e},r)=>{const o=st();return n.jsxs(De.Root,{ref:r,className:m("pr-twp tw-relative tw-flex tw-w-full tw-touch-none tw-select-none tw-items-center",t),...e,dir:o,children:[n.jsx(De.Track,{className:"tw-relative tw-h-2 tw-w-full tw-grow tw-overflow-hidden tw-rounded-full tw-bg-secondary",children:n.jsx(De.Range,{className:"tw-absolute tw-h-full tw-bg-primary"})}),n.jsx(De.Thumb,{className:"tw-block tw-h-5 tw-w-5 tw-rounded-full tw-border-2 tw-border-primary tw-bg-background tw-ring-offset-background tw-transition-colors focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-pointer-events-none disabled:tw-opacity-50"})]})});Zs.displayName=De.Root.displayName;const Qs=i.forwardRef(({className:t,...e},r)=>{const o=st();return n.jsx(Mn.Root,{className:m("tw-peer pr-twp tw-inline-flex tw-h-6 tw-w-11 tw-shrink-0 tw-cursor-pointer tw-items-center tw-rounded-full tw-border-2 tw-border-transparent tw-transition-colors focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 focus-visible:tw-ring-offset-background disabled:tw-cursor-not-allowed disabled:tw-opacity-50 data-[state=checked]:tw-bg-primary data-[state=unchecked]:tw-bg-input",t),...e,ref:r,children:n.jsx(Mn.Thumb,{className:m("pr-twp tw-pointer-events-none tw-block tw-h-5 tw-w-5 tw-rounded-full tw-bg-background tw-shadow-lg tw-ring-0 tw-transition-transform",{"data-[state=checked]:tw-translate-x-5 data-[state=unchecked]:tw-translate-x-0":o==="ltr"},{"data-[state=checked]:tw-translate-x-[-20px] data-[state=unchecked]:tw-translate-x-0":o==="rtl"})})})});Qs.displayName=Mn.Root.displayName;const Yc=ht.Root,ta=i.forwardRef(({className:t,...e},r)=>{const o=st();return n.jsx(ht.List,{ref:r,className:m("pr-twp tw-inline-flex tw-h-10 tw-items-center tw-justify-center tw-rounded-md tw-bg-muted tw-p-1 tw-text-muted-foreground",t),...e,dir:o})});ta.displayName=ht.List.displayName;const ea=i.forwardRef(({className:t,...e},r)=>n.jsx(ht.Trigger,{ref:r,className:m("pr-twp tw-inline-flex tw-items-center tw-justify-center tw-whitespace-nowrap tw-rounded-sm tw-px-3 tw-py-1.5 tw-text-sm tw-font-medium tw-ring-offset-background tw-transition-all hover:tw-text-foreground focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-pointer-events-none disabled:tw-opacity-50 data-[state=active]:tw-bg-background data-[state=active]:tw-text-foreground data-[state=active]:tw-shadow-sm",t),...e}));ea.displayName=ht.Trigger.displayName;const na=i.forwardRef(({className:t,...e},r)=>n.jsx(ht.Content,{ref:r,className:m("pr-twp tw-mt-2 tw-ring-offset-background focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2",t),...e}));na.displayName=ht.Content.displayName;const ra=i.forwardRef(({className:t,...e},r)=>n.jsx("textarea",{className:m("pr-twp tw-flex tw-min-h-[80px] tw-w-full tw-rounded-md tw-border tw-border-input tw-bg-background tw-px-3 tw-py-2 tw-text-base tw-ring-offset-background placeholder:tw-text-muted-foreground focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-cursor-not-allowed disabled:tw-opacity-50 md:tw-text-sm",t),ref:r,...e}));ra.displayName="Textarea";const Xc=(t,e)=>{i.useEffect(()=>{if(!t)return()=>{};const r=t(e);return()=>{r()}},[t,e])};function Wc(t){return{preserveValue:!0,...t}}const oa=(t,e,r={})=>{const o=i.useRef(e);o.current=e;const s=i.useRef(r);s.current=Wc(s.current);const[a,l]=i.useState(()=>o.current),[c,d]=i.useState(!0);return i.useEffect(()=>{let w=!0;return d(!!t),(async()=>{if(t){const p=await t();w&&(l(()=>p),d(!1))}})(),()=>{w=!1,s.current.preserveValue||l(()=>o.current)}},[t]),[a,c]},En=()=>!1,Jc=(t,e)=>{const[r]=oa(i.useCallback(async()=>{if(!t)return En;const o=await Promise.resolve(t(e));return async()=>o()},[e,t]),En,{preserveValue:!1});i.useEffect(()=>()=>{r!==En&&r()},[r])};function Zc(t){i.useEffect(()=>{let e;return t&&(e=document.createElement("style"),e.appendChild(document.createTextNode(t)),document.head.appendChild(e)),()=>{e&&document.head.removeChild(e)}},[t])}Object.defineProperty(exports,"sonner",{enumerable:!0,get:()=>Gr.toast});exports.Alert=Ms;exports.AlertDescription=Is;exports.AlertTitle=Ds;exports.Avatar=Yn;exports.AvatarFallback=Xn;exports.AvatarImage=Po;exports.BOOK_CHAPTER_CONTROL_STRING_KEYS=Ga;exports.BOOK_SELECTOR_STRING_KEYS=qa;exports.Badge=le;exports.BookChapterControl=za;exports.BookSelectionMode=ro;exports.BookSelector=Ua;exports.Button=V;exports.COMMENT_EDITOR_STRING_KEYS=nl;exports.COMMENT_LIST_STRING_KEYS=rl;exports.Card=Un;exports.CardContent=Hn;exports.CardDescription=Oo;exports.CardFooter=Ao;exports.CardHeader=Do;exports.CardTitle=Io;exports.ChapterRangeSelector=no;exports.Checkbox=un;exports.Checklist=Dc;exports.ComboBox=Ze;exports.Command=At;exports.CommandEmpty=Ee;exports.CommandGroup=Ot;exports.CommandInput=pe;exports.CommandItem=Et;exports.CommandList=Pt;exports.CommentEditor=el;exports.CommentList=il;exports.ContextMenu=Pc;exports.ContextMenuCheckboxItem=$s;exports.ContextMenuContent=Ps;exports.ContextMenuGroup=$c;exports.ContextMenuItem=Ls;exports.ContextMenuLabel=Fs;exports.ContextMenuPortal=Vc;exports.ContextMenuRadioGroup=zc;exports.ContextMenuRadioItem=Vs;exports.ContextMenuSeparator=zs;exports.ContextMenuShortcut=Gs;exports.ContextMenuSub=Fc;exports.ContextMenuSubContent=As;exports.ContextMenuSubTrigger=Os;exports.ContextMenuTrigger=Lc;exports.DataTable=Ho;exports.Drawer=Ks;exports.DrawerClose=Bc;exports.DrawerContent=Us;exports.DrawerDescription=Ws;exports.DrawerFooter=Ys;exports.DrawerHeader=Hs;exports.DrawerOverlay=fr;exports.DrawerPortal=qs;exports.DrawerTitle=Xs;exports.DrawerTrigger=Gc;exports.DropdownMenu=Qt;exports.DropdownMenuCheckboxItem=It;exports.DropdownMenuContent=Kt;exports.DropdownMenuGroup=Jn;exports.DropdownMenuItem=$e;exports.DropdownMenuItemType=Wo;exports.DropdownMenuLabel=Se;exports.DropdownMenuPortal=Lo;exports.DropdownMenuRadioGroup=Vo;exports.DropdownMenuRadioItem=tr;exports.DropdownMenuSeparator=me;exports.DropdownMenuShortcut=Fo;exports.DropdownMenuSub=$o;exports.DropdownMenuSubContent=Qn;exports.DropdownMenuSubTrigger=Zn;exports.DropdownMenuTrigger=ue;exports.ERROR_DUMP_STRING_KEYS=Yo;exports.ERROR_POPOVER_STRING_KEYS=fl;exports.ErrorDump=Xo;exports.ErrorPopover=hl;exports.FOOTNOTE_EDITOR_STRING_KEYS=Ml;exports.Filter=yl;exports.FilterDropdown=gl;exports.Footer=vl;exports.FootnoteEditor=Tl;exports.FootnoteItem=Qo;exports.FootnoteList=Ol;exports.INVENTORY_STRING_KEYS=ql;exports.Input=fe;exports.Inventory=Yl;exports.Label=et;exports.MARKER_MENU_STRING_KEYS=Xl;exports.MarkdownRenderer=ml;exports.MarkerMenu=Jl;exports.MoreInfo=xl;exports.MultiSelectComboBox=Jo;exports.NavigationContentSearch=vc;exports.Popover=zt;exports.PopoverAnchor=Pa;exports.PopoverContent=Lt;exports.PopoverTrigger=Gt;exports.Progress=Js;exports.RadioGroup=cn;exports.RadioGroupItem=Pe;exports.RecentSearches=eo;exports.ResizableHandle=Uc;exports.ResizablePanel=qc;exports.ResizablePanelGroup=Kc;exports.ResultsCard=Ic;exports.SCOPE_SELECTOR_STRING_KEYS=pc;exports.ScopeSelector=uc;exports.ScriptureResultsViewer=cc;exports.ScrollGroupSelector=mc;exports.SearchBar=mn;exports.Select=we;exports.SelectContent=Jt;exports.SelectGroup=zo;exports.SelectItem=gt;exports.SelectLabel=Bo;exports.SelectScrollDownButton=nr;exports.SelectScrollUpButton=er;exports.SelectSeparator=Ko;exports.SelectTrigger=Wt;exports.SelectValue=de;exports.Separator=ce;exports.SettingsList=fc;exports.SettingsListHeader=gc;exports.SettingsListItem=hc;exports.SettingsSidebar=fs;exports.SettingsSidebarContentSearch=ec;exports.Sidebar=sr;exports.SidebarContent=ir;exports.SidebarFooter=as;exports.SidebarGroup=nn;exports.SidebarGroupAction=ls;exports.SidebarGroupContent=on;exports.SidebarGroupLabel=rn;exports.SidebarHeader=ss;exports.SidebarInput=os;exports.SidebarInset=ar;exports.SidebarMenu=lr;exports.SidebarMenuAction=cs;exports.SidebarMenuBadge=ws;exports.SidebarMenuButton=wr;exports.SidebarMenuItem=cr;exports.SidebarMenuSkeleton=ds;exports.SidebarMenuSub=ps;exports.SidebarMenuSubButton=ms;exports.SidebarMenuSubItem=us;exports.SidebarProvider=or;exports.SidebarRail=rs;exports.SidebarSeparator=is;exports.SidebarTrigger=ns;exports.Skeleton=en;exports.Slider=Zs;exports.Sonner=Hc;exports.Spinner=Ts;exports.Switch=Qs;exports.TabDropdownMenu=an;exports.TabFloatingMenu=bc;exports.TabToolbar=xc;exports.Table=Fe;exports.TableBody=Ge;exports.TableCaption=Uo;exports.TableCell=Yt;exports.TableFooter=qo;exports.TableHead=Ne;exports.TableHeader=ze;exports.TableRow=Dt;exports.Tabs=Yc;exports.TabsContent=na;exports.TabsList=ta;exports.TabsTrigger=ea;exports.TextField=Oc;exports.Textarea=ra;exports.ToggleGroup=pn;exports.ToggleGroupItem=ve;exports.Toolbar=Sc;exports.Tooltip=jt;exports.TooltipContent=bt;exports.TooltipProvider=xt;exports.TooltipTrigger=Nt;exports.UiLanguageSelector=Tc;exports.VerticalTabs=pr;exports.VerticalTabsContent=mr;exports.VerticalTabsList=ur;exports.VerticalTabsTrigger=ys;exports.badgeVariants=Mo;exports.buttonVariants=Vn;exports.cn=m;exports.getBookIdFromUSFM=Kl;exports.getInventoryHeader=Be;exports.getLinesFromUSFM=Gl;exports.getNumberFromUSFM=Bl;exports.getStatusForItem=ts;exports.getToolbarOSReservedSpaceClassName=Ec;exports.inventoryCountColumn=Fl;exports.inventoryItemColumn=$l;exports.inventoryStatusColumn=zl;exports.selectTriggerVariants=Go;exports.useEvent=Xc;exports.useEventAsync=Jc;exports.useListbox=To;exports.usePromise=oa;exports.useRecentSearches=La;exports.useSidebar=Ke;exports.useStylesheet=Zc;function Qc(t,e="top"){if(!t||typeof document>"u")return;const r=document.head||document.querySelector("head"),o=r.querySelector(":first-child"),s=document.createElement("style");s.appendChild(document.createTextNode(t)),e==="top"&&o?r.insertBefore(s,o):r.appendChild(s)}Qc(`*, ::before, ::after { --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; @@ -1894,6 +1868,9 @@ video:where(.pr-twp,.pr-twp *) { .tw-max-w-\\[220px\\] { max-width: 220px; } +.tw-max-w-fit { + max-width: fit-content; +} .tw-max-w-full { max-width: 100%; } @@ -2123,6 +2100,9 @@ video:where(.pr-twp,.pr-twp *) { .tw-justify-between { justify-content: space-between; } +.tw-gap-0 { + gap: 0px; +} .tw-gap-1 { gap: 0.25rem; } @@ -2319,6 +2299,10 @@ video:where(.pr-twp,.pr-twp *) { border-bottom-right-radius: 10px; border-bottom-left-radius: 10px; } +.tw-rounded-e-none { + border-start-end-radius: 0px; + border-end-end-radius: 0px; +} .tw-rounded-l-\\[10px\\] { border-top-left-radius: 10px; border-bottom-left-radius: 10px; @@ -2331,6 +2315,10 @@ video:where(.pr-twp,.pr-twp *) { border-top-right-radius: 0.75rem; border-bottom-right-radius: 0.75rem; } +.tw-rounded-s-none { + border-start-start-radius: 0px; + border-end-start-radius: 0px; +} .tw-rounded-t-\\[10px\\] { border-top-left-radius: 10px; border-top-right-radius: 10px; @@ -2365,6 +2353,9 @@ video:where(.pr-twp,.pr-twp *) { .tw-border-l-4 { border-left-width: 4px; } +.tw-border-s-0 { + border-inline-start-width: 0px; +} .tw-border-s-2 { border-inline-start-width: 2px; } @@ -4284,6 +4275,32 @@ video:where(.pr-twp,.pr-twp *) { [data-side=secondary] .\\[\\[data-side\\=secondary\\]_\\&\\]\\:tw-cursor-w-resize { cursor: w-resize; } +.banded-row:hover { + cursor: pointer; +} + +.banded-row[data-state='selected']:hover { + cursor: default; +} +/* By default the editor is too tall for the footnote editor, even while empty, so this makes it + shorter. */ +.footnote-editor .editor-input { + min-height: 75px; +} + +.footnote-editor .typeahead-popover { + z-index: 300; +} + +.footnote-editor .immutable-note-caller { + display: none; +} + +/* Need to be able to override the styles for the editor that happens to have an underscore */ +/* stylelint-disable selector-class-pattern */ +.footnote-editor .text-spacing .usfm_p { + text-indent: 0; +} /** * This file was automatically generated on installation of the Shadcn/Lexical editor. The default * location of this file has been changed to integrate better with our project structure. diff --git a/lib/platform-bible-react/dist/index.cjs.map b/lib/platform-bible-react/dist/index.cjs.map index d80656e51d4..914deb6446b 100644 --- a/lib/platform-bible-react/dist/index.cjs.map +++ b/lib/platform-bible-react/dist/index.cjs.map @@ -1 +1 @@ -{"version":3,"file":"index.cjs","sources":["../src/utils/shadcn-ui.util.ts","../src/utils/dir-helper.util.ts","../src/components/shadcn-ui/dialog.tsx","../src/components/shadcn-ui/command.tsx","../src/components/shared/book.utils.ts","../src/components/shared/book-item.component.tsx","../src/components/shadcn-ui/button.tsx","../src/components/shadcn-ui/popover.tsx","../src/components/shared/book-item.utils.ts","../src/components/advanced/recent-searches.component.tsx","../src/components/advanced/book-chapter-control/book-chapter-control.utils.ts","../src/components/advanced/book-chapter-control/book-chapter-control.navigation.ts","../src/components/advanced/book-chapter-control/chapter-grid.component.tsx","../src/components/advanced/book-chapter-control/book-chapter-control.component.tsx","../src/components/advanced/book-chapter-control/book-chapter-control.types.ts","../src/components/shadcn-ui/label.tsx","../src/components/shadcn-ui/radio-group.tsx","../src/components/basics/combo-box.component.tsx","../src/components/basics/chapter-range-selector.component.tsx","../src/components/advanced/book-selector.component.tsx","../../../node_modules/@lexical/react/LexicalComposerContext.prod.mjs","../../../node_modules/@lexical/react/LexicalComposer.prod.mjs","../../../node_modules/@lexical/react/LexicalOnChangePlugin.prod.mjs","../src/components/advanced/editor/themes/editor-theme.ts","../src/components/shadcn-ui/tooltip.tsx","../src/components/advanced/editor/nodes.ts","../../../node_modules/react-error-boundary/dist/react-error-boundary.js","../../../node_modules/@lexical/react/LexicalErrorBoundary.prod.mjs","../../../node_modules/@lexical/react/useLexicalEditable.prod.mjs","../../../node_modules/@lexical/selection/LexicalSelection.prod.mjs","../../../node_modules/@lexical/utils/LexicalUtils.prod.mjs","../../../node_modules/@lexical/extension/LexicalExtension.prod.mjs","../../../node_modules/@lexical/react/LexicalReactProviderExtension.prod.mjs","../../../node_modules/@lexical/text/LexicalText.prod.mjs","../../../node_modules/@lexical/dragon/LexicalDragon.prod.mjs","../../../node_modules/@lexical/react/LexicalRichTextPlugin.prod.mjs","../../../node_modules/@lexical/react/LexicalAutoFocusPlugin.prod.mjs","../../../node_modules/@lexical/react/LexicalClearEditorPlugin.prod.mjs","../../../node_modules/@lexical/react/LexicalContentEditable.prod.mjs","../src/components/advanced/editor/editor-ui/content-editable.tsx","../src/components/advanced/editor/context/toolbar-context.tsx","../src/components/advanced/editor/editor-hooks/use-modal.tsx","../src/components/advanced/editor/plugins/toolbar/toolbar-plugin.tsx","../src/components/advanced/editor/editor-hooks/use-update-toolbar.ts","../src/components/shadcn-ui/toggle.tsx","../src/components/shadcn-ui/toggle-group.tsx","../src/components/advanced/editor/plugins/toolbar/font-format-toolbar-plugin.tsx","../src/components/advanced/editor/plugins.tsx","../src/components/advanced/editor/editor.tsx","../../../node_modules/@lexical/html/LexicalHtml.prod.mjs","../src/components/advanced/editor/editor-utils.ts","../src/components/advanced/comment-editor/comment-editor.component.tsx","../src/components/advanced/comment-editor/comment-editor.types.ts","../src/components/advanced/comment-list/comment-list.types.ts","../src/hooks/listbox-keyboard-navigation.hook.ts","../src/components/shadcn-ui/badge.tsx","../src/components/shadcn-ui/card.tsx","../src/components/shadcn-ui/separator.tsx","../src/components/shadcn-ui/avatar.tsx","../src/context/menu.context.ts","../src/components/shadcn-ui/dropdown-menu.tsx","../src/components/advanced/comment-list/comment-list.utils.ts","../src/components/advanced/comment-list/comment-item.component.tsx","../src/components/advanced/comment-list/comment-thread.component.tsx","../src/components/advanced/comment-list/comment-list.component.tsx","../src/components/advanced/data-table/data-table-column-toggle.component.tsx","../src/components/shadcn-ui/select.tsx","../src/components/advanced/data-table/data-table-pagination.component.tsx","../src/utils/focus.util.ts","../src/components/shadcn-ui/table.tsx","../src/components/shadcn-ui/skeleton.tsx","../src/components/advanced/data-table/data-table.component.tsx","../src/components/advanced/extension-marketplace/markdown-renderer.component.tsx","../src/components/basics/error-dump.component.tsx","../src/components/advanced/error-popover.component.tsx","../src/components/advanced/extension-marketplace/filter-dropdown.component.tsx","../src/components/advanced/extension-marketplace/more-info.component.tsx","../src/components/advanced/extension-marketplace/version-history.component.tsx","../src/components/advanced/extension-marketplace/footer.component.tsx","../src/components/advanced/multi-select-combo-box.component.tsx","../src/components/advanced/filter.component.tsx","../src/components/shadcn-ui/input.tsx","../src/components/advanced/footnote-editor/footnote-caller-dropdown.component.tsx","../src/components/advanced/footnote-editor/footnote-type-dropdown.component.tsx","../src/components/advanced/footnote-editor/footnote-editor.component.tsx","../src/components/advanced/footnote-editor/footnote-editor.types.ts","../src/components/advanced/footnotes/footnote-item.component.tsx","../src/components/advanced/footnotes/footnote-list.component.tsx","../src/components/advanced/inventory/occurrences-table.component.tsx","../src/components/shadcn-ui/checkbox.tsx","../src/components/advanced/inventory/inventory-columns.tsx","../src/components/advanced/inventory/inventory-utils.ts","../src/components/advanced/inventory/inventory.component.tsx","../src/components/advanced/marker-menu.component.tsx","../src/components/shadcn-ui/sidebar.tsx","../src/components/advanced/settings-components/settings-sidebar.component.tsx","../src/components/basics/search-bar.component.tsx","../src/components/advanced/settings-components/settings-sidebar-content-search.component.tsx","../src/components/advanced/scripture-results-viewer/scripture-results-viewer.component.tsx","../src/components/advanced/scope-selector/scope-selector.utils.ts","../src/components/advanced/scope-selector/section-button.component.tsx","../src/components/advanced/scope-selector/book-selector.component.tsx","../src/components/advanced/scope-selector/scope-selector.component.tsx","../src/components/advanced/scroll-group-selector.component.tsx","../src/components/advanced/settings-components/settings-list.component.tsx","../src/components/advanced/menus/menu.util.ts","../src/components/advanced/menus/menu-icon.component.tsx","../src/components/advanced/menus/tab-dropdown-menu.component.tsx","../src/components/advanced/tab-toolbar/tab-toolbar-container.component.tsx","../src/components/advanced/tab-toolbar/tab-toolbar.component.tsx","../src/components/advanced/tab-toolbar/tab-floating-menu.component.tsx","../src/components/basics/tabs-vertical.tsx","../src/components/advanced/tab-navigation-content-search.component.tsx","../src/components/shadcn-ui/menubar.tsx","../src/components/advanced/menus/platform-menubar.component.tsx","../src/components/advanced/toolbar.component.tsx","../src/components/advanced/ui-language-selector.component.tsx","../src/components/basics/smart-label.component.tsx","../src/components/basics/checklist.component.tsx","../src/components/basics/results-card.component.tsx","../src/components/basics/spinner.component.tsx","../src/components/basics/text-field.component.tsx","../src/components/shadcn-ui/alert.tsx","../src/components/shadcn-ui/context-menu.tsx","../src/components/shadcn-ui/drawer.tsx","../src/components/shadcn-ui/progress.tsx","../src/components/shadcn-ui/resizable.tsx","../src/components/shadcn-ui/sonner.tsx","../src/components/shadcn-ui/slider.tsx","../src/components/shadcn-ui/switch.tsx","../src/components/shadcn-ui/tabs.tsx","../src/components/shadcn-ui/textarea.tsx","../src/hooks/use-event.hook.ts","../src/hooks/use-promise.hook.ts","../src/hooks/use-event-async.hook.ts","../src/hooks/use-stylesheet.hook.ts"],"sourcesContent":["import { type ClassValue, clsx } from 'clsx';\nimport { extendTailwindMerge } from 'tailwind-merge';\n\nconst twMergeCustom = extendTailwindMerge({ prefix: 'tw-' });\n\n/**\n * Tailwind and CSS class application helper function. Uses\n * [`clsx`](https://www.npmjs.com/package/clsx) to make it easy to apply classes conditionally using\n * object syntax, and uses [`tailwind-merge`](https://www.npmjs.com/package/tailwind-merge) to make\n * it easy to merge/overwrite Tailwind classes in a programmer-logic-friendly way.\n *\n * Note: `tailwind-merge` is configured to use the prefix `tw-`, so you must use the same prefix\n * with any Tailwind classes you use with this function to successfully overwrite other Tailwind\n * classes. `platform-bible-react` is configured to use `tw-` as its Tailwind prefix, so any\n * Tailwind classes you pass into `platform-bible-react` components will be compared using the `tw-`\n * prefix.\n *\n * This function was popularized by\n * [shadcn/ui](https://ui.shadcn.com/docs/installation/manual#add-a-cn-helper). See [ByteGrad's\n * explanation video](https://www.youtube.com/watch?v=re2JFITR7TI) for more information.\n *\n * @example\n *\n * ```typescript\n * const borderShouldBeBlue = true;\n * const textShouldBeRed = true;\n * const heightShouldBe20 = false;\n * const classString = cn(\n * 'tw-bg-primary tw-h-10 tw-text-primary-foreground',\n * 'tw-bg-secondary',\n * {\n * 'tw-border-blue-500': borderShouldBeBlue,\n * 'tw-text-red-500': textShouldBeRed,\n * 'tw-h-20': heightShouldBe20,\n * },\n * 'some-class',\n * );\n * ```\n *\n * The resulting `classString` is `'tw-h-10 tw-bg-secondary tw-border-blue-500 tw-text-red-500\n * some-class'`\n *\n * - Notice that `'tw-bg-secondary'`, specified later, overwrote `'tw-bg-primary'`, specified earlier,\n * because they are Tailwind classes that affect the same css property\n * - Notice that `'tw-text-red-500'`, specified later, overwrote `'tw-text-primary-foreground'`,\n * specified earlier, because they are Tailwind classes that affect the same css property\n * - Notice that `'tw-h-20'`, specified later, did not overwrite `'tw-h-10'`, specified earlier,\n * because `'tw-h-20'` is part of a conditional class object and its value evaluated to `false`;\n * therefore it was not applied\n * - Notice that `'some-class'` was applied. This function is not limited only to Tailwind classes.\n *\n *\n * @param inputs Class strings or `clsx` conditional class objects to merge. Tailwind classes\n * specified later in the arguments overwrite similar Tailwind classes specified earlier in the\n * arguments\n * @returns Class string containing all applicable classes from the arguments based on the rules\n * described above\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMergeCustom(clsx(inputs));\n}\n","/** Text and layout direction */\nexport type Direction = 'rtl' | 'ltr';\n\nconst STORAGE_KEY: string = 'layoutDirection';\n\n/** Read layout direction from localStorage or return 'ltr' */\nexport function readDirection(): Direction {\n const retrieved = localStorage.getItem(STORAGE_KEY);\n if (retrieved === 'rtl') {\n return retrieved;\n }\n return 'ltr';\n}\n\n/** Write layout direction to localStorage */\nexport function persistDirection(dir: Direction): void {\n localStorage.setItem(STORAGE_KEY, dir);\n}\n","import React from 'react';\nimport * as DialogPrimitive from '@radix-ui/react-dialog';\nimport { X } from 'lucide-react';\n\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { readDirection } from '@/utils/dir-helper.util';\n\nconst Dialog = DialogPrimitive.Root;\n\nconst DialogTrigger = DialogPrimitive.Trigger;\n\nconst DialogPortal = DialogPrimitive.Portal;\n\nconst DialogClose = DialogPrimitive.Close;\n\nconst DialogOverlay = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName;\n\nconst DialogContent = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, ...props }, ref) => {\n const dir = readDirection();\n return (\n \n \n \n {children}\n \n \n Close\n \n \n \n );\n});\nDialogContent.displayName = DialogPrimitive.Content.displayName;\nfunction DialogHeader({ className, ...props }: React.HTMLAttributes) {\n return (\n \n );\n}\nDialogHeader.displayName = 'DialogHeader';\n\nfunction DialogFooter({ className, ...props }: React.HTMLAttributes) {\n return (\n \n );\n}\nDialogFooter.displayName = 'DialogFooter';\n\nconst DialogTitle = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nDialogTitle.displayName = DialogPrimitive.Title.displayName;\n\nconst DialogDescription = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nDialogDescription.displayName = DialogPrimitive.Description.displayName;\n\nexport {\n Dialog,\n DialogPortal,\n DialogOverlay,\n DialogClose,\n DialogTrigger,\n DialogContent,\n DialogHeader,\n DialogFooter,\n DialogTitle,\n DialogDescription,\n};\n","import React from 'react';\nimport { type DialogProps } from '@radix-ui/react-dialog';\nimport { Command as CommandPrimitive } from 'cmdk';\nimport { Search } from 'lucide-react';\n\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Dialog, DialogContent } from '@/components/shadcn-ui/dialog';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\n\n/**\n * Command menu for React. These components are built on cmdk and styled with Shadcn UI. See Shadcn\n * UI documentation: https://ui.shadcn.com/docs/components/command See cmdk documentation:\n * https://cmdk.paco.me/\n */\nconst Command = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nCommand.displayName = CommandPrimitive.displayName;\n\ninterface CommandDialogProps extends DialogProps {}\n\n/** @inheritdoc Command */\nfunction CommandDialog({ children, ...props }: CommandDialogProps) {\n return (\n \n \n \n {children}\n \n \n \n );\n}\n\n/** @inheritdoc Command */\nconst CommandInput = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => {\n const dir: Direction = readDirection();\n return (\n
\n \n \n
\n );\n});\n\nCommandInput.displayName = CommandPrimitive.Input.displayName;\n\n/** @inheritdoc Command */\nconst CommandList = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\n\nCommandList.displayName = CommandPrimitive.List.displayName;\n\n/** @inheritdoc Command */\nconst CommandEmpty = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>((props, ref) => (\n \n));\n\nCommandEmpty.displayName = CommandPrimitive.Empty.displayName;\n\n/** @inheritdoc Command */\nconst CommandGroup = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\n\nCommandGroup.displayName = CommandPrimitive.Group.displayName;\n\n/** @inheritdoc Command */\nconst CommandSeparator = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nCommandSeparator.displayName = CommandPrimitive.Separator.displayName;\n\n/** @inheritdoc Command */\nconst CommandItem = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\n\nCommandItem.displayName = CommandPrimitive.Item.displayName;\n\n/** @inheritdoc Command */\nfunction CommandShortcut({ className, ...props }: React.HTMLAttributes) {\n return (\n \n );\n}\nCommandShortcut.displayName = 'CommandShortcut';\n\nexport {\n Command,\n CommandDialog,\n CommandInput,\n CommandList,\n CommandEmpty,\n CommandGroup,\n CommandItem,\n CommandShortcut,\n CommandSeparator,\n};\n","import { Canon } from '@sillsdev/scripture';\nimport { includes, Section } from 'platform-bible-utils';\n\n/**\n * Gets the long name of a Bible section from its enum value\n *\n * @param section - The section enum value to get the name for\n * @param otLongName - Optional localized name for Old Testament section\n * @param ntLongName - Optional localized name for New Testament section\n * @param dcLongName - Optional localized name for Deuterocanonical section\n * @param extraLongName - Optional localized name for Extra Materials section\n * @returns {string} The human-readable localized name of the section. Defaults to English names\n * @throws {Error} When the section enum value is not recognized\n */\nexport const getSectionLongName = (\n section: Section,\n otLongName?: string,\n ntLongName?: string,\n dcLongName?: string,\n extraLongName?: string,\n): string => {\n switch (section) {\n case Section.OT:\n return otLongName ?? 'Old Testament';\n case Section.NT:\n return ntLongName ?? 'New Testament';\n case Section.DC:\n return dcLongName ?? 'Deuterocanon';\n case Section.Extra:\n return extraLongName ?? 'Extra Materials';\n default:\n throw new Error(`Unknown section: ${section}`);\n }\n};\n\n/**\n * Gets the short name of a Bible section from its enum value\n *\n * @param section - The section enum value to get the short name for\n * @param otShortName - Optional localized short name for Old Testament section\n * @param ntShortName - Optional localized short name for New Testament section\n * @param dcShortName - Optional localized short name for Deuterocanonical section\n * @param extraShortName - Optional localized short name for Extra Materials section\n * @returns {string} The short name of the section. Defaults to English\n * @throws {Error} When the section enum value is not recognized\n */\nexport const getSectionShortName = (\n section: Section,\n otShortName?: string,\n ntShortName?: string,\n dcShortName?: string,\n extraShortName?: string,\n): string => {\n switch (section) {\n case Section.OT:\n return otShortName ?? 'OT';\n case Section.NT:\n return ntShortName ?? 'NT';\n case Section.DC:\n return dcShortName ?? 'DC';\n case Section.Extra:\n return extraShortName ?? 'Extra';\n default:\n throw new Error(`Unknown section: ${section}`);\n }\n};\n\n/**\n * Gets the localized name for a book from the localized book names map, with fallback to English\n * name\n *\n * @param bookId - The book ID to get the localized name for\n * @param localizedBookNames - Optional map of localized book names\n * @returns The localized name, English name, or fallback value\n */\nexport function getLocalizedBookName(\n bookId: string,\n localizedBookNames?: Map,\n): string {\n const localizedName = localizedBookNames?.get(bookId)?.localizedName;\n return localizedName ?? Canon.bookIdToEnglishName(bookId);\n}\n\n/**\n * Gets the localized ID for a book from the localized book names map, with fallback to uppercase\n * book ID\n *\n * @param bookId - The book ID to get the localized ID for\n * @param localizedBookNames - Optional map of localized book names\n * @returns The localized ID, uppercase book ID, or fallback value\n */\nexport function getLocalizedBookId(\n bookId: string,\n localizedBookNames?: Map,\n): string {\n const localizedId = localizedBookNames?.get(bookId)?.localizedId;\n return localizedId ?? bookId.toUpperCase();\n}\n\n/** Book IDs for all books that are not considered obsolete in the SIL Canon library */\nexport const ALL_BOOK_IDS = Canon.allBookIds.filter(\n (bookId) => !Canon.isObsolete(Canon.bookIdToNumber(bookId)),\n);\n\n/** English names for all books that are not considered obsolete in the SIL Canon library */\nexport const ALL_ENGLISH_BOOK_NAMES = Object.fromEntries(\n ALL_BOOK_IDS.map((bookId) => [bookId, Canon.bookIdToEnglishName(bookId)]),\n);\n\n/**\n * Checks if a book matches a search query by comparing against English and localized book names/IDs\n *\n * @example\n *\n * ```typescript\n * // Optional localized names/IDs map\n * const localized = new Map([\n * ['GEN', { localizedId: 'GEN', localizedName: 'Gรชnesis' }],\n * ['PSA', { localizedId: 'SAL', localizedName: 'Salmos' }],\n * ]);\n *\n * // Matches by English name (partial, case-insensitive)\n * doesBookMatchQuery('GEN', 'genes'); // true\n *\n * // Matches by 3-letter book ID (case-insensitive)\n * doesBookMatchQuery('PSA', 'PSA'); // true\n *\n * // Matches by localized name when provided\n * doesBookMatchQuery('GEN', 'gรชn', localized); // true (The localized book name \"Gรชnesis\" includes \"gรชn\")\n *\n * // Matches by localized ID when provided\n * doesBookMatchQuery('PSA', 'sal', localized); // true (The localized book ID is \"SAL\")\n *\n * // Leading/trailing whitespace is ignored\n * doesBookMatchQuery('PSA', ' psal '); // true\n *\n * // Empty or whitespace-only queries don't match\n * doesBookMatchQuery('PSA', ' '); // false\n *\n * // No match example\n * doesBookMatchQuery('PSA', 'john'); // false\n * ```\n *\n * @param bookId - The book ID to check\n * @param query - The string search query\n * @param localizedBookNames - Optional map of localized book IDs/short names and full names. The\n * key is the standard book ID (e.g., \"2CH\"), the value contains a localized version of the ID and\n * related book name (e.g. { localizedId: '2CR', localizedName: '2 Crรณnicas' })\n * @returns True if the query (partially) matches one of the book's names or IDs, in either English\n * or localized form\n */\nexport function doesBookMatchQuery(\n bookId: string,\n query: string,\n localizedBookNames?: Map,\n): boolean {\n const normalizedQuery = query.trim().toLowerCase();\n if (!normalizedQuery) return false;\n\n const englishName = Canon.bookIdToEnglishName(bookId);\n const localizedBook = localizedBookNames?.get(bookId);\n\n // Check English name and ID\n const matchesEnglishNameOrId =\n includes(englishName.toLowerCase(), normalizedQuery) ||\n includes(bookId.toLowerCase(), normalizedQuery);\n\n if (matchesEnglishNameOrId) return true;\n\n // Check localized name and ID if available\n\n const matchesLocalizedNameOrId = localizedBook\n ? includes(localizedBook.localizedName.toLowerCase(), normalizedQuery) ||\n includes(localizedBook.localizedId.toLowerCase(), normalizedQuery)\n : false;\n\n if (matchesLocalizedNameOrId) return true;\n\n return false;\n}\n","import { CommandItem } from '@/components/shadcn-ui/command';\nimport { getLocalizedBookId, getLocalizedBookName } from '@/components/shared/book.utils';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Canon } from '@sillsdev/scripture';\nimport { Check } from 'lucide-react';\nimport { Section } from 'platform-bible-utils';\nimport { forwardRef, MouseEvent, useMemo, useRef } from 'react';\n\ntype BookItemProps = {\n /** The book ID (e.g., 'GEN', 'EXO') */\n bookId: string;\n /** Whether this book is currently selected */\n isSelected?: boolean;\n /** Callback function to handle book selection/deselection */\n onSelect?: (bookId: string) => void;\n /** Optional custom mouse down handler */\n onMouseDown?: (e: MouseEvent) => void;\n /** The section this book belongs to */\n section: Section;\n /** Additional CSS classes for the wrapper CommandItem */\n className?: string;\n /** Whether to show the check icon (for multiselect mode) */\n showCheck?: boolean;\n /**\n * Optional map of localized book IDs/short names and full names. Key is the (English) book ID,\n * value contains localized versions of the ID and full book name\n */\n localizedBookNames?: Map;\n /** Value to use for Command component matching */\n commandValue?: string;\n};\n\n/**\n * A reusable component that represents a single book item in book selectors. The component shows\n * the book's localized name, its ID, and visually indicates its testament (OT/NT/DC/Extra) through\n * color coding.\n *\n * For simple selection, use the `onSelect` prop. For complex interactions (like shift-click range\n * selection), implement custom `onSelect` and `onMouseDown` handlers that manage the logic\n * externally.\n */\nexport const BookItem = forwardRef(\n (\n {\n bookId,\n isSelected,\n onSelect,\n onMouseDown,\n section,\n className,\n showCheck = false,\n localizedBookNames,\n commandValue,\n },\n ref,\n ) => {\n const isMouseClick = useRef(false);\n\n const handleSelect = () => {\n if (!isMouseClick.current) {\n onSelect?.(bookId);\n }\n // Reset the mouse flag after a short delay\n setTimeout(() => {\n isMouseClick.current = false;\n }, 100);\n };\n\n const handleMouseDown = (e: MouseEvent) => {\n isMouseClick.current = true;\n\n if (onMouseDown) {\n onMouseDown(e);\n } else {\n // If no custom mouse handler, fall back to calling onSelect\n onSelect?.(bookId);\n }\n };\n\n const bookDisplayName = useMemo(\n () => getLocalizedBookName(bookId, localizedBookNames),\n [bookId, localizedBookNames],\n );\n\n const bookDisplayId = useMemo(\n () => getLocalizedBookId(bookId, localizedBookNames),\n [bookId, localizedBookNames],\n );\n\n return (\n \n \n {showCheck && (\n \n )}\n {bookDisplayName}\n \n {bookDisplayId}\n \n \n \n );\n },\n);\n","import React from 'react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '@/utils/shadcn-ui.util';\n\n/**\n * Style variants for the Button component.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/button}\n */\nexport const buttonVariants = cva(\n 'pr-twp tw-inline-flex tw-items-center tw-justify-center tw-gap-2 tw-whitespace-nowrap tw-rounded-md tw-text-sm tw-font-medium tw-ring-offset-background tw-transition-colors focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-pointer-events-none disabled:tw-opacity-50 [&_svg]:tw-pointer-events-none [&_svg]:tw-size-4 [&_svg]:tw-shrink-0',\n {\n variants: {\n variant: {\n default: 'tw-bg-primary tw-text-primary-foreground hover:tw-bg-primary/90',\n destructive: 'tw-bg-destructive tw-text-destructive-foreground hover:tw-bg-destructive/90',\n outline:\n 'tw-border tw-border-input tw-bg-background hover:tw-bg-accent hover:tw-text-accent-foreground',\n secondary: 'tw-bg-secondary tw-text-secondary-foreground hover:tw-bg-secondary/80',\n ghost: 'hover:tw-bg-accent hover:tw-text-accent-foreground',\n link: 'tw-text-primary tw-underline-offset-4 hover:tw-underline',\n },\n size: {\n default: 'tw-h-10 tw-px-4 tw-py-2',\n sm: 'tw-h-9 tw-rounded-md tw-px-3',\n lg: 'tw-h-11 tw-rounded-md tw-px-8',\n icon: 'tw-h-10 tw-w-10',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n);\n\n/**\n * Props for Button component\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/button}\n */\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes,\n VariantProps {\n asChild?: boolean;\n}\n\n/**\n * The Button component displays a button or a component that looks like a button. The component is\n * built and styled by Shadcn UI.\n *\n * @param ButtonProps\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/button}\n */\nexport const Button = React.forwardRef(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : 'button';\n return (\n \n );\n },\n);\nButton.displayName = 'Button';\n","import React from 'react';\nimport * as PopoverPrimitive from '@radix-ui/react-popover';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\n\nimport { cn } from '@/utils/shadcn-ui.util';\n\n/**\n * The Popover component displays rich content in a portal, triggered by a button. This popover is\n * built on Radix UI's Popover component and styled by Shadcn UI.\n *\n * See Shadcn UI Documentation https://ui.shadcn.com/docs/components/popover See Radix UI\n * Documentation https://www.radix-ui.com/docs/primitives/components/popover\n */\nconst Popover = PopoverPrimitive.Root;\n\n/** @inheritdoc Popover */\nconst PopoverTrigger = PopoverPrimitive.Trigger;\n\n/** @inheritdoc Popover */\nconst PopoverAnchor = PopoverPrimitive.Anchor;\n\n/** @inheritdoc Popover */\nconst PopoverContent = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => {\n const dir: Direction = readDirection();\n return (\n \n \n \n );\n});\nPopoverContent.displayName = PopoverPrimitive.Content.displayName;\n\nexport { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };\n","import {\n ALL_ENGLISH_BOOK_NAMES,\n getLocalizedBookId,\n getLocalizedBookName,\n} from '@/components/shared/book.utils';\n\nexport function generateCommandValue(\n bookId: string,\n localizedBookNames?: Map,\n chapter?: number,\n): string {\n return `${bookId} ${ALL_ENGLISH_BOOK_NAMES[bookId]}${localizedBookNames ? ` ${getLocalizedBookId(bookId, localizedBookNames)} ${getLocalizedBookName(bookId, localizedBookNames)}` : ''}${chapter ? ` ${chapter}` : ''}`;\n}\n","import { Clock } from 'lucide-react';\nimport { Button } from '@/components/shadcn-ui/button';\nimport { Command, CommandGroup, CommandItem, CommandList } from '@/components/shadcn-ui/command';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/shadcn-ui/popover';\nimport { useState } from 'react';\nimport { cn } from '@/utils/shadcn-ui.util';\n\n/** Interface defining the properties for the RecentSearches component */\nexport interface RecentSearchesProps {\n /** Array of recent search items */\n recentSearches: T[];\n /** Callback when a recent search item is selected */\n onSearchItemSelect: (item: T) => void;\n /** Function to render each search item as a string for display */\n renderItem?: (item: T) => string;\n /** Function to create a unique key for each item */\n getItemKey?: (item: T) => string;\n /** Aria label for the recent searches button */\n ariaLabel?: string;\n /** Heading text for the recent searches group */\n groupHeading?: string;\n /** Optional ID for the popover content for accessibility */\n id?: string;\n /** Class name for styling the `CommandItem` for each recent search result */\n classNameForItems?: string;\n}\n\n/**\n * Generic component that displays a button to show recent searches in a popover. Only renders if\n * there are recent searches available. Works with any data type T.\n */\nexport default function RecentSearches({\n recentSearches,\n onSearchItemSelect,\n renderItem = (item) => String(item),\n getItemKey = (item) => String(item),\n ariaLabel = 'Show recent searches',\n groupHeading = 'Recent',\n id,\n classNameForItems,\n}: RecentSearchesProps) {\n const [isOpen, setIsOpen] = useState(false);\n\n if (recentSearches.length === 0) {\n return undefined;\n }\n\n const handleSearchItemSelect = (item: T) => {\n onSearchItemSelect(item);\n setIsOpen(false);\n };\n\n return (\n \n \n \n \n \n \n \n \n \n \n {recentSearches.map((item) => (\n handleSearchItemSelect(item)}\n className={cn('tw-flex tw-items-center', classNameForItems)}\n >\n \n {renderItem(item)}\n \n ))}\n \n \n \n \n \n );\n}\n\n/** Generic hook for managing recent searches state and operations. */\nexport function useRecentSearches(\n recentSearches: T[],\n setRecentSearches: (items: T[]) => void,\n areItemsEqual: (a: T, b: T) => boolean = (a, b) => a === b,\n maxItems: number = 15,\n) {\n return (item: T) => {\n // Add the current item to recent searches, moving it to the top if it already exists\n const recentSearchesWithoutCurrent = recentSearches.filter(\n (existingItem) => !areItemsEqual(existingItem, item),\n );\n const updatedRecentSearches = [item, ...recentSearchesWithoutCurrent.slice(0, maxItems - 1)];\n setRecentSearches(updatedRecentSearches);\n };\n}\n","import { Canon } from '@sillsdev/scripture';\nimport { getChaptersForBook } from 'platform-bible-utils';\nimport { ALL_ENGLISH_BOOK_NAMES, doesBookMatchQuery } from '@/components/shared/book.utils';\nimport { BookWithOptionalChapterAndVerse } from './book-chapter-control.types';\n\n// Smart parsing regex patterns\nexport const SCRIPTURE_REGEX_PATTERNS = {\n // Matches start of string (`^`), one or more non-colon/space words, optionally followed by space and more words (`([^:\\s]+(?:\\s+[^:\\s]+)*)`), end of string (`$`), case-insensitive (`i`)\n BOOK_ONLY: /^([^:\\s]+(?:\\s+[^:\\s]+)*)$/i,\n // Same as above, but followed by a space and a chapter number (`\\s+(\\d+)`)\n BOOK_CHAPTER: /^([^:\\s]+(?:\\s+[^:\\s]+)*)\\s+(\\d+)$/i,\n // Same as above, but followed by a colon and optionally a verse number (`:(\\d*)`)\n BOOK_CHAPTER_VERSE: /^([^:\\s]+(?:\\s+[^:\\s]+)*)\\s+(\\d+):(\\d*)$/i,\n} as const;\n\nexport const SEARCH_QUERY_FORMATS = [\n SCRIPTURE_REGEX_PATTERNS.BOOK_ONLY,\n SCRIPTURE_REGEX_PATTERNS.BOOK_CHAPTER,\n SCRIPTURE_REGEX_PATTERNS.BOOK_CHAPTER_VERSE,\n];\n\nexport function getKeyCharacterType(key: string) {\n const isLetter = /^[a-zA-Z]$/.test(key);\n const isDigit = /^[0-9]$/.test(key);\n return { isLetter, isDigit };\n}\n\nexport function fetchEndChapter(bookId: string) {\n // getChaptersForBook returns -1 if not found in scrBookData\n // scrBookData only includes OT and NT, so all DC will return -1\n return getChaptersForBook(Canon.bookIdToNumber(bookId));\n}\n\nexport function calculateTopMatch(\n query: string,\n availableBooks: string[],\n localizedBookNames?: Map,\n): BookWithOptionalChapterAndVerse | undefined {\n if (!query.trim() || availableBooks.length === 0) return undefined;\n\n // First try smart parsing with regex patterns\n const topMatch = SEARCH_QUERY_FORMATS.reduce(\n (result: BookWithOptionalChapterAndVerse | undefined, format) => {\n if (result) return result;\n\n const matches = format.exec(query.trim());\n if (matches) {\n const [book, chapter = undefined, verse = undefined] = matches.slice(1);\n\n let validBookId: string | undefined;\n\n // Match for partial book name or id\n\n const allPotentialMatches = availableBooks.filter((bookId) => {\n return doesBookMatchQuery(bookId, book, localizedBookNames);\n });\n\n // Only create a topMatch if exactly one book could match\n if (allPotentialMatches.length === 1) {\n [validBookId] = allPotentialMatches;\n }\n\n // Match for exact book id (English or localized)\n // This is only performed when a chapter number is provided, to prevent edge cases where\n // a search for e.g. `jud` would generate a top match for 'Jude', even though 'Judges' would\n // also be a valid match\n if (!validBookId && chapter) {\n // Check exact English book ID\n if (Canon.isBookIdValid(book)) {\n const bookIdUpperCase = book.toUpperCase();\n if (availableBooks.includes(bookIdUpperCase)) {\n validBookId = bookIdUpperCase;\n }\n }\n\n // Check exact localized book ID\n if (!validBookId && localizedBookNames) {\n const matchingLocalizedBookId = Array.from(localizedBookNames.entries()).find(\n ([, localizedBook]) => localizedBook.localizedId.toLowerCase() === book.toLowerCase(),\n );\n if (matchingLocalizedBookId && availableBooks.includes(matchingLocalizedBookId[0])) {\n [validBookId] = matchingLocalizedBookId;\n }\n }\n }\n\n // Match for exact full book name (English or localized)\n // This is only performed when a chapter number is provided, to prevent edge cases where\n // a search for e.g. `john` only matches `John` but not `1 John`, `2 John` and `3 John`\n if (!validBookId && chapter) {\n // Check exact English book name\n const getBookIdFromEnglishName = (bookName: string): string | undefined => {\n return Object.keys(ALL_ENGLISH_BOOK_NAMES).find(\n (bookId) => ALL_ENGLISH_BOOK_NAMES[bookId].toLowerCase() === bookName.toLowerCase(),\n );\n };\n\n const matchingBookIdForFullName = getBookIdFromEnglishName(book);\n if (matchingBookIdForFullName && availableBooks.includes(matchingBookIdForFullName)) {\n validBookId = matchingBookIdForFullName;\n }\n\n // Check exact localized book name\n if (!validBookId && localizedBookNames) {\n const matchingLocalizedBookName = Array.from(localizedBookNames.entries()).find(\n ([, localizedBook]) =>\n localizedBook.localizedName.toLowerCase() === book.toLowerCase(),\n );\n if (\n matchingLocalizedBookName &&\n availableBooks.includes(matchingLocalizedBookName[0])\n ) {\n [validBookId] = matchingLocalizedBookName;\n }\n }\n }\n\n if (validBookId) {\n let chapterNum = chapter ? parseInt(chapter, 10) : undefined;\n if (chapterNum && chapterNum > fetchEndChapter(validBookId))\n chapterNum = Math.max(fetchEndChapter(validBookId), 1);\n const verseNum = verse ? parseInt(verse, 10) : undefined;\n\n return {\n book: validBookId,\n chapterNum,\n verseNum,\n };\n }\n }\n\n return undefined;\n },\n undefined,\n );\n\n if (topMatch) return topMatch;\n\n return undefined;\n}\n","import { Direction } from '@/utils/dir-helper.util';\nimport { SerializedVerseRef } from '@sillsdev/scripture';\nimport { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';\nimport { ComponentType, useCallback, useMemo } from 'react';\nimport { fetchEndChapter } from './book-chapter-control.utils';\n\nexport interface QuickNavButton {\n onClick: () => void;\n disabled?: boolean;\n title: string;\n icon: ComponentType<{ className?: string }>;\n}\n\nexport function useQuickNavButtons(\n scrRef: SerializedVerseRef,\n availableBooks: string[],\n direction: Direction,\n handleSubmit: (scrRef: SerializedVerseRef) => void,\n): QuickNavButton[] {\n const handlePreviousChapter = useCallback(() => {\n if (scrRef.chapterNum > 1) {\n handleSubmit({\n book: scrRef.book,\n chapterNum: scrRef.chapterNum - 1,\n verseNum: 1,\n });\n } else {\n // Go to previous book's last chapter\n const currentBookIndex = availableBooks.indexOf(scrRef.book);\n if (currentBookIndex > 0) {\n const previousBook = availableBooks[currentBookIndex - 1];\n const lastChapter = Math.max(fetchEndChapter(previousBook), 1);\n handleSubmit({\n book: previousBook,\n chapterNum: lastChapter,\n verseNum: 1,\n });\n }\n }\n }, [scrRef, availableBooks, handleSubmit]);\n\n const handleNextChapter = useCallback(() => {\n const maxChapter = fetchEndChapter(scrRef.book);\n if (scrRef.chapterNum < maxChapter) {\n handleSubmit({\n book: scrRef.book,\n chapterNum: scrRef.chapterNum + 1,\n verseNum: 1,\n });\n } else {\n // Go to next book's first chapter\n const currentBookIndex = availableBooks.indexOf(scrRef.book);\n if (currentBookIndex < availableBooks.length - 1) {\n const nextBook = availableBooks[currentBookIndex + 1];\n handleSubmit({\n book: nextBook,\n chapterNum: 1,\n verseNum: 1,\n });\n }\n }\n }, [scrRef, availableBooks, handleSubmit]);\n\n const handlePreviousVerse = useCallback(() => {\n handleSubmit({\n book: scrRef.book,\n chapterNum: scrRef.chapterNum,\n verseNum: scrRef.verseNum > 1 ? scrRef.verseNum - 1 : 0,\n });\n }, [scrRef, handleSubmit]);\n\n const handleNextVerse = useCallback(() => {\n handleSubmit({\n book: scrRef.book,\n chapterNum: scrRef.chapterNum,\n verseNum: scrRef.verseNum + 1,\n });\n }, [scrRef, handleSubmit]);\n\n return useMemo(() => {\n return [\n {\n onClick: handlePreviousChapter,\n disabled:\n availableBooks.length === 0 ||\n (scrRef.chapterNum === 1 && availableBooks.indexOf(scrRef.book) === 0),\n title: 'Previous chapter',\n icon: direction === 'ltr' ? ChevronsLeft : ChevronsRight,\n },\n {\n onClick: handlePreviousVerse,\n disabled: availableBooks.length === 0 || scrRef.verseNum === 0,\n title: 'Previous verse',\n icon: direction === 'ltr' ? ChevronLeft : ChevronRight,\n },\n {\n onClick: handleNextVerse,\n disabled: availableBooks.length === 0,\n title: 'Next verse',\n icon: direction === 'ltr' ? ChevronRight : ChevronLeft,\n },\n {\n onClick: handleNextChapter,\n disabled:\n availableBooks.length === 0 ||\n ((scrRef.chapterNum === fetchEndChapter(scrRef.book) ||\n fetchEndChapter(scrRef.book) <= 0) &&\n availableBooks.indexOf(scrRef.book) === availableBooks.length - 1),\n title: 'Next chapter',\n icon: direction === 'ltr' ? ChevronsRight : ChevronsLeft,\n },\n ];\n }, [\n scrRef,\n availableBooks,\n direction,\n handlePreviousChapter,\n handlePreviousVerse,\n handleNextVerse,\n handleNextChapter,\n ]);\n}\n","import { CommandGroup, CommandItem } from '@/components/shadcn-ui/command';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { ALL_ENGLISH_BOOK_NAMES } from '@/components/shared/book.utils';\nimport { fetchEndChapter } from './book-chapter-control.utils';\n\nexport interface ChapterGridProps {\n /** The book ID to render chapters for */\n bookId: string;\n /** Current scripture reference for highlighting */\n scrRef: { book: string; chapterNum: number };\n /** Callback when a chapter is selected */\n onChapterSelect: (chapter: number) => void;\n /** Function to set chapter refs for keyboard navigation */\n setChapterRef: (chapter: number) => (element: HTMLDivElement | null) => void;\n /** Optional function to determine if a chapter should be dimmed */\n isChapterDimmed?: (chapter: number) => boolean;\n /** Optional additional class name for styling */\n className?: string;\n}\n\n/**\n * Renders a grid of chapter numbers for a given book, with highlighting for the current chapter and\n * optional dimmed chapters based on state logic.\n */\nexport function ChapterGrid({\n bookId,\n scrRef,\n onChapterSelect,\n setChapterRef,\n isChapterDimmed,\n className,\n}: ChapterGridProps) {\n if (!bookId) return undefined;\n\n return (\n \n
\n {Array.from({ length: fetchEndChapter(bookId) }, (_, i) => i + 1).map((chapter) => (\n onChapterSelect(chapter)}\n ref={setChapterRef(chapter)}\n className={cn(\n 'tw-h-8 tw-w-8 tw-cursor-pointer tw-justify-center tw-rounded-md tw-text-center tw-text-sm',\n {\n 'tw-bg-primary tw-text-primary-foreground':\n bookId === scrRef.book && chapter === scrRef.chapterNum,\n },\n {\n 'tw-bg-muted/50 tw-text-muted-foreground/50': isChapterDimmed?.(chapter) ?? false,\n },\n )}\n >\n {chapter}\n \n ))}\n
\n
\n );\n}\n","import { BookItem } from '@/components/shared/book-item.component';\nimport { Button } from '@/components/shadcn-ui/button';\nimport {\n Command,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from '@/components/shadcn-ui/command';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/shadcn-ui/popover';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Canon, SerializedVerseRef } from '@sillsdev/scripture';\nimport { ArrowLeft, ArrowRight } from 'lucide-react';\nimport { formatScrRef, getSectionForBook, Section } from 'platform-bible-utils';\nimport {\n getSectionLongName,\n getLocalizedBookName,\n getLocalizedBookId,\n ALL_BOOK_IDS,\n ALL_ENGLISH_BOOK_NAMES,\n doesBookMatchQuery,\n} from '@/components/shared/book.utils';\nimport { KeyboardEvent, useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport { generateCommandValue } from '@/components/shared/book-item.utils';\nimport RecentSearches from '../recent-searches.component';\nimport { useQuickNavButtons } from './book-chapter-control.navigation';\nimport { BookChapterControlProps, ViewMode } from './book-chapter-control.types';\nimport {\n calculateTopMatch,\n fetchEndChapter,\n getKeyCharacterType,\n} from './book-chapter-control.utils';\nimport { ChapterGrid } from './chapter-grid.component';\n\n/**\n * `BookChapterControl` is a component that provides an interactive UI for selecting book chapters.\n * It allows users to input a search query to find specific books and chapters, navigate through\n * options with keyboard interactions, and submit selections. The component handles various\n * interactions such as opening and closing the dropdown menu, filtering book lists based on search\n * input, and managing highlighted selections. It also integrates with external handlers for\n * submitting selected references and retrieving active book IDs.\n */\nexport function BookChapterControl({\n scrRef,\n handleSubmit,\n className,\n getActiveBookIds,\n localizedBookNames,\n localizedStrings,\n recentSearches,\n onAddRecentSearch,\n id,\n}: BookChapterControlProps) {\n const direction: Direction = readDirection();\n\n // Indicates if the Command popover is open or not\n const [isCommandOpen, setIsCommandOpen] = useState(false);\n // The value of the Command, mainly needed for reliable keyboard navigation\n const [commandValue, setCommandValue] = useState('');\n // The value of the Input inside the Command\n const [inputValue, setInputValue] = useState('');\n // The current view mode (books or chapters)\n const [viewMode, setViewMode] = useState('books');\n // The book currently selected for chapter view, if any\n const [selectedBookForChaptersView, setSelectedBookForChaptersView] = useState<\n string | undefined\n >(undefined);\n const [isCommandListHidden, setIsCommandListHidden] = useState(false);\n\n // Reference to the Command component\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const commandRef = useRef(undefined!);\n // Reference to the Input component inside the Command\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const commandInputRef = useRef(undefined!);\n // Reference to the CommandList inside the Command\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const commandListRef = useRef(undefined!);\n // Reference to the selected book item in the CommandList\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const selectedBookItemRef = useRef(undefined!);\n // References to the chapters that are shown as CommandItems\n const chapterRefs = useRef>({});\n\n // Wrapper function to handle submit and add to recent searches\n const handleSubmitAndAddToRecent = useCallback(\n (newScrRef: SerializedVerseRef) => {\n handleSubmit(newScrRef);\n if (onAddRecentSearch) {\n onAddRecentSearch(newScrRef);\n }\n },\n [handleSubmit, onAddRecentSearch],\n );\n\n // #region Available books, filtering and top match logic\n\n const activeBookIds = useMemo(() => {\n return getActiveBookIds ? getActiveBookIds() : ALL_BOOK_IDS;\n }, [getActiveBookIds]);\n\n const availableBooksByType = useMemo(() => {\n const grouped: Record = {\n [Section.OT]: activeBookIds.filter((bookId) => Canon.isBookOT(bookId)),\n [Section.NT]: activeBookIds.filter((bookId) => Canon.isBookNT(bookId)),\n [Section.DC]: activeBookIds.filter((bookId) => Canon.isBookDC(bookId)),\n [Section.Extra]: activeBookIds.filter((bookId) => Canon.extraBooks().includes(bookId)),\n };\n return grouped;\n }, [activeBookIds]);\n\n const availableBooks = useMemo(() => {\n return Object.values(availableBooksByType).flat();\n }, [availableBooksByType]);\n\n // Filter books based on search input\n const filteredBooksByType = useMemo(() => {\n if (!inputValue.trim()) return availableBooksByType;\n\n const filteredBooks: Record = {\n [Section.OT]: [],\n [Section.NT]: [],\n [Section.DC]: [],\n [Section.Extra]: [],\n };\n\n const bookTypes: Section[] = [Section.OT, Section.NT, Section.DC, Section.Extra];\n bookTypes.forEach((type) => {\n filteredBooks[type] = availableBooksByType[type].filter((bookId) => {\n return doesBookMatchQuery(bookId, inputValue, localizedBookNames);\n });\n });\n\n return filteredBooks;\n }, [availableBooksByType, inputValue, localizedBookNames]);\n\n // Get the current top match\n const topMatch = useMemo(\n () => calculateTopMatch(inputValue, availableBooks, localizedBookNames),\n [inputValue, availableBooks, localizedBookNames],\n );\n\n // #endregion\n\n // #region Submitting references\n\n const handleTopMatchSelect = useCallback(() => {\n // If we have a top match (smart parsed or single book filter), use its specific chapter/verse\n if (topMatch) {\n handleSubmitAndAddToRecent({\n book: topMatch.book,\n chapterNum: topMatch.chapterNum ?? 1,\n verseNum: topMatch.verseNum ?? 1,\n });\n setIsCommandOpen(false);\n setInputValue('');\n setCommandValue(''); // Reset command value\n }\n }, [handleSubmitAndAddToRecent, topMatch]);\n\n const handleBookSelect = useCallback(\n (bookId: string) => {\n // Check if book has chapters - if not, submit immediately\n const endChapter = fetchEndChapter(bookId);\n if (endChapter <= 1) {\n handleSubmitAndAddToRecent({\n book: bookId,\n chapterNum: 1,\n verseNum: 1,\n });\n setIsCommandOpen(false);\n setInputValue('');\n return;\n }\n\n // Book has multiple chapters - transition to chapter view\n setSelectedBookForChaptersView(bookId);\n setViewMode('chapters');\n },\n [handleSubmitAndAddToRecent],\n );\n\n const handleChapterSelect = useCallback(\n (chapterNumber: number) => {\n // Determine which book we're selecting a chapter for\n const bookId = viewMode === 'chapters' ? selectedBookForChaptersView : topMatch?.book;\n if (!bookId) return;\n\n handleSubmitAndAddToRecent({\n book: bookId,\n chapterNum: chapterNumber,\n verseNum: 1,\n });\n setIsCommandOpen(false);\n setViewMode('books');\n setSelectedBookForChaptersView(undefined);\n setInputValue('');\n },\n [handleSubmitAndAddToRecent, viewMode, selectedBookForChaptersView, topMatch],\n );\n\n const handleRecentItemSelect = useCallback(\n (item: SerializedVerseRef) => {\n handleSubmitAndAddToRecent(item);\n setIsCommandOpen(false);\n setInputValue('');\n },\n [handleSubmitAndAddToRecent],\n );\n\n // #endregion\n\n // #region Navigation and view changes\n\n // Hook that provides navigation buttons for quick chapter/verse navigation\n const quickNavButtons = useQuickNavButtons(scrRef, availableBooks, direction, handleSubmit);\n\n const handleBackToBooks = useCallback(() => {\n setViewMode('books');\n setSelectedBookForChaptersView(undefined);\n\n // Focus the search input when returning to book view\n setTimeout(() => {\n if (commandInputRef.current) {\n commandInputRef.current.focus();\n }\n }, 0);\n }, []);\n\n // Reset view state when popover opens\n const handleOpenChange = useCallback(\n (shouldCommandBeOpen: boolean) => {\n // If we're closing from chapter view, don't close popover but go back to books view instead\n if (!shouldCommandBeOpen && viewMode === 'chapters') {\n handleBackToBooks();\n return;\n }\n\n setIsCommandOpen(shouldCommandBeOpen);\n\n if (shouldCommandBeOpen) {\n // Reset Command state when opening\n setViewMode('books');\n setSelectedBookForChaptersView(undefined);\n setInputValue('');\n }\n },\n [viewMode, handleBackToBooks],\n );\n\n // #endregion\n\n // #region Helper functions and variables\n\n const { otLong, ntLong, dcLong, extraLong } = {\n otLong: localizedStrings?.['%scripture_section_ot_long%'],\n ntLong: localizedStrings?.['%scripture_section_nt_long%'],\n dcLong: localizedStrings?.['%scripture_section_dc_long%'],\n extraLong: localizedStrings?.['%scripture_section_extra_long%'],\n };\n\n const getSectionLabel = useCallback(\n (section: Section): string => {\n return getSectionLongName(section, otLong, ntLong, dcLong, extraLong);\n },\n [otLong, ntLong, dcLong, extraLong],\n );\n\n const doesChapterMatch = useCallback(\n (chapter: number) => {\n if (!topMatch) return false;\n return !!topMatch.chapterNum && !chapter.toString().includes(topMatch.chapterNum.toString());\n },\n [topMatch],\n );\n\n const currentDisplayValue = useMemo(\n () =>\n formatScrRef(\n scrRef,\n localizedBookNames ? getLocalizedBookName(scrRef.book, localizedBookNames) : 'English',\n ),\n [scrRef, localizedBookNames],\n );\n\n const setChapterRef = useCallback((chapter: number) => {\n return (element: HTMLDivElement | null) => {\n chapterRefs.current[chapter] = element;\n };\n }, []);\n\n // #endregion\n\n // #region Keyboard handling\n\n // Handle keyboard navigation for CommandInput\n const handleInputKeyDown = useCallback((event: KeyboardEvent) => {\n // Override default Home and End key behavior to work normally for cursor movement.\n // Default behavior was to jump to the start/end of the list of items in the Command\n if (event.key === 'Home' || event.key === 'End') {\n event.stopPropagation(); // Prevent Command component from handling these\n }\n }, []);\n\n // Grid-aware keyboard navigation using Command's controlled value\n const handleCommandKeyDown = useCallback(\n (event: KeyboardEvent) => {\n if (event.ctrlKey) return;\n\n const { isLetter, isDigit } = getKeyCharacterType(event.key);\n\n // Handle keypresses in chapter viewmode\n if (viewMode === 'chapters') {\n // Handle backspace for going back to books\n if (event.key === 'Backspace') {\n event.preventDefault();\n event.stopPropagation();\n handleBackToBooks();\n return;\n }\n\n if (isLetter || isDigit) {\n event.preventDefault();\n event.stopPropagation();\n setViewMode('books');\n setSelectedBookForChaptersView(undefined);\n\n if (isDigit && selectedBookForChaptersView) {\n // Digit pressed: go back to book list and start search with current book name + digit\n const currentBookName = ALL_ENGLISH_BOOK_NAMES[selectedBookForChaptersView];\n setInputValue(`${currentBookName} ${event.key}`);\n } else {\n setInputValue(event.key);\n }\n\n setTimeout(() => {\n if (commandInputRef.current) {\n commandInputRef.current.focus();\n }\n }, 0);\n return;\n }\n }\n\n // Handle grid navigation for arrow keys in chapter views\n if (\n (viewMode === 'chapters' || (viewMode === 'books' && topMatch)) &&\n ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)\n ) {\n // Extract current chapter from commandValue\n const currentBookId =\n viewMode === 'chapters' ? selectedBookForChaptersView : topMatch?.book;\n if (!currentBookId) return;\n\n // Parse chapter from current command value\n const currentChapter = (() => {\n if (!commandValue) return 1;\n const match = commandValue.match(/(\\d+)$/);\n return match ? parseInt(match[1], 10) : 0;\n })();\n\n const maxChapter = fetchEndChapter(currentBookId);\n\n if (!maxChapter) return;\n\n let targetChapter = currentChapter;\n const GRID_COLS = 6;\n\n switch (event.key) {\n case 'ArrowLeft':\n if (currentChapter !== 0)\n targetChapter = currentChapter > 1 ? currentChapter - 1 : maxChapter;\n break;\n case 'ArrowRight':\n if (currentChapter !== 0)\n targetChapter = currentChapter < maxChapter ? currentChapter + 1 : 1;\n break;\n case 'ArrowUp':\n targetChapter =\n currentChapter === 0 ? maxChapter : Math.max(1, currentChapter - GRID_COLS);\n break;\n case 'ArrowDown':\n targetChapter =\n currentChapter === 0 ? 1 : Math.min(maxChapter, currentChapter + GRID_COLS);\n break;\n default:\n return;\n }\n\n if (targetChapter !== currentChapter) {\n event.preventDefault();\n event.stopPropagation();\n\n // Update the command value to the target chapter\n setCommandValue(generateCommandValue(currentBookId, localizedBookNames, targetChapter));\n\n // Scroll the target chapter into view using refs\n setTimeout(() => {\n const targetElement = chapterRefs.current[targetChapter];\n if (targetElement) {\n targetElement.scrollIntoView({ block: 'nearest', behavior: 'smooth' });\n }\n }, 0);\n }\n }\n },\n [\n viewMode,\n topMatch,\n handleBackToBooks,\n selectedBookForChaptersView,\n commandValue,\n localizedBookNames,\n ],\n );\n\n const handleQuickNavButtonKeyDown = useCallback((event: KeyboardEvent) => {\n if (event.shiftKey || event.key === 'Tab' || event.key === ' ') return;\n\n const { isLetter, isDigit } = getKeyCharacterType(event.key);\n\n if (isLetter || isDigit) {\n event.preventDefault();\n\n setInputValue((prevValue) => prevValue + event.key);\n commandInputRef.current.focus();\n\n setIsCommandListHidden(false);\n }\n }, []);\n\n // #endregion\n\n // #region Auto-scroll\n\n // Auto-scroll to currently selected book when dropdown opens in book view\n useLayoutEffect(() => {\n const scrollTimeout = setTimeout(() => {\n if (\n isCommandOpen &&\n viewMode === 'books' &&\n commandListRef.current &&\n selectedBookItemRef.current\n ) {\n const listElement = commandListRef.current;\n const itemElement = selectedBookItemRef.current;\n\n // Calculate scroll position to center the selected item\n const itemOffsetTop = itemElement.offsetTop;\n const listHeight = listElement.clientHeight;\n const itemHeight = itemElement.clientHeight;\n const scrollPosition = itemOffsetTop - listHeight / 2 + itemHeight / 2;\n\n listElement.scrollTo({\n top: Math.max(0, scrollPosition),\n behavior: 'smooth',\n });\n\n // Set the selected book as the active item for keyboard navigation\n setCommandValue(generateCommandValue(scrRef.book));\n }\n }, 0);\n\n return () => {\n clearTimeout(scrollTimeout);\n };\n }, [isCommandOpen, viewMode, inputValue, topMatch, scrRef.book]);\n\n // Auto-scroll to appropriate chapter\n useLayoutEffect(() => {\n if (viewMode === 'chapters' && selectedBookForChaptersView) {\n // Check if we're entering chapter view for the currently selected book\n const isCurrentlySelectedBook = selectedBookForChaptersView === scrRef.book;\n\n // Reset scroll position to top, except when viewing the currently selected book\n setTimeout(() => {\n if (commandListRef.current) {\n if (isCurrentlySelectedBook) {\n // Scroll to the currently selected chapter\n const targetElement = chapterRefs.current[scrRef.chapterNum];\n if (targetElement) {\n targetElement.scrollIntoView({ block: 'center', behavior: 'smooth' });\n }\n } else {\n // Reset to top for other books\n commandListRef.current.scrollTo({ top: 0 });\n }\n }\n\n // Ensure Command component has focus for keyboard navigation\n if (commandRef.current) {\n commandRef.current.focus();\n }\n }, 0);\n }\n }, [viewMode, selectedBookForChaptersView, topMatch, scrRef.book, scrRef.chapterNum]);\n\n // #endregion\n\n return (\n \n \n \n {currentDisplayValue}\n \n \n \n \n {/* Header: Input (with quick nav buttons) for book view, fixed header for chapter view */}\n {viewMode === 'books' ? (\n
\n
\n setIsCommandListHidden(false)}\n className={recentSearches && recentSearches.length > 0 ? '!tw-pr-10' : ''}\n />\n {recentSearches && recentSearches.length > 0 && (\n formatScrRef(verseRef, 'English')}\n getItemKey={(verseRef) =>\n `${verseRef.book}-${verseRef.chapterNum}-${verseRef.verseNum}`\n }\n ariaLabel={localizedStrings?.['%history_recentSearches_ariaLabel%']}\n groupHeading={localizedStrings?.['%history_recent%']}\n />\n )}\n
\n {/* Navigation buttons for previous/next chapter/book */}\n
\n {quickNavButtons.map(({ onClick, disabled, title, icon: Icon }) => (\n {\n setIsCommandListHidden(true);\n onClick();\n }}\n disabled={disabled}\n className=\"tw-h-10 tw-w-4 tw-p-0\"\n title={title}\n onKeyDown={handleQuickNavButtonKeyDown}\n >\n \n \n ))}\n
\n
\n ) : (\n
\n \n {direction === 'ltr' ? (\n \n ) : (\n \n )}\n \n {selectedBookForChaptersView && (\n \n {getLocalizedBookName(selectedBookForChaptersView, localizedBookNames)}\n \n )}\n
\n )}\n\n {/** Body */}\n {!isCommandListHidden && (\n \n {/** Book list mode (also used in case of top matches) */}\n {viewMode === 'books' && (\n <>\n {/* Book List - Show when we don't have a top match */}\n {!topMatch &&\n Object.entries(filteredBooksByType).map(([type, books]) => {\n if (books.length === 0) return undefined;\n\n return (\n // We are mapping over filteredBooksByType, which uses Section as key type\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n \n {books.map((bookId) => (\n \n handleBookSelect(selectedBookId)\n }\n section={getSectionForBook(bookId)}\n commandValue={`${bookId} ${ALL_ENGLISH_BOOK_NAMES[bookId]}`}\n ref={bookId === scrRef.book ? selectedBookItemRef : undefined}\n localizedBookNames={localizedBookNames}\n />\n ))}\n \n );\n })}\n\n {/* Top match scripture reference */}\n {topMatch && (\n \n \n {formatScrRef(\n {\n book: topMatch.book,\n chapterNum: topMatch.chapterNum ?? 1,\n verseNum: topMatch.verseNum ?? 1,\n },\n localizedBookNames\n ? getLocalizedBookId(topMatch.book, localizedBookNames)\n : undefined,\n )}\n \n \n )}\n\n {/* Chapter Selector - Show when we have a top match */}\n {topMatch && fetchEndChapter(topMatch.book) > 1 && (\n <>\n
\n {getLocalizedBookName(topMatch.book, localizedBookNames)}\n
\n \n \n )}\n \n )}\n\n {/* Basic chapter view mode */}\n {viewMode === 'chapters' && selectedBookForChaptersView && (\n \n )}\n
\n )}\n \n
\n
\n );\n}\n\nexport default BookChapterControl;\n","import { SerializedVerseRef } from '@sillsdev/scripture';\nimport { LanguageStrings } from 'platform-bible-utils';\n\n/**\n * Object containing all keys used for localization in the BookChapterControl component. If you're\n * using this component in an extension, you can pass it into the useLocalizedStrings hook to easily\n * obtain the localized strings and pass them into the localizedStrings prop of this component\n */\nexport const BOOK_CHAPTER_CONTROL_STRING_KEYS = Object.freeze([\n '%scripture_section_ot_long%',\n '%scripture_section_nt_long%',\n '%scripture_section_dc_long%',\n '%scripture_section_extra_long%',\n '%history_recent%',\n '%history_recentSearches_ariaLabel%',\n] as const);\n\n/** Type definition for the localized strings used in the BookChapterControl component */\nexport type BookChapterControlLocalizedStrings = {\n [localizedKey in (typeof BOOK_CHAPTER_CONTROL_STRING_KEYS)[number]]?: string;\n};\n\nexport type BookWithOptionalChapterAndVerse = Omit &\n Partial>;\n\nexport type ViewMode = 'books' | 'chapters';\n\nexport type BookChapterControlProps = {\n /** The current scripture reference */\n scrRef: SerializedVerseRef;\n /** Callback to handle the submission of a selected reference */\n handleSubmit: (scrRef: SerializedVerseRef) => void;\n /** Optional additional class name for styling */\n className?: string;\n /** Callback to retrieve book IDs that are available in the current context */\n getActiveBookIds?: () => string[];\n /**\n * Optional map of localized book IDs/short names and full names. The key is the standard book ID\n * (e.g., \"2CH\"), the value contains a localized version of the ID and related book name (e.g. {\n * localizedId: '2CR', localizedName: '2 Crรณnicas' })\n */\n localizedBookNames?: Map;\n /** Optional localized strings for the component */\n localizedStrings?: LanguageStrings;\n /** Array of recent scripture references for quick access */\n recentSearches?: SerializedVerseRef[];\n /** Callback to add a new recent scripture reference */\n onAddRecentSearch?: (scrRef: SerializedVerseRef) => void;\n /** Optional ID for the popover content for accessibility */\n id?: string;\n};\n","import React from 'react';\nimport * as LabelPrimitive from '@radix-ui/react-label';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/utils/shadcn-ui.util';\n\n/**\n * Style variants for the Label component.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/label}\n * @see Radix UI Documentation: {@link https://www.radix-ui.com/primitives/docs/components/label}\n */\nconst labelVariants = cva(\n 'tw-text-sm tw-font-medium tw-leading-none peer-disabled:tw-cursor-not-allowed peer-disabled:tw-opacity-70',\n);\n\n/**\n * The Label component renders an accessible label associated with controls. This components is\n * built on Radix UI primitives and styled with Shadcn UI.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/label}\n * @see Radix UI Documentation: {@link https://www.radix-ui.com/primitives/docs/components/label}\n */\nexport const Label = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & VariantProps\n>(({ className, ...props }, ref) => (\n \n));\nLabel.displayName = LabelPrimitive.Root.displayName;\n","import React from 'react';\nimport * as RadioGroupPrimitive from '@radix-ui/react-radio-group';\nimport { Circle } from 'lucide-react';\n\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\n\n/**\n * Radio Group components providing a set of checkable buttonsโ€”known as radio buttonsโ€”where no more\n * than one of the buttons can be checked at a time. These components are built on Radix UI\n * primitives and styled with Shadcn UI.\n *\n * See Shadcn UI Documentation: https://ui.shadcn.com/docs/components/radio-group See Radix UI\n * Documentation: https://www.radix-ui.com/primitives/docs/components/radio-group\n */\nconst RadioGroup = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => {\n const dir: Direction = readDirection();\n return (\n \n );\n});\nRadioGroup.displayName = RadioGroupPrimitive.Root.displayName;\n\n/** @inheritdoc RadioGroup */\nconst RadioGroupItem = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => {\n return (\n \n \n \n \n \n );\n});\nRadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;\n\nexport { RadioGroup, RadioGroupItem };\n","import { ReactNode, useState } from 'react';\nimport { Check, ChevronDown } from 'lucide-react';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Button, ButtonProps } from '@/components/shadcn-ui/button';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/shadcn-ui/popover';\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from '@/components/shadcn-ui/command';\nimport { PopoverProps } from '@radix-ui/react-popover';\n\nexport type ComboBoxLabelOption = { label: string; secondaryLabel?: string };\nexport type ComboBoxOption = string | number | ComboBoxLabelOption;\n\n/** Represents a group of options with an optional heading */\nexport type ComboBoxGroup = {\n /** The heading text for this group of options */\n groupHeading: string;\n /** The options within this group */\n options: readonly T[];\n};\n\nexport type ComboBoxProps = {\n /** Optional unique identifier */\n id?: string;\n /**\n * List of available options for the dropdown menu. Can be either:\n *\n * - A flat array of options (single group, no heading)\n * - An array of group objects. Each group has a heading and an array of options\n */\n options?: readonly T[] | readonly ComboBoxGroup[];\n /** @deprecated 3 December 2024. Renamed to `buttonClassName` */\n className?: string;\n /** Additional css classes to help with unique styling of the combo box button */\n buttonClassName?: string;\n /** Additional css classes to help with unique styling of the combo box popover */\n popoverContentClassName?: string;\n /**\n * The selected value that the combo box currently holds. Must be shallow equal to one of the\n * options entries.\n */\n value?: T;\n /** Triggers when content of textfield is changed */\n onChange?: (newValue: T) => void;\n /** Used to determine the string value for a given option. */\n getOptionLabel?: (option: T) => string;\n /**\n * Used to determine the string value to display on the button for the selected value. If not\n * provided, falls back to `getOptionLabel`.\n */\n getButtonLabel?: (option: T) => string;\n /** Icon to be displayed on the trigger */\n icon?: ReactNode;\n /** Text displayed on button if `value` is undefined */\n buttonPlaceholder?: string;\n /** Placeholder text for text field */\n textPlaceholder?: string;\n /** Text to display when no options match input */\n commandEmptyMessage?: string;\n /** Variant of button */\n buttonVariant?: ButtonProps['variant'];\n /** Control how the popover menu should be aligned. Defaults to start */\n alignDropDown?: 'start' | 'center' | 'end';\n /** Optional boolean to set if trigger should be disabled */\n isDisabled?: boolean;\n /** Optional aria-label for the trigger button for accessibility */\n ariaLabel?: string;\n} & PopoverProps;\n\nfunction getOptionLabelDefault(option: ComboBoxOption): string {\n if (typeof option === 'string') {\n return option;\n }\n if (typeof option === 'number') {\n return option.toString();\n }\n return option.label;\n}\n\n/**\n * Autocomplete input and command palette with a list of suggestions.\n *\n * Thanks to Shadcn for heavy inspiration and documentation\n * https://ui.shadcn.com/docs/components/combobox\n */\nexport function ComboBox({\n id,\n options = [],\n className,\n buttonClassName,\n popoverContentClassName,\n value,\n onChange = () => {},\n getOptionLabel = getOptionLabelDefault,\n getButtonLabel,\n icon = undefined,\n buttonPlaceholder = '',\n textPlaceholder = '',\n commandEmptyMessage = 'No option found',\n buttonVariant = 'outline',\n alignDropDown = 'start',\n isDisabled = false,\n ariaLabel,\n ...props\n}: ComboBoxProps) {\n const [open, setOpen] = useState(false);\n\n const buttonLabel = getButtonLabel ?? getOptionLabel;\n\n const isGroupedOptions = (\n groupOptions: readonly T[] | readonly ComboBoxGroup[],\n ): groupOptions is readonly ComboBoxGroup[] => {\n return Boolean(\n groupOptions.length > 0 &&\n typeof groupOptions[0] === 'object' &&\n 'options' in groupOptions[0],\n );\n };\n\n const renderCommandItem = (option: T, groupHeading?: string) => {\n const optionLabel = getOptionLabel(option);\n const secondaryLabel =\n typeof option === 'object' && 'secondaryLabel' in option ? option.secondaryLabel : undefined;\n\n const key = `${groupHeading ?? ''}${optionLabel}${secondaryLabel ?? ''}`;\n\n return (\n {\n onChange(option);\n setOpen(false);\n }}\n className=\"tw-flex tw-items-center\"\n >\n \n \n {optionLabel}\n {secondaryLabel && ยท {secondaryLabel}}\n \n \n );\n };\n\n return (\n \n \n \n
\n {icon &&
{icon}
}\n \n {value ? buttonLabel(value) : buttonPlaceholder}\n \n
\n\n \n \n
\n \n \n \n {commandEmptyMessage}\n \n {isGroupedOptions(options)\n ? options.map((group) => (\n \n {group.options.map((option) => renderCommandItem(option, group.groupHeading))}\n \n ))\n : options.map((option) => renderCommandItem(option))}\n \n \n \n
\n );\n}\n\nexport default ComboBox;\n","import { ComboBox } from '@/components/basics/combo-box.component';\nimport { Label } from '@/components/shadcn-ui/label';\nimport { useMemo } from 'react';\n\nexport type ChapterRangeSelectorProps = {\n /** The selected start chapter */\n startChapter: number;\n /** The selected end chapter */\n endChapter: number;\n /** Callback function to handle the selection of the start chapter */\n handleSelectStartChapter: (chapter: number) => void;\n /** Callback function to handle the selection of the end chapter */\n handleSelectEndChapter: (chapter: number) => void;\n /** Flag to disable the component */\n isDisabled?: boolean;\n /** The total number of chapters available */\n chapterCount: number;\n};\n\n/**\n * ChapterRangeSelector is a component that provides a UI for selecting a range of chapters. It\n * consists of two combo boxes for selecting the start and end chapters. The component ensures that\n * the selected start chapter is always less than or equal to the end chapter, and vice versa.\n *\n * @deprecated Jul 18 2025. This component is no longer supported or tested. Use of this component\n * is discouraged and it may be removed in the future.\n * @param {ChapterRangeSelectorProps} props - The props for the component.\n */\n\nexport function ChapterRangeSelector({\n startChapter,\n endChapter,\n handleSelectStartChapter,\n handleSelectEndChapter,\n isDisabled = false,\n chapterCount,\n}: ChapterRangeSelectorProps) {\n const chapterOptions = useMemo(\n () => Array.from({ length: chapterCount }, (_, index) => index + 1),\n [chapterCount],\n );\n\n const onChangeStartChapter = (value: number) => {\n handleSelectStartChapter(value);\n if (value > endChapter) {\n handleSelectEndChapter(value);\n }\n };\n\n const onChangeEndChapter = (value: number) => {\n handleSelectEndChapter(value);\n if (value < startChapter) {\n handleSelectStartChapter(value);\n }\n };\n\n return (\n <>\n \n option.toString()}\n value={startChapter}\n />\n\n \n option.toString()}\n value={endChapter}\n />\n \n );\n}\n\nexport default ChapterRangeSelector;\n","import { Button } from '@/components/shadcn-ui/button';\nimport { Label } from '@/components/shadcn-ui/label';\nimport { RadioGroup, RadioGroupItem } from '@/components/shadcn-ui/radio-group';\nimport { Canon } from '@sillsdev/scripture';\nimport { LocalizedStringValue } from 'platform-bible-utils';\nimport { useState } from 'react';\nimport {\n ChapterRangeSelector,\n ChapterRangeSelectorProps,\n} from '../basics/chapter-range-selector.component';\n\n/** Enumeration of possible book selection modes */\nexport enum BookSelectionMode {\n CURRENT_BOOK = 'current book',\n CHOOSE_BOOKS = 'choose books',\n}\n\n/**\n * Object containing all keys used for localization in this component. If you're using this\n * component in an extension, you can pass it into the useLocalizedStrings hook to easily obtain the\n * localized strings and pass them into the localizedStrings prop of this component\n */\nexport const BOOK_SELECTOR_STRING_KEYS = Object.freeze([\n '%webView_bookSelector_currentBook%',\n '%webView_bookSelector_choose%',\n '%webView_bookSelector_chooseBooks%',\n] as const);\n\nexport type BookSelectorLocalizedStrings = {\n [localizedBookSelectorKey in (typeof BOOK_SELECTOR_STRING_KEYS)[number]]?: LocalizedStringValue;\n};\n\n/**\n * Gets the localized value for the provided key\n *\n * @param strings Object containing localized string\n * @param key Key for a localized string\n * @returns The localized value for the provided key, if available. Returns the key if no localized\n * value is available\n */\nconst localizeString = (\n strings: BookSelectorLocalizedStrings,\n key: keyof BookSelectorLocalizedStrings,\n) => {\n return strings[key] ?? key;\n};\n\ntype BookSelectorProps = ChapterRangeSelectorProps & {\n handleBookSelectionModeChange: (newMode: BookSelectionMode) => void;\n currentBookName: string;\n onSelectBooks: () => void;\n selectedBookIds: string[];\n localizedStrings: BookSelectorLocalizedStrings;\n};\n\n/**\n * BookSelector is a component that provides an interactive UI for selecting books. It can be set to\n * either allow the user to select a single book or to choose multiple books. In the former case, it\n * will display the range of chapters in the selected book, and in the latter case it will display a\n * list of the selected books.\n *\n * @deprecated Jul 18 2025. This component is no longer supported or tested. Use of this component\n * is discouraged and it may be removed in the future.\n * @param {BookSelectorProps} props\n * @param {function} props.handleBookSelectionModeChange - Callback function to handle changes in\n * book selection mode.\n * @param {string} props.currentBookName - The name of the currently selected book.\n * @param {function} props.onSelectBooks - Callback function to handle book selection.\n * @param {string[]} props.selectedBookIds - An array of book IDs that have been selected.\n * @param {BookSelectorLocalizedStrings} props.localizedStrings - Object containing localized\n * strings for the component.\n */\nexport function BookSelector({\n handleBookSelectionModeChange,\n currentBookName,\n onSelectBooks,\n selectedBookIds,\n chapterCount,\n endChapter,\n handleSelectEndChapter,\n startChapter,\n handleSelectStartChapter,\n localizedStrings,\n}: BookSelectorProps) {\n const currentBookText = localizeString(localizedStrings, '%webView_bookSelector_currentBook%');\n const chooseText = localizeString(localizedStrings, '%webView_bookSelector_choose%');\n const chooseBooksText = localizeString(localizedStrings, '%webView_bookSelector_chooseBooks%');\n\n const [bookSelectionMode, setBookSelectionMode] = useState(\n BookSelectionMode.CURRENT_BOOK,\n );\n\n const onSelectionModeChange = (newMode: BookSelectionMode) => {\n setBookSelectionMode(newMode);\n handleBookSelectionModeChange(newMode);\n };\n\n return (\n onSelectionModeChange(value as BookSelectionMode)}\n >\n
\n
\n
\n \n \n
\n \n
\n \n
\n
\n
\n
\n \n \n
\n \n onSelectBooks()}\n >\n {chooseText}\n \n
\n
\n \n );\n}\n\nexport default BookSelector;\n","/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport{createContext as n,useContext as e}from\"react\";const r=n(null);function t(n,e){let r=null;return null!=n&&(r=n[1]),{getTheme:function(){return null!=e?e:null!=r?r.getTheme():null}}}function o(){const n=e(r);return null==n&&function(n,...e){const r=new URL(\"https://lexical.dev/docs/error\"),t=new URLSearchParams;t.append(\"code\",n);for(const n of e)t.append(\"v\",n);throw r.search=t.toString(),Error(`Minified Lexical error #${n}; visit ${r.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}(8),n}export{r as LexicalComposerContext,t as createLexicalComposerContext,o as useLexicalComposerContext};\n","/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport{createLexicalComposerContext as e,LexicalComposerContext as t}from\"@lexical/react/LexicalComposerContext\";import{createEditor as o,$getRoot as n,$createParagraphNode as i,$getSelection as r,HISTORY_MERGE_TAG as a}from\"lexical\";import{useLayoutEffect as c,useEffect as l,useMemo as d}from\"react\";import{jsx as s}from\"react/jsx-runtime\";const m=\"undefined\"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement,u=m?c:l,p={tag:a};function f({initialConfig:a,children:c}){const l=d(()=>{const{theme:t,namespace:c,nodes:l,onError:d,editorState:s,html:u}=a,f=e(null,t),E=o({editable:a.editable,html:u,namespace:c,nodes:l,onError:e=>d(e,E),theme:t});return function(e,t){if(null===t)return;if(void 0===t)e.update(()=>{const t=n();if(t.isEmpty()){const o=i();t.append(o);const n=m?document.activeElement:null;(null!==r()||null!==n&&n===e.getRootElement())&&o.select()}},p);else if(null!==t)switch(typeof t){case\"string\":{const o=e.parseEditorState(t);e.setEditorState(o,p);break}case\"object\":e.setEditorState(t,p);break;case\"function\":e.update(()=>{n().isEmpty()&&t(e)},p)}}(E,s),[E,f]},[]);return u(()=>{const e=a.editable,[t]=l;t.setEditable(void 0===e||e)},[]),s(t.Provider,{value:l,children:c})}export{f as LexicalComposer};\n","/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport{useLexicalComposerContext as e}from\"@lexical/react/LexicalComposerContext\";import{HISTORY_MERGE_TAG as t}from\"lexical\";import{useLayoutEffect as o,useEffect as i}from\"react\";const r=\"undefined\"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement?o:i;function n({ignoreHistoryMergeTagChange:o=!0,ignoreSelectionChange:i=!1,onChange:n}){const[a]=e();return r(()=>{if(n)return a.registerUpdateListener(({editorState:e,dirtyElements:r,dirtyLeaves:d,prevEditorState:s,tags:c})=>{i&&0===r.size&&0===d.size||o&&c.has(t)||s.isEmpty()||n(e,a,c)})},[a,o,i,n]),null}export{n as OnChangePlugin};\n","/**\n * This file was automatically generated on installation of the Shadcn/Lexical editor. The default\n * location of this file has been changed to integrate better with our project structure. Also,\n * modifications have been made to integrate with our codebase.\n *\n * Original file location: src/components/editor/themes/editor-theme.ts\n *\n * Shadcn/Lexical Editor Documentation: https://shadcn-editor.vercel.app/docs/\n */\n\nimport { EditorThemeClasses } from 'lexical';\n\nimport './editor-theme.css';\n\nexport const editorTheme: EditorThemeClasses = {\n ltr: 'tw-text-left',\n rtl: 'tw-text-right',\n heading: {\n h1: 'tw-scroll-m-20 tw-text-4xl tw-font-extrabold tw-tracking-tight lg:tw-text-5xl',\n h2: 'tw-scroll-m-20 tw-border-b tw-pb-2 tw-text-3xl tw-font-semibold tw-tracking-tight first:tw-mt-0',\n h3: 'tw-scroll-m-20 tw-text-2xl tw-font-semibold tw-tracking-tight',\n h4: 'tw-scroll-m-20 tw-text-xl tw-font-semibold tw-tracking-tight',\n h5: 'tw-scroll-m-20 tw-text-lg tw-font-semibold tw-tracking-tight',\n h6: 'tw-scroll-m-20 tw-text-base tw-font-semibold tw-tracking-tight',\n },\n paragraph: 'tw-outline-none',\n quote: 'tw-mt-6 tw-border-l-2 tw-pl-6 tw-italic',\n link: 'tw-text-blue-600 hover:tw-underline hover:tw-cursor-pointer',\n list: {\n checklist: 'tw-relative',\n listitem: 'tw-mx-8',\n listitemChecked:\n 'tw-relative tw-mx-2 tw-px-6 tw-list-none tw-outline-none tw-line-through before:tw-content-[\"\"] before:tw-w-4 before:tw-h-4 before:tw-top-0.5 before:tw-left-0 before:tw-cursor-pointer before:tw-block before:tw-bg-cover before:tw-absolute before:tw-border before:tw-border-primary before:tw-rounded before:tw-bg-primary before:tw-bg-no-repeat after:tw-content-[\"\"] after:tw-cursor-pointer after:tw-border-white after:tw-border-solid after:tw-absolute after:tw-block after:tw-top-[6px] after:tw-w-[3px] after:tw-left-[7px] after:tw-right-[7px] after:tw-h-[6px] after:tw-rotate-45 after:tw-border-r-2 after:tw-border-b-2 after:tw-border-l-0 after:tw-border-t-0',\n listitemUnchecked:\n 'tw-relative tw-mx-2 tw-px-6 tw-list-none tw-outline-none before:tw-content-[\"\"] before:tw-w-4 before:tw-h-4 before:tw-top-0.5 before:tw-left-0 before:tw-cursor-pointer before:tw-block before:tw-bg-cover before:tw-absolute before:tw-border before:tw-border-primary before:tw-rounded',\n nested: {\n listitem: 'tw-list-none before:tw-hidden after:tw-hidden',\n },\n ol: 'tw-m-0 tw-p-0 tw-list-decimal [&>li]:tw-mt-2',\n olDepth: [\n 'tw-list-outside !tw-list-decimal',\n 'tw-list-outside !tw-list-[upper-roman]',\n 'tw-list-outside !tw-list-[lower-roman]',\n 'tw-list-outside !tw-list-[upper-alpha]',\n 'tw-list-outside !tw-list-[lower-alpha]',\n ],\n ul: 'tw-m-0 tw-p-0 tw-list-outside [&>li]:tw-mt-2',\n ulDepth: [\n 'tw-list-outside !tw-list-disc',\n 'tw-list-outside !tw-list-disc',\n 'tw-list-outside !tw-list-disc',\n 'tw-list-outside !tw-list-disc',\n 'tw-list-outside !tw-list-disc',\n ],\n },\n hashtag: 'tw-text-blue-600 tw-bg-blue-100 tw-rounded-md tw-px-1',\n text: {\n bold: 'tw-font-bold',\n code: 'tw-bg-gray-100 tw-p-1 tw-rounded-md',\n italic: 'tw-italic',\n strikethrough: 'tw-line-through',\n subscript: 'tw-sub',\n superscript: 'tw-sup',\n underline: 'tw-underline',\n underlineStrikethrough: 'tw-underline tw-line-through',\n },\n image: 'tw-relative tw-inline-block tw-user-select-none tw-cursor-default editor-image',\n inlineImage:\n 'tw-relative tw-inline-block tw-user-select-none tw-cursor-default inline-editor-image',\n keyword: 'tw-text-purple-900 tw-font-bold',\n code: 'EditorTheme__code',\n codeHighlight: {\n atrule: 'EditorTheme__tokenAttr',\n attr: 'EditorTheme__tokenAttr',\n boolean: 'EditorTheme__tokenProperty',\n builtin: 'EditorTheme__tokenSelector',\n cdata: 'EditorTheme__tokenComment',\n char: 'EditorTheme__tokenSelector',\n class: 'EditorTheme__tokenFunction',\n 'class-name': 'EditorTheme__tokenFunction',\n comment: 'EditorTheme__tokenComment',\n constant: 'EditorTheme__tokenProperty',\n deleted: 'EditorTheme__tokenProperty',\n doctype: 'EditorTheme__tokenComment',\n entity: 'EditorTheme__tokenOperator',\n function: 'EditorTheme__tokenFunction',\n important: 'EditorTheme__tokenVariable',\n inserted: 'EditorTheme__tokenSelector',\n keyword: 'EditorTheme__tokenAttr',\n namespace: 'EditorTheme__tokenVariable',\n number: 'EditorTheme__tokenProperty',\n operator: 'EditorTheme__tokenOperator',\n prolog: 'EditorTheme__tokenComment',\n property: 'EditorTheme__tokenProperty',\n punctuation: 'EditorTheme__tokenPunctuation',\n regex: 'EditorTheme__tokenVariable',\n selector: 'EditorTheme__tokenSelector',\n string: 'EditorTheme__tokenSelector',\n symbol: 'EditorTheme__tokenProperty',\n tag: 'EditorTheme__tokenProperty',\n url: 'EditorTheme__tokenOperator',\n variable: 'EditorTheme__tokenVariable',\n },\n characterLimit: '!tw-bg-destructive/50',\n table: 'EditorTheme__table tw-w-fit tw-overflow-scroll tw-border-collapse',\n tableCell:\n 'EditorTheme__tableCell tw-w-24 tw-relative tw-border tw-px-4 tw-py-2 tw-text-left [&[align=center]]:tw-text-center [&[align=right]]:tw-text-right',\n tableCellActionButton:\n 'EditorTheme__tableCellActionButton tw-bg-background tw-block tw-border-0 tw-rounded-2xl tw-w-5 tw-h-5 tw-text-foreground tw-cursor-pointer',\n tableCellActionButtonContainer:\n 'EditorTheme__tableCellActionButtonContainer tw-block tw-right-1 tw-top-1.5 tw-absolute tw-z-10 tw-w-5 tw-h-5',\n tableCellEditing: 'EditorTheme__tableCellEditing tw-rounded-sm tw-shadow-sm',\n tableCellHeader:\n 'EditorTheme__tableCellHeader tw-bg-muted tw-border tw-px-4 tw-py-2 tw-text-left tw-font-bold [&[align=center]]:tw-text-center [&[align=right]]:tw-text-right',\n tableCellPrimarySelected:\n 'EditorTheme__tableCellPrimarySelected tw-border tw-border-primary tw-border-solid tw-block tw-h-[calc(100%-2px)] tw-w-[calc(100%-2px)] tw-absolute tw--left-[1px] tw--top-[1px] tw-z-10 ',\n tableCellResizer:\n 'EditorTheme__tableCellResizer tw-absolute tw--right-1 tw-h-full tw-w-2 tw-cursor-ew-resize tw-z-10 tw-top-0',\n tableCellSelected: 'EditorTheme__tableCellSelected tw-bg-muted',\n tableCellSortedIndicator:\n 'EditorTheme__tableCellSortedIndicator tw-block tw-opacity-50 tw-absolute tw-bottom-0 tw-left-0 tw-w-full tw-h-1 tw-bg-muted',\n tableResizeRuler:\n 'EditorTheme__tableCellResizeRuler tw-block tw-absolute tw-w-[1px] tw-h-full tw-bg-primary tw-top-0',\n tableRowStriping: 'EditorTheme__tableRowStriping tw-m-0 tw-border-t tw-p-0 even:tw-bg-muted',\n tableSelected: 'EditorTheme__tableSelected tw-ring-2 tw-ring-primary tw-ring-offset-2',\n tableSelection: 'EditorTheme__tableSelection tw-bg-transparent',\n layoutItem: 'tw-border tw-border-dashed tw-px-4 tw-py-2',\n layoutContainer: 'tw-grid tw-gap-2.5 tw-my-2.5 tw-mx-0',\n autocomplete: 'tw-text-muted-foreground',\n blockCursor: '',\n embedBlock: {\n base: 'tw-user-select-none',\n focus: 'tw-ring-2 tw-ring-primary tw-ring-offset-2',\n },\n hr: 'tw-p-0.5 tw-border-none tw-my-1 tw-mx-0 tw-cursor-pointer after:tw-content-[\"\"] after:tw-block after:tw-h-0.5 after:tw-bg-muted selected:tw-ring-2 selected:tw-ring-primary selected:tw-ring-offset-2 selected:tw-user-select-none',\n indent: '[--lexical-indent-base-value:40px]',\n mark: '',\n markOverlap: '',\n};\n","import React from 'react';\nimport * as TooltipPrimitive from '@radix-ui/react-tooltip';\n\nimport { cn } from '@/utils/shadcn-ui.util';\n\n/** @inheritdoc Tooltip */\nconst TooltipProvider = TooltipPrimitive.Provider;\n\n/**\n * Tooltip components provide a popover that displays information related to an element when hovered\n * or focused. These components are built on Radix UI primitives and styled with Shadcn UI. See\n * Shadcn UI Documentation: https://ui.shadcn.com/docs/components/tooltip See Radix UI\n * Documentation: https://www.radix-ui.com/primitives/docs/components/tooltip\n */\nconst Tooltip = TooltipPrimitive.Root;\n\n/** @inheritdoc Tooltip */\nconst TooltipTrigger = TooltipPrimitive.Trigger;\n\n/** @inheritdoc Tooltip */\nconst TooltipContent = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, sideOffset = 4, ...props }, ref) => (\n \n));\nTooltipContent.displayName = TooltipPrimitive.Content.displayName;\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n","/**\n * This file was automatically generated on installation of the Shadcn/Lexical editor. The default\n * location of this file has been changed to integrate better with our project structure.\n *\n * Original file location: src/components/blocks/editor-00/nodes.ts\n *\n * Shadcn/Lexical Editor Documentation: https://shadcn-editor.vercel.app/docs/\n */\n\nimport { HeadingNode, QuoteNode } from '@lexical/rich-text';\nimport { Klass, LexicalNode, LexicalNodeReplacement, ParagraphNode, TextNode } from 'lexical';\n\nexport const nodes: ReadonlyArray | LexicalNodeReplacement> = [\n HeadingNode,\n ParagraphNode,\n TextNode,\n QuoteNode,\n];\n","'use client';\nimport { createContext, Component, createElement, useContext, useState, useMemo, forwardRef } from 'react';\n\nconst ErrorBoundaryContext = createContext(null);\n\nconst initialState = {\n didCatch: false,\n error: null\n};\nclass ErrorBoundary extends Component {\n constructor(props) {\n super(props);\n this.resetErrorBoundary = this.resetErrorBoundary.bind(this);\n this.state = initialState;\n }\n static getDerivedStateFromError(error) {\n return {\n didCatch: true,\n error\n };\n }\n resetErrorBoundary() {\n const {\n error\n } = this.state;\n if (error !== null) {\n var _this$props$onReset, _this$props;\n for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n (_this$props$onReset = (_this$props = this.props).onReset) === null || _this$props$onReset === void 0 ? void 0 : _this$props$onReset.call(_this$props, {\n args,\n reason: \"imperative-api\"\n });\n this.setState(initialState);\n }\n }\n componentDidCatch(error, info) {\n var _this$props$onError, _this$props2;\n (_this$props$onError = (_this$props2 = this.props).onError) === null || _this$props$onError === void 0 ? void 0 : _this$props$onError.call(_this$props2, error, info);\n }\n componentDidUpdate(prevProps, prevState) {\n const {\n didCatch\n } = this.state;\n const {\n resetKeys\n } = this.props;\n\n // There's an edge case where if the thing that triggered the error happens to *also* be in the resetKeys array,\n // we'd end up resetting the error boundary immediately.\n // This would likely trigger a second error to be thrown.\n // So we make sure that we don't check the resetKeys on the first call of cDU after the error is set.\n\n if (didCatch && prevState.error !== null && hasArrayChanged(prevProps.resetKeys, resetKeys)) {\n var _this$props$onReset2, _this$props3;\n (_this$props$onReset2 = (_this$props3 = this.props).onReset) === null || _this$props$onReset2 === void 0 ? void 0 : _this$props$onReset2.call(_this$props3, {\n next: resetKeys,\n prev: prevProps.resetKeys,\n reason: \"keys\"\n });\n this.setState(initialState);\n }\n }\n render() {\n const {\n children,\n fallbackRender,\n FallbackComponent,\n fallback\n } = this.props;\n const {\n didCatch,\n error\n } = this.state;\n let childToRender = children;\n if (didCatch) {\n const props = {\n error,\n resetErrorBoundary: this.resetErrorBoundary\n };\n if (typeof fallbackRender === \"function\") {\n childToRender = fallbackRender(props);\n } else if (FallbackComponent) {\n childToRender = createElement(FallbackComponent, props);\n } else if (fallback !== undefined) {\n childToRender = fallback;\n } else {\n throw error;\n }\n }\n return createElement(ErrorBoundaryContext.Provider, {\n value: {\n didCatch,\n error,\n resetErrorBoundary: this.resetErrorBoundary\n }\n }, childToRender);\n }\n}\nfunction hasArrayChanged() {\n let a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];\n let b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];\n return a.length !== b.length || a.some((item, index) => !Object.is(item, b[index]));\n}\n\nfunction assertErrorBoundaryContext(value) {\n if (value == null || typeof value.didCatch !== \"boolean\" || typeof value.resetErrorBoundary !== \"function\") {\n throw new Error(\"ErrorBoundaryContext not found\");\n }\n}\n\nfunction useErrorBoundary() {\n const context = useContext(ErrorBoundaryContext);\n assertErrorBoundaryContext(context);\n const [state, setState] = useState({\n error: null,\n hasError: false\n });\n const memoized = useMemo(() => ({\n resetBoundary: () => {\n context.resetErrorBoundary();\n setState({\n error: null,\n hasError: false\n });\n },\n showBoundary: error => setState({\n error,\n hasError: true\n })\n }), [context.resetErrorBoundary]);\n if (state.hasError) {\n throw state.error;\n }\n return memoized;\n}\n\nfunction withErrorBoundary(component, errorBoundaryProps) {\n const Wrapped = forwardRef((props, ref) => createElement(ErrorBoundary, errorBoundaryProps, createElement(component, {\n ...props,\n ref\n })));\n\n // Format for display in DevTools\n const name = component.displayName || component.name || \"Unknown\";\n Wrapped.displayName = \"withErrorBoundary(\".concat(name, \")\");\n return Wrapped;\n}\n\nexport { ErrorBoundary, ErrorBoundaryContext, useErrorBoundary, withErrorBoundary };\n","/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport{ErrorBoundary as r}from\"react-error-boundary\";import{jsx as o}from\"react/jsx-runtime\";function n({children:n,onError:e}){return o(r,{fallback:o(\"div\",{style:{border:\"1px solid #f00\",color:\"#f00\",padding:\"8px\"},children:\"An error was thrown.\"}),onError:e,children:n})}export{n as LexicalErrorBoundary};\n","/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport{useLexicalComposerContext as e}from\"@lexical/react/LexicalComposerContext\";import{useLayoutEffect as n,useEffect as t,useMemo as i,useState as r,useRef as o}from\"react\";const c=\"undefined\"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement?n:t;function u(e){return{initialValueFn:()=>e.isEditable(),subscribe:n=>e.registerEditableListener(n)}}function a(){return function(n){const[t]=e(),u=i(()=>n(t),[t,n]),[a,l]=r(()=>u.initialValueFn()),d=o(a);return c(()=>{const{initialValueFn:e,subscribe:n}=u,t=e();return d.current!==t&&(d.current=t,l(t)),n(e=>{d.current=e,l(e)})},[u,n]),a}(u)}export{a as useLexicalEditable};\n","/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport{$isTextNode as e,$getEditor as t,$isRootNode as n,$isElementNode as o,$getNodeByKey as l,$getPreviousSelection as r,$createTextNode as s,$isRangeSelection as i,$getSelection as c,$caretRangeFromSelection as f,$isTokenOrSegmented as u,$getCharacterOffsets as g,$cloneWithPropertiesEphemeral as a,$createRangeSelection as d,$findMatchingParent as p,INTERNAL_$isBlock as h,$setSelection as y,$caretFromPoint as m,$isExtendableTextPointCaret as S,$extendCaretToRange as x,$isChildCaret as T,$isDecoratorNode as w,$isRootOrShadowRoot as N,$hasAncestor as v,$isLeafNode as C}from\"lexical\";export{$cloneWithProperties,$selectAll}from\"lexical\";function K(e,...t){const n=new URL(\"https://lexical.dev/docs/error\"),o=new URLSearchParams;o.append(\"code\",e);for(const e of t)o.append(\"v\",e);throw n.search=o.toString(),Error(`Minified Lexical error #${e}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}const E=new Map;function P(e){let t=e;for(;null!=t;){if(t.nodeType===Node.TEXT_NODE)return t;t=t.firstChild}return null}function k(e){const t=e.parentNode;if(null==t)throw new Error(\"Should never happen\");return[t,Array.from(t.childNodes).indexOf(e)]}function I(t,n,o,l,r){const s=n.getKey(),i=l.getKey(),c=document.createRange();let f=t.getElementByKey(s),u=t.getElementByKey(i),g=o,a=r;if(e(n)&&(f=P(f)),e(l)&&(u=P(u)),void 0===n||void 0===l||null===f||null===u)return null;\"BR\"===f.nodeName&&([f,g]=k(f)),\"BR\"===u.nodeName&&([u,a]=k(u));const d=f.firstChild;f===u&&null!=d&&\"BR\"===d.nodeName&&0===g&&0===a&&(a=1);try{c.setStart(f,g),c.setEnd(u,a)}catch(e){return null}return!c.collapsed||g===a&&s===i||(c.setStart(u,a),c.setEnd(f,g)),c}function B(e,t){const n=e.getRootElement();if(null===n)return[];const o=n.getBoundingClientRect(),l=getComputedStyle(n),r=parseFloat(l.paddingLeft)+parseFloat(l.paddingRight),s=Array.from(t.getClientRects());let i,c=s.length;s.sort((e,t)=>{const n=e.top-t.top;return Math.abs(n)<=3?e.left-t.left:n});for(let e=0;et.top&&i.left+i.width>t.left,l=t.width+r===o.width;n||l?(s.splice(e--,1),c--):i=t}return s}function F(e){const t={};if(!e)return t;const n=e.split(\";\");for(const e of n)if(\"\"!==e){const[n,o]=e.split(/:([^]+)/);n&&o&&(t[n.trim()]=o.trim())}return t}function b(e){let t=E.get(e);return void 0===t&&(t=F(e),E.set(e,t)),t}function R(e){let t=\"\";for(const n in e)n&&(t+=`${n}: ${e[n]};`);return t}function z(e){const n=t().getElementByKey(e.getKey());if(null===n)return null;const o=n.ownerDocument.defaultView;return null===o?null:o.getComputedStyle(n)}function O(e){return z(n(e)?e:e.getParentOrThrow())}function A(e){const t=O(e);return null!==t&&\"rtl\"===t.direction}function M(e,t,n=\"self\"){const o=e.getStartEndPoints();if(t.isSelected(e)&&!u(t)&&null!==o){const[l,r]=o,s=e.isBackward(),i=l.getNode(),c=r.getNode(),f=t.is(i),u=t.is(c);if(f||u){const[o,l]=g(e),r=i.is(c),f=t.is(s?c:i),u=t.is(s?i:c);let d,p=0;if(r)p=o>l?l:o,d=o>l?o:l;else if(f){p=s?l:o,d=void 0}else if(u){p=0,d=s?o:l}const h=t.__text.slice(p,d);h!==t.__text&&(\"clone\"===n&&(t=a(t)),t.__text=h)}}return t}function _(e){if(\"text\"===e.type)return e.offset===e.getNode().getTextContentSize();const t=e.getNode();return o(t)||K(177),e.offset===t.getChildrenSize()}function L(t,c,f){let u=c.getNode(),g=f;if(o(u)){const e=u.getDescendantByIndex(c.offset);null!==e&&(u=e)}for(;g>0&&null!==u;){if(o(u)){const e=u.getLastDescendant();null!==e&&(u=e)}let f=u.getPreviousSibling(),a=0;if(null===f){let e=u.getParentOrThrow(),t=e.getPreviousSibling();for(;null===t;){if(e=e.getParent(),null===e){f=null;break}t=e.getPreviousSibling()}null!==e&&(a=e.isInline()?0:2,f=t)}let d=u.getTextContent();\"\"===d&&o(u)&&!u.isInline()&&(d=\"\\n\\n\");const p=d.length;if(!e(u)||g>=p){const e=u.getParent();u.remove(),null==e||0!==e.getChildrenSize()||n(e)||e.remove(),g-=p+a,u=f}else{const n=u.getKey(),o=t.getEditorState().read(()=>{const t=l(n);return e(t)&&t.isSimpleText()?t.getTextContent():null}),f=p-g,a=d.slice(0,f);if(null!==o&&o!==d){const e=r();let t=u;if(u.isSimpleText())u.setTextContent(o);else{const e=s(o);u.replace(e),t=e}if(i(e)&&e.isCollapsed()){const n=e.anchor.offset;t.select(n,n)}}else if(u.isSimpleText()){const e=c.key===n;let t=c.offset;t(\"function\"==typeof o?e[n]=o(l[n],t):null===o?delete e[n]:e[n]=o,e),{...l}),s=R(r);i(t)||e(t)?t.setStyle(s):t.setTextStyle(s),E.set(s,r)}function U(e,t){if(i(e)&&e.isCollapsed()){D(e,t);const n=e.anchor.getNode();o(n)&&n.isEmpty()&&D(n,t)}j(e=>{D(e,t)});const n=e.getNodes();if(n.length>0){const e=new Set;for(const l of n){if(!o(l)||!l.canBeEmpty()||0!==l.getChildrenSize())continue;const n=l.getKey();e.has(n)||(e.add(n),D(l,t))}}}function j(t){const n=c();if(!n)return;const o=new Map,l=e=>o.get(e.getKey())||[0,e.getTextContentSize()];if(i(n))for(const e of f(n).getTextSlices())e&&o.set(e.caret.origin.getKey(),e.getSliceIndices());const r=n.getNodes();for(const n of r){if(!e(n)||!n.canHaveFormat())continue;const[o,r]=l(n);if(r!==o)if(u(n)||0===o&&r===n.getTextContentSize())t(n);else{t(n.splitText(o,r)[0===o?0:1])}}i(n)&&\"text\"===n.anchor.type&&\"text\"===n.focus.type&&n.anchor.key===n.focus.key&&H(n)}function H(e){if(e.isBackward()){const{anchor:t,focus:n}=e,{key:o,offset:l,type:r}=t;t.set(n.key,n.offset,n.type),n.set(o,l,r)}}function V(e,t){const n=e.getFormatType(),o=e.getIndent();n!==t.getFormatType()&&t.setFormat(n),o!==t.getIndent()&&t.setIndent(o)}function W(e,t,n=V){if(null===e)return;const l=e.getStartEndPoints(),r=new Map;let s=null;if(l){const[e,t]=l;s=d(),s.anchor.set(e.key,e.offset,e.type),s.focus.set(t.key,t.offset,t.type);const n=p(e.getNode(),h),i=p(t.getNode(),h);o(n)&&r.set(n.getKey(),n),o(i)&&r.set(i.getKey(),i)}for(const t of e.getNodes())if(o(t)&&h(t))r.set(t.getKey(),t);else if(null===l){const e=p(t,h);o(e)&&r.set(e.getKey(),e)}for(const[e,o]of r){const l=t();n(o,l),o.replace(l,!0),s&&(e===s.anchor.key&&s.anchor.set(l.getKey(),s.anchor.offset,s.anchor.type),e===s.focus.key&&s.focus.set(l.getKey(),s.focus.offset,s.focus.type))}s&&e.is(c())&&y(s)}function X(e){return e.getNode().isAttached()}function q(e){let t=e;for(;null!==t&&!N(t);){const e=t.getLatest(),n=t.getParent();0===e.getChildrenSize()&&t.remove(!0),t=n}}function G(e,t,n=null){const o=e.getStartEndPoints(),l=o?o[0]:null,r=e.getNodes(),s=r.length;if(null!==l&&(0===s||1===s&&\"element\"===l.type&&0===l.getNode().getChildrenSize())){const e=\"text\"===l.type?l.getNode().getParentOrThrow():l.getNode(),o=e.getChildren();let r=t();return r.setFormat(e.getFormatType()),r.setIndent(e.getIndent()),o.forEach(e=>r.append(e)),n&&(r=n.append(r)),void e.replace(r)}let i=null,c=[];for(let o=0;o{t.append(e),p.add(e.getKey()),o(e)&&e.getChildrenKeys().forEach(e=>p.add(e))}),q(r)}}else if(d.has(n.getKey())){o(n)||K(179);const e=l();e.setFormat(n.getFormatType()),e.setIndent(n.getIndent()),u.push(e),n.remove(!0)}}if(null!==s)for(let e=0;e=0;e--){const t=u[e];g.insertAfter(t)}else{const e=g.getFirstChild();if(o(e)&&(g=e),null===e)if(s)g.append(s);else for(let e=0;e=0;e--){const t=u[e];g.insertAfter(t),h=t}const m=r();i(m)&&X(m.anchor)&&X(m.focus)?y(m.clone()):null!==h?h.selectEnd():e.dirty=!0}function Q(e){const t=Y(e);return null!==t&&\"vertical-rl\"===t.writingMode}function Y(e){const t=e.anchor.getNode();return o(t)?z(t):O(t)}function Z(e,t){let n=Q(e)?!t:t;te(e)&&(n=!n);const l=m(e.focus,n?\"previous\":\"next\");if(S(l))return!1;for(const e of x(l)){if(T(e))return!e.origin.isInline();if(!o(e.origin)){if(w(e.origin))return!0;break}}return!1}function ee(e,t,n,o){e.modify(t?\"extend\":\"move\",n,o)}function te(e){const t=Y(e);return null!==t&&\"rtl\"===t.direction}function ne(e,t,n){const o=te(e);let l;l=Q(e)||o?!n:n,ee(e,t,l,\"character\")}function oe(e,t,n){const o=b(e.getStyle());return null!==o&&o[t]||n}function le(t,n,o=\"\"){let l=null;const r=t.getNodes(),s=t.anchor,c=t.focus,f=t.isBackward(),u=f?c.offset:s.offset,g=f?c.getNode():s.getNode();if(i(t)&&t.isCollapsed()&&\"\"!==t.style){const e=b(t.style);if(null!==e&&n in e)return e[n]}for(let t=0;tc.length;)u.pop();f&&o(u)}function a(){i=null,r=null,null!==l&&l.disconnect(),l=null,s.remove();for(const t of u)t.remove();u=[]}s.style.position=\"relative\";const d=e.registerRootListener(function n(){const o=e.getRootElement();if(null===o)return a();const u=o.parentElement;if(!t(u))return a();a(),r=o,i=u,l=new MutationObserver(t=>{const o=e.getRootElement(),l=o&&o.parentElement;if(o!==r||l!==i)return n();for(const e of t)if(!s.contains(e.target))return c()}),l.observe(u,z),c()});return()=>{d(),a()}}function W(t,e,n){if(\"text\"!==t.type&&r(e)){const o=e.getDOMSlot(n);return[o.element,o.getFirstChildOffset()+t.offset]}return[i(n)||n,t.offset]}function G(t,r){let i=null,l=null,u=null,s=null,c=null,a=null,d=()=>{};function f(e){e.read(()=>{const e=n();if(!o(e))return i=null,u=null,s=null,a=null,d(),void(d=()=>{});const[f,g]=function(t){const e=t.getStartEndPoints();return t.isBackward()?[e[1],e[0]]:e}(e),p=f.getNode(),m=p.getKey(),h=f.offset,v=g.getNode(),y=v.getKey(),w=g.offset,E=t.getElementByKey(m),x=t.getElementByKey(y),S=null===i||E!==l||h!==u||m!==i.getKey(),C=null===s||x!==c||w!==a||y!==s.getKey();if((S||C)&&null!==E&&null!==x){const e=function(t,e,n,o,r,i,l){const u=(t._window?t._window.document:document).createRange();return u.setStart(...W(e,n,o)),u.setEnd(...W(r,i,l)),u}(t,f,p,E,g,v,x);d(),d=V(t,e,t=>{if(void 0===r)for(const e of t){const t=e.style;\"Highlight\"!==t.background&&(t.background=\"Highlight\"),\"HighlightText\"!==t.color&&(t.color=\"HighlightText\"),t.marginTop!==U(-1.5)&&(t.marginTop=U(-1.5)),t.paddingTop!==U(4)&&(t.paddingTop=U(4)),t.paddingBottom!==U(0)&&(t.paddingBottom=U(0))}else r(t)})}i=p,l=E,u=h,s=v,c=x,a=w})}return f(t.getEditorState()),e(t.registerUpdateListener(({editorState:t})=>f(t)),()=>{d()})}function q(t,e){let n=null;const o=()=>{const o=getSelection(),r=o&&o.anchorNode,i=t.getRootElement();null!==r&&null!==i&&i.contains(r)?null!==n&&(n(),n=null):null===n&&(n=G(t,e))};return document.addEventListener(\"selectionchange\",o),()=>{null!==n&&n(),document.removeEventListener(\"selectionchange\",o)}}const J=K,Q=B,X=j,Y=F,Z=k,tt=I,et=D,nt=$,ot=H,rt=O;function it(t,e){for(const n of e)if(t.type.startsWith(n))return!0;return!1}function lt(t,e){const n=t[Symbol.iterator]();return new Promise((t,o)=>{const r=[],i=()=>{const{done:l,value:u}=n.next();if(l)return t(r);const s=new FileReader;s.addEventListener(\"error\",o),s.addEventListener(\"load\",()=>{const t=s.result;\"string\"==typeof t&&r.push({file:u,result:t}),i()}),it(u,e)?s.readAsDataURL(u):i()};i()})}function ut(t,e){return Array.from(at(t,e))}function st(t){return t?t.getAdjacentCaret():null}function ct(t,e){return Array.from(ht(t,e))}function at(t,e){return ft(\"next\",t,e)}function dt(t,e){const n=l(u(t,e));return n&&n[0]}function ft(t,e,n){const o=m(),i=e||o,c=r(i)?p(i,t):u(i,t),a=pt(i),d=n?v(s(u(n,t)))||dt(n,t):dt(i,t);let f=a;return L({hasNext:t=>null!==t,initial:c,map:t=>({depth:f,node:t.origin}),step:t=>{if(t.isSameNodeCaret(d))return null;y(t)&&f++;const e=l(t);return!e||e[0].isSameNodeCaret(d)?null:(f+=e[1],e[0])}})}function gt(t){const e=l(u(t,\"next\"));return e&&[e[0].origin,e[1]]}function pt(t){let e=-1;for(let n=t;null!==n;n=n.getParent())e++;return e}function mt(t){const e=s(u(t,\"previous\")),n=l(e,\"root\");return n&&n[0].origin}function ht(t,e){return ft(\"previous\",t,e)}function vt(t,e){let n=t;for(;null!=n;){if(n instanceof e)return n;n=n.getParent()}return null}function yt(t){const e=c(t,t=>r(t)&&!t.isInline());return r(e)||T(4,t.__key),e}function wt(t,e,n,o){const r=t=>t instanceof e;return t.registerNodeTransform(e,t=>{const e=(t=>{const e=t.getChildren();for(let t=0;ti.insertAfter(t))),i.remove());return o}function Bt(t,e){const n=[],o=Array.from(t).reverse();for(let t=o.pop();void 0!==t;t=o.pop())if(e(t))n.push(t);else if(r(t))for(const e of kt(t))o.push(e);return n}function _t(t){return $t(p(t,\"next\"))}function kt(t){return $t(p(t,\"previous\"))}function $t(t){return L({hasNext:M,initial:t.getAdjacentCaret(),map:t=>t.origin.getLatest(),step:t=>t.getAdjacentCaret()})}function Kt(t){b(u(t,\"next\")).splice(1,t.getChildren())}function Ot(t){const e=e=>N(e,t),n=(e,n)=>P(e,t,n);return{$get:e,$set:n,accessors:[e,n],makeGetterMethod:()=>function(){return e(this)},makeSetterMethod:()=>function(t){return n(this,t)},stateConfig:t}}export{Bt as $descendantsMatching,ut as $dfs,at as $dfsIterator,bt as $filter,_t as $firstToLastIterator,st as $getAdjacentCaret,pt as $getDepth,yt as $getNearestBlockElementAncestorOrThrow,vt as $getNearestNodeOfType,mt as $getNextRightPreorderNode,gt as $getNextSiblingOrParentSibling,Nt as $insertFirst,xt as $insertNodeToNearestRoot,St as $insertNodeToNearestRootAtCaret,Mt as $isEditorIsNestedEditor,kt as $lastToFirstIterator,Et as $restoreEditorState,ct as $reverseDfs,ht as $reverseDfsIterator,Rt as $unwrapAndFilterDescendants,Kt as $unwrapNode,Ct as $wrapNodeInElement,J as CAN_USE_BEFORE_INPUT,Q as CAN_USE_DOM,X as IS_ANDROID,Y as IS_ANDROID_CHROME,Z as IS_APPLE,tt as IS_APPLE_WEBKIT,et as IS_CHROME,nt as IS_FIREFOX,ot as IS_IOS,rt as IS_SAFARI,Lt as calculateZoomLevel,it as isMimeType,Ot as makeStateWrapper,G as markSelection,lt as mediaFileReader,At as objectKlassEquals,V as positionNodeOnRange,wt as registerNestedElementResolver,q as selectionAlwaysOnDisplay};\n","/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport{defineExtension as t,safeCast as e,CLEAR_EDITOR_COMMAND as n,COMMAND_PRIORITY_EDITOR as i,$getRoot as o,$getSelection as s,$createParagraphNode as r,$isRangeSelection as c,shallowMergeConfig as d,RootNode as a,TextNode as f,LineBreakNode as u,TabNode as h,ParagraphNode as l,$isEditorState as g,HISTORY_MERGE_TAG as p,createEditor as m,mergeRegister as v,$getNodeByKey as x,createCommand as y,$create as S,CLICK_COMMAND as E,isDOMNode as b,$getNodeFromDOMNode as w,COMMAND_PRIORITY_LOW as N,DecoratorNode as O,addClassNamesToElement as R,$isNodeSelection as M,$createNodeSelection as C,$setSelection as D,removeClassNamesFromElement as _,KEY_TAB_COMMAND as I,OUTDENT_CONTENT_COMMAND as j,INDENT_CONTENT_COMMAND as A,INSERT_TAB_COMMAND as P,COMMAND_PRIORITY_CRITICAL as K,$isBlockElementNode as k,$createRangeSelection as $,$normalizeSelection__EXPERIMENTAL as z}from\"lexical\";export{configExtension,declarePeerDependency,defineExtension,safeCast,shallowMergeConfig}from\"lexical\";import{$getNearestBlockElementAncestorOrThrow as U}from\"@lexical/utils\";const L=Symbol.for(\"preact-signals\");function T(){if(W>1)return void W--;let t,e=!1;for(;void 0!==G;){let n=G;for(G=void 0,Z++;void 0!==n;){const i=n.o;if(n.o=void 0,n.f&=-3,!(8&n.f)&&X(n))try{n.c()}catch(n){e||(t=n,e=!0)}n=i}}if(Z=0,W--,e)throw t}function B(t){if(W>0)return t();W++;try{return t()}finally{T()}}let F,G;function V(t){const e=F;F=void 0;try{return t()}finally{F=e}}let W=0,Z=0,J=0;function H(t){if(void 0===F)return;let e=t.n;return void 0===e||e.t!==F?(e={i:0,S:t,p:F.s,n:void 0,t:F,e:void 0,x:void 0,r:e},void 0!==F.s&&(F.s.n=e),F.s=e,t.n=e,32&F.f&&t.S(e),e):-1===e.i?(e.i=0,void 0!==e.n&&(e.n.p=e.p,void 0!==e.p&&(e.p.n=e.n),e.p=F.s,e.n=void 0,F.s.n=e,F.s=e),e):void 0}function q(t,e){this.v=t,this.i=0,this.n=void 0,this.t=void 0,this.W=null==e?void 0:e.watched,this.Z=null==e?void 0:e.unwatched,this.name=null==e?void 0:e.name}function Q(t,e){return new q(t,e)}function X(t){for(let e=t.s;void 0!==e;e=e.n)if(e.S.i!==e.i||!e.S.h()||e.S.i!==e.i)return!0;return!1}function Y(t){for(let e=t.s;void 0!==e;e=e.n){const n=e.S.n;if(void 0!==n&&(e.r=n),e.S.n=e,e.i=-1,void 0===e.n){t.s=e;break}}}function tt(t){let e,n=t.s;for(;void 0!==n;){const t=n.p;-1===n.i?(n.S.U(n),void 0!==t&&(t.n=n.n),void 0!==n.n&&(n.n.p=t)):e=n,n.S.n=n.r,void 0!==n.r&&(n.r=void 0),n=t}t.s=e}function et(t,e){q.call(this,void 0),this.x=t,this.s=void 0,this.g=J-1,this.f=4,this.W=null==e?void 0:e.watched,this.Z=null==e?void 0:e.unwatched,this.name=null==e?void 0:e.name}function nt(t,e){return new et(t,e)}function it(t){const e=t.u;if(t.u=void 0,\"function\"==typeof e){W++;const n=F;F=void 0;try{e()}catch(e){throw t.f&=-2,t.f|=8,ot(t),e}finally{F=n,T()}}}function ot(t){for(let e=t.s;void 0!==e;e=e.n)e.S.U(e);t.x=void 0,t.s=void 0,it(t)}function st(t){if(F!==this)throw new Error(\"Out-of-order effect\");tt(this),F=t,this.f&=-2,8&this.f&&ot(this),T()}function rt(t,e){this.x=t,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32,this.name=null==e?void 0:e.name}function ct(t,e){const n=new rt(t,e);try{n.c()}catch(t){throw n.d(),t}const i=n.d.bind(n);return i[Symbol.dispose]=i,i}function dt(t,e={}){const n={};for(const i in t){const o=e[i],s=Q(void 0===o?t[i]:o);n[i]=s}return n}q.prototype.brand=L,q.prototype.h=function(){return!0},q.prototype.S=function(t){const e=this.t;e!==t&&void 0===t.e&&(t.x=e,this.t=t,void 0!==e?e.e=t:V(()=>{var t;null==(t=this.W)||t.call(this)}))},q.prototype.U=function(t){if(void 0!==this.t){const e=t.e,n=t.x;void 0!==e&&(e.x=n,t.e=void 0),void 0!==n&&(n.e=e,t.x=void 0),t===this.t&&(this.t=n,void 0===n&&V(()=>{var t;null==(t=this.Z)||t.call(this)}))}},q.prototype.subscribe=function(t){return ct(()=>{const e=this.value,n=F;F=void 0;try{t(e)}finally{F=n}},{name:\"sub\"})},q.prototype.valueOf=function(){return this.value},q.prototype.toString=function(){return this.value+\"\"},q.prototype.toJSON=function(){return this.value},q.prototype.peek=function(){const t=F;F=void 0;try{return this.value}finally{F=t}},Object.defineProperty(q.prototype,\"value\",{get(){const t=H(this);return void 0!==t&&(t.i=this.i),this.v},set(t){if(t!==this.v){if(Z>100)throw new Error(\"Cycle detected\");this.v=t,this.i++,J++,W++;try{for(let t=this.t;void 0!==t;t=t.x)t.t.N()}finally{T()}}}}),et.prototype=new q,et.prototype.h=function(){if(this.f&=-3,1&this.f)return!1;if(32==(36&this.f))return!0;if(this.f&=-5,this.g===J)return!0;if(this.g=J,this.f|=1,this.i>0&&!X(this))return this.f&=-2,!0;const t=F;try{Y(this),F=this;const t=this.x();(16&this.f||this.v!==t||0===this.i)&&(this.v=t,this.f&=-17,this.i++)}catch(t){this.v=t,this.f|=16,this.i++}return F=t,tt(this),this.f&=-2,!0},et.prototype.S=function(t){if(void 0===this.t){this.f|=36;for(let t=this.s;void 0!==t;t=t.n)t.S.S(t)}q.prototype.S.call(this,t)},et.prototype.U=function(t){if(void 0!==this.t&&(q.prototype.U.call(this,t),void 0===this.t)){this.f&=-33;for(let t=this.s;void 0!==t;t=t.n)t.S.U(t)}},et.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(let t=this.t;void 0!==t;t=t.x)t.t.N()}},Object.defineProperty(et.prototype,\"value\",{get(){if(1&this.f)throw new Error(\"Cycle detected\");const t=H(this);if(this.h(),void 0!==t&&(t.i=this.i),16&this.f)throw this.v;return this.v}}),rt.prototype.c=function(){const t=this.S();try{if(8&this.f)return;if(void 0===this.x)return;const t=this.x();\"function\"==typeof t&&(this.u=t)}finally{t()}},rt.prototype.S=function(){if(1&this.f)throw new Error(\"Cycle detected\");this.f|=1,this.f&=-9,it(this),Y(this),W++;const t=F;return F=this,st.bind(this,t)},rt.prototype.N=function(){2&this.f||(this.f|=2,this.o=G,G=this)},rt.prototype.d=function(){this.f|=8,1&this.f||ot(this)},rt.prototype.dispose=function(){this.d()};const at=t({build:(t,e,n)=>dt(e),config:e({defaultSelection:\"rootEnd\",disabled:!1}),name:\"@lexical/extension/AutoFocus\",register(t,e,n){const i=n.getOutput();return ct(()=>i.disabled.value?void 0:t.registerRootListener(e=>{t.focus(()=>{const t=document.activeElement;null===e||null!==t&&e.contains(t)||e.focus({preventScroll:!0})},{defaultSelection:i.defaultSelection.peek()})}))}});function ft(){const t=o(),e=s(),n=r();t.clear(),t.append(n),null!==e&&n.select(),c(e)&&(e.format=0)}function ut(t,e=ft){return t.registerCommand(n,n=>(t.update(e),!0),i)}const ht=t({build:(t,e,n)=>dt(e),config:e({$onClear:ft}),name:\"@lexical/extension/ClearEditor\",register(t,e,n){const{$onClear:i}=n.getOutput();return ct(()=>ut(t,i.value))}});function lt(t){const e=new Set,n=new Set;for(const i of gt(t)){const t=\"function\"==typeof i?i:i.replace;e.add(t.getType()),n.add(t)}return{nodes:n,types:e}}function gt(t){return(\"function\"==typeof t.nodes?t.nodes():t.nodes)||[]}function pt(t,e){let n;return Q(t(),{unwatched(){n&&(n(),n=void 0)},watched(){this.value=t(),n=e(this)}})}const mt=t({build:t=>pt(()=>t.getEditorState(),e=>t.registerUpdateListener(t=>{e.value=t.editorState})),name:\"@lexical/extension/EditorState\"});function vt(t,...e){const n=new URL(\"https://lexical.dev/docs/error\"),i=new URLSearchParams;i.append(\"code\",t);for(const t of e)i.append(\"v\",t);throw n.search=i.toString(),Error(`Minified Lexical error #${t}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}function xt(t,e){if(t&&e&&!Array.isArray(e)&&\"object\"==typeof t&&\"object\"==typeof e){const n=t,i=e;for(const t in i)n[t]=xt(n[t],i[t]);return t}return e}const yt=0,St=1,Et=2,bt=3,wt=4,Nt=5,Ot=6,Rt=7;function Mt(t){return t.id===yt}function Ct(t){return t.id===Et}function Dt(t){return function(t){return t.id===St}(t)||vt(305,String(t.id),String(St)),Object.assign(t,{id:Et})}const _t=new Set;class It{builder;configs;_dependency;_peerNameSet;extension;state;_signal;constructor(t,e){this.builder=t,this.extension=e,this.configs=new Set,this.state={id:yt}}mergeConfigs(){let t=this.extension.config||{};const e=this.extension.mergeConfig?this.extension.mergeConfig.bind(this.extension):d;for(const n of this.configs)t=e(t,n);return t}init(t){const e=this.state;Ct(e)||vt(306,String(e.id));const n={getDependency:this.getInitDependency.bind(this),getDirectDependentNames:this.getDirectDependentNames.bind(this),getPeer:this.getInitPeer.bind(this),getPeerNameSet:this.getPeerNameSet.bind(this)},i={...n,getDependency:this.getDependency.bind(this),getInitResult:this.getInitResult.bind(this),getPeer:this.getPeer.bind(this)},o=function(t,e,n){return Object.assign(t,{config:e,id:bt,registerState:n})}(e,this.mergeConfigs(),n);let s;this.state=o,this.extension.init&&(s=this.extension.init(t,o.config,n)),this.state=function(t,e,n){return Object.assign(t,{id:wt,initResult:e,registerState:n})}(o,s,i)}build(t){const e=this.state;let n;e.id!==wt&&vt(307,String(e.id),String(Nt)),this.extension.build&&(n=this.extension.build(t,e.config,e.registerState));const i={...e.registerState,getOutput:()=>n,getSignal:this.getSignal.bind(this)};this.state=function(t,e,n){return Object.assign(t,{id:Nt,output:e,registerState:n})}(e,n,i)}register(t,e){this._signal=e;const n=this.state;n.id!==Nt&&vt(308,String(n.id),String(Nt));const i=this.extension.register&&this.extension.register(t,n.config,n.registerState);return this.state=function(t){return Object.assign(t,{id:Ot})}(n),()=>{const t=this.state;t.id!==Rt&&vt(309,String(n.id),String(Rt)),this.state=function(t){return Object.assign(t,{id:Nt})}(t),i&&i()}}afterRegistration(t){const e=this.state;let n;return e.id!==Ot&&vt(310,String(e.id),String(Ot)),this.extension.afterRegistration&&(n=this.extension.afterRegistration(t,e.config,e.registerState)),this.state=function(t){return Object.assign(t,{id:Rt})}(e),n}getSignal(){return void 0===this._signal&&vt(311),this._signal}getInitResult(){void 0===this.extension.init&&vt(312,this.extension.name);const t=this.state;return function(t){return t.id>=wt}(t)||vt(313,String(t.id),String(wt)),t.initResult}getInitPeer(t){const e=this.builder.extensionNameMap.get(t);return e?e.getExtensionInitDependency():void 0}getExtensionInitDependency(){const t=this.state;return function(t){return t.id>=bt}(t)||vt(314,String(t.id),String(bt)),{config:t.config}}getPeer(t){const e=this.builder.extensionNameMap.get(t);return e?e.getExtensionDependency():void 0}getInitDependency(t){const e=this.builder.getExtensionRep(t);return void 0===e&&vt(315,this.extension.name,t.name),e.getExtensionInitDependency()}getDependency(t){const e=this.builder.getExtensionRep(t);return void 0===e&&vt(315,this.extension.name,t.name),e.getExtensionDependency()}getState(){const t=this.state;return function(t){return t.id>=Rt}(t)||vt(316,String(t.id),String(Rt)),t}getDirectDependentNames(){return this.builder.incomingEdges.get(this.extension.name)||_t}getPeerNameSet(){let t=this._peerNameSet;return t||(t=new Set((this.extension.peerDependencies||[]).map(([t])=>t)),this._peerNameSet=t),t}getExtensionDependency(){if(!this._dependency){const t=this.state;(function(t){return t.id>=Nt})(t)||vt(317,this.extension.name),this._dependency={config:t.config,init:t.initResult,output:t.output}}return this._dependency}}const jt={tag:p};function At(){const t=o();t.isEmpty()&&t.append(r())}const Pt=t({config:e({setOptions:jt,updateOptions:jt}),init:({$initialEditorState:t=At})=>({$initialEditorState:t,initialized:!1}),afterRegistration(t,{updateOptions:e,setOptions:n},i){const o=i.getInitResult();if(!o.initialized){o.initialized=!0;const{$initialEditorState:i}=o;if(g(i))t.setEditorState(i,n);else if(\"function\"==typeof i)t.update(()=>{i(t)},e);else if(i&&(\"string\"==typeof i||\"object\"==typeof i)){const e=t.parseEditorState(i);t.setEditorState(e,n)}}return()=>{}},name:\"@lexical/extension/InitialState\",nodes:[a,f,u,h,l]}),Kt=Symbol.for(\"@lexical/extension/LexicalBuilder\");function kt(...t){return Tt.fromExtensions(t).buildEditor()}function $t(){}function zt(t){throw t}function Ut(t){return Array.isArray(t)?t:[t]}const Lt=\"0.40.0+prod.esm\";class Tt{roots;extensionNameMap;outgoingConfigEdges;incomingEdges;conflicts;_sortedExtensionReps;PACKAGE_VERSION;constructor(t){this.outgoingConfigEdges=new Map,this.incomingEdges=new Map,this.extensionNameMap=new Map,this.conflicts=new Map,this.PACKAGE_VERSION=Lt,this.roots=t;for(const e of t)this.addExtension(e)}static fromExtensions(t){const e=[Ut(Pt)];for(const n of t)e.push(Ut(n));return new Tt(e)}static maybeFromEditor(t){const e=t[Kt];return e&&(e.PACKAGE_VERSION!==Lt&&vt(292,e.PACKAGE_VERSION,Lt),e instanceof Tt||vt(293)),e}static fromEditor(t){const e=Tt.maybeFromEditor(t);return void 0===e&&vt(294),e}constructEditor(){const{$initialEditorState:t,onError:e,...n}=this.buildCreateEditorArgs(),i=Object.assign(m({...n,...e?{onError:t=>{e(t,i)}}:{}}),{[Kt]:this});for(const t of this.sortedExtensionReps())t.build(i);return i}buildEditor(){let t=$t;function e(){try{t()}finally{t=$t}}const n=Object.assign(this.constructEditor(),{dispose:e,[Symbol.dispose]:e});return t=v(this.registerEditor(n),()=>n.setRootElement(null)),n}hasExtensionByName(t){return this.extensionNameMap.has(t)}getExtensionRep(t){const e=this.extensionNameMap.get(t.name);if(e)return e.extension!==t&&vt(295,t.name),e}addEdge(t,e,n){const i=this.outgoingConfigEdges.get(t);i?i.set(e,n):this.outgoingConfigEdges.set(t,new Map([[e,n]]));const o=this.incomingEdges.get(e);o?o.add(t):this.incomingEdges.set(e,new Set([t]))}addExtension(t){void 0!==this._sortedExtensionReps&&vt(296);const e=Ut(t),[n]=e;\"string\"!=typeof n.name&&vt(297,typeof n.name);let i=this.extensionNameMap.get(n.name);if(void 0!==i&&i.extension!==n&&vt(298,n.name),!i){i=new It(this,n),this.extensionNameMap.set(n.name,i);const t=this.conflicts.get(n.name);\"string\"==typeof t&&vt(299,n.name,t);for(const t of n.conflictsWith||[])this.extensionNameMap.has(t)&&vt(299,n.name,t),this.conflicts.set(t,n.name);for(const t of n.dependencies||[]){const e=Ut(t);this.addEdge(n.name,e[0].name,e.slice(1)),this.addExtension(e)}for(const[t,e]of n.peerDependencies||[])this.addEdge(n.name,t,e?[e]:[])}}sortedExtensionReps(){if(this._sortedExtensionReps)return this._sortedExtensionReps;const t=[],e=(n,i)=>{let o=n.state;if(Ct(o))return;const s=n.extension.name;var r;Mt(o)||vt(300,s,i||\"[unknown]\"),Mt(r=o)||vt(304,String(r.id),String(yt)),o=Object.assign(r,{id:St}),n.state=o;const c=this.outgoingConfigEdges.get(s);if(c)for(const t of c.keys()){const n=this.extensionNameMap.get(t);n&&e(n,s)}o=Dt(o),n.state=o,t.push(n)};for(const t of this.extensionNameMap.values())Mt(t.state)&&e(t);for(const e of t)for(const[t,n]of this.outgoingConfigEdges.get(e.extension.name)||[])if(n.length>0){const e=this.extensionNameMap.get(t);if(e)for(const t of n)e.configs.add(t)}for(const[t,...e]of this.roots)if(e.length>0){const n=this.extensionNameMap.get(t.name);void 0===n&&vt(301,t.name);for(const t of e)n.configs.add(t)}return this._sortedExtensionReps=t,this._sortedExtensionReps}registerEditor(t){const e=this.sortedExtensionReps(),n=new AbortController,i=[()=>n.abort()],o=n.signal;for(const n of e){const e=n.register(t,o);e&&i.push(e)}for(const n of e){const e=n.afterRegistration(t);e&&i.push(e)}return v(...i)}buildCreateEditorArgs(){const t={},e=new Set,n=new Map,i=new Map,o={},s={},r=this.sortedExtensionReps();for(const c of r){const{extension:r}=c;if(void 0!==r.onError&&(t.onError=r.onError),void 0!==r.disableEvents&&(t.disableEvents=r.disableEvents),void 0!==r.parentEditor&&(t.parentEditor=r.parentEditor),void 0!==r.editable&&(t.editable=r.editable),void 0!==r.namespace&&(t.namespace=r.namespace),void 0!==r.$initialEditorState&&(t.$initialEditorState=r.$initialEditorState),r.nodes)for(const t of gt(r)){if(\"function\"!=typeof t){const e=n.get(t.replace);e&&vt(302,r.name,t.replace.name,e.extension.name),n.set(t.replace,c)}e.add(t)}if(r.html){if(r.html.export)for(const[t,e]of r.html.export.entries())i.set(t,e);r.html.import&&Object.assign(o,r.html.import)}r.theme&&xt(s,r.theme)}Object.keys(s).length>0&&(t.theme=s),e.size&&(t.nodes=[...e]);const c=Object.keys(o).length>0,d=i.size>0;(c||d)&&(t.html={},c&&(t.html.import=o),d&&(t.html.export=i));for(const e of r)e.init(t);return t.onError||(t.onError=zt),t}}function Bt(t,e){const n=Tt.fromEditor(t).getExtensionRep(e);return void 0===n&&vt(303,e.name),n.getExtensionDependency()}function Ft(t,e){const n=Tt.fromEditor(t).extensionNameMap.get(e);return n?n.getExtensionDependency():void 0}function Gt(t,e){const n=Ft(t,e);return void 0===n&&vt(291,e),n}const Vt=new Set,Wt=t({build(t,e,n){const i=n.getDependency(mt).output,o=Q({watchedNodeKeys:new Map}),r=pt(()=>{},()=>ct(()=>{const t=r.peek(),{watchedNodeKeys:e}=o.value;let n,c=!1;i.value.read(()=>{if(s())for(const[i,o]of e.entries()){if(0===o.size){e.delete(i);continue}const s=x(i),r=s&&s.isSelected()||!1;c=c||r!==(!!t&&t.has(i)),r&&(n=n||new Set,n.add(i))}}),!c&&n&&t&&n.size===t.size||(r.value=n)}));return{watchNodeKey:function(t){const e=nt(()=>(r.value||Vt).has(t)),{watchedNodeKeys:n}=o.peek();let i=n.get(t);const s=void 0!==i;return i=i||new Set,i.add(e),s||(n.set(t,i),o.value={watchedNodeKeys:n}),e}}},dependencies:[mt],name:\"@lexical/extension/NodeSelection\"}),Zt=y(\"INSERT_HORIZONTAL_RULE_COMMAND\");class Jt extends O{static getType(){return\"horizontalrule\"}static clone(t){return new Jt(t.__key)}static importJSON(t){return qt().updateFromJSON(t)}static importDOM(){return{hr:()=>({conversion:Ht,priority:0})}}exportDOM(){return{element:document.createElement(\"hr\")}}createDOM(t){const e=document.createElement(\"hr\");return R(e,t.theme.hr),e}getTextContent(){return\"\\n\"}isInline(){return!1}updateDOM(){return!1}}function Ht(){return{node:qt()}}function qt(){return S(Jt)}function Qt(t){return t instanceof Jt}const Xt=t({dependencies:[mt,Wt],name:\"@lexical/extension/HorizontalRule\",nodes:()=>[Jt],register(t,e,n){const{watchNodeKey:i}=n.getDependency(Wt).output,o=Q({nodeSelections:new Map}),r=t._config.theme.hrSelected??\"selected\";return v(t.registerCommand(E,t=>{if(b(t.target)){const e=w(t.target);if(Qt(e))return function(t,e=!1){const n=s(),i=t.isSelected(),o=t.getKey();let r;e&&M(n)?r=n:(r=C(),D(r)),i?r.delete(o):r.add(o)}(e,t.shiftKey),!0}return!1},N),t.registerMutationListener(Jt,(e,n)=>{B(()=>{let n=!1;const{nodeSelections:s}=o.peek();for(const[o,r]of e.entries())if(\"destroyed\"===r)s.delete(o),n=!0;else{const e=s.get(o),r=t.getElementByKey(o);e?e.domNode.value=r:(n=!0,s.set(o,{domNode:Q(r),selectedSignal:i(o)}))}n&&(o.value={nodeSelections:s})})}),ct(()=>{const t=[];for(const{domNode:e,selectedSignal:n}of o.value.nodeSelections.values())t.push(ct(()=>{const t=e.value;if(t){n.value?R(t,r):_(t,r)}}));return v(...t)}))}});function Yt(t,e){return v(t.registerCommand(I,e=>{const n=s();if(!c(n))return!1;e.preventDefault();const i=function(t){if(t.getNodes().filter(t=>k(t)&&t.canIndent()).length>0)return!0;const e=t.anchor,n=t.focus,i=n.isBefore(e)?n:e,o=i.getNode(),s=U(o);if(s.canIndent()){const t=s.getKey();let e=$();if(e.anchor.set(t,0,\"element\"),e.focus.set(t,0,\"element\"),e=z(e),e.anchor.is(i))return!0}return!1}(n)?e.shiftKey?j:A:P;return t.dispatchCommand(i,void 0)},i),t.registerCommand(A,()=>{const t=\"number\"==typeof e?e:e?e.peek():null;if(null==t)return!1;const n=s();if(!c(n))return!1;const i=n.getNodes().map(t=>U(t).getIndent());return Math.max(...i)+1>=t},K))}const te=t({build:(t,e,n)=>dt(e),config:e({disabled:!1,maxIndent:null}),name:\"@lexical/extension/TabIndentation\",register(t,e,n){const{disabled:i,maxIndent:o}=n.getOutput();return ct(()=>{if(!i.value)return Yt(t,o)})}});export{qt as $createHorizontalRuleNode,Qt as $isHorizontalRuleNode,at as AutoFocusExtension,ht as ClearEditorExtension,mt as EditorStateExtension,Xt as HorizontalRuleExtension,Jt as HorizontalRuleNode,Zt as INSERT_HORIZONTAL_RULE_COMMAND,Pt as InitialStateExtension,Tt as LexicalBuilder,Wt as NodeSelectionExtension,te as TabIndentationExtension,B as batch,kt as buildEditorFromExtensions,nt as computed,ct as effect,Bt as getExtensionDependencyFromEditor,lt as getKnownTypesAndNodes,Ft as getPeerDependencyFromEditor,Gt as getPeerDependencyFromEditorOrThrow,dt as namedSignals,ut as registerClearEditor,Yt as registerTabIndentation,Q as signal,V as untracked,pt as watchedSignal};\n","/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport{defineExtension as e}from\"lexical\";const r=e({name:\"@lexical/react/ReactProvider\"});export{r as ReactProviderExtension};\n","/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport{$getRoot as t,$isDecoratorNode as e,$isElementNode as n,$isParagraphNode as r,$isTextNode as i,TextNode as o,$createTextNode as l}from\"lexical\";function s(){return t().getTextContent()}function f(t,e=!0){if(t)return!1;let n=s();return e&&(n=n.trim()),\"\"===n}function u(t,e){return()=>f(t,e)}function c(o){if(!f(o,!1))return!1;const l=t().getChildren(),s=l.length;if(s>1)return!1;for(let t=0;tc(t)}function a(t,e){let r=t.getFirstChild(),o=0;t:for(;null!==r;){if(n(r)){const t=r.getFirstChild();if(null!==t){r=t;continue}}else if(i(r)){const t=r.getTextContentSize();if(o+t>e)return{node:r,offset:e-o};o+=t}const t=r.getNextSibling();if(null!==t){r=t;continue}let l=r.getParent();for(;null!==l;){const t=l.getNextSibling();if(null!==t){r=t;continue t}l=l.getParent()}break}return null}function d(t,...e){const n=new URL(\"https://lexical.dev/docs/error\"),r=new URLSearchParams;r.append(\"code\",t);for(const t of e)r.append(\"v\",t);throw n.search=r.toString(),Error(`Minified Lexical error #${t}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}function x(t,e,n,r){const s=t=>t instanceof n,f=t=>{const e=l(t.getTextContent());e.setFormat(t.getFormat()),t.replace(e)};return[t.registerNodeTransform(o,t=>{if(!t.isSimpleText())return;let n,o=t.getPreviousSibling(),l=t.getTextContent(),u=t;if(i(o)){const n=o.getTextContent(),r=e(n+l);if(s(o)){if(null===r||0!==(t=>t.getLatest().__mode)(o))return void f(o);{const e=r.end-n.length;if(e>0){const r=n+l.slice(0,e);if(o.select(),o.setTextContent(r),e===l.length)t.remove();else{const n=l.slice(e);t.setTextContent(n)}return}}}else if(null===r||r.start{const n=t.getTextContent(),r=e(n);if(null===r||0!==r.start)return void f(t);if(n.length>r.end)return void t.splitText(r.end);const o=t.getPreviousSibling();i(o)&&o.isTextEntity()&&(f(o),f(t));const l=t.getNextSibling();i(l)&&l.isTextEntity()&&(f(l),s(t)&&f(t))})]}export{c as $canShowPlaceholder,g as $canShowPlaceholderCurry,a as $findTextIntersectionFromCharacters,f as $isRootTextContentEmpty,u as $isRootTextContentEmptyCurry,s as $rootTextContent,x as registerLexicalTextEntity};\n","/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport{effect as e,namedSignals as t}from\"@lexical/extension\";import{defineExtension as n,safeCast as o,$getSelection as i,$isRangeSelection as a,$isTextNode as r}from\"lexical\";function s(e){const t=window.location.origin,n=n=>{if(n.origin!==t)return;const o=e.getRootElement();if(document.activeElement!==o)return;const s=n.data;if(\"string\"==typeof s){let t;try{t=JSON.parse(s)}catch(e){return}if(t&&\"nuanria_messaging\"===t.protocol&&\"request\"===t.type){const o=t.payload;if(o&&\"makeChanges\"===o.functionId){const t=o.args;if(t){const[o,s,d,c,g]=t;e.update(()=>{const e=i();if(a(e)){const t=e.anchor;let i=t.getNode(),a=0,l=0;if(r(i)&&o>=0&&s>=0&&(a=o,l=o+s,e.setTextNodeRange(i,a,i,l)),a===l&&\"\"===d||(e.insertRawText(d),i=t.getNode()),r(i)){a=c,l=c+g;const t=i.getTextContentSize();a=a>t?t:a,l=l>t?t:l,e.setTextNodeRange(i,a,i,l)}n.stopImmediatePropagation()}})}}}}};return window.addEventListener(\"message\",n,!0),()=>{window.removeEventListener(\"message\",n,!0)}}const d=n({build:(e,n,o)=>t(n),config:o({disabled:\"undefined\"==typeof window}),name:\"@lexical/dragon\",register:(t,n,o)=>e(()=>o.getOutput().disabled.value?void 0:s(t))});export{d as DragonExtension,s as registerDragonSupport};\n","/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport{useLexicalComposerContext as r}from\"@lexical/react/LexicalComposerContext\";import{useLexicalEditable as e}from\"@lexical/react/useLexicalEditable\";import{LexicalBuilder as t}from\"@lexical/extension\";import{ReactProviderExtension as o}from\"@lexical/react/ReactProviderExtension\";import{useLayoutEffect as n,useEffect as i,useState as c,useMemo as a,Suspense as l}from\"react\";import{flushSync as s,createPortal as u}from\"react-dom\";import{jsx as d,jsxs as f,Fragment as m}from\"react/jsx-runtime\";import{$canShowPlaceholderCurry as p}from\"@lexical/text\";import{mergeRegister as x}from\"@lexical/utils\";import{registerDragonSupport as E}from\"@lexical/dragon\";import{registerRichText as h}from\"@lexical/rich-text\";function g(r,...e){const t=new URL(\"https://lexical.dev/docs/error\"),o=new URLSearchParams;o.append(\"code\",r);for(const r of e)o.append(\"v\",r);throw t.search=o.toString(),Error(`Minified Lexical error #${r}; visit ${t.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}const y=\"undefined\"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement?n:i;function w({editor:r,ErrorBoundary:e}){return function(r,e){const[t,o]=c(()=>r.getDecorators());return y(()=>r.registerDecoratorListener(r=>{s(()=>{o(r)})}),[r]),i(()=>{o(r.getDecorators())},[r]),a(()=>{const o=[],n=Object.keys(t);for(let i=0;ir._onError(e),children:d(l,{fallback:null,children:t[c]})}),s=r.getElementByKey(c);null!==s&&o.push(u(a,s,c))}return o},[e,t,r])}(r,e)}function v({editor:r,ErrorBoundary:e}){return function(r){const e=t.maybeFromEditor(r);if(e&&e.hasExtensionByName(o.name)){for(const r of[\"@lexical/plain-text\",\"@lexical/rich-text\"])e.hasExtensionByName(r)&&g(320,r);return!0}return!1}(r)?null:d(w,{editor:r,ErrorBoundary:e})}function B(r){return r.getEditorState().read(p(r.isComposing()))}function L({contentEditable:e,placeholder:t=null,ErrorBoundary:o}){const[n]=r();return function(r){y(()=>x(h(r),E(r)),[r])}(n),f(m,{children:[e,d(b,{content:t}),d(v,{editor:n,ErrorBoundary:o})]})}function b({content:t}){const[o]=r(),n=function(r){const[e,t]=c(()=>B(r));return y(()=>{function e(){const e=B(r);t(e)}return e(),x(r.registerUpdateListener(()=>{e()}),r.registerEditableListener(()=>{e()}))},[r]),e}(o),i=e();return n?\"function\"==typeof t?t(i):t:null}export{L as RichTextPlugin};\n","/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport{useLexicalComposerContext as e}from\"@lexical/react/LexicalComposerContext\";import{useEffect as t}from\"react\";function o({defaultSelection:o}){const[l]=e();return t(()=>{l.focus(()=>{const e=document.activeElement,t=l.getRootElement();null===t||null!==e&&t.contains(e)||t.focus({preventScroll:!0})},{defaultSelection:o})},[o,l]),null}export{o as AutoFocusPlugin};\n","/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport{registerClearEditor as o}from\"@lexical/extension\";import{useLexicalComposerContext as e}from\"@lexical/react/LexicalComposerContext\";import{useLayoutEffect as n,useEffect as t}from\"react\";const i=\"undefined\"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement?n:t;function r({onClear:n}){const[t]=e();return i(()=>o(t,n),[t,n]),null}export{r as ClearEditorPlugin};\n","/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport{useLexicalComposerContext as e}from\"@lexical/react/LexicalComposerContext\";import{useLayoutEffect as t,useEffect as i,forwardRef as a,useState as r,useCallback as n,useMemo as o}from\"react\";import{jsx as l,jsxs as d,Fragment as c}from\"react/jsx-runtime\";import{$canShowPlaceholderCurry as s}from\"@lexical/text\";import{mergeRegister as u}from\"@lexical/utils\";const m=\"undefined\"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement?t:i;function f({editor:e,ariaActiveDescendant:t,ariaAutoComplete:i,ariaControls:a,ariaDescribedBy:d,ariaErrorMessage:c,ariaExpanded:s,ariaInvalid:u,ariaLabel:f,ariaLabelledBy:b,ariaMultiline:p,ariaOwns:x,ariaRequired:E,autoCapitalize:v,className:w,id:y,role:C=\"textbox\",spellCheck:g=!0,style:L,tabIndex:h,\"data-testid\":D,...I},R){const[k,q]=r(e.isEditable()),z=n(t=>{t&&t.ownerDocument&&t.ownerDocument.defaultView?e.setRootElement(t):e.setRootElement(null)},[e]),A=o(()=>function(...e){return t=>{for(const i of e)\"function\"==typeof i?i(t):null!=i&&(i.current=t)}}(R,z),[z,R]);return m(()=>(q(e.isEditable()),e.registerEditableListener(e=>{q(e)})),[e]),l(\"div\",{\"aria-activedescendant\":k?t:void 0,\"aria-autocomplete\":k?i:\"none\",\"aria-controls\":k?a:void 0,\"aria-describedby\":d,...null!=c?{\"aria-errormessage\":c}:{},\"aria-expanded\":k&&\"combobox\"===C?!!s:void 0,...null!=u?{\"aria-invalid\":u}:{},\"aria-label\":f,\"aria-labelledby\":b,\"aria-multiline\":p,\"aria-owns\":k?x:void 0,\"aria-readonly\":!k||void 0,\"aria-required\":E,autoCapitalize:v,className:w,contentEditable:k,\"data-testid\":D,id:y,ref:A,role:C,spellCheck:g,style:L,tabIndex:h,...I})}const b=a(f);function p(e){return e.getEditorState().read(s(e.isComposing()))}const x=a(E);function E(t,i){const{placeholder:a,...r}=t,[n]=e();return d(c,{children:[l(b,{editor:n,...r,ref:i}),null!=a&&l(v,{editor:n,content:a})]})}function v({content:e,editor:i}){const a=function(e){const[t,i]=r(()=>p(e));return m(()=>{function t(){const t=p(e);i(t)}return t(),u(e.registerUpdateListener(()=>{t()}),e.registerEditableListener(()=>{t()}))},[e]),t}(i),[n,o]=r(i.isEditable());if(t(()=>(o(i.isEditable()),i.registerEditableListener(e=>{o(e)})),[i]),!a)return null;let d=null;return\"function\"==typeof e?d=e(n):null!==e&&(d=e),null===d?null:l(\"div\",{\"aria-hidden\":!0,children:d})}export{x as ContentEditable,b as ContentEditableElement};\n","/**\n * This file was automatically generated on installation of the Shadcn/Lexical editor. The default\n * location of this file has been changed to integrate better with our project structure. Also,\n * modifications have been made to integrate with our codebase.\n *\n * Original file location: src/components/editor/editor-ui/content-editable.tsx\n *\n * Shadcn/Lexical Editor Documentation: https://shadcn-editor.vercel.app/docs/\n */\n\nimport { ReactNode } from 'react';\nimport { ContentEditable as LexicalContentEditable } from '@lexical/react/LexicalContentEditable';\n\ntype Props = {\n placeholder: string;\n className?: string;\n placeholderClassName?: string;\n};\n\nexport function ContentEditable({\n placeholder,\n className,\n placeholderClassName,\n}: Props): ReactNode {\n return (\n \n {placeholder}\n \n }\n />\n );\n}\n","/**\n * This file was automatically generated on installation of the Shadcn/Lexical editor. The default\n * location of this file has been changed to integrate better with our project structure.\n *\n * Original file location: src/components/editor/context/toolbar-context.tsx\n *\n * Shadcn/Lexical Editor Documentation: https://shadcn-editor.vercel.app/docs/\n */\n\n'use client';\n\nimport { createContext, ReactNode, useContext, useMemo } from 'react';\nimport { LexicalEditor } from 'lexical';\n\nconst Context = createContext<\n | {\n activeEditor: LexicalEditor;\n $updateToolbar: () => void;\n blockType: string;\n setBlockType: (blockType: string) => void;\n showModal: (title: string, showModal: (onClose: () => void) => ReactNode) => void;\n }\n | undefined\n>(undefined);\n\nexport function ToolbarContext({\n activeEditor,\n $updateToolbar,\n blockType,\n setBlockType,\n showModal,\n children,\n}: {\n activeEditor: LexicalEditor;\n $updateToolbar: () => void;\n blockType: string;\n setBlockType: (blockType: string) => void;\n showModal: (title: string, showModal: (onClose: () => void) => ReactNode) => void;\n children: ReactNode;\n}) {\n const contextValue = useMemo(\n () => ({\n activeEditor,\n $updateToolbar,\n blockType,\n setBlockType,\n showModal,\n }),\n [activeEditor, $updateToolbar, blockType, setBlockType, showModal],\n );\n\n return {children};\n}\n\nexport function useToolbarContext() {\n const context = useContext(Context);\n if (!context) {\n throw new Error('useToolbarContext must be used within a ToolbarContext provider');\n }\n return context;\n}\n","/**\n * This file was automatically generated on installation of the Shadcn/Lexical editor. The default\n * location of this file has been changed to integrate better with our project structure.\n *\n * Original file location: src/components/editor/editor-hooks/use-modal.tsx\n *\n * Shadcn/Lexical Editor Documentation: https://shadcn-editor.vercel.app/docs/\n */\n\nimport { ReactNode, useCallback, useMemo, useState } from 'react';\n\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/shadcn-ui/dialog';\n\nexport function useEditorModal(): [\n ReactNode | undefined,\n (title: string, showModal: (onClose: () => void) => ReactNode) => void,\n] {\n const [modalContent, setModalContent] = useState<\n | {\n closeOnClickOutside: boolean;\n content: ReactNode;\n title: string;\n }\n | undefined\n >(undefined);\n\n const onClose = useCallback(() => {\n setModalContent(undefined);\n }, []);\n\n const modal = useMemo(() => {\n if (modalContent === undefined) {\n return undefined;\n }\n const { title, content } = modalContent;\n return (\n \n \n \n {title}\n \n {content}\n \n \n );\n }, [modalContent, onClose]);\n\n const showModal = useCallback(\n (\n title: string,\n getContent: (onClose: () => void) => ReactNode,\n closeOnClickOutside = false,\n ) => {\n setModalContent({\n closeOnClickOutside,\n content: getContent(onClose),\n title,\n });\n },\n [onClose],\n );\n\n return [modal, showModal];\n}\n","/**\n * This file was automatically generated on installation of the Shadcn/Lexical editor. The default\n * location of this file has been changed to integrate better with our project structure.\n *\n * Original file location: src/components/editor/plugins/toolbar/toolbar-plugin.tsx\n *\n * Shadcn/Lexical Editor Documentation: https://shadcn-editor.vercel.app/docs/\n *\n * Documentation for the Toolbar Plugin-framework:\n * https://shadcn-editor.vercel.app/docs/plugins/toolbar\n */\n\nimport { ReactNode, useEffect, useState } from 'react';\nimport { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';\nimport { COMMAND_PRIORITY_CRITICAL, SELECTION_CHANGE_COMMAND } from 'lexical';\n\nimport { ToolbarContext } from '@/components/advanced/editor/context/toolbar-context';\nimport { useEditorModal } from '@/components/advanced/editor/editor-hooks/use-modal';\n\nexport function ToolbarPlugin({\n children,\n}: {\n children: (props: { blockType: string }) => ReactNode;\n}) {\n const [editor] = useLexicalComposerContext();\n\n const [activeEditor, setActiveEditor] = useState(editor);\n const [blockType, setBlockType] = useState('paragraph');\n\n const [modal, showModal] = useEditorModal();\n\n const $updateToolbar = () => {};\n\n useEffect(() => {\n return activeEditor.registerCommand(\n SELECTION_CHANGE_COMMAND,\n (_payload, newEditor) => {\n setActiveEditor(newEditor);\n return false;\n },\n COMMAND_PRIORITY_CRITICAL,\n );\n }, [activeEditor]);\n\n return (\n \n {modal}\n\n {children({ blockType })}\n \n );\n}\n","/**\n * This file was automatically generated on installation of the Shadcn/Lexical editor. The default\n * location of this file has been changed to integrate better with our project structure.\n *\n * Original file location: src/components/editor/editor-hooks/use-update-toolbar.ts\n *\n * Shadcn/Lexical Editor Documentation: https://shadcn-editor.vercel.app/docs/\n */\n\nimport { useEffect } from 'react';\nimport { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';\nimport {\n $getSelection,\n BaseSelection,\n COMMAND_PRIORITY_CRITICAL,\n SELECTION_CHANGE_COMMAND,\n} from 'lexical';\n\nimport { useToolbarContext } from '@/components/advanced/editor/context/toolbar-context';\n\nexport function useUpdateToolbarHandler(callback: (selection: BaseSelection) => void) {\n const [editor] = useLexicalComposerContext();\n const { activeEditor } = useToolbarContext();\n\n useEffect(() => {\n return activeEditor.registerCommand(\n SELECTION_CHANGE_COMMAND,\n () => {\n const selection = $getSelection();\n if (selection) {\n callback(selection);\n }\n return false;\n },\n COMMAND_PRIORITY_CRITICAL,\n );\n /**\n * We use `editor` (not `activeEditor`) in the dependency array because `activeEditor` can\n * change frequently. Re-registering the command on every `activeEditor` change would be\n * unnecessary. We only need to re-register when the main editor instance or callback changes.\n */\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editor, callback]);\n\n useEffect(() => {\n activeEditor.getEditorState().read(() => {\n const selection = $getSelection();\n if (selection) {\n callback(selection);\n }\n });\n }, [activeEditor, callback]);\n}\n","import React from 'react';\nimport * as TogglePrimitive from '@radix-ui/react-toggle';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/utils/shadcn-ui.util';\n\nconst toggleVariants = cva(\n 'pr-twp tw-inline-flex tw-items-center tw-justify-center tw-rounded-md tw-text-sm tw-font-medium tw-ring-offset-background tw-transition-colors hover:tw-bg-muted hover:tw-text-muted-foreground focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-pointer-events-none disabled:tw-opacity-50 data-[state=on]:tw-bg-accent data-[state=on]:tw-text-accent-foreground',\n {\n variants: {\n variant: {\n default: 'tw-bg-transparent',\n outline:\n 'tw-border tw-border-input tw-bg-transparent hover:tw-bg-accent hover:tw-text-accent-foreground',\n },\n size: {\n default: 'tw-h-10 tw-px-3',\n sm: 'tw-h-9 tw-px-2.5',\n lg: 'tw-h-11 tw-px-5',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n);\n\nconst Toggle = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & VariantProps\n>(({ className, variant, size, ...props }, ref) => (\n \n));\n\nToggle.displayName = TogglePrimitive.Root.displayName;\n\nexport { Toggle, toggleVariants };\n","import React from 'react';\nimport * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';\nimport { type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { toggleVariants } from '@/components/shadcn-ui/toggle';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\n\n/** @inheritdoc ToggleGroup */\nconst ToggleGroupContext = React.createContext>({\n size: 'default',\n variant: 'default',\n});\n\n/**\n * ToggleGroup components provide a set of two-state buttons that can be toggled on or off. These\n * components are built on Radix UI primitives and styled with Shadcn UI. See Shadcn UI\n * Documentation: https://ui.shadcn.com/docs/components/toggle-group See Radix UI Documentation:\n * https://www.radix-ui.com/primitives/docs/components/toggle-group\n */\nconst ToggleGroup = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef &\n VariantProps\n>(({ className, variant, size, children, ...props }, ref) => {\n const dir: Direction = readDirection();\n return (\n \n \n {children}\n \n \n );\n});\n\nToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;\n\n/** @inheritdoc ToggleGroup */\nconst ToggleGroupItem = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef &\n VariantProps\n>(({ className, children, variant, size, ...props }, ref) => {\n const context = React.useContext(ToggleGroupContext);\n\n return (\n \n {children}\n \n );\n});\n\nToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;\n\nexport { ToggleGroup, ToggleGroupItem };\n","/**\n * This file was automatically generated on installation of the Shadcn/Lexical editor. The default\n * location of this file has been changed to integrate better with our project structure.\n *\n * Original file location: src/components/editor/plugins/toolbar/font-format-toolbar-plugin.tsx\n *\n * Shadcn/Lexical Editor Documentation: https://shadcn-editor.vercel.app/docs/\n *\n * Documentation for this specific plugin:\n * https://shadcn-editor.vercel.app/docs/plugins/toolbar/font-format-toolbar\n */\n\nimport { useCallback, useState } from 'react';\nimport { $isTableSelection } from '@lexical/table';\nimport { $isRangeSelection, BaseSelection, FORMAT_TEXT_COMMAND } from 'lexical';\nimport { BoldIcon, ItalicIcon, StrikethroughIcon, UnderlineIcon } from 'lucide-react';\n\nimport { useToolbarContext } from '@/components/advanced/editor/context/toolbar-context';\nimport { useUpdateToolbarHandler } from '@/components/advanced/editor/editor-hooks/use-update-toolbar';\nimport { ToggleGroup, ToggleGroupItem } from '@/components/shadcn-ui/toggle-group';\n\nconst FORMATS = [\n { format: 'bold', icon: BoldIcon, label: 'Bold' },\n { format: 'italic', icon: ItalicIcon, label: 'Italic' },\n { format: 'underline', icon: UnderlineIcon, label: 'Underline' },\n { format: 'strikethrough', icon: StrikethroughIcon, label: 'Strikethrough' },\n] as const;\n\nexport function FontFormatToolbarPlugin() {\n const { activeEditor } = useToolbarContext();\n const [activeFormats, setActiveFormats] = useState([]);\n\n const $updateToolbar = useCallback((selection: BaseSelection) => {\n if ($isRangeSelection(selection) || $isTableSelection(selection)) {\n const formats: string[] = [];\n FORMATS.forEach(({ format }) => {\n if (selection.hasFormat(format)) {\n formats.push(format);\n }\n });\n setActiveFormats((prev) => {\n // Only update if formats have changed\n if (prev.length !== formats.length || !formats.every((f) => prev.includes(f))) {\n return formats;\n }\n return prev;\n });\n }\n }, []);\n\n useUpdateToolbarHandler($updateToolbar);\n\n return (\n \n {FORMATS.map(({ format, icon: Icon, label }) => (\n {\n activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, format);\n }}\n >\n \n \n ))}\n \n );\n}\n","/**\n * This file was automatically generated on installation of the Shadcn/Lexical editor. The default\n * location of this file has been changed to integrate better with our project structure. Also,\n * modifications have been made to integrate with our codebase.\n *\n * Original file location: src/components/blocks/editor-00/plugins.tsx\n *\n * Shadcn/Lexical Editor Documentation: https://shadcn-editor.vercel.app/docs/\n */\n\nimport { useEffect, useState } from 'react';\nimport { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';\nimport { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';\nimport { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin';\nimport { ClearEditorPlugin } from '@lexical/react/LexicalClearEditorPlugin';\nimport { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';\nimport { CLEAR_EDITOR_COMMAND } from 'lexical';\n\nimport { ContentEditable } from '@/components/advanced/editor/editor-ui/content-editable';\nimport { ToolbarPlugin } from '@/components/advanced/editor/plugins/toolbar/toolbar-plugin';\nimport { FontFormatToolbarPlugin } from '@/components/advanced/editor/plugins/toolbar/font-format-toolbar-plugin';\n\nfunction ClearEditorBridge({ onClear }: { onClear?: (clearFn: () => void) => void }) {\n const [editor] = useLexicalComposerContext();\n\n useEffect(() => {\n if (onClear) {\n onClear(() => {\n editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined);\n });\n }\n }, [editor, onClear]);\n\n return undefined;\n}\n\nexport function Plugins({\n placeholder = 'Start typing ...',\n autoFocus = false,\n onClear,\n}: {\n placeholder?: string;\n autoFocus?: boolean;\n onClear?: (clearFn: () => void) => void;\n}) {\n const [, setFloatingAnchorElem] = useState(undefined);\n\n const onRef = (_floatingAnchorElem: HTMLDivElement) => {\n if (_floatingAnchorElem !== undefined) {\n setFloatingAnchorElem(_floatingAnchorElem);\n }\n };\n\n return (\n
\n {/* toolbar plugins */}\n \n {() => (\n
\n \n
\n )}\n
\n\n
\n \n \n
\n }\n ErrorBoundary={LexicalErrorBoundary}\n />\n {autoFocus && }\n\n \n \n {/* editor plugins */}\n
\n {/* actions plugins */}\n \n );\n}\n","/**\n * This file was automatically generated on installation of the Shadcn/Lexical editor. The default\n * location of this file has been changed to integrate better with our project structure. Also,\n * modifications have been made to integrate with our codebase.\n *\n * Original file location: src/components/blocks/editor-00/editor.tsx\n *\n * Shadcn/Lexical Editor Documentation: https://shadcn-editor.vercel.app/docs/\n */\n\nimport { InitialConfigType, LexicalComposer } from '@lexical/react/LexicalComposer';\nimport { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';\nimport { EditorState, SerializedEditorState } from 'lexical';\n\nimport { editorTheme } from '@/components/advanced/editor/themes/editor-theme';\nimport { TooltipProvider } from '@/components/shadcn-ui/tooltip';\nimport { cn } from '@/utils/shadcn-ui.util';\n\nimport { nodes } from './nodes';\nimport { Plugins } from './plugins';\n\nconst editorConfig: InitialConfigType = {\n namespace: 'commentEditor',\n theme: editorTheme,\n nodes,\n onError: (error: Error) => {\n console.error(error);\n },\n};\n\n/**\n * Shadcn UI based Lexical Editor component\n *\n * Documentation: https://shadcn-editor.vercel.app/docs/\n */\nexport function Editor({\n editorState,\n editorSerializedState,\n onChange,\n onSerializedChange,\n placeholder = 'Start typingโ€ฆ',\n autoFocus = false,\n onClear,\n className,\n}: {\n editorState?: EditorState;\n editorSerializedState?: SerializedEditorState;\n onChange?: (editorState: EditorState) => void;\n onSerializedChange?: (editorSerializedState: SerializedEditorState) => void;\n placeholder?: string;\n autoFocus?: boolean;\n onClear?: (clearFn: () => void) => void;\n className?: string;\n}) {\n return (\n // CUSTOM: Added `className` prop\n \n \n \n \n\n {\n onChange?.(latestEditorState);\n onSerializedChange?.(latestEditorState.toJSON());\n }}\n />\n \n \n \n );\n}\n","/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport{$sliceSelectedTextNodeContent as e}from\"@lexical/selection\";import{isHTMLElement as n,isBlockDomNode as t}from\"@lexical/utils\";import{isDOMDocumentNode as o,$getRoot as l,$isElementNode as r,$isTextNode as i,getRegisteredNode as s,isDocumentFragment as c,$isRootOrShadowRoot as u,$isBlockElementNode as f,$createLineBreakNode as a,ArtificialNode__DO_NOT_USE as d,isInlineDomNode as p,$createParagraphNode as h}from\"lexical\";function m(e,n){const t=o(n)?n.body.childNodes:n.childNodes;let l=[];const r=[];for(const n of t)if(!w.has(n.nodeName)){const t=y(n,e,r,!1);null!==t&&(l=l.concat(t))}return function(e){for(const n of e)n.getNextSibling()instanceof d&&n.insertAfter(a());for(const n of e){const e=n.getChildren();for(const t of e)n.insertBefore(t);n.remove()}}(r),l}function g(e,n){if(\"undefined\"==typeof document||\"undefined\"==typeof window&&void 0===global.window)throw new Error(\"To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom before calling this function.\");const t=document.createElement(\"div\"),o=l().getChildren();for(let l=0;l{const e=new d;return o.push(e),e}:h)),null==m?v.length>0?c=c.concat(v):t(e)&&function(e){if(null==e.nextSibling||null==e.previousSibling)return!1;return p(e.nextSibling)&&p(e.previousSibling)}(e)&&(c=c.concat(a())):r(m)&&m.append(...v),c}function C(e,n,t){const o=e.style.textAlign,l=[];let r=[];for(let e=0;e('[contenteditable=\"true\"]');\n if (!contentEditableField) return false;\n\n contentEditableField.focus();\n\n // Move cursor to the end\n const selection = window.getSelection();\n const range = document.createRange();\n range.selectNodeContents(contentEditableField);\n range.collapse(false); // false = collapse to end\n selection?.removeAllRanges();\n selection?.addRange(range);\n\n return true;\n}\n\n/**\n * Recursively check if any children have meaningful editor content\n *\n * @param children - Array of serialized lexical nodes to check\n * @returns True if any child has content, false otherwise\n */\nfunction doChildrenHaveEditorContent(\n children: (SerializedLexicalNode | SerializedElementNode | SerializedTextNode)[] | undefined,\n): boolean {\n if (!children) return false;\n\n return children.some(\n (child: SerializedLexicalNode | SerializedElementNode | SerializedTextNode) => {\n if (child && 'text' in child && child.text.trim().length > 0) return true;\n\n if (!child || !('children' in child)) return false;\n\n return doChildrenHaveEditorContent(child.children);\n },\n );\n}\n\n/**\n * Check if the editor state has any meaningful content\n *\n * @param editorState - SerializedEditorState to check\n * @returns True if the editor has content, false if it's empty\n */\nexport function hasEditorContent(editorState: SerializedEditorState | undefined): boolean {\n if (!editorState?.root?.children) return false;\n return doChildrenHaveEditorContent(editorState.root.children);\n}\n\n/**\n * Convert HTML string to Lexical SerializedEditorState\n *\n * @param html - HTML string to convert\n * @returns SerializedEditorState that can be used with the Editor component\n */\nexport function htmlToEditorState(html: string): SerializedEditorState {\n if (!html || html.trim() === '') {\n throw new Error('Input HTML is empty');\n }\n\n const editor = createHeadlessEditor({\n namespace: 'EditorUtils',\n theme: editorTheme,\n nodes,\n onError: (error: Error) => {\n console.error(error);\n },\n });\n\n let serializedState: SerializedEditorState | undefined;\n\n editor.update(\n () => {\n const parser = new DOMParser();\n const dom = parser.parseFromString(html, 'text/html');\n const generatedNodes = $generateNodesFromDOM(editor, dom);\n\n $getRoot().clear();\n $insertNodes(generatedNodes);\n },\n {\n discrete: true,\n },\n );\n\n editor.getEditorState().read(() => {\n serializedState = editor.getEditorState().toJSON();\n });\n\n if (!serializedState) {\n throw new Error('Failed to convert HTML to editor state');\n }\n\n return serializedState;\n}\n\n/**\n * Convert Lexical SerializedEditorState to HTML string\n *\n * @param editorState - SerializedEditorState to convert\n * @returns HTML string\n */\nexport function editorStateToHtml(editorState: SerializedEditorState): string {\n const editor = createHeadlessEditor({\n namespace: 'EditorUtils',\n theme: editorTheme,\n nodes,\n onError: (error: Error) => {\n console.error(error);\n },\n });\n\n const parsedEditorState = editor.parseEditorState(JSON.stringify(editorState));\n editor.setEditorState(parsedEditorState);\n\n let html = '';\n\n editor.getEditorState().read(() => {\n html = $generateHtmlFromNodes(editor);\n });\n\n // Clean up the HTML to remove Shadcn/Lexical-specific attributes and simplify structure\n html = html\n // Remove style attributes\n .replace(/\\s+style=\"[^\"]*\"/g, '')\n // Remove all class attributes (including Tailwind classes)\n .replace(/\\s+class=\"[^\"]*\"/g, '')\n // Remove empty spans\n .replace(/(.*?)<\\/span>/g, '$1')\n // Simplify nested bold tags (e.g., text -> text)\n .replace(/]*>(.*?)<\\/strong><\\/b>/g, '$1')\n .replace(/]*>(.*?)<\\/b><\\/strong>/g, '$1')\n // Simplify nested italic tags (e.g., text -> text)\n .replace(/]*>(.*?)<\\/em><\\/i>/g, '$1')\n .replace(/]*>(.*?)<\\/i><\\/em>/g, '$1')\n // Simplify nested underline tags (e.g., text -> text)\n .replace(/]*>(.*?)<\\/span><\\/u>/g, '$1')\n // Simplify nested strikethrough tags (e.g., text -> text)\n .replace(/]*>(.*?)<\\/span><\\/s>/g, '$1')\n // Convert all
variants to XML-compatible
for Paratext\n .replace(//gi, '
');\n\n return html;\n}\n\n/**\n * Handle keyboard events for editor navigation to prevent parent listbox from intercepting\n * navigation keys. This should be used on a container wrapping the Editor component.\n *\n * @param event - The keyboard event\n * @returns True if the event was handled (and should be stopped from propagating), false otherwise\n */\nexport function handleEditorKeyNavigation(event: React.KeyboardEvent): boolean {\n // Keys that should be kept within the editor for navigation\n const navigationKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End'];\n\n if (navigationKeys.includes(event.key)) {\n event.stopPropagation();\n return true;\n }\n\n return false;\n}\n","import { Editor } from '@/components/advanced/editor/editor';\nimport {\n editorStateToHtml,\n focusContentEditable,\n handleEditorKeyNavigation,\n hasEditorContent,\n} from '@/components/advanced/editor/editor-utils';\nimport { Button } from '@/components/shadcn-ui/button';\nimport { Command, CommandItem, CommandList } from '@/components/shadcn-ui/command';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/shadcn-ui/popover';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@/components/shadcn-ui/tooltip';\nimport {\n SerializedEditorState,\n SerializedElementNode,\n SerializedParagraphNode,\n SerializedTextNode,\n} from 'lexical';\nimport { AtSign, Check, X } from 'lucide-react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { CommentEditorLocalizedStrings } from './comment-editor.types';\n\nconst initialValue: SerializedEditorState<\n SerializedParagraphNode & SerializedElementNode\n> = {\n root: {\n children: [\n {\n children: [\n {\n detail: 0,\n format: 0,\n mode: 'normal',\n style: '',\n text: '',\n type: 'text',\n version: 1,\n },\n ],\n direction: 'ltr',\n format: '',\n indent: 0,\n type: 'paragraph',\n version: 1,\n textFormat: 0,\n textStyle: '',\n },\n ],\n direction: 'ltr',\n format: '',\n indent: 0,\n type: 'root',\n version: 1,\n },\n};\n\n/** Interface containing the types of the properties that are passed to the `CommentEditor` */\nexport interface CommentEditorProps {\n /** List of users that can be assigned to the new comment thread */\n assignableUsers: string[];\n /**\n * External function to handle saving the new comment\n *\n * @param contents HTML content of the comment\n * @param assignedUser Optional user to assign the comment to\n */\n onSave: (contents: string, assignedUser?: string) => void;\n /**\n * External function to handle closing the comment editor. Gets called when the editor is closed\n * without saving changes\n */\n onClose: () => void;\n /** Localized strings to be passed to the comment editor component */\n localizedStrings: CommentEditorLocalizedStrings;\n}\n\n/**\n * Gets the display name for an assigned user\n *\n * @param user The user identifier (empty string for unassigned, \"Team\" for team, or username)\n * @param localizedStrings Localized strings for special values\n * @returns The display name for the user\n */\nfunction getAssignedUserDisplayName(\n user: string,\n localizedStrings: CommentEditorLocalizedStrings,\n): string {\n if (user === '') {\n return localizedStrings['%commentEditor_unassigned%'] ?? 'Unassigned';\n }\n if (user === 'Team') {\n return localizedStrings['%commentEditor_team%'] ?? 'Team';\n }\n return user;\n}\n\n/**\n * Component to create a new project comment from within the scripture editor\n *\n * @param CommentEditorProps - The properties for the comment editor component\n */\nexport default function CommentEditor({\n assignableUsers,\n onSave,\n onClose,\n localizedStrings,\n}: CommentEditorProps) {\n const [editorState, setEditorState] = useState(initialValue);\n const [selectedUser, setSelectedUser] = useState(undefined);\n const [isAssignPopoverOpen, setIsAssignPopoverOpen] = useState(false);\n const clearEditorRef = useRef<(() => void) | undefined>(undefined);\n\n // Using null for React ref compatibility\n // eslint-disable-next-line no-null/no-null\n const editorContainerRef = useRef(null);\n\n // Focus the editor after a delay to allow any closing popover/dropdown to finish\n useEffect(() => {\n let isMounted = true;\n const container = editorContainerRef.current;\n if (!container) return undefined;\n\n const timeoutId = setTimeout(() => {\n if (!isMounted) return;\n focusContentEditable(container);\n }, 300);\n\n return () => {\n isMounted = false;\n clearTimeout(timeoutId);\n };\n }, []);\n\n const handleSave = useCallback(() => {\n if (!hasEditorContent(editorState)) return;\n\n const contents = editorStateToHtml(editorState);\n onSave(contents, selectedUser);\n }, [editorState, onSave, selectedUser]);\n\n const placeholder =\n localizedStrings['%commentEditor_placeholder%'] ?? 'Type your comment here...';\n const saveTooltip = localizedStrings['%commentEditor_saveButton_tooltip%'] ?? 'Save comment';\n const cancelTooltip = localizedStrings['%commentEditor_cancelButton_tooltip%'] ?? 'Cancel';\n const assignToLabel = localizedStrings['%commentEditor_assignTo_label%'] ?? 'Assign to';\n\n return (\n
\n
\n {assignToLabel}\n
\n \n \n \n \n \n \n

{cancelTooltip}

\n
\n
\n
\n \n \n \n \n \n \n \n \n

{saveTooltip}

\n
\n
\n
\n
\n
\n\n
\n \n \n \n \n \n {selectedUser !== undefined\n ? getAssignedUserDisplayName(selectedUser, localizedStrings)\n : getAssignedUserDisplayName('', localizedStrings)}\n \n \n \n {\n if (e.key === 'Escape') {\n e.stopPropagation();\n setIsAssignPopoverOpen(false);\n }\n }}\n >\n \n \n {assignableUsers.map((user) => (\n {\n setSelectedUser(user === '' ? undefined : user);\n setIsAssignPopoverOpen(false);\n }}\n className=\"tw-flex tw-items-center\"\n >\n {getAssignedUserDisplayName(user, localizedStrings)}\n \n ))}\n \n \n \n \n
\n\n {\n if (e.key === 'Escape') {\n e.preventDefault();\n e.stopPropagation();\n onClose();\n } else if (e.key === 'Enter') {\n const isMac = /Macintosh/i.test(navigator.userAgent);\n if ((isMac && e.metaKey) || (!isMac && e.ctrlKey)) {\n e.preventDefault();\n e.stopPropagation();\n if (hasEditorContent(editorState)) {\n handleSave();\n }\n }\n }\n }}\n onKeyDown={(e) => {\n handleEditorKeyNavigation(e);\n if (e.key === 'Enter' || e.key === ' ') {\n e.stopPropagation();\n }\n }}\n >\n setEditorState(value)}\n placeholder={placeholder}\n onClear={(clearFn) => {\n clearEditorRef.current = clearFn;\n }}\n />\n
\n \n );\n}\n","/**\n * Object containing all keys used for localization in the CommentEditor component. If you're using\n * this component in an extension, you can pass it into the useLocalizedStrings hook to easily\n * obtain the localized strings and pass them into the localizedStrings prop of this component\n */\nexport const COMMENT_EDITOR_STRING_KEYS = Object.freeze([\n '%commentEditor_placeholder%',\n '%commentEditor_saveButton_tooltip%',\n '%commentEditor_cancelButton_tooltip%',\n '%commentEditor_assignTo_label%',\n '%commentEditor_unassigned%',\n '%commentEditor_team%',\n] as const);\n\n/** Localized strings needed for the comment editor component */\nexport type CommentEditorLocalizedStrings = {\n [localizedKey in (typeof COMMENT_EDITOR_STRING_KEYS)[number]]?: string;\n};\n","import {\n CommentStatus,\n LanguageStrings,\n LegacyComment,\n LegacyCommentThread,\n LocalizeKey,\n} from 'platform-bible-utils';\n\n/** Options for adding a comment to a thread */\nexport type AddCommentToThreadOptions = {\n /** The ID of the thread to add the comment to */\n threadId: string;\n /** The content of the comment (optional - can be omitted when only changing status or assignment) */\n contents?: string;\n /** Status to set on the thread ('Resolved' or 'Todo') */\n status?: CommentStatus;\n /** User to assign to the thread. Use \"\" for unassigned, \"Team\" for team assignment. */\n assignedUser?: string;\n};\n\n/**\n * Object containing all keys used for localization in the CommentList component. If you're using\n * this component in an extension, you can pass it into the useLocalizedStrings hook to easily\n * obtain the localized strings and pass them into the localizedStrings prop of this component\n */\nexport const COMMENT_LIST_STRING_KEYS: LocalizeKey[] = [\n '%comment_assign_team%',\n '%comment_assign_unassigned%',\n '%comment_assigned_to%',\n '%comment_assigning_to%',\n '%comment_dateAtTime%',\n '%comment_date_today%',\n '%comment_date_yesterday%',\n '%comment_deleteComment%',\n '%comment_editComment%',\n '%comment_replyOrAssign%',\n '%comment_reopenResolved%',\n '%comment_status_resolved%',\n '%comment_status_todo%',\n '%comment_thread_multiple_replies%',\n '%comment_thread_single_reply%',\n];\n\n/** Type definition for the localized strings used in the CommentList component */\nexport type CommentListLocalizedStrings = {\n [localizedKey in (typeof COMMENT_LIST_STRING_KEYS)[number]]?: string;\n};\n\n/** Props for the CommentList component */\nexport interface CommentListProps {\n /** Additional class name for the component */\n className?: string;\n /** Class name to apply to the display of the verse text for the first comment in the thread */\n classNameForVerseText?: string;\n /** Comment threads to render */\n threads: LegacyCommentThread[];\n /** Name of the current user, retrieved from the current user's Paratext Registry user information */\n currentUser: string;\n /** Localized strings for the component */\n localizedStrings: LanguageStrings;\n /**\n * Externally controlled selected thread ID. When provided, this will be used as the selected\n * thread instead of internal state. The parent component is responsible for updating this value\n * when the selection changes.\n */\n selectedThreadId?: string;\n /**\n * Callback when the selected thread changes. Called when a thread is selected via click or\n * keyboard navigation. Parent components can use this to sync their state with the internal\n * selection.\n */\n onSelectedThreadChange?: (threadId: string | undefined) => void;\n /**\n * Handler for adding a comment to a thread. This unified handler supports:\n *\n * - Adding a comment (provide contents)\n * - Resolving/unresolving a thread (provide status: 'Resolved' or 'Todo')\n * - Assigning a user (provide assignedUser)\n * - Any combination of the above\n *\n * If successful, returns the auto-generated comment ID (format: \"threadId/userName/date\").\n * Otherwise, returns undefined.\n */\n handleAddCommentToThread: (options: AddCommentToThreadOptions) => Promise;\n /** Handler for updating a comment's content */\n handleUpdateComment: (commentId: string, contents: string) => Promise;\n /** Handler for deleting a comment */\n handleDeleteComment: (commentId: string) => Promise;\n /** Handler for updating a thread's read status */\n handleReadStatusChange: (threadId: string, markRead: boolean) => Promise;\n /**\n * Users that can be assigned to threads. Includes special values: \"Team\" for team assignment, \"\"\n * (empty string) for unassigned.\n */\n assignableUsers?: string[];\n /**\n * Whether the current user can add comments to existing threads in this project. When false, UI\n * elements for adding comments to threads should be hidden or disabled.\n */\n canUserAddCommentToThread?: boolean;\n /**\n * Callback to check if the current user can assign a specific thread. Returns a promise that\n * resolves to true if the user can assign the thread, false otherwise.\n */\n canUserAssignThreadCallback?: (threadId: string) => Promise;\n /**\n * Callback to check if the current user can resolve or re-open a specific thread. Returns a\n * promise that resolves to true if the user can resolve the thread, false otherwise.\n */\n canUserResolveThreadCallback?: (threadId: string) => Promise;\n /**\n * Callback to check if the current user can edit or delete a specific comment. Returns a promise\n * that resolves to true if the user can edit or delete the comment, false otherwise.\n */\n canUserEditOrDeleteCommentCallback?: (commentId: string) => Promise;\n /**\n * Callback when the user clicks a verse reference in a comment thread. The related project editor\n * web view can navigate and position the editor cursor at the start of the comment inside the\n * verse.\n */\n onVerseRefClick?: (thread: LegacyCommentThread) => void;\n}\n\n/** Props for the CommentThread component */\nexport interface CommentThreadProps {\n /** Class name to apply to the display of the verse text for the first comment in the thread */\n classNameForVerseText?: string;\n /** Comments in the thread */\n comments: LegacyComment[];\n /** Localized strings for the component */\n localizedStrings: LanguageStrings;\n /** Whether the thread is selected */\n isSelected?: boolean;\n /** Verse reference for the thread */\n verseRef?: string;\n /** Name of the current user, retrieved from the current user's Paratext Registry user information */\n currentUser: string;\n /** User assigned to the thread */\n assignedUser?: string;\n /** Handler for selecting the thread */\n handleSelectThread: (threadId: string) => void;\n /** ID of the thread */\n threadId: string;\n /** The full thread object, passed through so the onVerseRefClick callback can access all data */\n thread: LegacyCommentThread;\n /** Status of the thread */\n threadStatus?: CommentStatus;\n /**\n * Handler for adding a comment to a thread. This unified handler supports:\n *\n * - Adding a comment (provide contents)\n * - Resolving/unresolving a thread (provide status: 'Resolved' or 'Todo')\n * - Assigning a user (provide assignedUser)\n * - Any combination of the above\n *\n * If successful, returns the auto-generated comment ID (format: \"threadId/userName/date\").\n * Otherwise, returns undefined.\n */\n handleAddCommentToThread: (options: AddCommentToThreadOptions) => Promise;\n /** Handler for updating a comment's content */\n handleUpdateComment: (commentId: string, contents: string) => Promise;\n /** Handler for deleting a comment */\n handleDeleteComment: (commentId: string) => Promise;\n /** Handler for updating read status */\n handleReadStatusChange?: (threadId: string, markRead: boolean) => void;\n /**\n * Users that can be assigned to threads. Includes special values: \"Team\" for team assignment, \"\"\n * (empty string) for unassigned.\n */\n assignableUsers?: string[];\n /**\n * Whether the current user can add comments to existing threads in this project. When false, UI\n * elements for adding comments to threads should be hidden or disabled.\n */\n canUserAddCommentToThread?: boolean;\n /**\n * Callback to check if the current user can assign a specific thread. Returns a promise that\n * resolves to true if the user can assign the thread, false otherwise.\n */\n canUserAssignThreadCallback?: (threadId: string) => Promise;\n /**\n * Callback to check if the current user can resolve or re-open a specific thread. Returns a\n * promise that resolves to true if the user can resolve the thread, false otherwise.\n */\n canUserResolveThreadCallback?: (threadId: string) => Promise;\n /**\n * Callback to check if the current user can edit or delete a specific comment. Returns a promise\n * that resolves to true if the user can edit or delete the comment, false otherwise.\n */\n canUserEditOrDeleteCommentCallback?: (commentId: string) => Promise;\n /** Whether the thread has been read (by the current user) */\n isRead?: boolean;\n /** Delay in seconds before auto-marking as read when selected, default 5s */\n autoReadDelay?: number;\n /**\n * Callback when the user clicks a verse reference in a comment thread. The related project editor\n * web view can navigate and position the editor cursor at the start of the comment inside the\n * verse.\n */\n onVerseRefClick?: (thread: LegacyCommentThread) => void;\n}\n\n/** Props for the CommentItem component */\nexport interface CommentItemProps {\n /** Comment to render */\n comment: LegacyComment;\n /** Whether the comment is a reply or a top-level comment */\n isReply?: boolean;\n /** Localized strings for the component */\n localizedStrings: LanguageStrings;\n /** Whether the thread is expanded */\n isThreadExpanded?: boolean;\n /** Current status of the thread */\n threadStatus?: CommentStatus;\n /**\n * Handler for adding a comment to a thread (used for resolving). If successful, returns the\n * auto-generated comment ID. Otherwise, returns undefined.\n */\n handleAddCommentToThread?: (options: AddCommentToThreadOptions) => Promise;\n /** Handler for updating a comment's content */\n handleUpdateComment?: (commentId: string, contents: string) => Promise;\n /** Handler for deleting a comment */\n handleDeleteComment?: (commentId: string) => Promise;\n /** Callback when editing state changes */\n onEditingChange?: (isEditing: boolean) => void;\n /** Whether the current user can edit or delete this comment */\n canEditOrDelete?: boolean;\n /** Whether the current user can resolve or re-open this thread. */\n canUserResolveThread?: boolean;\n}\n","import React, { useCallback, useRef, useState } from 'react';\n\n/** Tags of interactive HTML elements to look for in the listbox */\nconst INTERACTIVE_ELEMENT_TAG_SELECTORS = ['input', 'select', 'textarea', 'button'];\n\n/** Roles of interactive HTML elements to look for in the listbox */\nconst INTERACTIVE_ELEMENT_ROLE_SELECTORS = ['button', 'textbox'];\n\n/** Properties of one option contained in a listbox */\nexport interface ListboxOption {\n /** Unique identifier for the option */\n id: string;\n}\n\n/** Props for the useListbox hook */\nexport interface UseListboxProps {\n /** Array of options for the listbox */\n options: ListboxOption[];\n /** Callback when the focus changes to a different option */\n onFocusChange?: (option: ListboxOption) => void;\n /** Callback to toggle the selection of an option */\n onOptionSelect?: (option: ListboxOption) => void;\n /** Callback when a character key is pressed */\n onCharacterPress?: (char: string) => void;\n}\n\n/**\n * Hook for handling keyboard navigation of a listbox.\n *\n * @param UseListboxProps - The properties for configuring the listbox behavior.\n * @returns An object containing:\n *\n * - `listboxRef`: A ref to be attached to the listbox container element (e.g., `
    `), used for\n * focus management.\n * - `activeId`: The id of the currently focused (active) option, or `undefined` if none is focused.\n * - `selectedId`: The id of the currently selected option, or `undefined` if none is selected.\n * - `handleKeyDown`: A keyboard event handler to be attached to the listbox container for handling\n * navigation and selection.\n * - `focusOption`: A function to programmatically focus a specific option by id.\n */\nexport const useListbox = ({\n options,\n onFocusChange,\n onOptionSelect,\n onCharacterPress,\n}: UseListboxProps) => {\n // ul/div ref property expects null and not undefined\n // eslint-disable-next-line no-null/no-null\n const listboxRef = useRef(null);\n const [activeId, setActiveId] = useState(undefined);\n const [selectedId, setSelectedId] = useState(undefined);\n\n const focusOption = useCallback(\n (id: string) => {\n setActiveId(id);\n const option = options.find((opt) => opt.id === id);\n if (option) {\n onFocusChange?.(option);\n }\n\n const element = document.getElementById(id);\n if (element) {\n element.scrollIntoView({ block: 'center' });\n element.focus();\n }\n\n // Ensure aria-activedescendant is set on the listbox container for internal focus tracking\n if (listboxRef.current) {\n listboxRef.current.setAttribute('aria-activedescendant', id);\n }\n },\n [onFocusChange, options],\n );\n\n const toggleSelectInternal = useCallback(\n (id: string) => {\n const option = options.find((opt) => opt.id === id);\n if (!option) return;\n\n setSelectedId((prev) => (prev === id ? undefined : id));\n onOptionSelect?.(option);\n },\n [onOptionSelect, options],\n );\n\n // Detect if the key event originated from an interactive element inside the currently selected option\n const isInteractiveElement = (element: HTMLElement | undefined) => {\n if (!element) return false;\n const tag = element.tagName.toLowerCase();\n if (element.isContentEditable) return true;\n if (INTERACTIVE_ELEMENT_TAG_SELECTORS.includes(tag)) return true;\n const role = element.getAttribute('role');\n if (role && INTERACTIVE_ELEMENT_ROLE_SELECTORS.includes(role)) return true;\n const tabIndex = element.getAttribute('tabindex');\n if (tabIndex !== undefined && tabIndex !== '-1') return true;\n return false;\n };\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n // Need to cast event.target to HTMLElement because the keyboard navigation can be used with multiple types of elements\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const targetElement = event.target as HTMLElement;\n const getElementById = (id?: string) => (id ? document.getElementById(id) : undefined);\n const selectedElement = getElementById(selectedId);\n const activeElement = getElementById(activeId);\n\n // Check if the event target is inside the selected option\n const isInsideSelected = !!(\n selectedElement &&\n targetElement &&\n selectedElement.contains(targetElement) &&\n targetElement !== selectedElement\n );\n const isInteractiveInsideSelected = isInsideSelected && isInteractiveElement(targetElement);\n\n // When focus is inside a selected option, don't hijack typical keys; allow an escape hatch back to the option\n if (isInteractiveInsideSelected) {\n if (\n event.key === 'Escape' ||\n (event.key === 'ArrowLeft' && !targetElement.isContentEditable)\n ) {\n if (selectedId) {\n // Return focus to the selected option root\n event.preventDefault();\n event.stopPropagation();\n const opt = options.find((o) => o.id === selectedId);\n if (opt) {\n focusOption(opt.id);\n }\n }\n return;\n }\n\n // Handle ArrowUp/ArrowDown to navigate between interactive elements within the selected option\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n if (!selectedElement) return;\n\n // Get all focusable elements within the selected option\n const focusableElements = Array.from(\n selectedElement.querySelectorAll(\n 'button:not([disabled]), input:not([disabled]):not([type=\"hidden\"]), textarea:not([disabled]), select:not([disabled]), [href], [tabindex]:not([tabindex=\"-1\"])',\n ),\n );\n\n if (focusableElements.length === 0) return;\n\n const currentIndex = focusableElements.findIndex((el) => el === targetElement);\n if (currentIndex === -1) return;\n\n let nextIndex: number;\n if (event.key === 'ArrowDown') {\n nextIndex = Math.min(currentIndex + 1, focusableElements.length - 1);\n } else {\n nextIndex = Math.max(currentIndex - 1, 0);\n }\n\n if (nextIndex !== currentIndex) {\n event.preventDefault();\n event.stopPropagation();\n focusableElements[nextIndex]?.focus();\n }\n return;\n }\n\n return; // Do not handle other keys while interacting within the selected option\n }\n\n const currentIndex = options.findIndex((opt) => opt.id === activeId);\n let nextIndex = currentIndex;\n\n switch (event.key) {\n case 'ArrowDown':\n nextIndex = Math.min(currentIndex + 1, options.length - 1);\n event.preventDefault();\n break;\n case 'ArrowUp':\n nextIndex = Math.max(currentIndex - 1, 0);\n event.preventDefault();\n break;\n case 'Home':\n nextIndex = 0;\n event.preventDefault();\n break;\n case 'End':\n nextIndex = options.length - 1;\n event.preventDefault();\n break;\n case ' ':\n case 'Enter':\n if (activeId) {\n toggleSelectInternal(activeId);\n }\n event.preventDefault();\n event.stopPropagation();\n return;\n case 'ArrowRight': {\n // If on an option, try to move focus into its first focusable control\n const container = activeElement;\n if (container) {\n const preferred = container.querySelector(\n 'input:not([disabled]):not([type=\"hidden\"]), textarea:not([disabled]), select:not([disabled])',\n );\n const fallback = container.querySelector(\n 'button:not([disabled]), [href], [tabindex]:not([tabindex=\"-1\"]), [contenteditable=\"true\"]',\n );\n const toFocus = preferred ?? fallback;\n if (toFocus) {\n event.preventDefault();\n toFocus.focus();\n return;\n }\n }\n break;\n }\n default:\n // Only handle character keys when not inside an interactive element\n if (event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {\n // Don't intercept typing in interactive elements\n const isInInteractiveElement = isInteractiveElement(targetElement);\n if (!isInInteractiveElement) {\n onCharacterPress?.(event.key);\n event.preventDefault();\n }\n }\n return;\n }\n\n const nextOption = options[nextIndex];\n if (nextOption) focusOption(nextOption.id);\n },\n [options, focusOption, activeId, selectedId, toggleSelectInternal, onCharacterPress],\n );\n\n return {\n listboxRef,\n activeId,\n selectedId,\n /** Keyboard event handler for listbox navigation and selection */\n handleKeyDown,\n /** Focus an option by its ID */\n focusOption,\n };\n};\n","import React from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/utils/shadcn-ui.util';\n\n/**\n * Style variants for the Badge component.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/badge}\n */\nconst badgeVariants = cva(\n 'pr-twp tw-inline-flex tw-items-center tw-rounded-full tw-px-2.5 tw-py-0.5 tw-text-xs tw-font-semibold tw-transition-colors focus:tw-outline-none focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-2',\n {\n variants: {\n variant: {\n default:\n 'tw-border tw-border-transparent tw-bg-primary tw-text-primary-foreground hover:tw-bg-primary/80',\n secondary:\n 'tw-border tw-border-transparent tw-bg-secondary tw-text-secondary-foreground hover:tw-bg-secondary/80',\n muted:\n 'tw-border tw-border-transparent tw-bg-muted tw-text-muted-foreground hover:tw-bg-muted/80',\n destructive:\n 'tw-border tw-border-transparent tw-bg-destructive tw-text-destructive-foreground hover:tw-bg-destructive/80',\n outline: 'tw-border tw-text-foreground',\n blueIndicator: 'tw-w-[5px] tw-h-[5px] tw-bg-blue-400 tw-px-0',\n mutedIndicator: 'tw-w-[5px] tw-h-[5px] tw-bg-zinc-400 tw-px-0',\n ghost: 'hover:tw-bg-accent hover:tw-text-accent-foreground tw-text-mu',\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n },\n);\n\n/**\n * Props for the Badge component.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/badge}\n */\nexport interface BadgeProps\n extends React.HTMLAttributes,\n VariantProps {}\n\n/**\n * The Badge component displays a badge or a component that looks like a badge. The component is\n * built and styled by Shadcn UI.\n *\n * @param BadgeProps\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/badge}\n */\nconst Badge = React.forwardRef(\n ({ className, variant, ...props }, ref) => {\n return (\n
    \n );\n },\n);\n\nBadge.displayName = 'Badge';\n\nexport { Badge, badgeVariants };\n","import React from 'react';\n\nimport { cn } from '@/utils/shadcn-ui.util';\n\n/**\n * The Card component displays a card with header, content, and footer. This component is built and\n * styled with Shadcn UI. See Shadcn UI Documentation: https://ui.shadcn.com/docs/components/card\n */\nconst Card = React.forwardRef>(\n ({ className, ...props }, ref) => (\n \n ),\n);\nCard.displayName = 'Card';\n\n/** @inheritdoc Card */\nconst CardHeader = React.forwardRef>(\n ({ className, ...props }, ref) => (\n \n ),\n);\nCardHeader.displayName = 'CardHeader';\n\n/** @inheritdoc Card */\nconst CardTitle = React.forwardRef>(\n ({ className, ...props }, ref) => (\n \n {/* added because of https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/heading-has-content.md */}\n {props.children}\n \n ),\n);\nCardTitle.displayName = 'CardTitle';\n\n/** @inheritdoc Card */\nconst CardDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n

    \n));\nCardDescription.displayName = 'CardDescription';\n\n/** @inheritdoc Card */\nconst CardContent = React.forwardRef>(\n ({ className, ...props }, ref) => (\n

    \n ),\n);\nCardContent.displayName = 'CardContent';\n\n/** @inheritdoc Card */\nconst CardFooter = React.forwardRef>(\n ({ className, ...props }, ref) => (\n \n ),\n);\nCardFooter.displayName = 'CardFooter';\n\nexport { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };\n","import React from 'react';\nimport * as SeparatorPrimitive from '@radix-ui/react-separator';\n\nimport { cn } from '@/utils/shadcn-ui.util';\n\n/**\n * The Separator component visually or semantically separates content. This component is built on\n * Radix UI primitives and styled with Shadcn UI.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/separator}\n * @see Radix UI Documentation: {@link https://www.radix-ui.com/primitives/docs/components/separator}\n */\nconst Separator = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (\n \n));\nSeparator.displayName = SeparatorPrimitive.Root.displayName;\n\nexport { Separator };\n","import React from 'react';\nimport * as AvatarPrimitive from '@radix-ui/react-avatar';\n\nimport { cn } from '@/utils/shadcn-ui.util';\n\n/**\n * The Avatar component displays a user's profile picture or initials. The component is built and\n * styled by Shadcn UI. See Shadcn UI Documentation https://ui.shadcn.com/docs/components/avatar\n */\nconst Avatar = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nAvatar.displayName = AvatarPrimitive.Root.displayName;\n\n/** @inheritdoc Avatar */\nconst AvatarImage = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nAvatarImage.displayName = AvatarPrimitive.Image.displayName;\n\n/** @inheritdoc Avatar */\nconst AvatarFallback = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nAvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;\n\nexport { Avatar, AvatarImage, AvatarFallback };\n","import { cva } from 'class-variance-authority';\nimport { createContext, useContext } from 'react';\n\nexport type MenuContextProps = {\n variant?: 'default' | 'muted';\n};\n\nexport const MenuContext = createContext(undefined);\n\nexport function useMenuContext() {\n const context = useContext(MenuContext);\n if (!context) {\n throw new Error('useMenuContext must be used within a MenuContext.Provider.');\n }\n\n return context;\n}\n\nexport const menuVariants = cva('', {\n variants: {\n variant: {\n default: '',\n muted:\n 'hover:tw-bg-muted hover:tw-text-foreground focus:tw-bg-muted focus:tw-text-foreground data-[state=open]:tw-bg-muted data-[state=open]:tw-text-foreground',\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n});\n","import {\n MenuContext,\n MenuContextProps,\n menuVariants,\n useMenuContext,\n} from '@/context/menu.context';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';\nimport { Check, ChevronRight, Circle } from 'lucide-react';\nimport React from 'react';\n\n/**\n * Dropdown Menu components providing accessible dropdown menus and submenus. These components are\n * built on Radix UI primitives and styled with Shadcn UI. See Shadcn UI Documentation:\n * https://ui.shadcn.com/docs/components/dropdown-menu See Radix UI Documentation:\n * https://www.radix-ui.com/primitives/docs/components/dropdown-menu\n */\n/* #region CUSTOM Add variant prop to support different styles */\nexport type DropdownMenuProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.Root\n> & {\n variant?: MenuContextProps['variant'];\n};\n/* #endregion CUSTOM */\n\n/** @inheritdoc DropdownMenuProps */\nexport const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;\n\n/** @inheritdoc DropdownMenuProps */\nexport const DropdownMenuGroup = DropdownMenuPrimitive.Group;\n\n/** @inheritdoc DropdownMenuProps */\nexport const DropdownMenuPortal = DropdownMenuPrimitive.Portal;\n\n/** @inheritdoc DropdownMenuProps */\nexport const DropdownMenuSub = DropdownMenuPrimitive.Sub;\n\n/** @inheritdoc DropdownMenuProps */\nexport const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;\n\n/** @inheritdoc DropdownMenuProps */\nexport type DropdownMenuSubTriggerProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.SubTrigger\n> & {\n className?: string;\n inset?: boolean;\n};\n\n/** @inheritdoc DropdownMenuProps */\nexport type DropdownMenuSubContentProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.SubContent\n> & {\n className?: string;\n};\n\n/** @inheritdoc DropdownMenuProps */\nexport type DropdownMenuContentProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.Content\n> & {\n className?: string;\n sideOffset?: number;\n};\n\n/** @inheritdoc DropdownMenuProps */\nexport type DropdownMenuItemProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.Item\n> & {\n className?: string;\n inset?: boolean;\n};\n\n/** @inheritdoc DropdownMenuProps */\nexport type DropdownMenuCheckboxItemProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.CheckboxItem\n> & {\n className?: string;\n checked?: boolean;\n};\n\n/** @inheritdoc DropdownMenuProps */\nexport type DropdownMenuRadioItemProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.RadioItem\n> & {\n className?: string;\n};\n\n/** @inheritdoc DropdownMenuProps */\nexport type DropdownMenuLabelProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.Label\n> & {\n className?: string;\n inset?: boolean;\n};\n\n/** @inheritdoc DropdownMenuProps */\nexport type DropdownMenuSeparatorProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.Separator\n> & {\n className?: string;\n};\n\n/** @inheritdoc DropdownMenuProps */\nexport type DropdownMenuShortcutProps = React.HTMLAttributes & {\n className?: string;\n};\n\n/* #region CUSTOM Provide context to add variants */\n/** @inheritdoc DropdownMenuProps */\nexport function DropdownMenu({ variant = 'default', ...props }: DropdownMenuProps) {\n const contextValue = React.useMemo(\n () => ({\n variant,\n }),\n [variant],\n );\n return (\n \n \n \n );\n}\n/* #endregion CUSTOM */\n\n/** @inheritdoc DropdownMenuProps */\nexport const DropdownMenuSubTrigger = React.forwardRef<\n React.ElementRef,\n DropdownMenuSubTriggerProps\n>(({ className, inset, children, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n {children}\n \n \n );\n});\nDropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;\n\n/** @inheritdoc DropdownMenuProps */\nexport const DropdownMenuSubContent = React.forwardRef<\n React.ElementRef,\n DropdownMenuSubContentProps\n>(({ className, ...props }, ref) => (\n \n));\nDropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;\n\n/* TODO: bug in shadcn component: DropdownMenuContent does not support a dir prop.\nFor the content we can work around this by adding a div with dir, but that would not cause\nthe scrollbar to appear left in an rtl layout (e.g. see book-chapter-control.component) */\n/** @inheritdoc DropdownMenuProps */\nexport const DropdownMenuContent = React.forwardRef<\n React.ElementRef,\n DropdownMenuContentProps\n>(({ className, sideOffset = 4, children, ...props }, ref) => {\n const dir: Direction = readDirection();\n return (\n \n \n
    {children}
    \n \n
    \n );\n});\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;\n\n/** @inheritdoc DropdownMenuProps */\nexport const DropdownMenuItem = React.forwardRef<\n React.ElementRef,\n DropdownMenuItemProps\n>(({ className, inset, ...props }, ref) => {\n const dir: Direction = readDirection();\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n );\n});\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;\n\n/** @inheritdoc DropdownMenuProps */\nexport const DropdownMenuCheckboxItem = React.forwardRef<\n React.ElementRef,\n DropdownMenuCheckboxItemProps\n>(({ className, children, checked, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n \n \n \n \n \n {children}\n \n );\n});\nDropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;\n\n/** @inheritdoc DropdownMenuProps */\nexport const DropdownMenuRadioItem = React.forwardRef<\n React.ElementRef,\n DropdownMenuRadioItemProps\n>(({ className, children, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n \n \n \n \n \n {children}\n \n );\n});\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;\n\n/** @inheritdoc DropdownMenuProps */\nexport const DropdownMenuLabel = React.forwardRef<\n React.ElementRef,\n DropdownMenuLabelProps\n>(({ className, inset, ...props }, ref) => (\n \n));\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;\n\n/** @inheritdoc DropdownMenuProps */\nexport const DropdownMenuSeparator = React.forwardRef<\n React.ElementRef,\n DropdownMenuSeparatorProps\n>(({ className, ...props }, ref) => (\n \n));\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;\n\n/** @inheritdoc DropdownMenuProps */\nexport function DropdownMenuShortcut({ className, ...props }: DropdownMenuShortcutProps) {\n return (\n \n );\n}\nDropdownMenuShortcut.displayName = 'DropdownMenuShortcut';\n","import { LanguageStrings } from 'platform-bible-utils';\n\n/**\n * Gets the display name for an assigned user, with localized names for special values.\n *\n * @param user - The user identifier (empty string for unassigned, 'Team' for team)\n * @param localizedStrings - The localized strings to use for display names\n * @returns The display name for the user\n */\nexport function getAssignedUserDisplayName(\n user: string,\n localizedStrings: LanguageStrings,\n): string {\n if (user === '') {\n return localizedStrings['%comment_assign_unassigned%'] ?? 'Unassigned';\n }\n if (user === 'Team') {\n return localizedStrings['%comment_assign_team%'] ?? 'Team';\n }\n return user;\n}\n","import { Editor } from '@/components/advanced/editor/editor';\nimport {\n editorStateToHtml,\n focusContentEditable,\n handleEditorKeyNavigation,\n hasEditorContent,\n htmlToEditorState,\n} from '@/components/advanced/editor/editor-utils';\nimport { Avatar, AvatarFallback } from '@/components/shadcn-ui/avatar';\nimport { Badge } from '@/components/shadcn-ui/badge';\nimport { Button } from '@/components/shadcn-ui/button';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@/components/shadcn-ui/dropdown-menu';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { SerializedEditorState } from 'lexical';\nimport { ArrowUp, MoreHorizontal, Pencil, Trash2, X } from 'lucide-react';\nimport { formatRelativeDate, formatReplacementString, sanitizeHtml } from 'platform-bible-utils';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { CommentItemProps } from './comment-list.types';\nimport { getAssignedUserDisplayName } from './comment-list.utils';\n\n/**\n * A single comment item in the comment list.\n *\n * @param CommentItemProps The properties for the CommentItem component\n */\nexport function CommentItem({\n comment,\n isReply = false,\n localizedStrings,\n isThreadExpanded = false,\n handleUpdateComment,\n handleDeleteComment,\n onEditingChange,\n canEditOrDelete = false,\n}: CommentItemProps) {\n const [isEditing, setIsEditing] = useState(false);\n const [editorState, setEditorState] = useState();\n\n // eslint-disable-next-line no-null/no-null\n const editContainerRef = useRef(null);\n\n // Focus the editor when entering edit mode, after dropdown menu has fully closed\n useEffect(() => {\n if (!isEditing) return undefined;\n\n let isMounted = true;\n const container = editContainerRef.current;\n if (!container) return undefined;\n\n /**\n * The `Edit Comment` menu item is inside a dropdown that takes time to close. When the dropdown\n * closes, it brings focus back to the dropdown trigger button, which steals focus from the\n * editor. To work around this, we add a slight delay before focusing the editor. Unfortunately\n * there is no reliable way to detect when the dropdown has fully closed, which leaves us with\n * no other option than to use a timeout.\n */\n const timeoutId = setTimeout(() => {\n if (!isMounted) return;\n focusContentEditable(container);\n }, 300);\n\n return () => {\n isMounted = false;\n clearTimeout(timeoutId);\n };\n }, [isEditing]);\n\n const handleCancelEdit = useCallback(() => {\n setIsEditing(false);\n setEditorState(undefined);\n onEditingChange?.(false);\n }, [onEditingChange]);\n\n const handleSaveEdit = useCallback(async () => {\n if (!editorState || !handleUpdateComment) return;\n const isUpdateSuccessful = await handleUpdateComment(\n comment.id,\n editorStateToHtml(editorState),\n );\n if (isUpdateSuccessful) {\n setIsEditing(false);\n setEditorState(undefined);\n onEditingChange?.(false);\n }\n }, [editorState, handleUpdateComment, comment.id, onEditingChange]);\n\n const displayDate = useMemo(() => {\n const date = new Date(comment.date);\n const relativeDate = formatRelativeDate(\n date,\n localizedStrings['%comment_date_today%'],\n localizedStrings['%comment_date_yesterday%'],\n );\n const time = date.toLocaleTimeString(undefined, {\n hour: 'numeric',\n minute: '2-digit',\n });\n return formatReplacementString(localizedStrings['%comment_dateAtTime%'], {\n date: relativeDate,\n time,\n });\n }, [comment.date, localizedStrings]);\n\n const userLabel = useMemo(() => comment.user, [comment.user]);\n\n // Generate initials for avatar\n const initials = useMemo(\n () =>\n comment.user\n .split(' ')\n .map((name) => name[0])\n .join('')\n .toUpperCase()\n .slice(0, 2),\n [comment.user],\n );\n\n const sanitizedContent = useMemo(() => sanitizeHtml(comment.contents), [comment.contents]);\n\n const dropdownContent = useMemo(() => {\n if (!isThreadExpanded) return undefined;\n if (!canEditOrDelete) return undefined;\n\n return (\n <>\n {\n setIsEditing(true);\n setEditorState(htmlToEditorState(comment.contents));\n onEditingChange?.(true);\n }}\n >\n \n {localizedStrings['%comment_editComment%']}\n \n {\n if (handleDeleteComment) {\n await handleDeleteComment(comment.id);\n }\n }}\n >\n \n {localizedStrings['%comment_deleteComment%']}\n \n \n );\n }, [\n canEditOrDelete,\n isThreadExpanded,\n localizedStrings,\n comment.contents,\n comment.id,\n handleDeleteComment,\n onEditingChange,\n ]);\n\n return (\n \n \n {initials}\n \n
    \n
    \n

    {userLabel}

    \n

    {displayDate}

    \n
    \n {isReply && comment.assignedUser !== undefined && (\n \n โ†’ {getAssignedUserDisplayName(comment.assignedUser, localizedStrings)}\n \n )}\n
    \n {isEditing && (\n {\n if (e.key === 'Escape') {\n e.preventDefault();\n e.stopPropagation();\n handleCancelEdit();\n } else if (e.key === 'Enter' && e.shiftKey) {\n e.preventDefault();\n e.stopPropagation();\n if (hasEditorContent(editorState)) {\n handleSaveEdit();\n }\n }\n }}\n onKeyDown={(e) => {\n handleEditorKeyNavigation(e);\n if (e.key === 'Enter' || e.key === ' ') {\n e.stopPropagation();\n }\n }}\n >\n blockquote]:tw-mt-0 [&_[data-lexical-editor=\"true\"]>blockquote]:tw-border-s-0 [&_[data-lexical-editor=\"true\"]>blockquote]:tw-ps-0 [&_[data-lexical-editor=\"true\"]>blockquote]:tw-font-normal [&_[data-lexical-editor=\"true\"]>blockquote]:tw-not-italic [&_[data-lexical-editor=\"true\"]>blockquote]:tw-text-foreground',\n )}\n editorSerializedState={editorState}\n onSerializedChange={(value) => setEditorState(value)}\n />\n
    \n \n \n \n \n \n \n
    \n
    \n )}\n {!isEditing && (\n <>\n {comment.status === 'Resolved' && (\n
    \n {localizedStrings['%comment_status_resolved%']}\n
    \n )}\n {comment.status === 'Todo' && isReply && (\n
    \n {localizedStrings['%comment_status_todo%']}\n
    \n )}\n blockquote]:tw-border-s-0 [&>blockquote]:tw-p-0 [&>blockquote]:tw-ps-0 [&>blockquote]:tw-font-normal [&>blockquote]:tw-not-italic [&>blockquote]:tw-text-foreground',\n // Don't render quotes on blockquotes\n 'tw-prose-quoteless',\n {\n 'tw-line-clamp-3': !isThreadExpanded,\n },\n )}\n // The comment content is stored in HTML so it needs to be set directly. To make sure\n // it is safe we have sanitized it first.\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: sanitizedContent }}\n />\n \n )}\n
    \n {dropdownContent && (\n \n \n \n \n {dropdownContent}\n \n )}\n
    \n );\n}\n","import { Editor } from '@/components/advanced/editor/editor';\nimport {\n editorStateToHtml,\n handleEditorKeyNavigation,\n hasEditorContent,\n} from '@/components/advanced/editor/editor-utils';\nimport { Badge } from '@/components/shadcn-ui/badge';\nimport { Button } from '@/components/shadcn-ui/button';\nimport { Card, CardContent } from '@/components/shadcn-ui/card';\nimport { Separator } from '@/components/shadcn-ui/separator';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport {\n SerializedEditorState,\n SerializedElementNode,\n SerializedParagraphNode,\n SerializedTextNode,\n} from 'lexical';\nimport { ArrowUp, AtSign, Check, ChevronDown, ChevronUp, Mail, MailOpen } from 'lucide-react';\nimport { formatReplacementString } from 'platform-bible-utils';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/shadcn-ui/popover';\nimport { Command, CommandItem, CommandList } from '@/components/shadcn-ui/command';\nimport { CommentItem } from './comment-item.component';\nimport { AddCommentToThreadOptions, CommentThreadProps } from './comment-list.types';\nimport { getAssignedUserDisplayName } from './comment-list.utils';\n\nconst initialValue: SerializedEditorState<\n SerializedParagraphNode & SerializedElementNode\n> = {\n root: {\n children: [\n {\n children: [\n {\n detail: 0,\n format: 0,\n mode: 'normal',\n style: '',\n text: '',\n type: 'text',\n version: 1,\n },\n ],\n direction: 'ltr',\n format: '',\n indent: 0,\n type: 'paragraph',\n version: 1,\n textFormat: 0,\n textStyle: '',\n },\n ],\n direction: 'ltr',\n format: '',\n indent: 0,\n type: 'root',\n version: 1,\n },\n};\n\n/**\n * Represents a thread of comments\n *\n * @props CommentThreadProps\n */\nexport function CommentThread({\n classNameForVerseText,\n comments,\n localizedStrings,\n isSelected = false,\n verseRef,\n assignedUser,\n currentUser,\n handleSelectThread,\n threadId,\n thread,\n threadStatus,\n handleAddCommentToThread,\n handleUpdateComment,\n handleDeleteComment,\n handleReadStatusChange,\n assignableUsers,\n canUserAddCommentToThread,\n canUserAssignThreadCallback,\n canUserResolveThreadCallback,\n canUserEditOrDeleteCommentCallback,\n isRead: isReadProp = false,\n autoReadDelay = 5,\n onVerseRefClick,\n}: CommentThreadProps) {\n const [editorState, setEditorState] = useState(initialValue);\n const isVerseExpanded = isSelected;\n const [showAllReplies, setShowAllReplies] = useState(false);\n const [isAnyCommentEditing, setIsAnyCommentEditing] = useState(false);\n const [isAssignPopoverOpen, setIsAssignPopoverOpen] = useState(false);\n const [pendingAssignedUser, setPendingAssignedUser] = useState(undefined);\n const [canAssign, setCanAssign] = useState(false);\n const [canResolve, setCanResolve] = useState(false);\n const [isRead, setIsRead] = useState(isReadProp);\n const [manuallyUnread, setManuallyUnread] = useState(false);\n const autoReadTimerRef = useRef | undefined>(undefined);\n const [commentEditDeletePermissions, setCommentEditDeletePermissions] = useState<\n Map\n >(new Map());\n\n // Check resolve permission on mount so the button can appear on hover\n useEffect(() => {\n let isPromiseCurrent = true;\n\n const checkResolvePermission = async () => {\n const resolveResult = canUserResolveThreadCallback\n ? await canUserResolveThreadCallback(threadId)\n : false;\n\n if (!isPromiseCurrent) return;\n setCanResolve(resolveResult);\n };\n\n checkResolvePermission();\n return () => {\n isPromiseCurrent = false;\n };\n }, [threadId, canUserResolveThreadCallback]);\n\n // Check remaining async permissions when thread is selected\n useEffect(() => {\n let isPromiseCurrent = true;\n\n if (!isSelected) {\n setCanAssign(false);\n setCommentEditDeletePermissions(new Map());\n return undefined;\n }\n\n const checkPermissions = async () => {\n const assignResult = canUserAssignThreadCallback\n ? await canUserAssignThreadCallback(threadId)\n : false;\n\n if (!isPromiseCurrent) return;\n setCanAssign(assignResult);\n };\n\n checkPermissions();\n return () => {\n isPromiseCurrent = false;\n };\n }, [isSelected, threadId, canUserAssignThreadCallback]);\n\n const activeComments = useMemo(() => comments.filter((comment) => !comment.deleted), [comments]);\n\n // Check edit/delete permissions for all comments when thread is selected or comments change\n useEffect(() => {\n let isPromiseCurrent = true;\n\n if (!isSelected || !canUserEditOrDeleteCommentCallback) {\n setCommentEditDeletePermissions(new Map());\n return undefined;\n }\n\n const checkCommentPermissions = async () => {\n const permissionsMap = new Map();\n\n await Promise.all(\n activeComments.map(async (comment) => {\n const canEdit = await canUserEditOrDeleteCommentCallback(comment.id);\n if (isPromiseCurrent) {\n permissionsMap.set(comment.id, canEdit);\n }\n }),\n );\n\n if (isPromiseCurrent) {\n setCommentEditDeletePermissions(permissionsMap);\n }\n };\n\n checkCommentPermissions();\n return () => {\n isPromiseCurrent = false;\n };\n }, [isSelected, activeComments, canUserEditOrDeleteCommentCallback]);\n\n const firstComment = useMemo(() => activeComments[0], [activeComments]);\n\n //

    expects null and not undefined\n // eslint-disable-next-line no-null/no-null\n const verseTextRef = useRef(null);\n const clearEditorRef = useRef<(() => void) | undefined>(undefined);\n\n const clearEditor = useCallback(() => {\n clearEditorRef.current?.();\n setEditorState(initialValue);\n }, []);\n\n const toggleRead = useCallback(() => {\n const newIsRead = !isRead;\n setIsRead(newIsRead);\n if (!newIsRead) {\n setManuallyUnread(true);\n } else {\n setManuallyUnread(false);\n }\n handleReadStatusChange?.(threadId, newIsRead);\n }, [isRead, handleReadStatusChange, threadId]);\n\n useEffect(() => {\n setShowAllReplies(false);\n }, [isSelected]);\n\n useEffect((): void | (() => void) => {\n if (isSelected && !isRead && !manuallyUnread) {\n const timer = setTimeout(() => {\n setIsRead(true);\n handleReadStatusChange?.(threadId, true);\n }, autoReadDelay * 1000);\n autoReadTimerRef.current = timer;\n return () => clearTimeout(timer);\n }\n if (autoReadTimerRef.current) {\n clearTimeout(autoReadTimerRef.current);\n autoReadTimerRef.current = undefined;\n }\n }, [isSelected, isRead, manuallyUnread, autoReadDelay, threadId, handleReadStatusChange]);\n\n const localizedReplies = useMemo(\n () => ({\n singleReply: localizedStrings['%comment_thread_single_reply%'],\n multipleReplies: localizedStrings['%comment_thread_multiple_replies%'],\n }),\n [localizedStrings],\n );\n\n const localizedAssignedToText = useMemo(() => {\n if (assignedUser === undefined) {\n return undefined;\n }\n if (assignedUser === '') {\n return localizedStrings['%comment_assign_unassigned%'] ?? 'Unassigned';\n }\n const displayName = getAssignedUserDisplayName(assignedUser, localizedStrings);\n return formatReplacementString(localizedStrings['%comment_assigned_to%'], {\n assignedUser: displayName,\n });\n }, [assignedUser, localizedStrings]);\n\n const replies = useMemo(() => activeComments.slice(1), [activeComments]);\n const replyCount = useMemo(() => replies.length ?? 0, [replies.length]);\n const hasReplies = useMemo(() => replyCount > 0, [replyCount]);\n\n // For expanded threads with more than 2 replies, show only the last 2 replies\n const visibleReplies = useMemo(() => {\n if (showAllReplies || replyCount <= 2) {\n return replies;\n }\n // Show only the last 2 replies\n return replies.slice(-2);\n }, [replies, replyCount, showAllReplies]);\n\n const hiddenReplyCount = useMemo(() => {\n if (showAllReplies || replyCount <= 2) {\n return 0;\n }\n return replyCount - 2;\n }, [replyCount, showAllReplies]);\n\n const replyText = useMemo(\n () =>\n replyCount === 1\n ? localizedReplies.singleReply\n : formatReplacementString(localizedReplies.multipleReplies, { count: replyCount }),\n [replyCount, localizedReplies],\n );\n\n const hiddenReplyText = useMemo(\n () =>\n hiddenReplyCount === 1\n ? localizedReplies.singleReply\n : formatReplacementString(localizedReplies.multipleReplies, { count: hiddenReplyCount }),\n [hiddenReplyCount, localizedReplies],\n );\n\n const handleSubmitComment = useCallback(async () => {\n const contents = hasEditorContent(editorState) ? editorStateToHtml(editorState) : undefined;\n\n // If there's a pending assignment, include it\n if (pendingAssignedUser !== undefined) {\n const success = await handleAddCommentToThread({\n threadId,\n contents,\n assignedUser: pendingAssignedUser,\n });\n if (success) {\n setPendingAssignedUser(undefined);\n if (contents) {\n clearEditor();\n }\n }\n return;\n }\n // Otherwise, just add a comment if there's content\n if (contents) {\n const newCommentId = await handleAddCommentToThread({ threadId, contents });\n if (newCommentId) {\n clearEditor();\n }\n }\n }, [clearEditor, editorState, handleAddCommentToThread, pendingAssignedUser, threadId]);\n\n const handleAddCommentToThreadWithContents = useCallback(\n async (options: AddCommentToThreadOptions) => {\n const contents = hasEditorContent(editorState) ? editorStateToHtml(editorState) : undefined;\n const success = await handleAddCommentToThread({\n ...options,\n contents,\n assignedUser: pendingAssignedUser ?? options.assignedUser,\n });\n if (success && contents) {\n clearEditor();\n }\n if (success && pendingAssignedUser !== undefined) {\n setPendingAssignedUser(undefined);\n }\n return success;\n },\n [clearEditor, editorState, handleAddCommentToThread, pendingAssignedUser],\n );\n\n return (\n {\n handleSelectThread(threadId);\n }}\n tabIndex={-1}\n >\n \n
    \n
    \n {localizedAssignedToText && (\n \n {localizedAssignedToText}\n \n )}\n {\n e.stopPropagation();\n toggleRead();\n }}\n className=\"tw-text-muted-foreground tw-transition hover:tw-text-foreground\"\n aria-label={isRead ? 'Mark as unread' : 'Mark as read'}\n >\n {isRead ? : }\n \n {canResolve && threadStatus !== 'Resolved' && (\n {\n e.stopPropagation();\n handleAddCommentToThreadWithContents({\n threadId,\n status: 'Resolved',\n });\n }}\n aria-label=\"Resolve thread\"\n >\n \n \n )}\n
    \n
    \n {/* Allow clicking to expand thread when collapsed, but allow text selection when expanded */}\n {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions */}\n \n {verseRef && onVerseRefClick ? (\n {\n e.stopPropagation();\n onVerseRefClick(thread);\n }}\n >\n {verseRef}\n \n ) : (\n verseRef\n )}\n \n {firstComment.contextBefore}\n {firstComment.selectedText}\n {firstComment.contextAfter}\n \n

    \n
    \n \n
    \n <>\n {hasReplies && !isSelected && (\n
    \n
    \n \n
    \n

    {replyText}

    \n
    \n )}\n {/* Show Editor on an unselected thread when it has drafted content */}\n {!isSelected && hasEditorContent(editorState) && (\n setEditorState(value)}\n placeholder={localizedStrings['%comment_replyOrAssign%']}\n />\n )}\n {isSelected && (\n <>\n {/* Show \"hidden replies\" separator before the visible replies if there are hidden replies */}\n {hiddenReplyCount > 0 && (\n {\n e.stopPropagation();\n setShowAllReplies(true);\n }}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n e.stopPropagation();\n setShowAllReplies(true);\n }\n }}\n >\n
    \n \n
    \n
    \n

    {hiddenReplyText}

    \n {showAllReplies ? : }\n
    \n
    \n )}\n {visibleReplies.map((reply) => (\n
    \n \n
    \n ))}\n\n {/* Only show main Editor if user can add comments, no comment is being edited, or if it has draft content */}\n {canUserAddCommentToThread !== false &&\n (!isAnyCommentEditing || hasEditorContent(editorState)) && (\n e.stopPropagation()}\n onKeyDownCapture={(e) => {\n if (e.key === 'Enter' && e.shiftKey) {\n e.preventDefault();\n e.stopPropagation();\n if (hasEditorContent(editorState) || pendingAssignedUser !== undefined) {\n handleSubmitComment();\n }\n }\n }}\n onKeyDown={(e) => {\n handleEditorKeyNavigation(e);\n if (e.key === 'Enter' || e.key === ' ') {\n e.stopPropagation();\n }\n }}\n >\n setEditorState(value)}\n placeholder={\n threadStatus === 'Resolved'\n ? localizedStrings['%comment_reopenResolved%']\n : localizedStrings['%comment_replyOrAssign%']\n }\n autoFocus\n onClear={(clearFn) => {\n clearEditorRef.current = clearFn;\n }}\n />\n
    \n {pendingAssignedUser !== undefined && (\n \n {formatReplacementString(\n localizedStrings['%comment_assigning_to%'] ??\n 'Assigning to: {assignedUser}',\n {\n assignedUser: getAssignedUserDisplayName(\n pendingAssignedUser,\n localizedStrings,\n ),\n },\n )}\n \n )}\n \n \n \n \n \n \n {\n if (e.key === 'Escape') {\n e.stopPropagation();\n setIsAssignPopoverOpen(false);\n }\n }}\n >\n \n \n {assignableUsers?.map((user) => (\n {\n if (user !== assignedUser) {\n setPendingAssignedUser(user);\n } else {\n setPendingAssignedUser(undefined);\n }\n setIsAssignPopoverOpen(false);\n }}\n className=\"tw-flex tw-items-center\"\n >\n {getAssignedUserDisplayName(user, localizedStrings)}\n \n ))}\n \n \n \n \n \n \n \n
    \n \n )}\n \n )}\n \n \n \n );\n}\n","import { ListboxOption, useListbox } from '@/hooks/listbox-keyboard-navigation.hook';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport React, { RefObject, useCallback, useEffect, useState } from 'react';\nimport { CommentListProps } from './comment-list.types';\nimport { CommentThread } from './comment-thread.component';\n\n/**\n * Component for rendering a list of comment threads\n *\n * @param CommentListProps Props for the CommentList component\n */\nexport default function CommentList({\n className = '',\n classNameForVerseText,\n threads,\n currentUser,\n localizedStrings,\n handleAddCommentToThread,\n handleUpdateComment,\n handleDeleteComment,\n handleReadStatusChange,\n assignableUsers,\n canUserAddCommentToThread,\n canUserAssignThreadCallback,\n canUserResolveThreadCallback,\n canUserEditOrDeleteCommentCallback,\n selectedThreadId: externalSelectedThreadId,\n onSelectedThreadChange,\n onVerseRefClick,\n}: CommentListProps) {\n const [expandedThreadIds, setExpandedThreadIds] = useState>(new Set());\n const [lastInteractedThreadId, setLastInteractedThreadId] = useState();\n\n // When external selection changes, add it to expanded set\n useEffect(() => {\n if (externalSelectedThreadId) {\n setExpandedThreadIds((prev) => new Set(prev).add(externalSelectedThreadId));\n setLastInteractedThreadId(externalSelectedThreadId);\n }\n }, [externalSelectedThreadId]);\n\n const activeThreads = threads.filter((thread) =>\n thread.comments.some((comment) => !comment.deleted),\n );\n\n const options: ListboxOption[] = activeThreads.map((thread) => ({\n id: thread.id,\n }));\n\n const handleKeyboardSelectThread = useCallback(\n (option: ListboxOption) => {\n setExpandedThreadIds((prev) => new Set(prev).add(option.id));\n setLastInteractedThreadId(option.id);\n onSelectedThreadChange?.(option.id);\n },\n [onSelectedThreadChange],\n );\n\n const handleSelectThread = useCallback(\n (threadId: string) => {\n const isCollapsing = expandedThreadIds.has(threadId);\n setExpandedThreadIds((prev) => {\n const next = new Set(prev);\n if (next.has(threadId)) {\n next.delete(threadId);\n } else {\n next.add(threadId);\n }\n return next;\n });\n setLastInteractedThreadId(threadId);\n onSelectedThreadChange?.(isCollapsing ? undefined : threadId);\n },\n [expandedThreadIds, onSelectedThreadChange],\n );\n\n const { listboxRef, activeId, handleKeyDown } = useListbox({\n options,\n onOptionSelect: handleKeyboardSelectThread,\n });\n\n // Collapse the last interacted thread when Escape is pressed\n const handleKeyDownWithEscape = useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === 'Escape') {\n if (lastInteractedThreadId && expandedThreadIds.has(lastInteractedThreadId)) {\n setExpandedThreadIds((prev) => {\n const next = new Set(prev);\n next.delete(lastInteractedThreadId);\n return next;\n });\n setLastInteractedThreadId(undefined);\n onSelectedThreadChange?.(undefined);\n }\n event.preventDefault();\n event.stopPropagation();\n } else {\n handleKeyDown(event);\n }\n },\n [lastInteractedThreadId, expandedThreadIds, handleKeyDown, onSelectedThreadChange],\n );\n\n return (\n }\n aria-activedescendant={activeId ?? undefined}\n aria-label=\"Comments\"\n className={cn(\n 'tw-flex tw-w-full tw-flex-col tw-space-y-3 tw-outline-none focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-1 focus:tw-ring-offset-background',\n\n className,\n )}\n onKeyDown={handleKeyDownWithEscape}\n >\n {activeThreads.map((thread) => (\n \n \n \n ))}\n \n );\n}\n","import { DropdownMenuTrigger } from '@radix-ui/react-dropdown-menu';\nimport { FilterIcon } from 'lucide-react';\nimport { Table } from '@tanstack/react-table';\n\nimport { Button } from '@/components/shadcn-ui/button';\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n} from '@/components/shadcn-ui/dropdown-menu';\n\ninterface DataTableViewOptionsProps {\n table: Table;\n}\n\nexport function DataTableViewOptions({ table }: DataTableViewOptionsProps) {\n return (\n \n \n \n \n \n Toggle columns\n \n {table\n .getAllColumns()\n .filter((column) => column.getCanHide())\n .map((column) => {\n return (\n column.toggleVisibility(!!value)}\n >\n {column.id}\n \n );\n })}\n \n \n );\n}\n\nexport default DataTableViewOptions;\n","import React from 'react';\nimport * as SelectPrimitive from '@radix-ui/react-select';\nimport { Check, ChevronDown, ChevronUp } from 'lucide-react';\n\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\nimport { cva, VariantProps } from 'class-variance-authority';\n\n/**\n * Props for Select component\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/select}\n */\nexport interface SelectTriggerProps\n extends React.ComponentPropsWithoutRef,\n VariantProps {\n asChild?: boolean;\n}\n\n/**\n * Select components display a list of options for the user to pick fromโ€”triggered by a button.\n * These components are built on Radix UI primitives and styled with Shadcn UI.\n *\n * See Shadcn UI Documentation: https://ui.shadcn.com/docs/components/select See Radix UI\n * Documentation: https://www.radix-ui.com/primitives/docs/components/select\n */\nconst Select = SelectPrimitive.Root;\n\n/** @inheritdoc Select */\nconst SelectGroup = SelectPrimitive.Group;\n\n/** @inheritdoc Select */\nconst SelectValue = SelectPrimitive.Value;\n\n/**\n * Style variants for the Select Trigger component.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/button}\n */\nexport const selectTriggerVariants = cva(\n 'tw-flex tw-h-10 tw-w-full tw-items-center tw-justify-between tw-rounded-md tw-border tw-border-input tw-bg-background tw-px-3 tw-py-2 tw-text-sm tw-ring-offset-background placeholder:tw-text-muted-foreground focus:tw-outline-none focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-2 disabled:tw-cursor-not-allowed disabled:tw-opacity-50 [&>span]:tw-line-clamp-1',\n {\n variants: {\n size: {\n default: 'tw-h-10 tw-px-4 tw-py-2',\n sm: 'tw-h-8 tw-rounded-md tw-px-3',\n lg: 'tw-h-11 tw-rounded-md tw-px-8',\n icon: 'tw-h-10 tw-w-10',\n },\n },\n defaultVariants: {\n size: 'default',\n },\n },\n);\n\n/** @inheritdoc Select */\nconst SelectTrigger = React.forwardRef<\n React.ElementRef,\n SelectTriggerProps\n>(({ className, children, size, ...props }, ref) => {\n const dir: Direction = readDirection();\n return (\n \n {children}\n \n \n \n \n );\n});\nSelectTrigger.displayName = SelectPrimitive.Trigger.displayName;\n\n/** @inheritdoc Select */\nconst SelectScrollUpButton = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n \n \n));\nSelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;\n\n/** @inheritdoc Select */\nconst SelectScrollDownButton = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n \n \n));\nSelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;\n\n/** @inheritdoc Select */\nconst SelectContent = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, position = 'popper', ...props }, ref) => {\n const dir: Direction = readDirection();\n return (\n \n \n \n \n
    {children}
    \n \n \n \n
    \n );\n});\nSelectContent.displayName = SelectPrimitive.Content.displayName;\n\n/** @inheritdoc Select */\nconst SelectLabel = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nSelectLabel.displayName = SelectPrimitive.Label.displayName;\n\n/** @inheritdoc Select */\nconst SelectItem = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, ...props }, ref) => (\n \n \n \n \n \n \n\n {children}\n \n));\nSelectItem.displayName = SelectPrimitive.Item.displayName;\n\n/** @inheritdoc Select */\nconst SelectSeparator = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nSelectSeparator.displayName = SelectPrimitive.Separator.displayName;\n\nexport {\n Select,\n SelectGroup,\n SelectValue,\n SelectTrigger,\n SelectContent,\n SelectLabel,\n SelectItem,\n SelectSeparator,\n SelectScrollUpButton,\n SelectScrollDownButton,\n};\n","import { ChevronLeftIcon, ChevronRightIcon, ArrowLeftIcon, ArrowRightIcon } from 'lucide-react';\nimport { Table } from '@tanstack/react-table';\n\nimport { Button } from '@/components/shadcn-ui/button';\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@/components/shadcn-ui/select';\n\ninterface DataTablePaginationProps {\n table: Table;\n}\n\nexport function DataTablePagination({ table }: DataTablePaginationProps) {\n return (\n
    \n
    \n
    \n {table.getFilteredSelectedRowModel().rows.length} of{' '}\n {table.getFilteredRowModel().rows.length} row(s) selected\n
    \n
    \n

    Rows per page

    \n {\n table.setPageSize(Number(value));\n }}\n >\n \n \n \n \n {[10, 20, 30, 40, 50].map((pageSize) => (\n \n {pageSize}\n \n ))}\n \n \n
    \n
    \n Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}\n
    \n
    \n table.setPageIndex(0)}\n disabled={!table.getCanPreviousPage()}\n >\n Go to first page\n \n \n table.previousPage()}\n disabled={!table.getCanPreviousPage()}\n >\n Go to previous page\n \n \n table.nextPage()}\n disabled={!table.getCanNextPage()}\n >\n Go to next page\n \n \n table.setPageIndex(table.getPageCount() - 1)}\n disabled={!table.getCanNextPage()}\n >\n Go to last page\n \n \n
    \n
    \n
    \n );\n}\n\nexport default DataTablePagination;\n","/** Defines HTML elements that can be focusable by keyboard as a CSS selector string */\nconst FOCUSABLE_SELECTOR = `\n a[href],\n area[href],\n input:not([disabled]),\n select:not([disabled]),\n textarea:not([disabled]),\n button:not([disabled]),\n iframe,\n object,\n embed,\n [contenteditable],\n tr:not([disabled])\n`;\n\n/** Returns true if the element is visible in the DOM */\nfunction isVisible(el: HTMLElement): boolean {\n return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);\n}\n\n/**\n * Finds all focusable elements in the given container. Focusable elements are all HTML elements\n * that can receive keyboard focus, and are not disabled or hidden from screen readers.\n *\n * @param container The container element to search for focusable elements.\n * @param uniqueQuerySelector An optional CSS selector to filter the focusable elements by.\n * @returns An array of focusable elements.\n */\nexport function getFocusableElements(\n container: HTMLElement,\n uniqueQuerySelector?: string,\n): HTMLElement[] {\n const query = uniqueQuerySelector\n ? `${FOCUSABLE_SELECTOR}, ${uniqueQuerySelector}`\n : FOCUSABLE_SELECTOR;\n return Array.from(container.querySelectorAll(query)).filter(\n (el) => !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden') && isVisible(el),\n );\n}\n","import React from 'react';\nimport { getFocusableElements } from '@/utils/focus.util';\nimport { cn } from '@/utils/shadcn-ui.util';\n\n/**\n * Table components provide a responsive table. These components are built and styled with Shadcn\n * UI. See Shadcn UI Documentation: https://ui.shadcn.com/docs/components/table\n */\nconst Table = React.forwardRef<\n HTMLTableElement,\n React.HTMLAttributes & { stickyHeader?: boolean }\n>(({ className, stickyHeader, ...props }, ref) => {\n // CUSTOM: Use internal ref to manage keyboard navigation and Enter key behavior\n // This ref gets passed into the table row ref property which expects null and not undefined\n // eslint-disable-next-line no-null/no-null\n const tableRef = React.useRef(null);\n\n // CUSTOM: Assign internal ref to external ref if provided\n React.useEffect(() => {\n if (typeof ref === 'function') {\n ref(tableRef.current);\n } else if (ref && 'current' in ref) {\n ref.current = tableRef.current;\n }\n }, [ref]);\n\n // CUSTOM: Force tabindex -1 on all focusable elements within the table to prevent tab navigation\n React.useEffect(() => {\n const currentTable = tableRef.current;\n if (!currentTable) return;\n\n const setTabIndexes = () => {\n requestAnimationFrame(() => {\n const focusables = getFocusableElements(currentTable, `[tabindex]:not([tabindex=\"-1\"])`);\n focusables.forEach((el) => {\n el.setAttribute('tabindex', '-1');\n });\n });\n };\n\n setTabIndexes();\n\n const observer = new MutationObserver(() => {\n setTabIndexes();\n });\n\n observer.observe(currentTable, {\n childList: true, // Watch for added/removed elements\n subtree: true, // Include descendants\n attributes: true,\n attributeFilter: ['tabindex'], // Watch for tabindex changes\n });\n\n return () => {\n observer.disconnect();\n };\n }, []);\n\n // CUSTOM: Handle keydown events for the table\n const handleKeyDownInTable = (e: React.KeyboardEvent) => {\n const { current: currentTable } = tableRef;\n if (!currentTable) return;\n\n if (e.key === 'ArrowDown') {\n // Move focus to the first row in the table (header or body)\n e.preventDefault();\n const firstRow = getFocusableElements(currentTable)[0];\n firstRow.focus();\n return;\n }\n if (e.key === ' ' && document.activeElement === currentTable) {\n e.preventDefault(); // Prevent scrolling\n }\n };\n\n return (\n
    \n {/* Table element is not interactive by default but we need to add a keydown handler */}\n {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */}\n \n
    \n );\n});\nTable.displayName = 'Table';\n\n/** @inheritdoc Table */\nconst TableHeader = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes & { stickyHeader?: boolean }\n>(({ className, stickyHeader, ...props }, ref) => (\n \n));\nTableHeader.displayName = 'TableHeader';\n\n/** @inheritdoc Table */\nconst TableBody = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nTableBody.displayName = 'TableBody';\n\n/** @inheritdoc Table */\nconst TableFooter = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n tr]:last:tw-border-b-0', className)}\n {...props}\n />\n));\nTableFooter.displayName = 'TableFooter';\n\n// CUSTOM: Manage keyboard navigation and Enter key behavior for focusable elements in a row\nfunction useFocusableInRowKeyboardNavigation(rowRef: React.RefObject) {\n React.useEffect(() => {\n const row = rowRef.current;\n if (!row) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (!row.contains(document.activeElement)) return;\n\n if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {\n e.preventDefault();\n e.stopPropagation(); // Helps override internal widget handlers\n const focusables = rowRef.current ? getFocusableElements(rowRef.current) : [];\n // activeElement is generic Element, so we need to cast it to HTMLElement\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const index = focusables.indexOf(document.activeElement as HTMLElement);\n const nextIndex = e.key === 'ArrowRight' ? index + 1 : index - 1;\n if (nextIndex >= 0 && nextIndex < focusables.length) {\n focusables[nextIndex].focus();\n }\n }\n\n if (e.key === 'Escape') {\n e.preventDefault();\n row.focus();\n }\n\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {\n e.preventDefault();\n }\n };\n\n row.addEventListener('keydown', handleKeyDown);\n\n return () => {\n row.removeEventListener('keydown', handleKeyDown);\n };\n }, [rowRef]);\n}\n\n// CUSTOM: Move focus left or right to adjacent focusable items in the same row\nfunction focusAdjacentFocusableElementInRow(\n focusablesInRow: HTMLElement[],\n currentIndexOfFocusables: number,\n direction: 'ArrowLeft' | 'ArrowRight',\n) {\n let nextFocusable: HTMLElement | undefined;\n if (direction === 'ArrowLeft' && currentIndexOfFocusables > 0) {\n nextFocusable = focusablesInRow[currentIndexOfFocusables - 1];\n } else if (direction === 'ArrowRight' && currentIndexOfFocusables < focusablesInRow.length - 1) {\n nextFocusable = focusablesInRow[currentIndexOfFocusables + 1];\n }\n if (nextFocusable) {\n requestAnimationFrame(() => nextFocusable.focus());\n return true;\n }\n return false;\n}\n\n// CUSTOM: Move focus up or down to adjacent rows in the same table\nfunction focusAdjacentRow(\n rowsInTable: HTMLTableRowElement[],\n currentRowIndex: number,\n direction: 'ArrowDown' | 'ArrowUp',\n) {\n let nextRow: HTMLTableRowElement | undefined;\n if (direction === 'ArrowDown' && currentRowIndex < rowsInTable.length - 1) {\n nextRow = rowsInTable[currentRowIndex + 1];\n } else if (direction === 'ArrowUp' && currentRowIndex > 0) {\n nextRow = rowsInTable[currentRowIndex - 1];\n }\n if (nextRow) {\n requestAnimationFrame(() => nextRow.focus());\n return true;\n }\n return false;\n}\n\n/** @inheritdoc Table */\nconst TableRow = React.forwardRef<\n HTMLTableRowElement,\n React.HTMLAttributes & { setFocusAlsoRunsSelect?: boolean }\n>(({ className, onKeyDown, onSelect, setFocusAlsoRunsSelect = false, ...props }, ref) => {\n // CUSTOM: Use internal ref to manage keyboard navigation and Enter key behavior\n // This ref gets passed into the table row ref property which expects null and not undefined\n // eslint-disable-next-line no-null/no-null\n const rowRef = React.useRef(null);\n\n // CUSTOM: Assign internal ref to external ref if provided\n React.useEffect(() => {\n if (typeof ref === 'function') {\n ref(rowRef.current);\n } else if (ref && 'current' in ref) {\n ref.current = rowRef.current;\n }\n }, [ref]);\n\n // CUSTOM: Use internal ref to manage keyboard navigation and Enter key behavior\n useFocusableInRowKeyboardNavigation(rowRef);\n\n // CUSTOM: Get all focusable elements in the current row\n const focusablesInRow = React.useMemo(\n () => (rowRef.current ? getFocusableElements(rowRef.current) : []),\n [rowRef],\n );\n\n // CUSTOM: Handle keydown events for keyboard navigation\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n const { current: currentRow } = rowRef;\n if (!currentRow || !currentRow.parentElement) return;\n\n const closestTable = currentRow.closest('table');\n const rowsInTable = closestTable\n ? // getFocusableElements returns an HTMLElement[] but we are filtering for HTMLTableRowElements\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n (getFocusableElements(closestTable) as HTMLTableRowElement[]).filter(\n (element) => element.tagName === 'TR',\n )\n : [];\n const currentRowIndex = rowsInTable.indexOf(currentRow);\n const currentIndexOfFocusables = focusablesInRow.indexOf(\n // activeElement is generic Element, so we need to cast it to HTMLElement\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n document.activeElement as HTMLElement,\n );\n\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {\n e.preventDefault();\n focusAdjacentRow(rowsInTable, currentRowIndex, e.key);\n } else if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n e.preventDefault();\n focusAdjacentFocusableElementInRow(focusablesInRow, currentIndexOfFocusables, e.key);\n } else if (e.key === 'Escape') {\n e.preventDefault();\n const table = currentRow.closest('table');\n if (table) {\n table.focus();\n }\n }\n\n // Call user-defined onKeyDown handler if provided\n onKeyDown?.(e);\n },\n [rowRef, focusablesInRow, onKeyDown],\n );\n\n const handleFocus = React.useCallback(\n (e: React.FocusEvent) => {\n if (setFocusAlsoRunsSelect) onSelect?.(e);\n },\n [setFocusAlsoRunsSelect, onSelect],\n );\n\n return (\n \n );\n});\nTableRow.displayName = 'TableRow';\n\n/** @inheritdoc Table */\nconst TableHead = React.forwardRef<\n HTMLTableCellElement,\n React.ThHTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nTableHead.displayName = 'TableHead';\n\n/** @inheritdoc Table */\nconst TableCell = React.forwardRef<\n HTMLTableCellElement,\n React.TdHTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nTableCell.displayName = 'TableCell';\n\n/** @inheritdoc Table */\nconst TableCaption = React.forwardRef<\n HTMLTableCaptionElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nTableCaption.displayName = 'TableCaption';\n\nexport { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };\n","import React from 'react';\nimport { cn } from '@/utils/shadcn-ui.util';\n\n/**\n * Use to show a placeholder while content is loading. This component is from Shadcn UI. See Shadcn\n * UI documentation: https://ui.shadcn.com/docs/components/skeleton\n */\nfunction Skeleton({ className, ...props }: React.HTMLAttributes) {\n return (\n \n );\n}\n\nexport { Skeleton };\n","import React, { useMemo, useState } from 'react';\n\nimport {\n ColumnFiltersState,\n flexRender,\n getCoreRowModel,\n getFilteredRowModel,\n getPaginationRowModel,\n getSortedRowModel,\n SortingState,\n ColumnDef as TSColumnDef,\n Row as TSRow,\n RowSelectionState as TSRowSelectionState,\n SortDirection as TSSortDirection,\n Table as TSTable,\n useReactTable,\n VisibilityState,\n} from '@tanstack/react-table';\n\nimport { DataTableViewOptions } from '@/components/advanced/data-table/data-table-column-toggle.component';\nimport { DataTablePagination } from '@/components/advanced/data-table/data-table-pagination.component';\nimport { Button } from '@/components/shadcn-ui/button';\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@/components/shadcn-ui/table';\nimport { Skeleton } from '@/components/shadcn-ui/skeleton';\n\nexport type ColumnDef = TSColumnDef;\nexport type RowContents = TSRow;\nexport type TableContents = TSTable;\nexport type SortDirection = TSSortDirection;\nexport type RowSelectionState = TSRowSelectionState;\n\ninterface DataTableProps {\n columns: ColumnDef[];\n data: TData[] | undefined;\n enablePagination?: boolean;\n showPaginationControls?: boolean;\n showColumnVisibilityControls?: boolean;\n stickyHeader?: boolean;\n onRowClickHandler?: (row: RowContents, table: TableContents) => void;\n id?: string;\n isLoading?: boolean;\n noResultsMessage: string;\n}\n\n/**\n * Feature-rich table component that infuses our basic shadcn-based Table component with features\n * from TanStack's React Table library\n */\nexport function DataTable({\n columns,\n data,\n enablePagination = false,\n showPaginationControls = false,\n showColumnVisibilityControls = false,\n stickyHeader = false,\n onRowClickHandler = () => {},\n id,\n isLoading = false,\n noResultsMessage,\n}: DataTableProps) {\n const [sorting, setSorting] = useState([]);\n const [columnFilters, setColumnFilters] = useState([]);\n const [columnVisibility, setColumnVisibility] = useState({});\n const [rowSelection, setRowSelection] = useState({});\n\n const normalizedData = useMemo(() => data ?? [], [data]);\n\n const table = useReactTable({\n data: normalizedData,\n columns,\n getCoreRowModel: getCoreRowModel(),\n ...(enablePagination && { getPaginationRowModel: getPaginationRowModel() }),\n onSortingChange: setSorting,\n getSortedRowModel: getSortedRowModel(),\n onColumnFiltersChange: setColumnFilters,\n getFilteredRowModel: getFilteredRowModel(),\n onColumnVisibilityChange: setColumnVisibility,\n onRowSelectionChange: setRowSelection,\n state: {\n sorting,\n columnFilters,\n columnVisibility,\n rowSelection,\n },\n });\n\n const visibleColumns = table.getVisibleFlatColumns();\n let bodyContent: React.ReactNode;\n\n if (isLoading) {\n const rowCount = 10;\n const skeletonRowIds = Array.from({ length: rowCount }).map((_, idx) => `skeleton-row-${idx}`);\n bodyContent = skeletonRowIds.map((rowId) => (\n \n \n
    \n \n
    \n
    \n
    \n ));\n } else if (table.getRowModel().rows?.length > 0) {\n bodyContent = table.getRowModel().rows.map((row) => (\n onRowClickHandler(row, table)}\n key={row.id}\n data-state={row.getIsSelected() && 'selected'}\n >\n {row.getVisibleCells().map((cell) => (\n \n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n \n ))}\n \n ));\n } else {\n bodyContent = (\n \n \n {noResultsMessage}\n \n \n );\n }\n\n return (\n
    \n {showColumnVisibilityControls && }\n \n \n {table.getHeaderGroups().map((headerGroup) => (\n \n {headerGroup.headers.map((header) => {\n return (\n \n {header.isPlaceholder\n ? undefined\n : flexRender(header.column.columnDef.header, header.getContext())}\n \n );\n })}\n \n ))}\n \n {bodyContent}\n
    \n {enablePagination && (\n
    \n table.previousPage()}\n disabled={!table.getCanPreviousPage()}\n >\n Previous\n \n table.nextPage()}\n disabled={!table.getCanNextPage()}\n >\n Next\n \n
    \n )}\n {enablePagination && showPaginationControls && }\n
    \n );\n}\n\nexport default DataTable;\n","import { cn } from '@/utils/shadcn-ui.util';\nimport Markdown, { MarkdownToJSX } from 'markdown-to-jsx';\nimport { useMemo } from 'react';\n\ninterface MarkdownRendererProps {\n /** Optional unique identifier */\n id?: string;\n /** The markdown string to render */\n markdown: string;\n className?: string;\n /**\n * The [`target` attribute for `a` html\n * tags](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target). Defaults to not\n * adding a `target` to `a` tags\n */\n anchorTarget?: string;\n /** Optional flag to truncate the content to 3 lines */\n truncate?: boolean;\n}\n\n/**\n * This component renders markdown content given a markdown string. It uses typography styles from\n * the platform.\n *\n * @param MarkdownRendererProps\n * @returns A div containing the rendered markdown content.\n */\nexport function MarkdownRenderer({\n id,\n markdown,\n className,\n anchorTarget,\n truncate,\n}: MarkdownRendererProps) {\n const options: MarkdownToJSX.Options = useMemo(\n () => ({\n overrides: {\n a: {\n props: {\n target: anchorTarget,\n },\n },\n },\n }),\n [anchorTarget],\n );\n return (\n \n {markdown}\n \n );\n}\n\nexport default MarkdownRenderer;\n","import { Copy } from 'lucide-react';\nimport { Button } from '@/components/shadcn-ui/button';\nimport { LocalizedStringValue } from 'platform-bible-utils';\n\n/**\n * Object containing all keys used for localization in this component. If you're using this\n * component in an extension, you can pass it into the useLocalizedStrings hook to easily obtain the\n * localized strings and pass them into the localizedStrings prop of this component\n */\nexport const ERROR_DUMP_STRING_KEYS = Object.freeze([\n '%webView_error_dump_header%',\n '%webView_error_dump_info_message%',\n] as const);\n\nexport type ErrorDumpLocalizedStrings = {\n [localizedInventoryKey in (typeof ERROR_DUMP_STRING_KEYS)[number]]?: LocalizedStringValue;\n};\n\n/**\n * Gets the localized value for the provided key\n *\n * @param strings Object containing localized string\n * @param key Key for a localized string\n * @returns The localized value for the provided key, if available. Returns the key if no localized\n * value is available\n */\nconst localizeString = (\n strings: ErrorDumpLocalizedStrings,\n key: keyof ErrorDumpLocalizedStrings,\n) => {\n return strings[key] ?? key;\n};\n\n/** Interface to store the parameters for the ErrorDump component */\nexport interface ErrorDumpProps {\n /** String containing the error details to show */\n errorDetails: string;\n /** Handler function to notify the frontend when the error is copied */\n handleCopyNotify?: () => void;\n /**\n * List of localized strings to localize the strings in this component. Relevant keys can be found\n * in `ERROR_DUMP_STRING_KEYS`\n */\n localizedStrings: ErrorDumpLocalizedStrings;\n /** Optional id for the root element */\n id?: string;\n}\n\n/** Component to render an error dump */\nexport function ErrorDump({\n errorDetails,\n handleCopyNotify,\n localizedStrings,\n id,\n}: ErrorDumpProps) {\n const headerText = localizeString(localizedStrings, '%webView_error_dump_header%');\n const infoMessage = localizeString(localizedStrings, '%webView_error_dump_info_message%');\n\n function handleCopy() {\n navigator.clipboard.writeText(errorDetails);\n if (handleCopyNotify) {\n handleCopyNotify();\n }\n }\n\n return (\n \n
    \n
    \n
    \n {headerText}\n
    \n
    \n {infoMessage}\n
    \n
    \n \n
    \n
    \n
    {errorDetails}
    \n
    \n \n );\n}\n","import { PropsWithChildren, useState } from 'react';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { ErrorDump, ErrorDumpProps, ERROR_DUMP_STRING_KEYS } from '../basics/error-dump.component';\nimport { Popover, PopoverContent, PopoverTrigger } from '../shadcn-ui/popover';\nimport { Label } from '../shadcn-ui/label';\n\n/**\n * Object containing all keys used for localization in the ErrorPopover component. This extends\n * ERROR_DUMP_STRING_KEYS with additional keys specific to the ErrorPopover. If you're using this\n * component in an extension, you can pass it into the useLocalizedStrings hook to easily obtain the\n * localized strings and pass them into the localizedStrings prop of this component\n */\nexport const ERROR_POPOVER_STRING_KEYS = Object.freeze([\n ...ERROR_DUMP_STRING_KEYS,\n '%webView_error_dump_copied_message%',\n] as const);\n\nexport type ErrorPopoverLocalizedStrings = {\n [localizedKey in (typeof ERROR_POPOVER_STRING_KEYS)[number]]?: string;\n};\n\n/** Interface to store the parameters for the ErrorPopover component */\nexport type ErrorPopoverProps = PropsWithChildren &\n Omit & {\n /**\n * List of localized strings to localize the strings in this component. Relevant keys can be\n * found in `ERROR_POPOVER_STRING_KEYS`\n */\n localizedStrings: ErrorPopoverLocalizedStrings;\n /** Optional CSS classes to insert into the `PopoverContent` */\n className?: string;\n /** Optional ID for the popover content for accessibility */\n id?: string;\n };\n\n/** A popover component that displays detailed error information using the ErrorDump component. */\nexport function ErrorPopover({\n errorDetails,\n handleCopyNotify,\n localizedStrings,\n children,\n className,\n id,\n}: ErrorPopoverProps) {\n const [isCopySuccess, setIsCopySuccess] = useState(false);\n\n const handleCopyWithNotification = () => {\n setIsCopySuccess(true);\n if (handleCopyNotify) {\n handleCopyNotify();\n }\n };\n\n const handleOpenChange = (open: boolean) => {\n if (!open) {\n setIsCopySuccess(false);\n }\n };\n\n return (\n \n {children}\n \n {isCopySuccess && localizedStrings['%webView_error_dump_copied_message%'] && (\n \n )}\n \n \n \n );\n}\n","import {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuLabel,\n DropdownMenuGroup,\n DropdownMenuCheckboxItem,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuRadioGroup,\n} from '@/components/shadcn-ui/dropdown-menu';\nimport { Button } from '@/components/shadcn-ui/button';\nimport { ChevronDown, Filter } from 'lucide-react';\nimport { useState } from 'react';\n\n/** The DropdownMenuItemType enum is used to determine the type of the dropdown item */\nexport enum DropdownMenuItemType {\n Check,\n Radio,\n}\n\nexport type DropdownItem = {\n /** Unique identifier for this dropdown */\n id: string;\n /** The label is the text that will be displayed on the dropdown item. */\n label: string;\n /** The onUpdate function is called when the state of a dropdown item is changed. */\n onUpdate: (id: string, checked?: boolean) => void;\n};\n\nexport type DropdownGroup = {\n /**\n * The label is the text that will be displayed on the dropdown group. It is used to categorize\n * the items in the group.\n */\n label: string;\n /** The itemType determines the DropdownMenuItemType type as either Check or Radio. */\n itemType: DropdownMenuItemType;\n /** The items array contains the items that will be displayed in the dropdown group */\n items: DropdownItem[];\n};\n\nexport type FilterDropdownProps = {\n /** Object unique identifier */\n id?: string;\n /** Label for the trigger button */\n label: string;\n /** The groups array contains the groups that will be displayed in the dropdown */\n groups: DropdownGroup[];\n}; // TODO: extend the props later\n\n/**\n * The FilterDropdown component is a dropdown designed for filtering content. It includes groups of\n * items that can be checkboxes or radio items.\n *\n * @param FilterDropdownProps\n * @returns A filter dropdown.\n */\nexport function FilterDropdown({ id, label, groups }: FilterDropdownProps) {\n // Populates the boolean Arrays for the group indexes that are checkbox groups\n const [checkedStates, setCheckedStates] = useState>(\n Object.fromEntries(\n groups\n .map((group, index) =>\n group.itemType === DropdownMenuItemType.Check ? [index, []] : undefined,\n )\n .filter((entry) => !!entry),\n ),\n );\n const [radioStates, setRadioStates] = useState>({});\n\n const handleCheckboxUpdate = (groupIndex: number, index: number) => {\n const newCheckedState = !checkedStates[groupIndex][index];\n // Update the checked state first\n setCheckedStates((oldCheckedStates) => {\n oldCheckedStates[groupIndex][index] = newCheckedState;\n return { ...oldCheckedStates };\n });\n\n // Calls the `onUpdate()` handler function for the dropdown item\n const item = groups[groupIndex].items[index];\n item.onUpdate(item.id, newCheckedState);\n };\n\n const handleRadioUpdate = (groupIndex: number, value: string) => {\n // Updates the radio state first\n setRadioStates((oldRadioStates) => {\n oldRadioStates[groupIndex] = value;\n return { ...oldRadioStates };\n });\n\n // Calls the `onUpdate()` handler function for the dropdown item\n const currentItem = groups[groupIndex].items.find((item) => item.id === value);\n if (currentItem) {\n currentItem.onUpdate(value);\n } else {\n console.error(`Could not find dropdown radio item with id '${value}'!`);\n }\n };\n\n return (\n
    \n {/* TODO: remove this once the DropDown Menu shadcn has an id prop */}\n \n \n \n \n \n {groups.map((group, groupIndex) => (\n
    \n {group.label}\n \n {group.itemType === DropdownMenuItemType.Check ? (\n <>\n {group.items.map((item, index) => (\n
    \n handleCheckboxUpdate(groupIndex, index)}\n >\n {item.label}\n \n
    \n ))}\n \n ) : (\n handleRadioUpdate(groupIndex, value)}\n >\n {group.items.map((item) => (\n
    \n {item.label}\n
    \n ))}\n \n )}\n
    \n \n
    \n ))}\n
    \n
    \n
    \n );\n}\n\nexport default FilterDropdown;\n","import { Button } from '@/components/shadcn-ui/button';\nimport { CircleHelp, Link as LucideLink, User } from 'lucide-react';\nimport { NumberFormat } from 'platform-bible-utils';\n\n/** Interface that stores the parameters passed to the More Info component */\ninterface MoreInfoProps {\n /** Optional unique identifier */\n id?: string;\n /** The category of the extension */\n category: string;\n /** The number of downloads for the extension */\n downloads: Record;\n /** The languages supported by the extension */\n languages: string[];\n /** The URL to the more info page of the extension */\n moreInfoUrl: string;\n /** Handler function triggered when the more info (Website) link is clicked */\n handleMoreInfoLinkClick: () => void;\n /** Optional URL to a website link to get support for the extension */\n supportUrl: string;\n /** Handler function triggered when the support link is clicked */\n handleSupportLinkClick: () => void;\n}\n/**\n * This component displays the more info section of the extension which includes the category,\n * number of downloads, languages, and links to the website and support\n *\n * @param MoreInfoProps\n * @returns The more info component that displays the category, number of downloads, languages, and\n * links to the website and support\n */\nexport function MoreInfo({\n id,\n category,\n downloads,\n languages,\n moreInfoUrl,\n handleMoreInfoLinkClick,\n supportUrl,\n handleSupportLinkClick,\n}: MoreInfoProps) {\n /**\n * This constant formats the number of downloads into a more readable format.\n *\n * @example 1000 -> 1K\n *\n * @example 1000000 -> 1M\n *\n * @returns The formatted number of downloads\n */\n const numberFormatted = new NumberFormat('en', {\n notation: 'compact',\n compactDisplay: 'short',\n }).format(Object.values(downloads).reduce((a: number, b: number) => a + b, 0));\n\n /** This function scrolls the window to the bottom of the page. */\n const handleScrollToBottom = () => {\n window.scrollTo(0, document.body.scrollHeight);\n };\n\n return (\n \n {category && (\n
    \n
    \n {category}\n
    \n CATEGORY\n
    \n )}\n
    \n
    \n \n {numberFormatted}\n
    \n USERS\n
    \n
    \n
    \n {languages.slice(0, 3).map((locale) => (\n \n {locale.toUpperCase()}\n \n ))}\n
    \n {languages.length > 3 && (\n handleScrollToBottom()}\n className=\"tw-text-xs tw-text-foreground tw-underline\"\n >\n +{languages.length - 3} more languages\n \n )}\n
    \n {(moreInfoUrl || supportUrl) && (\n
    \n {moreInfoUrl && (\n
    \n handleMoreInfoLinkClick()}\n variant=\"link\"\n className=\"tw-flex tw-h-auto tw-gap-1 tw-py-0 tw-text-xs tw-font-semibold tw-text-foreground\"\n >\n Website\n \n \n
    \n )}\n {supportUrl && (\n
    \n handleSupportLinkClick()}\n variant=\"link\"\n className=\"tw-flex tw-h-auto tw-gap-1 tw-py-0 tw-text-xs tw-font-semibold tw-text-foreground\"\n >\n Support\n \n \n
    \n )}\n
    \n )}\n \n );\n}\n","import { useState } from 'react';\n\nexport type VersionInformation = {\n /** Date the version was published */\n date: string;\n /** Description of the changes in the version */\n description: string;\n};\n\n/** Type to store the version history information */\nexport type VersionHistoryType = Record;\n\n/** Interface that stores the parameters passed to the Version History component */\ninterface VersionHistoryProps {\n /** Optional unique identifier */\n id?: string;\n /** Object containing the versions mapped with their information */\n versionHistory: VersionHistoryType;\n}\n\n/**\n * Component to render the version history information shown in the footer component. Lists the 5\n * most recent versions, with the options to show all versions by pressing a button.\n *\n * @param VersionHistoryProps\n * @returns Rendered version history for the Footer component\n */\nexport function VersionHistory({ id, versionHistory }: VersionHistoryProps) {\n const [showAllVersions, setShowAllVersions] = useState(false);\n const currentDate = new Date();\n\n /**\n * Function to format the time string for the version history in the form of 'X year(s) ago'.\n *\n * @param dateString ISO Date string to determine the time string from\n * @returns Formatted time string\n */\n function formatTimeString(dateString: string) {\n const date = new Date(dateString);\n const dateDiff = new Date(currentDate.getTime() - date.getTime());\n const yearDiff = dateDiff.getUTCFullYear() - 1970;\n const monthDiff = dateDiff.getUTCMonth();\n const dayDiff = dateDiff.getUTCDate() - 1;\n\n // Determines how long ago the version was published\n let timeString = '';\n if (yearDiff > 0) {\n timeString = `${yearDiff.toString()} year${yearDiff === 1 ? '' : 's'} ago`;\n } else if (monthDiff > 0) {\n timeString = `${monthDiff.toString()} month${monthDiff === 1 ? '' : 's'} ago`;\n } else if (dayDiff === 0) {\n timeString = 'today';\n } else {\n timeString = `${dayDiff.toString()} day${dayDiff === 1 ? '' : 's'} ago`;\n }\n\n return timeString;\n }\n\n // Sorts the version history by version number\n const sortedEntries = Object.entries(versionHistory).sort((a, b) => b[0].localeCompare(a[0]));\n\n return (\n
    \n

    What`s New

    \n
      \n {(showAllVersions ? sortedEntries : sortedEntries.slice(0, 5)).map((entry) => (\n
      \n
      \n
    • \n {entry[1].description}\n
    • \n
      \n
      \n
      Version {entry[0]}
      \n
      {formatTimeString(entry[1].date)}
      \n
      \n
      \n ))}\n
    \n {sortedEntries.length > 5 && (\n setShowAllVersions(!showAllVersions)}\n className=\"tw-text-xs tw-text-foreground tw-underline\"\n >\n {showAllVersions ? 'Show Less Version History' : 'Show All Version History'}\n \n )}\n
    \n );\n}\n\nexport default VersionHistory;\n","import { useMemo } from 'react';\nimport { formatBytes, getCurrentLocale } from 'platform-bible-utils';\nimport { VersionHistory, VersionHistoryType } from './version-history.component';\n\n/** Interface to store the parameters passed to the Footer component */\ninterface FooterProps {\n /** Optional unique identifier */\n id?: string;\n /** Name of the publisher */\n publisherDisplayName: string;\n /** Size of the extension file in bytes */\n fileSize: number;\n /** List of language codes supported by the extension */\n locales: string[];\n /** Object containing the version history mapped with their information */\n versionHistory: VersionHistoryType;\n /** Current version of the extension */\n currentVersion: string;\n}\n\n/**\n * Component to render the footer for the extension details which contains information on the\n * publisher, version history, languages, and file size.\n *\n * @param FooterProps\n * @returns The rendered Footer component\n */\nexport function Footer({\n id,\n publisherDisplayName,\n fileSize,\n locales,\n versionHistory,\n currentVersion,\n}: FooterProps) {\n /** Formats the file size into a human-readable format */\n const formattedFileSize = useMemo(() => formatBytes(fileSize), [fileSize]);\n\n /**\n * This function gets the display names of the languages based on the language codes.\n *\n * @param codes The list of language codes\n * @returns The list of language names\n */\n const getLanguageNames = (codes: string[]) => {\n const displayNames = new Intl.DisplayNames(getCurrentLocale(), { type: 'language' });\n return codes.map((code) => displayNames.of(code));\n };\n\n const languageNames = getLanguageNames(locales);\n\n return (\n
    \n
    \n {Object.entries(versionHistory).length > 0 && (\n \n )}\n
    \n

    Information

    \n
    \n

    \n Publisher\n {publisherDisplayName}\n Size\n {formattedFileSize}\n

    \n
    \n

    \n Version\n {currentVersion}\n Languages\n {languageNames.join(', ')}\n

    \n
    \n
    \n
    \n
    \n
    \n );\n}\n\nexport default Footer;\n","import { Button, buttonVariants } from '@/components/shadcn-ui/button';\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from '@/components/shadcn-ui/command';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/shadcn-ui/popover';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Check, ChevronsUpDown, Star } from 'lucide-react';\nimport { ReactNode, useCallback, useMemo, useState } from 'react';\nimport { type VariantProps } from 'class-variance-authority';\n\nexport type MultiSelectComboBoxEntry = {\n value: string;\n label: string;\n secondaryLabel?: string;\n starred?: boolean;\n};\n\n/**\n * Props for MultiSelectComboBox component that provides a UI for selecting multiple items from a\n * list. It supports displaying a placeholder, custom selected text, and an optional icon. Users can\n * search through options and view starred items prominently.\n */\nexport interface MultiSelectComboBoxProps {\n /** The list of entries to select from. */\n entries: MultiSelectComboBoxEntry[];\n /** The currently selected values. */\n selected: string[];\n /** Callback function to handle changes in selection. */\n onChange: (values: string[]) => void;\n /** Placeholder text when no items are selected. */\n placeholder: string;\n /** Whether to show select all/clear all buttons. */\n hasToggleAllFeature?: boolean;\n /** Text for the select all button. */\n selectAllText?: string;\n /** Text for the clear all button. */\n clearAllText?: string;\n /** Message displayed when no entries are found. */\n commandEmptyMessage?: string;\n /** Custom text to display when items are selected. */\n customSelectedText?: string;\n /** Whether the dropdown is open (for controlled usage). */\n isOpen?: boolean;\n /** Handler that is called when the dropdown's open state changes. */\n onOpenChange?: (open: boolean) => void;\n /** Flag to disable the component. */\n isDisabled?: boolean;\n /** Flag to sort selected items. */\n sortSelected?: boolean;\n /** Optional icon to display in the button. */\n icon?: ReactNode;\n /** Additional class names for styling. */\n className?: string;\n /** Button variant to use for the trigger button. */\n variant?: VariantProps['variant'];\n /** Optional ID for the component. */\n id?: string;\n}\n\n/** MultiSelectComboBox component for selecting multiple items from a list. */\nexport function MultiSelectComboBox({\n entries,\n selected,\n onChange,\n placeholder,\n hasToggleAllFeature = false,\n selectAllText = 'Select All',\n clearAllText = 'Clear All',\n commandEmptyMessage = 'No entries found',\n customSelectedText,\n isOpen = undefined,\n onOpenChange = undefined,\n isDisabled = false,\n sortSelected = false,\n icon = undefined,\n className = undefined,\n variant = 'ghost',\n id,\n}: MultiSelectComboBoxProps) {\n const [isOpenLocal, setIsOpenLocal] = useState(false);\n\n const handleSelect = useCallback(\n (label: string) => {\n const value = entries.find((entry) => entry.label === label)?.value;\n if (!value) return;\n onChange(\n selected.includes(value) ? selected.filter((item) => item !== value) : [...selected, value],\n );\n },\n [entries, selected, onChange],\n );\n\n const getPlaceholderText = () => {\n if (customSelectedText) return customSelectedText;\n return placeholder;\n };\n\n const sortedOptions = useMemo(() => {\n if (!sortSelected) return entries;\n\n const starredItems = entries\n .filter((opt) => opt.starred)\n .sort((a, b) => a.label.localeCompare(b.label));\n const nonStarredItems = entries\n .filter((opt) => !opt.starred)\n .sort((a, b) => {\n const aSelected = selected.includes(a.value);\n const bSelected = selected.includes(b.value);\n if (aSelected && !bSelected) return -1;\n if (!aSelected && bSelected) return 1;\n return a.label.localeCompare(b.label);\n });\n\n return [...starredItems, ...nonStarredItems];\n }, [entries, selected, sortSelected]);\n\n const handleSelectAll = () => {\n onChange(entries.map((entry) => entry.value));\n };\n\n const handleClearAll = () => {\n onChange([]);\n };\n\n const actualIsOpen = isOpen ?? isOpenLocal;\n const actualOnOpenChange = onOpenChange ?? setIsOpenLocal;\n\n return (\n
    \n \n \n \n
    \n {icon && (\n
    \n \n {icon}\n \n
    \n )}\n \n {getPlaceholderText()}\n \n
    \n \n \n
    \n \n \n \n {hasToggleAllFeature && (\n
    \n \n \n
    \n )}\n \n {commandEmptyMessage}\n \n {sortedOptions.map((option) => {\n return (\n \n
    \n \n
    \n {option.starred && }\n
    {option.label}
    \n {option.secondaryLabel && (\n
    \n {option.secondaryLabel}\n
    \n )}\n \n );\n })}\n
    \n
    \n
    \n
    \n
    \n
    \n );\n}\n\nexport default MultiSelectComboBox;\n","import { Badge } from '@/components/shadcn-ui/badge';\nimport { Button } from '@/components/shadcn-ui/button';\nimport { Label } from '@/components/shadcn-ui/label';\nimport { X } from 'lucide-react';\nimport { MultiSelectComboBox, MultiSelectComboBoxProps } from './multi-select-combo-box.component';\n\ninterface FilterProps extends MultiSelectComboBoxProps {\n /**\n * Placeholder text that will be displayed when no items are selected. It will appear at the\n * location where the badges would be if any items were selected.\n */\n badgesPlaceholder: string;\n /** Optional id for the component */\n id?: string;\n}\n\n/**\n * This is a variant of the {@link MultiSelectComboBox}, that shows a {@link Badge} component for each\n * selected item in the combo box. Clicking the 'X' icon on the badge will clear the item from the\n * selected options. A placeholder text must be provided through 'badgesPlaceholder'. This will be\n * displayed if no items are selected,\n */\nexport function Filter({\n entries,\n selected,\n onChange,\n placeholder,\n commandEmptyMessage,\n customSelectedText,\n isDisabled,\n sortSelected,\n icon,\n className,\n badgesPlaceholder,\n id,\n}: FilterProps) {\n return (\n
    \n \n {selected.length > 0 ? (\n
    \n {selected.map((type) => (\n \n onChange(selected.filter((selectedType) => selectedType !== type))}\n >\n \n \n {entries.find((entry) => entry.value === type)?.label}\n \n ))}\n
    \n ) : (\n \n )}\n
    \n );\n}\n\nexport default Filter;\n","import React from 'react';\nimport { cn } from '@/utils/shadcn-ui.util';\n\n/**\n * Props for Input component\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/input}\n */\nexport interface InputProps extends React.InputHTMLAttributes {}\n\n/**\n * Input component displays a form input field or a component that looks like an input field. This\n * components is built and styled with Shadcn UI.\n *\n * @param InputProps\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/input}\n */\nexport const Input = React.forwardRef(\n ({ className, type, ...props }, ref) => {\n return (\n \n );\n },\n);\nInput.displayName = 'Input';\n","import {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '@/components/shadcn-ui/dropdown-menu';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@/components/shadcn-ui/tooltip';\nimport { Button } from '@/components/shadcn-ui/button';\nimport { GENERATOR_NOTE_CALLER, HIDDEN_NOTE_CALLER } from '@eten-tech-foundation/platform-editor';\nimport { Input } from '@/components/shadcn-ui/input';\nimport { KeyboardEvent, useEffect, useRef, useState } from 'react';\nimport { FootnoteCallerType, FootnoteEditorLocalizedStrings } from './footnote-editor.types';\n\ninterface FootnoteCallerDropdownProps {\n /** The caller type value to pass to the dropdown */\n callerType: FootnoteCallerType;\n /** Function to update the caller type */\n updateCallerType: (newCallerType: FootnoteCallerType) => void;\n /** The custom caller to pass to the custom caller input field */\n customCaller: string;\n /** FUnction to update the custom caller */\n updateCustomCaller: (newCustomCaller: string) => void;\n /** Localized strings from the parent component */\n localizedStrings: FootnoteEditorLocalizedStrings;\n}\n\nconst renderCallerButtonContent = (\n callerType: FootnoteCallerType,\n localizedStrings: FootnoteEditorLocalizedStrings,\n customCaller: string,\n) => {\n if (callerType === 'generated') {\n return (\n <>\n

    +

    {localizedStrings['%footnoteEditor_callerDropdown_item_generated%']}\n \n );\n }\n\n if (callerType === 'hidden') {\n return (\n <>\n

    -

    {localizedStrings['%footnoteEditor_callerDropdown_item_hidden%']}\n \n );\n }\n\n return (\n <>\n

    {customCaller}

    {localizedStrings['%footnoteEditor_callerDropdown_item_custom%']}\n \n );\n};\n\nexport function FootnoteCallerDropdown({\n callerType,\n updateCallerType,\n customCaller,\n updateCustomCaller,\n localizedStrings,\n}: FootnoteCallerDropdownProps) {\n // The ref must start with being null to be passed as an element ref\n // eslint-disable-next-line no-null/no-null\n const customCallerInputRef = useRef(null);\n // The ref must start with being null to be passed as an element ref\n // eslint-disable-next-line no-null/no-null\n const customCallerSelectRef = useRef(null);\n // The ref must start with being null to be passed as an element ref\n // eslint-disable-next-line no-null/no-null\n const isCustomCallerInputFocused = useRef(false);\n const [selectedCallerType, setSelectedCallerType] = useState(callerType);\n const [newCustomCaller, setNewCustomCaller] = useState(customCaller);\n const [isDropdownOpen, setIsDropdownOpen] = useState(false);\n\n // If the caller type changes, the selected caller type needs to change also\n useEffect(() => {\n setSelectedCallerType(callerType);\n }, [callerType]);\n\n // If the parent custom caller changes, then the new custom caller should reflect the changes\n useEffect(() => {\n if (newCustomCaller !== customCaller) {\n setNewCustomCaller(customCaller);\n }\n // This can't be triggered when the new custom caller updates because otherwise this will\n // completely prevent the input field from being edited\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [customCaller]);\n\n const handleDropdownOpenChange = (open: boolean) => {\n isCustomCallerInputFocused.current = false;\n setIsDropdownOpen(open);\n if (!open) {\n // This makes it so that if the custom caller is invalid, then reverts back to the previous\n // selected caller\n if (selectedCallerType !== 'custom' || newCustomCaller) {\n updateCallerType(selectedCallerType);\n updateCustomCaller(newCustomCaller);\n } else {\n setSelectedCallerType(callerType);\n setNewCustomCaller(customCaller);\n }\n }\n };\n\n const handleKeyDown = (event: KeyboardEvent) => {\n event.stopPropagation();\n // Allow to navigate to the input field\n if (\n (document.activeElement === customCallerSelectRef.current && event.key === 'ArrowDown') ||\n event.key === 'ArrowRight'\n ) {\n customCallerInputRef.current?.focus();\n isCustomCallerInputFocused.current = true;\n } else if (document.activeElement === customCallerInputRef.current && event.key === 'ArrowUp') {\n customCallerSelectRef.current?.focus();\n isCustomCallerInputFocused.current = false;\n } else if (\n document.activeElement === customCallerInputRef.current &&\n event.key === 'ArrowLeft' &&\n customCallerInputRef.current?.selectionStart === 0\n ) {\n customCallerSelectRef.current?.focus();\n isCustomCallerInputFocused.current = false;\n }\n\n // Allow the dropdown menu to be submitted if the custom caller is selected when you press enter\n if (\n selectedCallerType === 'custom' &&\n event.key === 'Enter' &&\n (document.activeElement === customCallerSelectRef.current ||\n document.activeElement === customCallerInputRef.current)\n ) {\n handleDropdownOpenChange(false);\n }\n };\n\n return (\n \n \n \n \n \n \n \n \n \n {localizedStrings['%footnoteEditor_callerDropdown_tooltip%']}\n \n \n \n {\n if (isCustomCallerInputFocused.current) isCustomCallerInputFocused.current = false;\n }}\n onKeyDown={handleKeyDown}\n onMouseMove={() => {\n if (isCustomCallerInputFocused.current) customCallerInputRef.current?.focus();\n }}\n >\n \n {localizedStrings['%footnoteEditor_callerDropdown_label%']}\n \n \n setSelectedCallerType('generated')}\n >\n
    \n {localizedStrings['%footnoteEditor_callerDropdown_item_generated%']}\n {GENERATOR_NOTE_CALLER}\n
    \n \n setSelectedCallerType('hidden')}\n >\n
    \n {localizedStrings['%footnoteEditor_callerDropdown_item_hidden%']}\n {HIDDEN_NOTE_CALLER}\n
    \n \n setSelectedCallerType('custom')}\n onClick={(event) => {\n event.stopPropagation();\n isCustomCallerInputFocused.current = true;\n customCallerInputRef.current?.focus();\n }}\n onSelect={(event) => event.preventDefault()}\n >\n
    \n {localizedStrings['%footnoteEditor_callerDropdown_item_custom%']}\n {\n event.stopPropagation();\n setSelectedCallerType('custom');\n isCustomCallerInputFocused.current = true;\n }}\n ref={customCallerInputRef}\n className=\"tw-h-auto tw-w-10 tw-p-0 tw-text-center\"\n value={newCustomCaller}\n onKeyDown={(event) => {\n if (\n !(\n event.key === 'Enter' ||\n event.key === 'ArrowUp' ||\n event.key === 'ArrowDown' ||\n event.key === 'ArrowLeft' ||\n event.key === 'ArrowRight'\n )\n )\n event.stopPropagation();\n }}\n maxLength={1}\n onChange={(event) => setNewCustomCaller(event.target.value)}\n />\n
    \n \n \n
    \n );\n}\n","import { Button } from '@/components/shadcn-ui/button';\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '@/components/shadcn-ui/dropdown-menu';\nimport { Tooltip, TooltipContent, TooltipProvider } from '@/components/shadcn-ui/tooltip';\nimport { TooltipTrigger } from '@radix-ui/react-tooltip';\nimport { FunctionSquare, SquareSigma, SquareX } from 'lucide-react';\nimport { formatReplacementString } from 'platform-bible-utils';\nimport { FootnoteEditorLocalizedStrings } from './footnote-editor.types';\n\ninterface FootnoteTypeDropdownProps {\n noteType: string;\n handleNoteTypeChange: (newNoteType: string) => void;\n localizedStrings: FootnoteEditorLocalizedStrings;\n isTypeSwitchable: boolean;\n}\n\nconst renderNoteTypeButtonContent = (\n noteType: string,\n localizedStrings: FootnoteEditorLocalizedStrings,\n) => {\n if (noteType === 'f') {\n return (\n <>\n {localizedStrings['%footnoteEditor_noteType_footnote_label%']}\n \n );\n }\n\n if (noteType === 'fe') {\n return (\n <>\n {localizedStrings['%footnoteEditor_noteType_endNote_label%']}\n \n );\n }\n\n return (\n <>\n {localizedStrings['%footnoteEditor_noteType_crossReference_label%']}\n \n );\n};\n\nconst formatNoteTypeTooltip = (\n noteType: string,\n localizedStrings: FootnoteEditorLocalizedStrings,\n) => {\n if (noteType === 'x') {\n return localizedStrings['%footnoteEditor_noteType_crossReference_label%'];\n }\n\n let noteTypeString = localizedStrings['%footnoteEditor_noteType_endNote_label%'];\n if (noteType === 'f') {\n noteTypeString = localizedStrings['%footnoteEditor_noteType_footnote_label%'];\n }\n\n return formatReplacementString(localizedStrings['%footnoteEditor_noteType_tooltip%'] ?? '', {\n noteType: noteTypeString,\n });\n};\n\nexport function FootnoteTypeDropdown({\n noteType,\n handleNoteTypeChange,\n localizedStrings,\n isTypeSwitchable,\n}: FootnoteTypeDropdownProps) {\n return (\n \n \n \n \n \n \n \n \n \n

    {formatNoteTypeTooltip(noteType, localizedStrings)}

    \n
    \n
    \n
    \n \n \n {localizedStrings['%footnoteEditor_noteTypeDropdown_label%']}\n \n \n handleNoteTypeChange('x')}\n className=\"tw-gap-2\"\n >\n \n {localizedStrings['%footnoteEditor_noteType_crossReference_label%']}\n \n handleNoteTypeChange('f')}\n className=\"tw-gap-2\"\n >\n \n {localizedStrings['%footnoteEditor_noteType_footnote_label%']}\n \n handleNoteTypeChange('fe')}\n className=\"tw-gap-2\"\n >\n \n {localizedStrings['%footnoteEditor_noteType_endNote_label%']}\n \n \n
    \n );\n}\n","import { Button } from '@/components/shadcn-ui/button';\nimport {\n DeltaOp,\n DeltaOpInsertNoteEmbed,\n Editorial,\n EditorOptions,\n EditorRef,\n GENERATOR_NOTE_CALLER,\n getDefaultViewOptions,\n HIDDEN_NOTE_CALLER,\n isInsertEmbedOpOfType,\n} from '@eten-tech-foundation/platform-editor';\nimport { Check, Copy, X } from 'lucide-react';\nimport { createRef, useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport '@/components/advanced/footnote-editor/editor-overrides.css';\nimport { SerializedVerseRef } from '@sillsdev/scripture';\nimport {\n Tooltip,\n TooltipProvider,\n TooltipTrigger,\n TooltipContent,\n} from '@/components/shadcn-ui/tooltip';\nimport { Usj } from '@eten-tech-foundation/scripture-utilities';\nimport { FootnoteCallerDropdown } from './footnote-caller-dropdown.component';\nimport { FootnoteTypeDropdown } from './footnote-type-dropdown.component';\nimport { FootnoteCallerType, FootnoteEditorLocalizedStrings } from './footnote-editor.types';\n\n/** Interface containing the types of the properties that are passed to the `FootnoteEditor` */\nexport interface FootnoteEditorProps {\n /** Class name for styling the embedded `Editor` component in this editor popover */\n classNameForEditor?: string;\n /** Delta ops for the current note being edited that are applied to the note editorial */\n noteOps: DeltaOpInsertNoteEmbed[] | undefined;\n /** External function to handle saving changes to the footnote */\n onSave: (noteOps: DeltaOpInsertNoteEmbed[]) => void;\n /**\n * External function to handle closing the footnote editor. Gets called when the editor is closed\n * without saving changes\n */\n onClose: () => void;\n /** The scripture reference for the parent editor */\n scrRef: SerializedVerseRef;\n /** The unique note key to identify the note being edited used to apply changes to the note */\n noteKey: string | undefined;\n /** View options of the parent editor */\n editorOptions: EditorOptions;\n /** Localized strings to be passed to the footnote editor component */\n localizedStrings: FootnoteEditorLocalizedStrings;\n}\n\n/**\n * Function to convert a footnote/endnote type node to a cross-reference type node\n *\n * @param op The node to be converted\n */\nfunction footnoteToCrossReferenceOp(op: DeltaOp) {\n // The built-in type for the delta note ops does not contain the types for the attributes\n // so have to cast it here\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const opCharAttribute = op.attributes?.char as Record;\n if (opCharAttribute.style) {\n if (opCharAttribute.style === 'ft') {\n opCharAttribute.style = 'xt';\n }\n\n if (opCharAttribute.style === 'fr') {\n opCharAttribute.style = 'xo';\n }\n\n if (opCharAttribute.style === 'fq') {\n opCharAttribute.style = 'xq';\n }\n }\n}\n\n/**\n * Function to convert a cross-reference type node to a footnote/endnote type node\n *\n * @param op THe node to be converted\n */\nfunction crossReferenceToFootnoteOp(op: DeltaOp) {\n // The built-in type for the delta note ops does not contain the types for the attributes\n // so have to cast it here\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const opCharAttribute = op.attributes?.char as Record;\n if (opCharAttribute.style) {\n if (opCharAttribute.style === 'xt') {\n opCharAttribute.style = 'ft';\n }\n\n if (opCharAttribute.style === 'xo') {\n opCharAttribute.style = 'fr';\n }\n\n if (opCharAttribute.style === 'xq') {\n opCharAttribute.style = 'fq';\n }\n }\n}\n\n// TODO: Remove this once the new marker menu is implemented with correct logic\n/**\n * This is for a temporary fix to get the markers menu to work by having the default usj include a\n * parent paragraph node\n */\nconst PARAGRAPH_USJ: Usj = {\n type: 'USJ',\n version: '3.1',\n content: [\n {\n type: 'para',\n },\n ],\n};\n\n/**\n * Component to edit footnotes from within the editor component\n *\n * @param FootnoteEditorProps - The properties for the footnote editor component\n */\nexport default function FootnoteEditor({\n classNameForEditor,\n noteOps,\n onSave,\n onClose,\n scrRef,\n noteKey,\n editorOptions,\n localizedStrings,\n}: FootnoteEditorProps) {\n // The editor ref component must be this\n // eslint-disable-next-line no-null/no-null\n const editorRef = useRef(null);\n const editorParentRef = createRef();\n\n const [callerType, setCallerType] = useState('generated');\n const [customCaller, setCustomCaller] = useState('*');\n\n const [noteType, setNoteType] = useState('f');\n\n const [isTypeSwitchable, setIsTypeSwitchable] = useState(false);\n\n // Options for the editorial component\n const options = useMemo(\n () => ({\n ...editorOptions,\n markerMenuTrigger: editorOptions.markerMenuTrigger ?? '\\\\',\n hasExternalUI: true,\n view: { ...(editorOptions.view ?? getDefaultViewOptions()), noteMode: 'expanded' },\n }),\n [editorOptions],\n );\n\n // Makes it so that the footnote type change tooltip doesn't automatically focus when the\n // component opens by focusing the editor\n useEffect(() => {\n editorRef.current?.focus();\n });\n\n // When the component loads, applies the note ops to the current editor, gets the note ref and caller\n useEffect(() => {\n let timeout: ReturnType;\n const noteOp = noteOps?.at(0);\n if (noteOp && isInsertEmbedOpOfType('note', noteOp)) {\n const rawCaller = noteOp.insert.note?.caller;\n // Parses the current caller\n let parsedCallerType: FootnoteCallerType = 'custom';\n if (rawCaller === GENERATOR_NOTE_CALLER) {\n parsedCallerType = 'generated';\n } else if (rawCaller === HIDDEN_NOTE_CALLER) {\n parsedCallerType = 'hidden';\n } else if (rawCaller) {\n setCustomCaller(rawCaller);\n }\n setCallerType(parsedCallerType);\n // Assigns note type\n setNoteType(noteOp.insert.note?.style ?? 'f');\n timeout = setTimeout(() => {\n // Inserts the note node to be edited as an delta operation\n editorRef.current?.applyUpdate([noteOp]);\n }, 0);\n }\n\n return () => {\n if (timeout) {\n clearTimeout(timeout);\n }\n };\n }, [noteOps, noteKey]);\n\n const handleSave = useCallback(() => {\n const currentNoteOp = editorRef.current?.getNoteOps(0)?.at(0);\n if (currentNoteOp && isInsertEmbedOpOfType('note', currentNoteOp)) {\n if (currentNoteOp.insert.note) {\n if (callerType === 'custom') {\n currentNoteOp.insert.note.caller = customCaller;\n } else {\n currentNoteOp.insert.note.caller =\n callerType === 'generated' ? GENERATOR_NOTE_CALLER : HIDDEN_NOTE_CALLER;\n }\n }\n onSave([currentNoteOp]);\n }\n }, [callerType, customCaller, onSave]);\n\n const handleCopy = () => {\n const editorInput = editorParentRef.current?.getElementsByClassName('editor-input')[0];\n if (editorInput?.textContent) {\n navigator.clipboard.writeText(editorInput.textContent);\n }\n };\n\n const handleNoteTypeChange = (value: string) => {\n setNoteType(value);\n\n // Changes the note type for the current note that is being edited\n const currentNoteOp = editorRef.current?.getNoteOps(0)?.at(0);\n if (currentNoteOp && isInsertEmbedOpOfType('note', currentNoteOp)) {\n if (currentNoteOp.insert.note) currentNoteOp.insert.note.style = value;\n\n // If switching between cross-reference and footnote/endnote, need to switch the nodes inside\n const innerNoteOps = currentNoteOp.insert.note?.contents?.ops;\n if (noteType !== 'x' && value === 'x') {\n innerNoteOps?.forEach((op) => footnoteToCrossReferenceOp(op));\n } else if (noteType === 'x' && value !== 'x') {\n innerNoteOps?.forEach((op) => crossReferenceToFootnoteOp(op));\n }\n\n // Inserts the new footnote/cross-reference and deletes the old one\n editorRef.current?.applyUpdate([currentNoteOp, { delete: 1 }]);\n }\n };\n\n const handleUsjChange = (usj: Usj) => {\n const noteOp = editorRef.current?.getNoteOps(0)?.at(0);\n if (noteOp && isInsertEmbedOpOfType('note', noteOp)) {\n // Prevents adding additional note nodes or other nodes after the main footnote node\n if (usj.content.length > 1) {\n setTimeout(() => {\n // Retains the first two nodes which are the added paragraph node (for now) and the\n // footnote/cross-reference and deletes the unwanted node that was just inserted\n editorRef.current?.applyUpdate([{ retain: 2 }, { delete: 1 }]);\n }, 0);\n }\n const currentNoteType = noteOp.insert.note?.style;\n const innerNoteOps = noteOp.insert.note?.contents?.ops;\n if (!currentNoteType) setIsTypeSwitchable(false);\n\n if (currentNoteType === 'x') {\n setIsTypeSwitchable(\n !!innerNoteOps?.every((op) => {\n if (!op.attributes?.char) return true;\n // The built-in type for the delta note ops does not contain the types for the attributes\n // so have to cast it here\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const nodeType = (op.attributes?.char as Record).style;\n return nodeType === 'xt' || nodeType === 'xo' || nodeType === 'xq';\n }),\n );\n } else {\n setIsTypeSwitchable(\n !!innerNoteOps?.every((op) => {\n if (!op.attributes?.char) return true;\n // The built-in type for the delta note ops does not contain the types for the attributes\n // so have to cast it here\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const nodeType = (op.attributes?.char as Record).style;\n return nodeType === 'ft' || nodeType === 'fr' || nodeType === 'fq';\n }),\n );\n }\n } else {\n setIsTypeSwitchable(false);\n }\n };\n\n return (\n
    \n
    \n
    \n \n \n
    \n
    \n \n \n \n \n \n \n

    {localizedStrings['%footnoteEditor_cancelButton_tooltip%']}

    \n
    \n
    \n
    \n \n \n \n \n \n \n \n \n {localizedStrings['%footnoteEditor_saveButton_tooltip%']}\n \n \n \n
    \n
    \n \n
    \n {}}\n scrRef={scrRef}\n ref={editorRef}\n />\n
    \n
    \n \n \n \n \n \n \n

    {localizedStrings['%footnoteEditor_copyButton_tooltip%']}

    \n
    \n
    \n
    \n
    \n
    \n \n );\n}\n","/**\n * Object containing all keys used for localization in the FootnoteEditor component. If you're using\n * this component in an extension, you can pass it into the useLocalizedStrings hook to easily\n * obtain the localized strings and pass them into the localizedStrings prop of this component\n */\nexport const FOOTNOTE_EDITOR_STRING_KEYS = Object.freeze([\n '%footnoteEditor_callerDropdown_label%',\n '%footnoteEditor_callerDropdown_item_generated%',\n '%footnoteEditor_callerDropdown_item_hidden%',\n '%footnoteEditor_callerDropdown_item_custom%',\n '%footnoteEditor_callerDropdown_tooltip%',\n '%footnoteEditor_cancelButton_tooltip%',\n '%footnoteEditor_copyButton_tooltip%',\n '%footnoteEditor_noteType_crossReference_label%',\n '%footnoteEditor_noteType_endNote_label%',\n '%footnoteEditor_noteType_footnote_label%',\n '%footnoteEditor_noteType_tooltip%',\n '%footnoteEditor_noteTypeDropdown_label%',\n '%footnoteEditor_saveButton_tooltip%',\n] as const);\n\nexport type FootnoteEditorLocalizedStrings = {\n [localizedKey in (typeof FOOTNOTE_EDITOR_STRING_KEYS)[number]]?: string;\n};\n\nexport type FootnoteCallerType = 'generated' | 'hidden' | 'custom';\n","import React from 'react';\nimport { MarkerContent, MarkerObject } from '@eten-tech-foundation/scripture-utilities';\nimport { AlertCircle } from 'lucide-react';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { FootnoteItemProps } from './footnotes.types';\n\nfunction makeKey(parentMarker: string | undefined, content?: MarkerContent[]): string {\n if (!content || content.length === 0) return parentMarker ?? 'empty';\n\n const firstString = content.find((part) => typeof part === 'string');\n if (firstString) {\n return `key-${parentMarker ?? 'unknown'}-${firstString.slice(0, 10)}`;\n }\n\n // Fallback: combine markers\n const firstMarker =\n typeof content[0] === 'string' ? 'impossible' : (content[0].marker ?? 'unknown');\n return `key-${parentMarker ?? 'unknown'}-${firstMarker}`;\n}\n\nfunction renderParagraphs(\n parentMarker: string | undefined,\n content?: MarkerContent[],\n showMarkers = true,\n footnoteClosing: React.ReactNode | undefined = undefined,\n): React.ReactNode {\n if (!content || content.length === 0) return undefined;\n\n const markerHierarchy: string[] = [];\n\n const paragraphs: MarkerContent[][] = [];\n let current: MarkerContent[] = [];\n\n content.forEach((part) => {\n if (typeof part !== 'string' && part.marker === 'fp') {\n // End current paragraph before starting new one\n if (current.length > 0) paragraphs.push(current);\n\n // Start new paragraph that *includes* the fp marker itself\n current = [part];\n } else {\n current.push(part);\n }\n });\n\n if (current.length > 0) paragraphs.push(current);\n\n return paragraphs.map((para, i) => {\n const isLast = i === paragraphs.length - 1;\n return (\n

    \n {renderContent(parentMarker, para, showMarkers, true, markerHierarchy)}\n {isLast && footnoteClosing}\n

    \n );\n });\n}\n\nfunction renderContent(\n parentMarker: string | undefined,\n content?: MarkerContent[],\n showMarkers = true,\n allowUnmarkedText = true,\n markerHierarchy: string[] = [],\n): React.ReactNode {\n if (!content || content.length === 0) return undefined;\n\n return content.map((footnotePart) => {\n if (typeof footnotePart === 'string') {\n // Build a key based on the hierarchy and text\n const key = `${parentMarker}-text-${footnotePart.slice(0, 10)}`;\n if (allowUnmarkedText) {\n const classes = cn(`usfm_${parentMarker}`);\n return (\n \n {footnotePart}\n \n );\n }\n return (\n \n \n {footnotePart}\n \n \n );\n }\n\n return renderMarkerObject(\n footnotePart,\n makeKey(`${parentMarker}\\\\${footnotePart.marker}`, [footnotePart]),\n showMarkers,\n [...markerHierarchy, parentMarker ?? 'unknown'],\n );\n });\n}\n\nfunction renderMarkerObject(\n markerObj: MarkerObject,\n key: React.Key,\n showMarkers: boolean,\n markerHierarchy: string[] = [],\n): React.ReactNode {\n const { marker } = markerObj;\n\n return (\n \n {marker ? (\n showMarkers && {`\\\\${marker} `}\n ) : (\n \n )}\n {renderContent(marker, markerObj.content, showMarkers, true, [\n ...markerHierarchy,\n marker ?? 'unknown',\n ])}\n \n );\n}\n\n/** `FootnoteItem` is a component that provides a read-only display of a single USFM/JSX footnote. */\nexport function FootnoteItem({\n footnote,\n layout = 'horizontal',\n formatCaller,\n showMarkers = true,\n}: FootnoteItemProps) {\n const caller = formatCaller ? formatCaller(footnote.caller) : footnote.caller;\n const isCallerFormatted = caller !== footnote.caller;\n\n // Split out target reference (first top-level fr/xo, if any)\n let targetRef: MarkerContent | undefined;\n let remainingContent = footnote.content;\n\n if (\n Array.isArray(footnote.content) &&\n footnote.content.length > 0 &&\n typeof footnote.content[0] !== 'string' &&\n (footnote.content[0].marker === 'fr' || footnote.content[0].marker === 'xo')\n ) {\n [targetRef, ...remainingContent] = footnote.content;\n }\n\n const footnoteOpening = showMarkers ? (\n {`\\\\${footnote.marker} `}\n ) : undefined;\n\n const footnoteClosing = showMarkers ? (\n {` \\\\${footnote.marker}*`}\n ) : undefined;\n\n const footnoteCaller = caller && (\n // USFM does not specify a marker for caller, so instead of a usfm_* class, we use a\n // specific class name in case styling is needed.\n \n {caller}{' '}\n \n );\n const footnoteTargetRef = targetRef && (\n <>{renderContent(footnote.marker, [targetRef], showMarkers, false)} \n );\n\n const layoutClass = layout === 'horizontal' ? 'horizontal' : 'vertical';\n const markerClass = showMarkers ? 'marker-visible' : '';\n const footnoteBodyClass =\n layout === 'horizontal' ? 'tw-col-span-1' : 'tw-col-span-2 tw-col-start-1 tw-row-start-2';\n const baseClasses = cn(layoutClass, markerClass);\n\n return (\n <>\n
    \n {footnoteOpening}\n {footnoteCaller}\n
    \n
    \n {footnoteTargetRef}\n
    \n \n {remainingContent && remainingContent.length > 0 && (\n <>{renderParagraphs(footnote.marker, remainingContent, showMarkers, footnoteClosing)}\n )}\n \n \n );\n}\n\nexport default FootnoteItem;\n","import { MarkerObject } from '@eten-tech-foundation/scripture-utilities';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Separator } from '@/components/shadcn-ui/separator';\nimport { getFormatCallerFunction } from 'platform-bible-utils';\nimport React, { useEffect, useRef, useState } from 'react';\nimport { FootnoteItem } from './footnote-item.component';\nimport { FootnoteListProps } from './footnotes.types';\n\n/** `FootnoteList` is a component that provides a read-only display of a list of USFM/JSX footnote. */\nexport function FootnoteList({\n className,\n classNameForItems,\n footnotes,\n layout = 'horizontal',\n listId,\n selectedFootnote,\n showMarkers = true,\n suppressFormatting = false,\n formatCaller,\n onFootnoteSelected,\n}: FootnoteListProps) {\n const handleFormatCaller = formatCaller ?? getFormatCallerFunction(footnotes, undefined);\n const handleFootnoteClick = (footnote: MarkerObject, index: number) => {\n onFootnoteSelected?.(footnote, index, listId);\n };\n\n const initialFocusedIndex = selectedFootnote\n ? footnotes.findIndex((f) => f === selectedFootnote)\n : -1;\n\n const [focusedIndex, setFocusedIndex] = useState(initialFocusedIndex);\n\n const handleFootnoteKeyDown = (\n e: React.KeyboardEvent,\n footnote: MarkerObject,\n index: number,\n ) => {\n if (!footnotes.length) return;\n\n switch (e.key) {\n case 'Enter':\n case ' ':\n e.preventDefault();\n onFootnoteSelected?.(footnote, index, listId);\n break;\n\n default:\n break;\n }\n };\n\n const handleListKeyDown = (e: React.KeyboardEvent) => {\n if (!footnotes.length) return;\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n setFocusedIndex((prev) => Math.min(prev + 1, footnotes.length - 1));\n break;\n\n case 'ArrowUp':\n e.preventDefault();\n setFocusedIndex((prev) => Math.max(prev - 1, 0));\n break;\n\n default:\n break;\n }\n };\n\n const rowRefs = useRef<(HTMLLIElement | null)[]>([]);\n\n useEffect(() => {\n if (focusedIndex >= 0 && focusedIndex < rowRefs.current.length) {\n rowRefs.current[focusedIndex]?.focus();\n }\n }, [focusedIndex]);\n\n /*\n * TODO(PT-3743): After upgrading to Tailwind v4, move to using @container and @sm/@lg css\n * styling to replace the use of the `layout` variable to distinguish between\n * wide/skinny layouts.\n */\n return (\n \n \n {footnotes.map((footnote, idx) => {\n const isSelected = footnote === selectedFootnote;\n const key = `${listId}-${idx}`;\n return (\n <>\n {\n rowRefs.current[idx] = el;\n }}\n role=\"option\"\n aria-selected={isSelected}\n key={key}\n data-marker={footnote.marker}\n data-state={isSelected ? 'selected' : undefined}\n tabIndex={idx === focusedIndex ? 0 : -1}\n className={cn(\n 'tw-gap-x-3 tw-gap-y-1 tw-p-2 data-[state=selected]:tw-bg-muted',\n onFootnoteSelected && 'hover:tw-bg-muted/50',\n 'tw-w-full tw-rounded-sm tw-border-0 tw-bg-transparent tw-shadow-none',\n 'focus:tw-outline-none focus-visible:tw-outline-none',\n /* ENHANCE: After considerable fiddling, this set of styles makes a focus ring\n that looks great in Storybook. However, the left edge of the ring is clipped in\n P.B app. These are similar, but not identical to, the customizations made in\n our shadcn table component.\n */\n 'focus-visible:tw-ring-offset-0.5 focus-visible:tw-relative focus-visible:tw-z-10 focus-visible:tw-ring-2 focus-visible:tw-ring-ring',\n 'tw-grid tw-grid-flow-col tw-grid-cols-subgrid',\n layout === 'horizontal' ? 'tw-col-span-3' : 'tw-col-span-2 tw-row-span-2',\n classNameForItems,\n )}\n onClick={() => handleFootnoteClick(footnote, idx)}\n onKeyDown={(e) => handleFootnoteKeyDown(e, footnote, idx)}\n >\n handleFormatCaller(footnote.caller, idx)}\n showMarkers={showMarkers}\n />\n \n {/* Only render separator if not the last item */}\n {idx < footnotes.length - 1 && layout === 'vertical' && (\n \n )}\n \n );\n })}\n
\n \n );\n}\n\nexport default FootnoteList;\n","import {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@/components/shadcn-ui/table';\nimport { SerializedVerseRef } from '@sillsdev/scripture';\nimport { formatScrRef, LanguageStrings } from 'platform-bible-utils';\nimport { ReactNode, useMemo } from 'react';\nimport { InventoryItemOccurrence } from './inventory-utils';\n\n/**\n * Convert text with `\\\\word\\\\` markers to React elements with bold formatting.\n *\n * @param text Text containing `\\\\word\\\\` markers for bolding\n * @returns Array of React nodes with text and bold elements\n */\nfunction formatTextWithBold(text: string): ReactNode[] {\n const parts: ReactNode[] = [];\n let lastIndex = 0;\n // Look for text wrapped in double backslashes (e.g., \\\\bolded text\\\\)\n const regex = /\\\\\\\\(.+?)\\\\\\\\/g;\n let match;\n\n // regex.exec() returns null when no match is found\n // eslint-disable-next-line no-null/no-null, no-cond-assign\n while ((match = regex.exec(text)) !== null) {\n // Add text before the match\n if (match.index > lastIndex) {\n parts.push(text.substring(lastIndex, match.index));\n }\n // Add the bold text\n parts.push({match[1]});\n lastIndex = regex.lastIndex;\n }\n\n // Add any remaining text after the last match\n if (lastIndex < text.length) {\n parts.push(text.substring(lastIndex));\n }\n\n return parts.length > 0 ? parts : [text];\n}\n\n/** Props for the OccurrencesTable component */\ntype OccurrencesTableProps = {\n /** Data that contains scriptures references and snippets of scripture */\n occurrenceData: InventoryItemOccurrence[];\n /** Callback function that is executed when the scripture reference is changed */\n setScriptureReference: (scriptureReference: SerializedVerseRef) => void;\n /**\n * Object with all localized strings that the OccurrencesTable needs to work well across multiple\n * languages\n */\n localizedStrings: LanguageStrings;\n /** Class name to apply to the occurrence text */\n classNameForText?: string;\n};\n\n/**\n * Table that shows occurrences of specified inventory item(s). The first column shows the related\n * scripture reference. The second column shows the snippet of scripture that contains the specified\n * inventory item\n */\nexport function OccurrencesTable({\n occurrenceData,\n setScriptureReference,\n localizedStrings,\n classNameForText,\n}: OccurrencesTableProps) {\n const referenceHeaderText =\n localizedStrings['%webView_inventory_occurrences_table_header_reference%'];\n const occurrenceHeaderText =\n localizedStrings['%webView_inventory_occurrences_table_header_occurrence%'];\n\n const occurrences: InventoryItemOccurrence[] = useMemo(() => {\n const uniqueOccurrences: InventoryItemOccurrence[] = [];\n const seen = new Set();\n\n occurrenceData.forEach((occurrence) => {\n const key = `${occurrence.reference.book}:${occurrence.reference.chapterNum}:${occurrence.reference.verseNum}:${occurrence.text}`;\n\n if (!seen.has(key)) {\n seen.add(key);\n uniqueOccurrences.push(occurrence);\n }\n });\n\n return uniqueOccurrences;\n }, [occurrenceData]);\n\n return (\n \n \n \n {referenceHeaderText}\n {occurrenceHeaderText}\n \n \n \n {occurrences.length > 0 &&\n occurrences.map((occurrence) => (\n {\n setScriptureReference(occurrence.reference);\n }}\n >\n {formatScrRef(occurrence.reference, 'English')}\n \n {formatTextWithBold(occurrence.text)}\n \n \n ))}\n \n
\n );\n}\n\nexport default OccurrencesTable;\n","import React from 'react';\nimport * as CheckboxPrimitive from '@radix-ui/react-checkbox';\nimport { Check } from 'lucide-react';\n\nimport { cn } from '@/utils/shadcn-ui.util';\n\n/**\n * Checkbox component provides a control that allows the user to toggle between checked and not\n * checked. This components is built on Radix UI primitives and styled with Shadcn UI.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/checkbox}\n * @see Radix UI Documentation: {@link https://www.radix-ui.com/primitives/docs/components/checkbox}\n */\nexport const Checkbox = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n \n \n \n \n));\nCheckbox.displayName = CheckboxPrimitive.Root.displayName;\n\nexport default Checkbox;\n","import { ColumnDef, SortDirection } from '@/components/advanced/data-table/data-table.component';\nimport { Button } from '@/components/shadcn-ui/button';\nimport { ToggleGroup, ToggleGroupItem } from '@/components/shadcn-ui/toggle-group';\nimport {\n ArrowDownIcon,\n ArrowUpDownIcon,\n ArrowUpIcon,\n CircleCheckIcon,\n CircleHelpIcon,\n CircleXIcon,\n} from 'lucide-react';\nimport { ReactNode } from 'react';\nimport { InventoryTableData, Status } from './inventory-utils';\n\n/**\n * Gets an icon that indicates the current sorting direction based on the provided input\n *\n * @param sortDirection Sorting direction. Can be ascending ('asc'), descending ('desc') or false (\n * i.e. not sorted)\n * @returns The appropriate sorting icon for the provided sorting direction\n */\nconst getSortingIcon = (sortDirection: false | SortDirection): ReactNode => {\n if (sortDirection === 'asc') {\n return ;\n }\n if (sortDirection === 'desc') {\n return ;\n }\n return ;\n};\n\n/**\n * Function that creates the item column for inventories\n *\n * @param itemLabel Localized label for the item column (e.g. 'Character', 'Repeated Word', etc.)\n * @returns Column that shows the inventory items. Should be used with the DataTable component\n */\nexport const inventoryItemColumn = (itemLabel: string): ColumnDef => {\n return {\n accessorKey: 'item',\n accessorFn: (row: InventoryTableData) => row.items[0],\n header: ({ column }) => (\n \n ),\n };\n};\n\n/**\n * Function that creates the additional item columns for inventories\n *\n * @param additionalItemLabel Localized label for the additional item column (e.g. 'Preceding\n * Marker')\n * @param additionalItemIndex Index that locates the desired item in the items array of the\n * inventory\n * @returns Column that shows additional inventory items. Should be used with the DataTable\n * component\n */\nexport const inventoryAdditionalItemColumn = (\n additionalItemLabel: string,\n additionalItemIndex: number,\n): ColumnDef => {\n return {\n accessorKey: `item${additionalItemIndex}`,\n accessorFn: (row: InventoryTableData) => row.items[additionalItemIndex],\n header: ({ column }) => (\n \n ),\n };\n};\n\n/**\n * Function that creates the count column for inventories. Should be used with the DataTable\n * component.\n *\n * @param countLabel Localized label for the count column\n * @returns Column that shows the number of occurrences of the related inventory items\n */\nexport const inventoryCountColumn = (countLabel: string): ColumnDef => {\n return {\n accessorKey: 'count',\n header: ({ column }) => (\n
\n \n
\n ),\n cell: ({ row }) =>
{row.getValue('count')}
,\n };\n};\n\n/**\n * Function that updates project settings when status for item(s) changes\n *\n * @param changedItems Array of items for which the status is being updated\n * @param newStatus The status that the items are being given\n * @param approvedItems Array of currently approved items\n * @param onApprovedItemsChange Callback function that stores the updated list of approved items\n * @param unapprovedItems Array of currently unapproved items\n * @param onUnapprovedItemsChange Callback function that stores the updated list of unapproved items\n */\nconst statusChangeHandler = (\n changedItems: string[],\n newStatus: Status,\n approvedItems: string[],\n onApprovedItemsChange: (items: string[]) => void,\n unapprovedItems: string[],\n onUnapprovedItemsChange: (items: string[]) => void,\n) => {\n let newApprovedItems: string[] = [...approvedItems];\n changedItems.forEach((item) => {\n if (newStatus === 'approved') {\n if (!newApprovedItems.includes(item)) {\n newApprovedItems.push(item);\n }\n } else {\n newApprovedItems = newApprovedItems.filter((validItem) => validItem !== item);\n }\n });\n onApprovedItemsChange(newApprovedItems);\n\n let newUnapprovedItems: string[] = [...unapprovedItems];\n changedItems.forEach((item) => {\n if (newStatus === 'unapproved') {\n if (!newUnapprovedItems.includes(item)) {\n newUnapprovedItems.push(item);\n }\n } else {\n newUnapprovedItems = newUnapprovedItems.filter((unapprovedItem) => unapprovedItem !== item);\n }\n });\n onUnapprovedItemsChange(newUnapprovedItems);\n};\n\n/**\n * Function that creates the status column for inventories. Should be used with the DataTable\n * component.\n *\n * @param statusLabel Localized label for the status column\n * @param approvedItems Array of approved items, typically as defined in `Settings.xml`\n * @param onApprovedItemsChange Callback function that stores the updated list of approved items\n * @param unapprovedItems Array of unapproved items, typically as defined in `Settings.xml`\n * @param onUnapprovedItemsChange Callback function that stores the updated list of unapproved items\n * @returns Column that shows the status buttons for the related inventory item. The button for the\n * current status of the item is selected\n */\nexport const inventoryStatusColumn = (\n statusLabel: string,\n approvedItems: string[],\n onApprovedItemsChange: (items: string[]) => void,\n unapprovedItems: string[],\n onUnapprovedItemsChange: (items: string[]) => void,\n): ColumnDef => {\n return {\n accessorKey: 'status',\n header: ({ column }) => {\n return (\n
\n \n
\n );\n },\n cell: ({ row }) => {\n const status: Status = row.getValue('status');\n const item: string = row.getValue('item');\n return (\n \n {\n event.stopPropagation();\n statusChangeHandler(\n [item],\n 'approved',\n approvedItems,\n onApprovedItemsChange,\n unapprovedItems,\n onUnapprovedItemsChange,\n );\n }}\n value=\"approved\"\n >\n \n \n {\n event.stopPropagation();\n statusChangeHandler(\n [item],\n 'unapproved',\n approvedItems,\n onApprovedItemsChange,\n unapprovedItems,\n onUnapprovedItemsChange,\n );\n }}\n value=\"unapproved\"\n >\n \n \n {\n event.stopPropagation();\n statusChangeHandler(\n [item],\n 'unknown',\n approvedItems,\n onApprovedItemsChange,\n unapprovedItems,\n onUnapprovedItemsChange,\n );\n }}\n value=\"unknown\"\n >\n \n \n \n );\n },\n };\n};\n","import { SerializedVerseRef } from '@sillsdev/scripture';\n\n/* #region Types */\n\n/**\n * Status of items that appear in inventories. 'approved' and 'unapproved' items are defined in the\n * project's `Settings.xml`. All other items are defined as 'unknown'\n */\nexport type Status = 'approved' | 'unapproved' | 'unknown';\n\n/** Occurrence of item in inventory. Primarily used by table that shows occurrences */\nexport type InventoryItemOccurrence = {\n /** Reference to scripture where the item appears */\n reference: SerializedVerseRef;\n /** Snippet of scripture that contains the occurrence */\n text: string;\n};\n\n/** Data structure that contains all information on an item that is shown in an inventory */\nexport type InventoryTableData = {\n /**\n * The item (e.g. a character in the characters inventory, a marker in the marker inventory) In\n * most cases the array will only have one element. In case of additional items (e.g. the\n * preceding marker in the markers check), the primary item should be stored in the first index.\n * To show additional items in the inventory, make sure to configure the `additionalItemsLabels`\n * prop for the Inventory component\n */\n items: string[];\n /** The number of times this item occurs in the selected scope */\n count: number;\n /** The status of this item (see documentation for `Status` type for more information) */\n status: Status;\n /** Occurrences of this item in the scripture text for the selected scope */\n occurrences: InventoryItemOccurrence[];\n};\n\n/* #endregion */\n\n/* #region Functions */\n\n/**\n * Splits USFM string into shorter line-like segments\n *\n * @param text A single (likely very large) USFM string\n * @returns An array containing the input text, split into shorter segments\n */\nexport const getLinesFromUSFM = (text: string) => {\n // Splits on (CR)LF, CR, \\v, \\c and \\id\n return text.split(/(?:\\r?\\n|\\r)|(?=(?:\\\\(?:v|c|id)))/g);\n};\n\n/**\n * Extracts chapter or verse number from USFM strings that start with a \\c or \\v marker\n *\n * @param text USFM string that is expected to start with \\c or \\v marker\n * @returns Chapter or verse number if one is found. Else returns 0.\n */\nexport const getNumberFromUSFM = (text: string): number | undefined => {\n // Captures all digits that follow \\v or \\c markers followed by whitespace located at the start of a string\n const regex = /^\\\\[vc]\\s+(\\d+)/;\n const match = text.match(regex);\n\n if (match) {\n return +match[1];\n }\n return undefined;\n};\n\n/**\n * Gets book ID from USFM string that starts with the \\id marker, and returns book number for it\n *\n * @param text USFM string that is expected to start with \\id marker\n * @returns Book number corresponding to the \\id marker in the input text. Returns 0 if no marker is\n * found or the marker is not valid\n */\nexport const getBookIdFromUSFM = (text: string): string => {\n // Captures all digits that follow an \\id marker followed by whitespace located at the start of a string\n const match = text.match(/^\\\\id\\s+([A-Za-z]+)/);\n if (match) {\n return match[1];\n }\n return '';\n};\n\n/**\n * Gets the status for an item, typically used in the Inventory component\n *\n * @param item The item for which the status is being requested\n * @param approvedItems Array of approved items, typically as defined in `Settings.xml`\n * @param unapprovedItems Array of unapproved items, typically as defined in `Settings.xml`\n * @returns The status for the specified item\n */\nexport const getStatusForItem = (\n item: string,\n approvedItems: string[],\n unapprovedItems: string[],\n): Status => {\n if (unapprovedItems.includes(item)) return 'unapproved';\n if (approvedItems.includes(item)) return 'approved';\n return 'unknown';\n};\n\n/* #endregion */\n","import {\n ColumnDef,\n DataTable,\n RowContents,\n RowSelectionState,\n TableContents,\n} from '@/components/advanced/data-table/data-table.component';\nimport { OccurrencesTable } from '@/components/advanced/inventory/occurrences-table.component';\nimport { Checkbox } from '@/components/shadcn-ui/checkbox';\nimport { Input } from '@/components/shadcn-ui/input';\nimport { Label } from '@/components/shadcn-ui/label';\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@/components/shadcn-ui/select';\nimport { Scope } from '@/components/utils/scripture.util';\nimport { SerializedVerseRef } from '@sillsdev/scripture';\nimport { deepEqual, isString, LocalizedStringValue } from 'platform-bible-utils';\nimport { useEffect, useMemo, useState } from 'react';\nimport { inventoryAdditionalItemColumn } from './inventory-columns';\nimport {\n getStatusForItem,\n InventoryItemOccurrence,\n InventoryTableData,\n Status,\n} from './inventory-utils';\n\n/**\n * Represents an item in the inventory with associated text and verse reference.\n *\n * @deprecated 12 January 2026. Use InventorySummaryItem instead for better performance and\n * functionality.\n */\nexport type InventoryItem = {\n /**\n * The label by which the item is shown in the inventory (e.g. the word that is repeated in case\n * of the Repeated Words check). It serves as a unique identifier for the item. It usually is a\n * string, but can be a string[] when there are multiple defining attributes (e.g. when 'show\n * preceding marker' is enabled for the Markers Inventory, the preceding marker will be stored as\n * the second item in the array)\n */\n inventoryText: string | string[];\n /** The snippet of scripture where this occurrence of the `inventoryItem` is found */\n verse: string;\n /** The reference to the location where the `verse` can be found in scripture */\n verseRef: SerializedVerseRef;\n /**\n * Offset used to locate the `inventoryText` (or inventoryText[0] in case of an array) in the\n * `verse` string\n */\n offset: number;\n};\n\n/**\n * Represents a summary item in the inventory with aggregated count and optional detailed\n * occurrences. This type is used for displaying inventory data in a summarized format, where each\n * item shows the total count and can optionally include detailed occurrence information that gets\n * loaded dynamically when the user selects the item.\n */\nexport type InventorySummaryItem = {\n /** The item key (e.g., character, word, etc.) */\n key: string | string[];\n /** Total count of occurrences */\n count: number;\n /** Status of the item */\n status?: Status;\n /** Detailed occurrences - optional, loaded on demand */\n occurrences?: InventoryItemOccurrence[];\n};\n\n/**\n * Object containing all keys used for localization in this component. If you're using this\n * component in an extension, you can pass it into the useLocalizedStrings hook to easily obtain the\n * localized strings and pass them into the localizedStrings prop of this component\n */\nexport const INVENTORY_STRING_KEYS = Object.freeze([\n '%webView_inventory_all%',\n '%webView_inventory_approved%',\n '%webView_inventory_unapproved%',\n '%webView_inventory_unknown%',\n '%webView_inventory_scope_currentBook%',\n '%webView_inventory_scope_chapter%',\n '%webView_inventory_scope_verse%',\n '%webView_inventory_filter_text%',\n '%webView_inventory_show_additional_items%',\n '%webView_inventory_occurrences_table_header_reference%',\n '%webView_inventory_occurrences_table_header_occurrence%',\n '%webView_inventory_no_results%',\n] as const);\n\nexport type InventoryLocalizedStrings = {\n [localizedInventoryKey in (typeof INVENTORY_STRING_KEYS)[number]]?: LocalizedStringValue;\n};\n\n/** Status values that the status filter can select from */\ntype StatusFilter = Status | 'all';\n\n/** Text labels for the inventory columns and the control components of additional inventory items */\ntype AdditionalItemsLabels = {\n checkboxText?: string;\n tableHeaders?: string[];\n};\n\n/**\n * Filters data that is shown in the DataTable section of the Inventory\n *\n * @param itemData All inventory items and their related information\n * @param statusFilter Allows filtering by status (i.e. show all items, or only items that are\n * 'approved', 'unapproved' or 'unknown')\n * @param textFilter Allows filtering by text. All items that include the filter text will be\n * selected.\n * @returns Array of items and their related information that are matched by the specified filters\n */\nconst filterItemData = (\n itemData: InventoryTableData[],\n statusFilter: StatusFilter,\n textFilter: string,\n): InventoryTableData[] => {\n let filteredItemData: InventoryTableData[] = itemData;\n\n if (statusFilter !== 'all') {\n filteredItemData = filteredItemData.filter(\n (item) =>\n (statusFilter === 'approved' && item.status === 'approved') ||\n (statusFilter === 'unapproved' && item.status === 'unapproved') ||\n (statusFilter === 'unknown' && item.status === 'unknown'),\n );\n }\n\n if (textFilter !== '')\n filteredItemData = filteredItemData.filter((item) => item.items[0].includes(textFilter));\n\n return filteredItemData;\n};\n\n/**\n * Processes InventorySummaryItem array into InventoryTableData for display\n *\n * @param inventoryItems Summary items with counts and optional occurrences\n * @param approvedItems Array of approved items\n * @param unapprovedItems Array of unapproved items\n * @returns Array of table data for display\n */\nconst processSummaryItems = (\n inventoryItems: InventorySummaryItem[],\n approvedItems: string[],\n unapprovedItems: string[],\n): InventoryTableData[] => {\n return inventoryItems.map((item) => {\n const itemKey = isString(item.key) ? item.key : item.key[0];\n const items = isString(item.key) ? [item.key] : item.key;\n\n return {\n items,\n count: item.count,\n status: item.status || getStatusForItem(itemKey, approvedItems, unapprovedItems),\n occurrences: item.occurrences || [],\n };\n });\n};\n\n/**\n * Gets the localized value for the provided key\n *\n * @param strings Object containing localized string\n * @param key Key for a localized string\n * @returns The localized value for the provided key, if available. Returns the key if no localized\n * value is available\n */\nconst localizeString = (\n strings: InventoryLocalizedStrings,\n key: keyof InventoryLocalizedStrings,\n) => {\n return strings[key] ?? key;\n};\n\n/** Props for the Inventory component */\ntype InventoryProps = {\n /** The inventory items that the inventory should be populated with */\n inventoryItems: InventorySummaryItem[] | undefined;\n /** Callback function that is executed when the scripture reference is changed */\n setVerseRef: (scriptureReference: SerializedVerseRef) => void;\n /**\n * Object with all localized strings that the Inventory needs to work well across multiple\n * languages. When using this component with Platform.Bible, you can import\n * `INVENTORY_STRING_KEYS` from this library, pass it in to the Platform's localization hook, and\n * pass the localized keys that are returned by the hook into this prop.\n */\n localizedStrings: InventoryLocalizedStrings;\n /**\n * Text labels for control elements and additional column headers in case your Inventory has more\n * than one item to show (e.g. The 'Preceding Marker' in the Markers Inventory)\n */\n additionalItemsLabels?: AdditionalItemsLabels;\n /** Array of approved items, typically as defined in `Settings.xml` */\n approvedItems: string[];\n /** Array of unapproved items, typically as defined in `Settings.xml` */\n unapprovedItems: string[];\n /** Scope of scripture that the inventory will operate on */\n scope: Scope;\n /** Callback function that is executed when the scope is changed from the Inventory */\n onScopeChange: (scope: Scope) => void;\n /**\n * Column definitions for the Inventory data table. The most commonly used column definitions are\n * pre-configured for your convenience and can be imported (e.g. inventoryItemColumn,\n * inventoryAdditionalItemColumn inventoryCountColumn, and inventoryStatusColumn). If you need any\n * other columns you can add these yourself\n */\n columns: ColumnDef[];\n /** Unique identifier for the Inventory component */\n id?: string;\n /** Whether the inventory items are still loading */\n areInventoryItemsLoading?: boolean;\n /** Class name to apply to the provided occurrence verse text in the `OccurrencesTable` component */\n classNameForVerseText?: string;\n /** Optional callback that is called when an item is selected. Receives the selected item key. */\n onItemSelected?: (itemKey: string) => void;\n};\n\n/** Inventory component that is used to view and control the status of provided project settings */\nexport function Inventory({\n inventoryItems,\n setVerseRef,\n localizedStrings,\n additionalItemsLabels,\n approvedItems,\n unapprovedItems,\n scope,\n onScopeChange,\n columns,\n id,\n areInventoryItemsLoading = false,\n classNameForVerseText,\n onItemSelected,\n}: InventoryProps) {\n const allItemsText = localizeString(localizedStrings, '%webView_inventory_all%');\n const approvedItemsText = localizeString(localizedStrings, '%webView_inventory_approved%');\n const unapprovedItemsText = localizeString(localizedStrings, '%webView_inventory_unapproved%');\n const unknownItemsText = localizeString(localizedStrings, '%webView_inventory_unknown%');\n const scopeBookText = localizeString(localizedStrings, '%webView_inventory_scope_currentBook%');\n const scopeChapterText = localizeString(localizedStrings, '%webView_inventory_scope_chapter%');\n const scopeVerseText = localizeString(localizedStrings, '%webView_inventory_scope_verse%');\n const filterText = localizeString(localizedStrings, '%webView_inventory_filter_text%');\n const showAdditionalItemsText = localizeString(\n localizedStrings,\n '%webView_inventory_show_additional_items%',\n );\n const noResultsText = localizeString(localizedStrings, '%webView_inventory_no_results%');\n\n const [showAdditionalItems, setShowAdditionalItems] = useState(false);\n const [statusFilter, setStatusFilter] = useState('all');\n const [textFilter, setTextFilter] = useState('');\n const [selectedItem, setSelectedItem] = useState([]);\n\n const tableData: InventoryTableData[] = useMemo(() => {\n const safeInventoryItems = inventoryItems ?? [];\n if (safeInventoryItems.length === 0) return [];\n return processSummaryItems(safeInventoryItems, approvedItems, unapprovedItems);\n }, [inventoryItems, approvedItems, unapprovedItems]);\n\n const reducedTableData: InventoryTableData[] = useMemo(() => {\n if (showAdditionalItems) return tableData;\n\n const newTableData: InventoryTableData[] = [];\n\n tableData.forEach((tableEntry) => {\n const firstItem = tableEntry.items[0];\n\n const existingEntry = newTableData.find(\n (newTableEntry) => newTableEntry.items[0] === firstItem,\n );\n\n if (existingEntry) {\n existingEntry.count += tableEntry.count;\n existingEntry.occurrences = existingEntry.occurrences.concat(tableEntry.occurrences);\n } else {\n newTableData.push({\n items: [firstItem],\n count: tableEntry.count,\n occurrences: tableEntry.occurrences,\n status: tableEntry.status,\n });\n }\n });\n\n return newTableData;\n }, [showAdditionalItems, tableData]);\n\n const filteredTableData: InventoryTableData[] = useMemo(() => {\n if (reducedTableData.length === 0) return [];\n return filterItemData(reducedTableData, statusFilter, textFilter);\n }, [reducedTableData, statusFilter, textFilter]);\n\n const allColumns: ColumnDef[] = useMemo(() => {\n if (!showAdditionalItems) return columns;\n\n const numberOfAdditionalItems = additionalItemsLabels?.tableHeaders?.length;\n if (!numberOfAdditionalItems) return columns;\n\n const additionalColumns: ColumnDef[] = [];\n\n for (let index = 0; index < numberOfAdditionalItems; index++) {\n additionalColumns.push(\n inventoryAdditionalItemColumn(\n additionalItemsLabels?.tableHeaders?.[index] || 'Additional Item',\n index + 1,\n ),\n );\n }\n\n return [...additionalColumns, ...columns];\n }, [additionalItemsLabels?.tableHeaders, columns, showAdditionalItems]);\n\n useEffect(() => {\n if (filteredTableData.length === 0) {\n setSelectedItem([]);\n } else if (filteredTableData.length === 1) {\n setSelectedItem(filteredTableData[0].items);\n }\n }, [filteredTableData]);\n\n const rowClickHandler = (\n row: RowContents,\n table: TableContents,\n ) => {\n table.setRowSelection(() => {\n const newSelection: RowSelectionState = {};\n newSelection[row.index] = true;\n return newSelection;\n });\n\n const selectedItems = row.original.items;\n setSelectedItem(selectedItems);\n\n // Call the callback if provided, passing the first item as the key\n if (onItemSelected && selectedItems.length > 0) {\n onItemSelected(selectedItems[0]);\n }\n };\n\n const handleScopeChange = (value: string) => {\n if (value === 'book' || value === 'chapter' || value === 'verse') {\n onScopeChange(value);\n } else {\n throw new Error(`Invalid scope value: ${value}`);\n }\n };\n\n const handleStatusFilterChange = (value: string) => {\n if (value === 'all' || value === 'approved' || value === 'unapproved' || value === 'unknown') {\n setStatusFilter(value);\n } else {\n throw new Error(`Invalid status filter value: ${value}`);\n }\n };\n\n const occurrenceData: InventoryItemOccurrence[] = useMemo(() => {\n if (reducedTableData.length === 0 || selectedItem.length === 0) return [];\n const occurrence = reducedTableData.filter((tableEntry: InventoryTableData) => {\n return deepEqual(\n showAdditionalItems ? tableEntry.items : [tableEntry.items[0]],\n selectedItem,\n );\n });\n if (occurrence.length > 1) throw new Error('Selected item is not unique');\n if (occurrence.length === 0) return [];\n return occurrence[0].occurrences;\n }, [selectedItem, showAdditionalItems, reducedTableData]);\n\n return (\n
\n
\n handleStatusFilterChange(value)}\n defaultValue={statusFilter}\n >\n \n \n \n \n {allItemsText}\n {approvedItemsText}\n {unapprovedItemsText}\n {unknownItemsText}\n \n \n \n {\n setTextFilter(event.target.value);\n }}\n />\n {additionalItemsLabels && (\n
\n {\n setShowAdditionalItems(checked);\n }}\n />\n \n
\n )}\n
\n
\n \n
\n {occurrenceData.length > 0 && (\n
\n \n
\n )}\n
\n );\n}\n\nexport default Inventory;\n","import { FC, useMemo, useState } from 'react';\nimport { Ban } from 'lucide-react';\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandShortcut,\n} from '../shadcn-ui/command';\n\n/**\n * Object containing all keys used for localization in the FootnoteEditor component. If you're using\n * this component in an extension, you can pass it into the useLocalizedStrings hook to easily\n * obtain the localized strings and pass them into the localizedStrings prop of this component\n */\nexport const MARKER_MENU_STRING_KEYS = Object.freeze([\n '%markerMenu_deprecated_label%',\n '%markerMenu_disallowed_label%',\n '%markerMenu_noResults%',\n '%markerMenu_searchPlaceholder%',\n] as const);\n\nexport type MarkerMenuLocalizedStrings = {\n [localizedKey in (typeof MARKER_MENU_STRING_KEYS)[number]]?: string;\n};\n\n/** Interface that includes the properties that the provided icon element should have */\nexport interface MarkerIconProps {\n /** CSS class name to apply to the icon */\n className?: string;\n /** Size in px that the icon should be */\n size?: string | number;\n}\n\n/** Type for the markers that contain all necessary information to be displayed in the list */\nexport interface MarkerMenuItem {\n /** If the item is a marker, then this is the marker code */\n marker?: string;\n /** The main title for the marker or command */\n title: string;\n /** An optional subtitle for the marker */\n subtitle?: string;\n /** Optional name of icon to use instead of the marker */\n icon?: FC;\n /** Whether the command/marker is deprecated */\n isDeprecated?: boolean;\n /** Whether the command/marker is disallowed for this project */\n isDisallowed?: boolean;\n /** Function to be triggered when the marker or command is selected */\n action: () => void;\n}\n\n/** Props for the marker menu component */\nexport interface MarkerMenuProps {\n /** Localized strings to pass through for the marker menu */\n localizedStrings: MarkerMenuLocalizedStrings;\n /**\n * A list of the marker menu items which can either be a marker to insert or some basic command\n * actions\n */\n markerMenuItems: MarkerMenuItem[];\n}\n\n/** Function to format the marker menu icon and size it accordingly */\nfunction MenuMarkerIcon({ icon, className }: { icon?: FC; className?: string }) {\n const IconComponent = icon ?? Ban;\n return ;\n}\n\n/** Marker menu component to render the list of markers and a few commands in the scripture editor */\nexport function MarkerMenu({ localizedStrings, markerMenuItems }: MarkerMenuProps) {\n const [commandSearch, setCommandSearch] = useState('');\n\n const filteredMarkerItems = useMemo(() => {\n const query = commandSearch.trim().toLowerCase();\n if (!query) {\n return markerMenuItems;\n }\n\n return markerMenuItems.filter(\n (markerItem) =>\n markerItem.marker?.toLowerCase().includes(query) ||\n markerItem.title.toLowerCase().includes(query),\n );\n }, [commandSearch, markerMenuItems]);\n\n return (\n \n setCommandSearch(value)}\n placeholder={localizedStrings['%markerMenu_searchPlaceholder%']}\n autoFocus={false}\n />\n \n {localizedStrings['%markerMenu_noResults%']}\n \n {filteredMarkerItems.map((item) => (\n \n
\n {item.marker ? (\n {item.marker}\n ) : (\n
\n \n
\n )}\n
\n
\n

{item.title}

\n {item.subtitle && (\n

{item.subtitle}

\n )}\n
\n {(item.isDisallowed || item.isDeprecated) && (\n \n {item.isDisallowed\n ? localizedStrings['%markerMenu_disallowed_label%']\n : localizedStrings['%markerMenu_deprecated_label%']}\n \n )}\n \n ))}\n
\n
\n
\n );\n}\n","import React from 'react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { VariantProps, cva } from 'class-variance-authority';\nimport { PanelLeft, PanelRight } from 'lucide-react';\n\nimport { Button } from '@/components/shadcn-ui/button';\nimport { Input } from '@/components/shadcn-ui/input';\nimport { Separator } from '@/components/shadcn-ui/separator';\nimport { Skeleton } from '@/components/shadcn-ui/skeleton';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@/components/shadcn-ui/tooltip';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\n\n/**\n * CUSTOM: Changes from the original code from Shadcn- Removed uses of useIsMobile, Sheet, and\n * SheetContent. Also removed the parts setting COOKIES.\n */\n\nconst SIDEBAR_WIDTH = '16rem';\nconst SIDEBAR_WIDTH_ICON = '3rem';\n// CUSTOM: Commented this out pending a discussion with UX about keyboard shortcuts\n// const SIDEBAR_KEYBOARD_SHORTCUT = 'b';\n\ntype Side = 'primary' | 'secondary';\n\ntype SidebarContextProps = {\n state: 'expanded' | 'collapsed';\n open: boolean;\n setOpen: (open: boolean) => void;\n toggleSidebar: () => void;\n // CUSTOM: this was moved from Sidebar to SidebarProvider to also be able to flip the icon based on the side\n side: Side;\n};\n\nconst SidebarContext = React.createContext(undefined);\n\n/** @inheritdoc SidebarProvider */\nfunction useSidebar() {\n const context = React.useContext(SidebarContext);\n if (!context) {\n throw new Error('useSidebar must be used within a SidebarProvider.');\n }\n\n return context;\n}\n\n/**\n * Sidebar components providing an accessible sidebar along with all the sub components that can be\n * used to populate and style it. These components are adapted from Shadcn UI. See Shadcn UI\n * Documentation: https://ui.shadcn.com/docs/components/sidebar\n */\nconst SidebarProvider = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<'div'> & {\n /** Whether the sidebar is initially open. */\n defaultOpen?: boolean;\n /** Whether the sidebar is open. */\n open?: boolean;\n /** Callback fired when the open state changes. */\n onOpenChange?: (open: boolean) => void;\n /** The side of the sidebar. */\n side?: Side;\n }\n>(\n (\n {\n defaultOpen = true,\n open: openProp,\n onOpenChange: setOpenProp,\n className,\n style,\n children,\n side = 'primary',\n ...props\n },\n ref,\n ) => {\n // This is the internal state of the sidebar.\n // We use openProp and setOpenProp for control from outside the component.\n // eslint-disable-next-line @typescript-eslint/naming-convention\n const [_open, _setOpen] = React.useState(defaultOpen);\n const isOpen = openProp ?? _open;\n const setOpen = React.useCallback(\n (value: boolean | ((value: boolean) => boolean)) => {\n const openState = typeof value === 'function' ? value(isOpen) : value;\n if (setOpenProp) {\n setOpenProp(openState);\n } else {\n _setOpen(openState);\n }\n },\n [setOpenProp, isOpen],\n );\n\n // Helper to toggle the sidebar.\n const toggleSidebar = React.useCallback(() => {\n return setOpen((open) => !open);\n }, [setOpen]);\n\n // CUSTOM: Commented this out pending a discussion with UX about keyboard shortcuts\n // Adds a keyboard shortcut to toggle the sidebar.\n // React.useEffect(() => {\n // const handleKeyDown = (event: KeyboardEvent) => {\n // if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {\n // event.preventDefault();\n // toggleSidebar();\n // }\n // };\n\n // window.addEventListener('keydown', handleKeyDown);\n // return () => window.removeEventListener('keydown', handleKeyDown);\n // }, [toggleSidebar]);\n\n // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n // This makes it easier to style the sidebar with Tailwind classes.\n const state = isOpen ? 'expanded' : 'collapsed';\n\n const dir: Direction = readDirection();\n const oppositeSide: Side = side === 'primary' ? 'secondary' : 'primary';\n const directionAwareSide = dir === 'ltr' ? side : oppositeSide;\n\n const contextValue = React.useMemo(\n () => ({\n state,\n open: isOpen,\n setOpen,\n toggleSidebar,\n side: directionAwareSide,\n }),\n [state, isOpen, setOpen, toggleSidebar, directionAwareSide],\n );\n\n return (\n \n \n \n {children}\n \n \n \n );\n },\n);\nSidebarProvider.displayName = 'SidebarProvider';\n\n/** @inheritdoc SidebarProvider */\nconst Sidebar = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<'div'> & {\n variant?: 'sidebar' | 'floating' | 'inset';\n collapsible?: 'offcanvas' | 'icon' | 'none';\n }\n>(({ variant = 'sidebar', collapsible = 'offcanvas', className, children, ...props }, ref) => {\n const context = useSidebar();\n\n if (collapsible === 'none') {\n return (\n \n {children}\n \n );\n }\n\n return (\n \n {/* This is what handles the sidebar gap on desktop */}\n \n \n \n {children}\n \n \n \n );\n});\nSidebar.displayName = 'Sidebar';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarTrigger = React.forwardRef<\n React.ElementRef,\n React.ComponentProps\n>(({ className, onClick, ...props }, ref) => {\n const context = useSidebar();\n\n return (\n {\n onClick?.(event);\n context.toggleSidebar();\n }}\n {...props}\n >\n {context.side === 'primary' ? : }\n Toggle Sidebar\n \n );\n});\nSidebarTrigger.displayName = 'SidebarTrigger';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarRail = React.forwardRef>(\n ({ className, ...props }, ref) => {\n const { toggleSidebar } = useSidebar();\n\n return (\n \n );\n },\n);\nSidebarRail.displayName = 'SidebarRail';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarInset = React.forwardRef>(\n ({ className, ...props }, ref) => {\n return (\n \n );\n },\n);\nSidebarInset.displayName = 'SidebarInset';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarInput = React.forwardRef<\n React.ElementRef,\n React.ComponentProps\n>(({ className, ...props }, ref) => {\n return (\n \n );\n});\nSidebarInput.displayName = 'SidebarInput';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarHeader = React.forwardRef>(\n ({ className, ...props }, ref) => {\n return (\n \n );\n },\n);\nSidebarHeader.displayName = 'SidebarHeader';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarFooter = React.forwardRef>(\n ({ className, ...props }, ref) => {\n return (\n \n );\n },\n);\nSidebarFooter.displayName = 'SidebarFooter';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarSeparator = React.forwardRef<\n React.ElementRef,\n React.ComponentProps\n>(({ className, ...props }, ref) => {\n return (\n \n );\n});\nSidebarSeparator.displayName = 'SidebarSeparator';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarContent = React.forwardRef>(\n ({ className, ...props }, ref) => {\n return (\n \n );\n },\n);\nSidebarContent.displayName = 'SidebarContent';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarGroup = React.forwardRef>(\n ({ className, ...props }, ref) => {\n return (\n \n );\n },\n);\nSidebarGroup.displayName = 'SidebarGroup';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarGroupLabel = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<'div'> & { asChild?: boolean }\n>(({ className, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : 'div';\n\n return (\n svg]:tw-size-4 [&>svg]:tw-shrink-0',\n 'group-data-[collapsible=icon]:tw--mt-8 group-data-[collapsible=icon]:tw-opacity-0',\n className,\n )}\n {...props}\n />\n );\n});\nSidebarGroupLabel.displayName = 'SidebarGroupLabel';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarGroupAction = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<'button'> & { asChild?: boolean }\n>(({ className, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : 'button';\n\n return (\n svg]:tw-size-4 [&>svg]:tw-shrink-0',\n // Increases the hit area of the button on mobile.\n 'after:tw-absolute after:tw--inset-2 after:md:tw-hidden',\n 'group-data-[collapsible=icon]:tw-hidden',\n className,\n )}\n {...props}\n />\n );\n});\nSidebarGroupAction.displayName = 'SidebarGroupAction';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarGroupContent = React.forwardRef>(\n ({ className, ...props }, ref) => (\n \n ),\n);\nSidebarGroupContent.displayName = 'SidebarGroupContent';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarMenu = React.forwardRef>(\n ({ className, ...props }, ref) => (\n \n ),\n);\nSidebarMenu.displayName = 'SidebarMenu';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarMenuItem = React.forwardRef>(\n ({ className, ...props }, ref) => (\n \n ),\n);\nSidebarMenuItem.displayName = 'SidebarMenuItem';\n\nconst sidebarMenuButtonVariants = cva(\n 'tw-peer/menu-button tw-flex tw-w-full tw-items-center tw-gap-2 tw-overflow-hidden tw-rounded-md tw-p-2 tw-text-left tw-text-sm tw-outline-none tw-ring-sidebar-ring tw-transition-[width,height,padding] hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground focus-visible:tw-ring-2 active:tw-bg-sidebar-accent active:tw-text-sidebar-accent-foreground disabled:tw-pointer-events-none disabled:tw-opacity-50 tw-group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:tw-pointer-events-none aria-disabled:tw-opacity-50 data-[active=true]:tw-font-medium data-[active=true]:tw-text-sidebar-accent-foreground data-[active=true]:tw-bg-sidebar-accent data-[state=open]:hover:tw-bg-sidebar-accent data-[state=open]:hover:tw-text-sidebar-accent-foreground group-data-[collapsible=icon]:tw-!size-8 group-data-[collapsible=icon]:tw-!p-2 [&>span:last-child]:tw-truncate [&>svg]:tw-size-4 [&>svg]:tw-shrink-0',\n {\n variants: {\n variant: {\n default: 'hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground',\n outline:\n 'tw-bg-background tw-shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground hover:tw-shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',\n },\n size: {\n default: 'tw-h-8 tw-text-sm',\n sm: 'tw-h-7 tw-text-xs',\n lg: 'tw-h-12 tw-text-sm group-data-[collapsible=icon]:tw-!p-0',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n);\n\n/** @inheritdoc SidebarProvider */\nconst SidebarMenuButton = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<'button'> & {\n asChild?: boolean;\n isActive?: boolean;\n tooltip?: string | React.ComponentProps;\n } & VariantProps\n>(\n (\n {\n asChild = false,\n isActive = false,\n variant = 'default',\n size = 'default',\n tooltip,\n className,\n ...props\n },\n ref,\n ) => {\n const Comp = asChild ? Slot : 'button';\n const { state } = useSidebar();\n\n const button = (\n \n );\n\n if (!tooltip) {\n return button;\n }\n\n if (typeof tooltip === 'string') {\n // eslint-disable-next-line no-param-reassign\n tooltip = {\n children: tooltip,\n };\n }\n\n return (\n \n {button}\n \n );\n },\n);\nSidebarMenuButton.displayName = 'SidebarMenuButton';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarMenuAction = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<'button'> & {\n asChild?: boolean;\n showOnHover?: boolean;\n }\n>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {\n const Comp = asChild ? Slot : 'button';\n\n return (\n svg]:tw-size-4 [&>svg]:tw-shrink-0',\n // Increases the hit area of the button on mobile.\n 'after:tw-absolute after:tw--inset-2 after:md:tw-hidden',\n 'tw-peer-data-[size=sm]/menu-button:top-1',\n 'tw-peer-data-[size=default]/menu-button:top-1.5',\n 'tw-peer-data-[size=lg]/menu-button:top-2.5',\n 'group-data-[collapsible=icon]:tw-hidden',\n showOnHover &&\n 'tw-group-focus-within/menu-item:opacity-100 tw-group-hover/menu-item:opacity-100 tw-peer-data-[active=true]/menu-button:text-sidebar-accent-foreground data-[state=open]:tw-opacity-100 md:tw-opacity-0',\n className,\n )}\n {...props}\n />\n );\n});\nSidebarMenuAction.displayName = 'SidebarMenuAction';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarMenuBadge = React.forwardRef>(\n ({ className, ...props }, ref) => (\n \n ),\n);\nSidebarMenuBadge.displayName = 'SidebarMenuBadge';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarMenuSkeleton = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<'div'> & {\n showIcon?: boolean;\n }\n>(({ className, showIcon = false, ...props }, ref) => {\n // Random width between 50 to 90%.\n const width = React.useMemo(() => {\n return `${Math.floor(Math.random() * 40) + 50}%`;\n }, []);\n\n return (\n \n {showIcon && (\n \n )}\n \n \n );\n});\nSidebarMenuSkeleton.displayName = 'SidebarMenuSkeleton';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarMenuSub = React.forwardRef>(\n ({ className, ...props }, ref) => (\n \n ),\n);\nSidebarMenuSub.displayName = 'SidebarMenuSub';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarMenuSubItem = React.forwardRef>(\n ({ ...props }, ref) =>
  • ,\n);\nSidebarMenuSubItem.displayName = 'SidebarMenuSubItem';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarMenuSubButton = React.forwardRef<\n HTMLAnchorElement,\n React.ComponentProps<'a'> & {\n asChild?: boolean;\n size?: 'sm' | 'md';\n isActive?: boolean;\n }\n>(({ asChild = false, size = 'md', isActive, className, ...props }, ref) => {\n const Comp = asChild ? Slot : 'a';\n\n return (\n span:last-child]:tw-truncate [&>svg]:tw-size-4 [&>svg]:tw-shrink-0 [&>svg]:tw-text-sidebar-accent-foreground',\n 'data-[active=true]:tw-bg-sidebar-accent data-[active=true]:tw-text-sidebar-accent-foreground',\n size === 'sm' && 'tw-text-xs',\n size === 'md' && 'tw-text-sm',\n 'group-data-[collapsible=icon]:tw-hidden',\n className,\n )}\n {...props}\n />\n );\n});\nSidebarMenuSubButton.displayName = 'SidebarMenuSubButton';\n\nexport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupAction,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarInput,\n SidebarInset,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuBadge,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSkeleton,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarProvider,\n SidebarRail,\n SidebarSeparator,\n SidebarTrigger,\n useSidebar,\n};\n","import { ComboBox } from '@/components/basics/combo-box.component';\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupLabel,\n SidebarGroupContent,\n SidebarMenu,\n SidebarMenuItem,\n SidebarMenuButton,\n} from '@/components/shadcn-ui/sidebar';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { ScrollText } from 'lucide-react';\nimport { useCallback } from 'react';\n\nexport type SelectedSettingsSidebarItem = {\n label: string;\n projectId?: string;\n};\n\nexport type ProjectInfo = { projectId: string; projectName: string };\n\nexport type SettingsSidebarProps = {\n /** Optional id for testing */\n id?: string;\n\n /** Extension labels from contribution */\n extensionLabels: Record;\n\n /** Project names and ids */\n projectInfo: ProjectInfo[];\n\n /** Handler for selecting a sidebar item */\n handleSelectSidebarItem: (key: string, projectId?: string) => void;\n\n /** The current selected value in the sidebar */\n selectedSidebarItem: SelectedSettingsSidebarItem;\n\n /** Label for the group of extensions setting groups */\n extensionsSidebarGroupLabel: string;\n\n /** Label for the group of projects settings */\n projectsSidebarGroupLabel: string;\n\n /** Placeholder text for the button */\n buttonPlaceholderText: string;\n\n /** Additional css classes to help with unique styling of the sidebar */\n className?: string;\n};\n\n/**\n * The SettingsSidebar component is a sidebar that displays a list of extension settings and project\n * settings. It can be used to navigate to different settings pages. Must be wrapped in a\n * SidebarProvider component otherwise produces errors.\n *\n * @param props - {@link SettingsSidebarProps} The props for the component.\n */\nexport function SettingsSidebar({\n id,\n extensionLabels,\n projectInfo,\n handleSelectSidebarItem,\n selectedSidebarItem,\n extensionsSidebarGroupLabel,\n projectsSidebarGroupLabel,\n buttonPlaceholderText,\n className,\n}: SettingsSidebarProps) {\n const handleSelectItem = useCallback(\n (item: string, projectId?: string) => {\n handleSelectSidebarItem(item, projectId);\n },\n [handleSelectSidebarItem],\n );\n\n const getProjectNameFromProjectId = useCallback(\n (projectId: string) => {\n const project = projectInfo.find((info) => info.projectId === projectId);\n return project ? project.projectName : projectId;\n },\n [projectInfo],\n );\n\n const getIsActive: (label: string) => boolean = useCallback(\n (label: string) => !selectedSidebarItem.projectId && label === selectedSidebarItem.label,\n [selectedSidebarItem],\n );\n\n return (\n \n \n \n \n {extensionsSidebarGroupLabel}\n \n \n \n {Object.entries(extensionLabels).map(([key, label]) => (\n \n handleSelectItem(key)}\n isActive={getIsActive(key)}\n >\n {label}\n \n \n ))}\n \n \n \n \n {projectsSidebarGroupLabel}\n \n info.projectId)}\n getOptionLabel={getProjectNameFromProjectId}\n buttonPlaceholder={buttonPlaceholderText}\n onChange={(projectId: string) => {\n const selectedProjectName = getProjectNameFromProjectId(projectId);\n handleSelectItem(selectedProjectName, projectId);\n }}\n value={selectedSidebarItem?.projectId ?? undefined}\n icon={}\n />\n \n \n \n \n );\n}\n\nexport default SettingsSidebar;\n","import { Button } from '@/components/shadcn-ui/button';\nimport { Input } from '@/components/shadcn-ui/input';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Search, X } from 'lucide-react';\nimport { forwardRef } from 'react';\n\n/** Props for the SearchBar component. */\nexport type SearchBarProps = {\n /** Search query for the search bar */\n value: string;\n /**\n * Callback fired to handle the search query is updated\n *\n * @param searchQuery\n */\n onSearch: (searchQuery: string) => void;\n\n /** Optional string that appears in the search bar without a search string */\n placeholder?: string;\n\n /** Optional boolean to set the input base to full width */\n isFullWidth?: boolean;\n\n /** Additional css classes to help with unique styling of the search bar */\n className?: string;\n\n /** Optional boolean to disable the search bar */\n isDisabled?: boolean;\n\n /** Optional id for the root element */\n id?: string;\n};\n\n/**\n * A search bar component with a search icon and a clear button when the search query is not empty.\n *\n * @param {SearchBarProps} props - The props for the component.\n * @param {string} props.value - The search query for the search bar\n * @param {(searchQuery: string) => void} props.onSearch - Callback fired to handle the search query\n * is updated\n * @param {string} [props.placeholder] - Optional string that appears in the search bar without a\n * search string\n * @param {boolean} [props.isFullWidth] - Optional boolean to set the input base to full width\n * @param {string} [props.className] - Additional css classes to help with unique styling of the\n * search bar\n * @param {boolean} [props.isDisabled] - Optional boolean to disable the search bar\n * @param {string} [props.id] - Optional id for the root element\n */\nexport const SearchBar = forwardRef(\n ({ value, onSearch, placeholder, isFullWidth, className, isDisabled = false, id }, inputRef) => {\n const dir: Direction = readDirection();\n\n return (\n
    \n \n onSearch(e.target.value)}\n disabled={isDisabled}\n />\n {value && (\n {\n onSearch('');\n }}\n >\n \n Clear\n \n )}\n
    \n );\n },\n);\n\nSearchBar.displayName = 'SearchBar';\n\nexport default SearchBar;\n","import { SidebarInset, SidebarProvider } from '@/components/shadcn-ui/sidebar';\nimport { PropsWithChildren } from 'react';\nimport { SearchBar } from '@/components/basics/search-bar.component';\nimport { SettingsSidebar, SettingsSidebarProps } from './settings-sidebar.component';\n\nexport type SettingsSidebarContentSearchProps = SettingsSidebarProps &\n PropsWithChildren & {\n /** The search query in the search bar */\n searchValue: string;\n\n /** Handler to run when the value of the search bar changes */\n onSearch: (searchQuery: string) => void;\n };\n\n/**\n * A component that wraps a search bar and a settings sidebar, providing a way to search and\n * navigate to different settings pages.\n *\n * @param {SettingsSidebarContentSearchProps} props - The props for the component.\n * @param {string} props.id - The id of the sidebar.\n */\nexport function SettingsSidebarContentSearch({\n id,\n extensionLabels,\n projectInfo,\n children,\n handleSelectSidebarItem,\n selectedSidebarItem,\n searchValue,\n onSearch,\n extensionsSidebarGroupLabel,\n projectsSidebarGroupLabel,\n buttonPlaceholderText,\n}: SettingsSidebarContentSearchProps) {\n return (\n
    \n
    \n \n
    \n \n \n {children}\n \n
    \n );\n}\n\nexport default SettingsSidebarContentSearch;\n","import { Button } from '@/components/shadcn-ui/button';\nimport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@/components/shadcn-ui/select';\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@/components/shadcn-ui/table';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Canon } from '@sillsdev/scripture';\nimport {\n Cell,\n ColumnDef,\n flexRender,\n getCoreRowModel,\n getExpandedRowModel,\n getGroupedRowModel,\n getSortedRowModel,\n GroupingState,\n Row,\n RowSelectionState,\n SortingState,\n useReactTable,\n} from '@tanstack/react-table';\nimport '@/components/advanced/scripture-results-viewer/scripture-results-viewer.component.css';\nimport {\n compareScrRefs,\n formatScrRef,\n ScriptureSelection,\n scrRefToBBBCCCVVV,\n} from 'platform-bible-utils';\nimport { MouseEvent, useEffect, useMemo, useState } from 'react';\nimport { ChevronDown, ChevronLeft, ChevronRight } from 'lucide-react';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\n\n/**\n * Information (e.g., a checking error or some other type of \"transient\" annotation) about something\n * noteworthy at a specific place in an instance of the Scriptures.\n */\nexport type ScriptureItemDetail = ScriptureSelection & {\n /**\n * Text of the error, note, etc. In the future, we might want to support something more than just\n * text so that a JSX element could be provided with a link or some other controls related to the\n * issue being reported.\n */\n detail: string;\n};\n\n/**\n * A uniquely identifiable source of results that can be displayed in the ScriptureResultsViewer.\n * Generally, the source will be a particular Scripture check, but there may be other types of\n * sources.\n */\nexport type ResultsSource = {\n /**\n * Uniquely identifies the source.\n *\n * @type {string}\n */\n id: string;\n\n /**\n * Name (potentially localized) of the source, suitable for display in the UI.\n *\n * @type {string}\n */\n displayName: string;\n};\n\nexport type ScriptureSrcItemDetail = ScriptureItemDetail & {\n /** Source/type of detail. Can be used for grouping. */\n source: ResultsSource;\n};\n\n/**\n * Represents a set of results keyed by Scripture reference. Generally, the source will be a\n * particular Scripture check, but this type also allows for other types of uniquely identifiable\n * sources.\n */\nexport type ResultsSet = {\n /**\n * The backing source associated with this set of results.\n *\n * @type {ResultsSource}\n */\n source: ResultsSource;\n\n /**\n * Array of Scripture item details (messages keyed by Scripture reference).\n *\n * @type {ScriptureItemDetail[]}\n */\n data: ScriptureItemDetail[];\n};\n\nconst scrBookColId = 'scrBook';\nconst scrRefColId = 'scrRef';\nconst typeColId = 'source';\nconst detailsColId = 'details';\n\nconst defaultScrRefColumnName = 'Scripture Reference';\nconst defaultScrBookGroupName = 'Scripture Book';\nconst defaultTypeColumnName = 'Type';\nconst defaultDetailsColumnName = 'Details';\n\nexport type ScriptureResultsViewerColumnInfo = {\n /** Optional header to display for the Reference column. Default value: 'Scripture Reference'. */\n scriptureReferenceColumnName?: string;\n\n /** Optional text to display to refer to the Scripture book group. Default value: 'Scripture Book'. */\n scriptureBookGroupName?: string;\n\n /** Optional header to display for the Type column. Default value: 'Type'. */\n typeColumnName?: string;\n\n /** Optional header to display for the Details column. Default value: 'Details' */\n detailsColumnName?: string;\n};\n\nexport type ScriptureResultsViewerProps = ScriptureResultsViewerColumnInfo & {\n /** Groups of ScriptureItemDetail objects from particular sources (e.g., Scripture checks) */\n sources: ResultsSet[];\n\n /** Flag indicating whether to display column headers. Default is false. */\n showColumnHeaders?: boolean;\n\n /** Flag indicating whether to display source column. Default is false. */\n showSourceColumn?: boolean;\n\n /** Callback function to notify when a row is selected */\n onRowSelected?: (selectedRow: ScriptureSrcItemDetail | undefined) => void;\n\n /** Optional id attribute for the outermost element */\n id?: string;\n};\n\nfunction getColumns(\n colInfo?: ScriptureResultsViewerColumnInfo,\n showSourceColumn?: boolean,\n): ColumnDef[] {\n const showSrcCol = showSourceColumn ?? false;\n return [\n {\n accessorFn: (row) => `${row.start.book} ${row.start.chapterNum}:${row.start.verseNum}`,\n id: scrBookColId,\n header: colInfo?.scriptureReferenceColumnName ?? defaultScrRefColumnName,\n cell: (info) => {\n const row = info.row.original;\n if (info.row.getIsGrouped()) {\n return Canon.bookIdToEnglishName(row.start.book);\n }\n return info.row.groupingColumnId === scrBookColId ? formatScrRef(row.start) : undefined;\n },\n getGroupingValue: (row) => Canon.bookIdToNumber(row.start.book),\n sortingFn: (a, b) => {\n return compareScrRefs(a.original.start, b.original.start);\n },\n enableGrouping: true,\n },\n {\n accessorFn: (row) => formatScrRef(row.start),\n id: scrRefColId,\n header: undefined,\n cell: (info) => {\n const row = info.row.original;\n return info.row.getIsGrouped() ? undefined : formatScrRef(row.start);\n },\n sortingFn: (a, b) => {\n return compareScrRefs(a.original.start, b.original.start);\n },\n enableGrouping: false,\n },\n {\n accessorFn: (row) => row.source.displayName,\n id: typeColId,\n header: showSrcCol ? (colInfo?.typeColumnName ?? defaultTypeColumnName) : undefined,\n cell: (info) => (showSrcCol || info.row.getIsGrouped() ? info.getValue() : undefined),\n getGroupingValue: (row) => row.source.id,\n sortingFn: (a, b) =>\n a.original.source.displayName.localeCompare(b.original.source.displayName),\n enableGrouping: true,\n },\n {\n accessorFn: (row) => row.detail,\n id: detailsColId,\n header: colInfo?.detailsColumnName ?? defaultDetailsColumnName,\n cell: (info) => info.getValue(),\n enableGrouping: false,\n },\n ];\n}\n\nconst toRefOrRange = (scriptureSelection: ScriptureSelection) => {\n if (!('offset' in scriptureSelection.start))\n throw new Error('No offset available in range start');\n if (scriptureSelection.end && !('offset' in scriptureSelection.end))\n throw new Error('No offset available in range end');\n const { offset: offsetStart } = scriptureSelection.start;\n let offsetEnd: number = 0;\n if (scriptureSelection.end) ({ offset: offsetEnd } = scriptureSelection.end);\n if (\n !scriptureSelection.end ||\n compareScrRefs(scriptureSelection.start, scriptureSelection.end) === 0\n )\n return `${scrRefToBBBCCCVVV(scriptureSelection.start)}+${offsetStart}`;\n return `${scrRefToBBBCCCVVV(scriptureSelection.start)}+${offsetStart}-${scrRefToBBBCCCVVV(scriptureSelection.end)}+${offsetEnd}`;\n};\n\nconst getRowKey = (row: ScriptureSrcItemDetail) =>\n `${toRefOrRange({ start: row.start, end: row.end })} ${row.source.displayName} ${row.detail}`;\n\n/**\n * Component to display a combined list of detailed items from one or more sources, where the items\n * are keyed primarily by Scripture reference. This is particularly useful for displaying a list of\n * results from Scripture checks, but more generally could be used to display any \"results\" from any\n * source(s). The component allows for grouping by Scripture book, source, or both. By default, it\n * displays somewhat \"tree-like\" which allows it to be more horizontally compact and intuitive. But\n * it also has the option of displaying as a traditional table with column headings (with or without\n * the source column showing).\n */\nexport function ScriptureResultsViewer({\n sources,\n showColumnHeaders = false,\n showSourceColumn = false,\n scriptureReferenceColumnName,\n scriptureBookGroupName,\n typeColumnName,\n detailsColumnName,\n onRowSelected,\n id,\n}: ScriptureResultsViewerProps) {\n const [grouping, setGrouping] = useState([]);\n const [sorting, setSorting] = useState([{ id: scrBookColId, desc: false }]);\n const [rowSelection, setRowSelection] = useState({});\n\n const scriptureResults = useMemo(\n () =>\n sources.flatMap((source) => {\n return source.data.map((item) => ({\n ...item,\n source: source.source,\n }));\n }),\n [sources],\n );\n\n const columns = useMemo(\n () =>\n getColumns(\n {\n scriptureReferenceColumnName,\n typeColumnName,\n detailsColumnName,\n },\n showSourceColumn,\n ),\n [scriptureReferenceColumnName, typeColumnName, detailsColumnName, showSourceColumn],\n );\n\n useEffect(() => {\n // Ensure sorting is applied correctly when grouped by type\n if (grouping.includes(typeColId)) {\n setSorting([\n { id: typeColId, desc: false },\n { id: scrBookColId, desc: false },\n ]);\n } else {\n setSorting([{ id: scrBookColId, desc: false }]);\n }\n }, [grouping]);\n\n const table = useReactTable({\n data: scriptureResults,\n columns,\n state: {\n grouping,\n sorting,\n rowSelection,\n },\n onGroupingChange: setGrouping,\n onSortingChange: setSorting,\n onRowSelectionChange: setRowSelection,\n getExpandedRowModel: getExpandedRowModel(),\n getGroupedRowModel: getGroupedRowModel(),\n getCoreRowModel: getCoreRowModel(),\n getSortedRowModel: getSortedRowModel(),\n getRowId: getRowKey,\n autoResetExpanded: false,\n enableMultiRowSelection: false,\n enableSubRowSelection: false,\n });\n\n useEffect(() => {\n if (onRowSelected) {\n const selectedRows = table.getSelectedRowModel().rowsById;\n const keys = Object.keys(selectedRows);\n if (keys.length === 1) {\n const selectedRow = scriptureResults.find((row) => getRowKey(row) === keys[0]) || undefined;\n if (selectedRow) onRowSelected(selectedRow);\n }\n }\n }, [rowSelection, scriptureResults, onRowSelected, table]);\n\n // Define possible grouping options\n const scrBookGroupName = scriptureBookGroupName ?? defaultScrBookGroupName;\n const typeGroupName = typeColumnName ?? defaultTypeColumnName;\n\n const groupingOptions = [\n { label: 'No Grouping', value: [] },\n { label: `Group by ${scrBookGroupName}`, value: [scrBookColId] },\n { label: `Group by ${typeGroupName}`, value: [typeColId] },\n {\n label: `Group by ${scrBookGroupName} and ${typeGroupName}`,\n value: [scrBookColId, typeColId],\n },\n {\n label: `Group by ${typeGroupName} and ${scrBookGroupName}`,\n value: [typeColId, scrBookColId],\n },\n ];\n\n const handleSelectChange = (selectedGrouping: string) => {\n setGrouping(JSON.parse(selectedGrouping));\n };\n\n const handleRowClick = (row: Row, event: MouseEvent) => {\n if (!row.getIsGrouped() && !row.getIsSelected()) {\n row.getToggleSelectedHandler()(event);\n }\n };\n\n const getEvenOrOddBandingStyle = (row: Row, index: number) => {\n if (row.getIsGrouped()) return '';\n // UX has now said they don't think they want banding. I'm leaving in the code to\n // set even and odd styles, but there's nothing in the CSS to style them differently.\n // The \"even\" style used to also have tw-bg-neutral-300 (along with even) to create\n // a visual banding effect. That could be added back in if UX changes the decision.\n return cn('banded-row', index % 2 === 0 ? 'even' : 'odd');\n };\n\n const getIndent = (\n groupingState: GroupingState,\n row: Row,\n cell: Cell,\n ) => {\n if (groupingState?.length === 0 || row.depth < cell.column.getGroupedIndex()) return undefined;\n if (row.getIsGrouped()) {\n switch (row.depth) {\n case 1:\n return 'tw-ps-4';\n default:\n return undefined;\n }\n }\n switch (row.depth) {\n case 1:\n return 'tw-ps-8';\n case 2:\n return 'tw-ps-12';\n default:\n return undefined;\n }\n };\n\n return (\n
    \n {!showColumnHeaders && (\n {\n handleSelectChange(value);\n }}\n >\n \n \n \n \n \n {groupingOptions.map((option) => (\n \n {option.label}\n \n ))}\n \n \n \n )}\n \n {showColumnHeaders && (\n \n {table.getHeaderGroups().map((headerGroup) => (\n \n {headerGroup.headers\n .filter((h) => h.column.columnDef.header)\n .map((header) => (\n /* For sticky column headers to work, we probably need to change the default definition of the shadcn Table component. See https://github.com/shadcn-ui/ui/issues/1151 */\n \n {header.isPlaceholder ? undefined : (\n
    \n {header.column.getCanGroup() ? (\n \n {header.column.getIsGrouped() ? `๐Ÿ›‘` : `๐Ÿ‘Š `}\n \n ) : undefined}{' '}\n {flexRender(header.column.columnDef.header, header.getContext())}\n
    \n )}\n
    \n ))}\n
    \n ))}\n
    \n )}\n \n {table.getRowModel().rows.map((row, rowIndex) => {\n const dir: Direction = readDirection();\n return (\n handleRowClick(row, event)}\n >\n {row.getVisibleCells().map((cell) => {\n if (\n cell.getIsPlaceholder() ||\n (cell.column.columnDef.enableGrouping &&\n !cell.getIsGrouped() &&\n (cell.column.columnDef.id !== typeColId || !showSourceColumn))\n )\n return undefined;\n return (\n \n {(() => {\n if (cell.getIsGrouped()) {\n return (\n \n {row.getIsExpanded() && }\n {!row.getIsExpanded() &&\n (dir === 'ltr' ? : )}{' '}\n {flexRender(cell.column.columnDef.cell, cell.getContext())} (\n {row.subRows.length})\n \n );\n }\n\n // if (cell.getIsAggregated()) {\n // flexRender(\n // cell.column.columnDef.aggregatedCell ?? cell.column.columnDef.cell,\n // cell.getContext(),\n // );\n // }\n\n return flexRender(cell.column.columnDef.cell, cell.getContext());\n })()}\n \n );\n })}\n \n );\n })}\n \n
    \n
    \n );\n}\n\nexport default ScriptureResultsViewer;\n","import { getSectionForBook, Section } from 'platform-bible-utils';\n\n/**\n * Filters an array of book IDs to only include books from a specific section\n *\n * @param bookIds Array of book IDs to filter\n * @param section The section to filter by\n * @returns Array of book IDs that belong to the specified section\n */\nexport const getBooksForSection = (bookIds: string[], section: Section) => {\n return bookIds.filter((bookId) => {\n try {\n return getSectionForBook(bookId) === section;\n } catch {\n return false;\n }\n });\n};\n\n/**\n * Checks if all books in a given section are included in the selectedBookIds array\n *\n * @param bookIds Array of all available book IDs\n * @param section The section to check\n * @param selectedBookIds Array of currently selected book IDs\n * @returns True if all books from the specified section are selected, false otherwise\n */\nexport const isSectionFullySelected = (\n bookIds: string[],\n section: Section,\n selectedBookIds: string[],\n) => getBooksForSection(bookIds, section).every((bookId) => selectedBookIds.includes(bookId));\n","import { Button } from '@/components/shadcn-ui/button';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { LanguageStrings, Section } from 'platform-bible-utils';\nimport { getSectionShortName } from '@/components/shared/book.utils';\nimport { getBooksForSection, isSectionFullySelected } from './scope-selector.utils';\n\n/**\n * A button component that represents a scripture section (testament) in the book selector. The\n * button shows a different state when all books in its section are selected and becomes disabled\n * when no books are available in its section.\n */\nfunction SectionButton({\n section,\n availableBookIds,\n selectedBookIds,\n onToggle,\n localizedStrings,\n}: {\n section: Section;\n availableBookIds: string[];\n selectedBookIds: string[];\n onToggle: (section: Section) => void;\n localizedStrings: LanguageStrings;\n}) {\n const isDisabled = getBooksForSection(availableBookIds, section).length === 0;\n\n const sectionOtShortText = localizedStrings['%scripture_section_ot_short%'];\n const sectionNtShortText = localizedStrings['%scripture_section_nt_short%'];\n const sectionDcShortText = localizedStrings['%scripture_section_dc_short%'];\n const sectionExtraShortText = localizedStrings['%scripture_section_extra_short%'];\n\n return (\n onToggle(section)}\n className={cn(\n isSectionFullySelected(availableBookIds, section, selectedBookIds) &&\n !isDisabled &&\n 'tw-bg-primary tw-text-primary-foreground hover:tw-bg-primary/70 hover:tw-text-primary-foreground',\n )}\n disabled={isDisabled}\n >\n {getSectionShortName(\n section,\n sectionOtShortText,\n sectionNtShortText,\n sectionDcShortText,\n sectionExtraShortText,\n )}\n \n );\n}\n\nexport default SectionButton;\n","import { BookItem } from '@/components/shared/book-item.component';\nimport { Badge } from '@/components/shadcn-ui/badge';\nimport { Button } from '@/components/shadcn-ui/button';\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandList,\n CommandSeparator,\n} from '@/components/shadcn-ui/command';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/shadcn-ui/popover';\nimport { Canon } from '@sillsdev/scripture';\nimport { ChevronsUpDown } from 'lucide-react';\nimport { getSectionForBook, LanguageStrings, Section } from 'platform-bible-utils';\nimport {\n getSectionLongName,\n getLocalizedBookName,\n doesBookMatchQuery,\n} from '@/components/shared/book.utils';\nimport { Fragment, MouseEvent, useCallback, useMemo, useRef, useState } from 'react';\nimport { generateCommandValue } from '@/components/shared/book-item.utils';\nimport { getBooksForSection, isSectionFullySelected } from './scope-selector.utils';\nimport SectionButton from './section-button.component';\n\n/** Maximum number of badges to show before collapsing into a \"+X more\" badge */\nconst VISIBLE_BADGES_COUNT = 5;\n/** Maximum number of badges that can be shown without triggering the collapse */\nconst MAX_VISIBLE_BADGES = 6;\n\ntype BookSelectorProps = {\n /**\n * Information about available books, formatted as a 123 character long string as defined in a\n * projects BooksPresent setting\n */\n availableBookInfo: string;\n /** Array of currently selected book IDs */\n selectedBookIds: string[];\n /** Callback function that is executed when the book selection changes */\n onChangeSelectedBookIds: (books: string[]) => void;\n /** Object containing the localized strings for the component */\n localizedStrings: LanguageStrings;\n /**\n * Optional map of localized book IDs/short names and full names. Key is the (English) book ID,\n * value contains localized versions of the ID and full book name\n */\n localizedBookNames?: Map;\n};\n\n/**\n * A component for selecting multiple books from the Bible canon. It provides:\n *\n * - Quick selection buttons for major sections (OT, NT, DC, Extra)\n * - A searchable dropdown with all available books\n * - Support for shift-click range selection\n * - Visual feedback with badges showing selected books\n */\nexport function BookSelector({\n availableBookInfo,\n selectedBookIds,\n onChangeSelectedBookIds,\n localizedStrings,\n localizedBookNames,\n}: BookSelectorProps) {\n const booksSelectedText = localizedStrings['%webView_book_selector_books_selected%'];\n const selectBooksText = localizedStrings['%webView_book_selector_select_books%'];\n const searchBooksText = localizedStrings['%webView_book_selector_search_books%'];\n const selectAllText = localizedStrings['%webView_book_selector_select_all%'];\n const clearAllText = localizedStrings['%webView_book_selector_clear_all%'];\n const noBookFoundText = localizedStrings['%webView_book_selector_no_book_found%'];\n const moreText = localizedStrings['%webView_book_selector_more%'];\n\n const { otLong, ntLong, dcLong, extraLong } = {\n otLong: localizedStrings?.['%scripture_section_ot_long%'],\n ntLong: localizedStrings?.['%scripture_section_nt_long%'],\n dcLong: localizedStrings?.['%scripture_section_dc_long%'],\n extraLong: localizedStrings?.['%scripture_section_extra_long%'],\n };\n\n const [isBooksSelectorOpen, setIsBooksSelectorOpen] = useState(false);\n const [inputValue, setInputValue] = useState('');\n const lastSelectedBookRef = useRef(undefined);\n const lastKeyEventShiftKey = useRef(false);\n\n if (availableBookInfo.length !== Canon.allBookIds.length) {\n throw new Error('availableBookInfo length must match Canon.allBookIds length');\n }\n\n const availableBooksIds = useMemo(() => {\n return Canon.allBookIds.filter(\n (bookId, index) =>\n availableBookInfo[index] === '1' && !Canon.isObsolete(Canon.bookIdToNumber(bookId)),\n );\n }, [availableBookInfo]);\n\n const filteredBooksBySection = useMemo(() => {\n if (!inputValue.trim()) {\n const allBooks: Record = {\n [Section.OT]: [],\n [Section.NT]: [],\n [Section.DC]: [],\n [Section.Extra]: [],\n };\n\n availableBooksIds.forEach((bookId) => {\n const section = getSectionForBook(bookId);\n allBooks[section].push(bookId);\n });\n\n return allBooks;\n }\n\n const filteredBooks = availableBooksIds.filter((bookId) =>\n doesBookMatchQuery(bookId, inputValue, localizedBookNames),\n );\n\n const matchingBooks: Record = {\n [Section.OT]: [],\n [Section.NT]: [],\n [Section.DC]: [],\n [Section.Extra]: [],\n };\n\n filteredBooks.forEach((bookId) => {\n const section = getSectionForBook(bookId);\n matchingBooks[section].push(bookId);\n });\n\n return matchingBooks;\n }, [availableBooksIds, inputValue, localizedBookNames]);\n\n const toggleBook = useCallback(\n (bookId: string, shiftKey = false) => {\n if (!shiftKey || !lastSelectedBookRef.current) {\n onChangeSelectedBookIds(\n selectedBookIds.includes(bookId)\n ? selectedBookIds.filter((id) => id !== bookId)\n : [...selectedBookIds, bookId],\n );\n lastSelectedBookRef.current = bookId;\n return;\n }\n\n const lastIndex = availableBooksIds.findIndex((id) => id === lastSelectedBookRef.current);\n const currentIndex = availableBooksIds.findIndex((id) => id === bookId);\n\n if (lastIndex === -1 || currentIndex === -1) return;\n\n const [startIndex, endIndex] = [\n Math.min(lastIndex, currentIndex),\n Math.max(lastIndex, currentIndex),\n ];\n const booksInRange = availableBooksIds.slice(startIndex, endIndex + 1).map((id) => id);\n\n onChangeSelectedBookIds(\n selectedBookIds.includes(bookId)\n ? selectedBookIds.filter((shortname) => !booksInRange.includes(shortname))\n : [...new Set([...selectedBookIds, ...booksInRange])],\n );\n },\n [selectedBookIds, onChangeSelectedBookIds, availableBooksIds],\n );\n\n const handleKeyboardSelect = (bookId: string) => {\n toggleBook(bookId, lastKeyEventShiftKey.current);\n lastKeyEventShiftKey.current = false;\n };\n\n const handleMouseDown = (event: MouseEvent, bookId: string) => {\n event.preventDefault();\n toggleBook(bookId, event.shiftKey);\n };\n\n const toggleSection = useCallback(\n (section: Section) => {\n const sectionBooks = getBooksForSection(availableBooksIds, section).map((bookId) => bookId);\n onChangeSelectedBookIds(\n isSectionFullySelected(availableBooksIds, section, selectedBookIds)\n ? selectedBookIds.filter((shortname) => !sectionBooks.includes(shortname))\n : [...new Set([...selectedBookIds, ...sectionBooks])],\n );\n },\n [selectedBookIds, onChangeSelectedBookIds, availableBooksIds],\n );\n\n const handleSelectAll = () => {\n onChangeSelectedBookIds(availableBooksIds.map((bookId) => bookId));\n };\n\n const handleClearAll = () => {\n onChangeSelectedBookIds([]);\n };\n\n return (\n
    \n
    \n {Object.values(Section).map((section) => {\n return (\n \n );\n })}\n
    \n\n {\n setIsBooksSelectorOpen(open);\n if (!open) {\n setInputValue(''); // Reset search when closing\n }\n }}\n >\n \n \n {selectedBookIds.length > 0\n ? `${booksSelectedText}: ${selectedBookIds.length}`\n : selectBooksText}\n \n \n \n \n {\n if (e.key === 'Enter') {\n // Store shift state in a ref that will be used by onSelect\n lastKeyEventShiftKey.current = e.shiftKey;\n }\n }}\n >\n \n
    \n \n \n
    \n \n {noBookFoundText}\n {Object.values(Section).map((section, index) => {\n const sectionBooks = filteredBooksBySection[section];\n\n if (sectionBooks.length === 0) return undefined;\n\n return (\n \n \n {sectionBooks.map((bookId) => (\n handleKeyboardSelect(bookId)}\n onMouseDown={(event) => handleMouseDown(event, bookId)}\n section={getSectionForBook(bookId)}\n showCheck\n localizedBookNames={localizedBookNames}\n commandValue={generateCommandValue(bookId, localizedBookNames)}\n className=\"tw-flex tw-items-center\"\n />\n ))}\n \n {index < Object.values(Section).length - 1 && }\n \n );\n })}\n \n \n
    \n \n\n {selectedBookIds.length > 0 && (\n
    \n {selectedBookIds\n .slice(\n 0,\n selectedBookIds.length === MAX_VISIBLE_BADGES\n ? MAX_VISIBLE_BADGES\n : VISIBLE_BADGES_COUNT,\n )\n .map((bookId) => (\n \n {getLocalizedBookName(bookId, localizedBookNames)}\n \n ))}\n {selectedBookIds.length > MAX_VISIBLE_BADGES && (\n {`+${selectedBookIds.length - VISIBLE_BADGES_COUNT} ${moreText}`}\n )}\n
    \n )}\n
    \n );\n}\n","import { BookSelector } from '@/components/advanced/scope-selector/book-selector.component';\nimport { Label } from '@/components/shadcn-ui/label';\nimport { RadioGroup, RadioGroupItem } from '@/components/shadcn-ui/radio-group';\nimport { Scope } from '@/components/utils/scripture.util';\nimport { LocalizedStringValue } from 'platform-bible-utils';\n\n/**\n * Object containing all keys used for localization in this component. If you're using this\n * component in an extension, you can pass it into the useLocalizedStrings hook to easily obtain the\n * localized strings and pass them into the localizedStrings prop of this component\n */\nexport const SCOPE_SELECTOR_STRING_KEYS = Object.freeze([\n '%webView_scope_selector_selected_text%',\n '%webView_scope_selector_current_verse%',\n '%webView_scope_selector_current_chapter%',\n '%webView_scope_selector_current_book%',\n '%webView_scope_selector_choose_books%',\n '%webView_scope_selector_scope%',\n '%webView_scope_selector_select_books%',\n '%webView_book_selector_books_selected%',\n '%webView_book_selector_select_books%',\n '%webView_book_selector_search_books%',\n '%webView_book_selector_select_all%',\n '%webView_book_selector_clear_all%',\n '%webView_book_selector_no_book_found%',\n '%webView_book_selector_more%',\n '%scripture_section_ot_long%',\n '%scripture_section_ot_short%',\n '%scripture_section_nt_long%',\n '%scripture_section_nt_short%',\n '%scripture_section_dc_long%',\n '%scripture_section_dc_short%',\n '%scripture_section_extra_long%',\n '%scripture_section_extra_short%',\n] as const);\n\n/** Type definition for the localized strings used in this component */\nexport type ScopeSelectorLocalizedStrings = {\n [localizedInventoryKey in (typeof SCOPE_SELECTOR_STRING_KEYS)[number]]?: LocalizedStringValue;\n};\n\n/**\n * Gets the localized value for the provided key\n *\n * @param strings Object containing localized string\n * @param key Key for a localized string\n * @returns The localized value for the provided key, if available. Returns the key if no localized\n * value is available\n */\nconst localizeString = (\n strings: ScopeSelectorLocalizedStrings,\n key: keyof ScopeSelectorLocalizedStrings,\n) => {\n return strings[key] ?? key;\n};\n\n/** Props for configuring the ScopeSelector component */\ninterface ScopeSelectorProps {\n /** The current scope selection */\n scope: Scope;\n\n /**\n * Optional array of scopes that should be available in the selector. If not provided, all scopes\n * will be shown as defined in the Scope type\n */\n availableScopes?: Scope[];\n\n /** Callback function that is executed when the user changes the scope selection */\n onScopeChange: (scope: Scope) => void;\n\n /**\n * Information about available books, formatted as a 123 character long string as defined in a\n * projects BooksPresent setting\n */\n availableBookInfo: string;\n\n /** Array of currently selected book IDs */\n selectedBookIds: string[];\n\n /** Callback function that is executed when the user changes the book selection */\n onSelectedBookIdsChange: (books: string[]) => void;\n\n /**\n * Object with all localized strings that the component needs to work well across multiple\n * languages. When using this component with Platform.Bible, you can import\n * `SCOPE_SELECTOR_STRING_KEYS` from this library, pass it in to the Platform's localization hook,\n * and pass the localized keys that are returned by the hook into this prop.\n */\n localizedStrings: ScopeSelectorLocalizedStrings;\n /**\n * Optional map of localized book IDs/short names and full names. Key is the (English) book ID,\n * value contains localized versions of the ID and full book name\n */\n localizedBookNames?: Map;\n /** Optional ID that is applied to the root element of this component */\n id?: string;\n}\n\n/**\n * A component that allows users to select the scope of their search or operation. Available scopes\n * are defined in the Scope type. When 'selectedBooks' is chosen as the scope, a BookSelector\n * component is displayed to allow users to choose specific books.\n */\nexport function ScopeSelector({\n scope,\n availableScopes,\n onScopeChange,\n availableBookInfo,\n selectedBookIds,\n onSelectedBookIdsChange,\n localizedStrings,\n localizedBookNames,\n id,\n}: ScopeSelectorProps) {\n const selectedTextText = localizeString(\n localizedStrings,\n '%webView_scope_selector_selected_text%',\n );\n const currentVerseText = localizeString(\n localizedStrings,\n '%webView_scope_selector_current_verse%',\n );\n const currentChapterText = localizeString(\n localizedStrings,\n '%webView_scope_selector_current_chapter%',\n );\n const currentBookText = localizeString(localizedStrings, '%webView_scope_selector_current_book%');\n const chooseBooksText = localizeString(localizedStrings, '%webView_scope_selector_choose_books%');\n const scopeText = localizeString(localizedStrings, '%webView_scope_selector_scope%');\n const selectBooksText = localizeString(localizedStrings, '%webView_scope_selector_select_books%');\n\n const SCOPE_OPTIONS: Array<{ value: Scope; label: string; id: string }> = [\n { value: 'selectedText', label: selectedTextText, id: 'scope-selected-text' },\n { value: 'verse', label: currentVerseText, id: 'scope-verse' },\n { value: 'chapter', label: currentChapterText, id: 'scope-chapter' },\n { value: 'book', label: currentBookText, id: 'scope-book' },\n { value: 'selectedBooks', label: chooseBooksText, id: 'scope-selected' },\n ];\n\n const displayedScopes = availableScopes\n ? SCOPE_OPTIONS.filter((option) => availableScopes.includes(option.value))\n : SCOPE_OPTIONS;\n\n return (\n
    \n
    \n \n \n {displayedScopes.map(({ value, label, id: scopeId }) => (\n
    \n \n \n
    \n ))}\n \n
    \n\n {scope === 'selectedBooks' && (\n
    \n \n \n
    \n )}\n
    \n );\n}\n\nexport default ScopeSelector;\n","import {\n getLocalizeKeyForScrollGroupId,\n LanguageStrings,\n ScrollGroupId,\n} from 'platform-bible-utils';\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@/components/shadcn-ui/select';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\nimport { cn } from '@/utils/shadcn-ui.util';\n\nconst DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS = {\n [getLocalizeKeyForScrollGroupId('undefined')]: 'ร˜',\n [getLocalizeKeyForScrollGroupId(0)]: 'A',\n [getLocalizeKeyForScrollGroupId(1)]: 'B',\n [getLocalizeKeyForScrollGroupId(2)]: 'C',\n [getLocalizeKeyForScrollGroupId(3)]: 'D',\n [getLocalizeKeyForScrollGroupId(4)]: 'E',\n [getLocalizeKeyForScrollGroupId(5)]: 'F',\n [getLocalizeKeyForScrollGroupId(6)]: 'G',\n [getLocalizeKeyForScrollGroupId(7)]: 'H',\n [getLocalizeKeyForScrollGroupId(8)]: 'I',\n [getLocalizeKeyForScrollGroupId(9)]: 'J',\n [getLocalizeKeyForScrollGroupId(10)]: 'K',\n [getLocalizeKeyForScrollGroupId(11)]: 'L',\n [getLocalizeKeyForScrollGroupId(12)]: 'M',\n [getLocalizeKeyForScrollGroupId(13)]: 'N',\n [getLocalizeKeyForScrollGroupId(14)]: 'O',\n [getLocalizeKeyForScrollGroupId(15)]: 'P',\n [getLocalizeKeyForScrollGroupId(16)]: 'Q',\n [getLocalizeKeyForScrollGroupId(17)]: 'R',\n [getLocalizeKeyForScrollGroupId(18)]: 'S',\n [getLocalizeKeyForScrollGroupId(19)]: 'T',\n [getLocalizeKeyForScrollGroupId(20)]: 'U',\n [getLocalizeKeyForScrollGroupId(21)]: 'V',\n [getLocalizeKeyForScrollGroupId(22)]: 'W',\n [getLocalizeKeyForScrollGroupId(23)]: 'X',\n [getLocalizeKeyForScrollGroupId(24)]: 'Y',\n [getLocalizeKeyForScrollGroupId(25)]: 'Z',\n};\n\nexport type ScrollGroupSelectorProps = {\n /**\n * List of scroll group ids to show to the user. Either a `ScrollGroupId` or `undefined` for no\n * scroll group\n */\n availableScrollGroupIds: (ScrollGroupId | undefined)[];\n /** Currently selected scroll group id. `undefined` for no scroll group */\n scrollGroupId: ScrollGroupId | undefined;\n /** Callback function run when the user tries to change the scroll group id */\n onChangeScrollGroupId: (newScrollGroupId: ScrollGroupId | undefined) => void;\n /**\n * Localized strings to use for displaying scroll group ids. Must be an object whose keys are\n * `getLocalizeKeyForScrollGroupId(scrollGroupId)` for all scroll group ids (and `undefined` if\n * included) in {@link ScrollGroupSelectorProps.availableScrollGroupIds} and whose values are the\n * localized strings to use for those scroll group ids.\n *\n * Defaults to English localizations of English alphabet for scroll groups 0-25 (e.g. 0 is A) and\n * ร˜ for `undefined`. Will fill in any that are not provided with these English localizations.\n * Also, if any values match the keys, the English localization will be used. This is useful in\n * case you want to pass in a temporary version of the localized strings while your localized\n * strings load.\n *\n * @example\n *\n * ```typescript\n * const myScrollGroupIdLocalizedStrings = {\n * [getLocalizeKeyForScrollGroupId('undefined')]: 'ร˜',\n * [getLocalizeKeyForScrollGroupId(0)]: 'A',\n * [getLocalizeKeyForScrollGroupId(1)]: 'B',\n * [getLocalizeKeyForScrollGroupId(2)]: 'C',\n * [getLocalizeKeyForScrollGroupId(3)]: 'D',\n * [getLocalizeKeyForScrollGroupId(4)]: 'E',\n * };\n * ```\n *\n * @example\n *\n * ```tsx\n * const availableScrollGroupIds = [undefined, 0, 1, 2, 3, 4];\n *\n * const localizeKeys = getLocalizeKeysForScrollGroupIds();\n *\n * const [localizedStrings] = useLocalizedStrings(localizeKeys);\n *\n * ...\n *\n * \n * ```\n */\n localizedStrings?: LanguageStrings;\n\n /** Size of the scroll group dropdown button. Defaults to 'sm' */\n size?: 'default' | 'sm' | 'lg' | 'icon';\n\n /** Additional css classes to help with unique styling */\n className?: string;\n\n /** Optional id for the select element */\n id?: string;\n};\n\n/** Selector component for choosing a scroll group */\nexport function ScrollGroupSelector({\n availableScrollGroupIds,\n scrollGroupId,\n onChangeScrollGroupId,\n localizedStrings = {},\n size = 'sm',\n className,\n id,\n}: ScrollGroupSelectorProps) {\n const localizedStringsDefaulted = {\n ...DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS,\n ...Object.fromEntries(\n Object.entries(localizedStrings).map(\n ([localizedStringKey, localizedStringValue]: [string, string]) => [\n localizedStringKey,\n localizedStringKey === localizedStringValue &&\n localizedStringKey in DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS\n ? DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS[localizedStringKey]\n : localizedStringValue,\n ],\n ),\n ),\n };\n\n const dir: Direction = readDirection();\n\n return (\n \n onChangeScrollGroupId(\n newScrollGroupString === 'undefined' ? undefined : parseInt(newScrollGroupString, 10),\n )\n }\n >\n \n \n \n \n {availableScrollGroupIds.map((scrollGroupOptionId) => (\n \n {localizedStringsDefaulted[getLocalizeKeyForScrollGroupId(scrollGroupOptionId)]}\n \n ))}\n \n \n );\n}\n\nexport default ScrollGroupSelector;\n","import { PropsWithChildren } from 'react';\nimport { Separator } from '@/components/shadcn-ui/separator';\n\n/** Props for the SettingsList component, currently just children */\ntype SettingsListProps = PropsWithChildren;\n\n/**\n * SettingsList component is a wrapper for list items. Rendered with a formatted div\n *\n * @deprecated Jul 18 2025. This component is no longer supported or tested. Use of this component\n * is discouraged and it may be removed in the future.\n * @param children To populate the list with\n * @returns Formatted div encompassing the children\n */\nexport function SettingsList({ children }: SettingsListProps) {\n return
    {children}
    ;\n}\n\n/** Props for SettingsListItem component */\ntype SettingsListItemProps = PropsWithChildren & {\n /** Primary text of the list item */\n primary: string;\n\n /** Optional text of the list item */\n secondary?: string | undefined;\n\n /** Optional boolean to display a message if the children aren't loaded yet. Defaults to false */\n isLoading?: boolean;\n\n /** Optional message to display if isLoading */\n loadingMessage?: string;\n};\n\n/**\n * SettingsListItem component is a common list item. Rendered with a formatted div\n *\n * @deprecated Jul 18 2025. This component is no longer supported or tested. Use of this component\n * is discouraged and it may be removed in the future.\n * @param SettingsListItemProps\n * @returns Formatted div encompassing the list item content\n */\nexport function SettingsListItem({\n primary,\n secondary,\n children,\n isLoading = false,\n loadingMessage,\n}: SettingsListItemProps) {\n return (\n
    \n
    \n

    {primary}

    \n

    \n {secondary}\n

    \n
    \n\n {isLoading ? (\n

    {loadingMessage}

    \n ) : (\n
    {children}
    \n )}\n
    \n );\n}\n\n/** Props for SettingsListHeader component */\ntype SettingsListHeaderProps = {\n /** The primary text of the list header */\n primary: string;\n\n /** Optional secondary text of the list header */\n secondary?: string | undefined;\n\n /** Optional boolean to include a separator underneath the secondary text. Defaults to false */\n includeSeparator?: boolean;\n};\n\n/**\n * SettingsListHeader component displays text above the list\n *\n * @deprecated Jul 18 2025. This component is no longer supported or tested. Use of this component\n * is discouraged and it may be removed in the future.\n * @param SettingsListHeaderProps\n * @returns Formatted div with list header content\n */\nexport function SettingsListHeader({\n primary,\n secondary,\n includeSeparator = false,\n}: SettingsListHeaderProps) {\n return (\n
    \n
    \n

    {primary}

    \n

    {secondary}

    \n
    \n {includeSeparator ? : ''}\n
    \n );\n}\n","import { GroupsInMultiColumnMenu, Localized } from 'platform-bible-utils';\n\n/**\n * Function that looks up the key of a sub-menu group using the value of it's `menuItem` property.\n *\n * @example\n *\n * ```ts\n * const groups = {\n * 'platform.subMenu': { menuItem: 'platform.subMenuId', order: 1 },\n * 'platform.subSubMenu': { menuItem: 'platform.subSubMenuId', order: 2 },\n * };\n * const id = 'platform.subMenuId';\n * const groupKey = getSubMenuGroupKeyForMenuItemId(groups, id);\n * console.log(groupKey); // Output: 'platform.subMenu'\n * ```\n *\n * @param groups The JSON Object containing the group definitions\n * @param id The value of the `menuItem` property of the group to look up\n * @returns The key of the group that has the `menuItem` property with the value of `id` or\n * `undefined` if no such group exists.\n */\nexport function getSubMenuGroupKeyForMenuItemId(\n groups: Localized,\n id: string,\n): string | undefined {\n return Object.entries(groups).find(\n ([, value]) => 'menuItem' in value && value.menuItem === id,\n )?.[0];\n}\n","import { cn } from '@/utils/shadcn-ui.util';\n\ntype MenuItemIconProps = {\n /** The icon to display */\n icon: string;\n /** The label of the menu item */\n menuLabel: string;\n /** Whether the icon is leading or trailing */\n leading?: boolean;\n};\n\nfunction MenuItemIcon({ icon, menuLabel, leading }: MenuItemIconProps) {\n return icon ? (\n \n ) : undefined;\n}\n\nexport default MenuItemIcon;\n","import {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuPortal,\n DropdownMenuSeparator,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n} from '@/components/shadcn-ui/dropdown-menu';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@/components/shadcn-ui/tooltip';\nimport { MenuIcon } from 'lucide-react';\nimport {\n GroupsInMultiColumnMenu,\n Localized,\n MenuItemContainingCommand,\n MenuItemContainingSubmenu,\n MultiColumnMenu,\n} from 'platform-bible-utils';\nimport { Fragment, ReactNode } from 'react';\nimport { Button } from '@/components/shadcn-ui/button';\nimport { getSubMenuGroupKeyForMenuItemId } from './menu.util';\nimport { SelectMenuItemHandler } from './platform-menubar.component';\nimport MenuItemIcon from './menu-icon.component';\n\nconst getGroupContent = (\n groups: Localized,\n items: Localized<(MenuItemContainingCommand | MenuItemContainingSubmenu)[]>,\n columnOrSubMenuKey: string | undefined,\n onSelectMenuItem: SelectMenuItemHandler,\n) => {\n if (!columnOrSubMenuKey) return undefined;\n\n const sortedGroupsForColumn = Object.entries(groups)\n .filter(\n ([key, group]) =>\n ('column' in group && group.column === columnOrSubMenuKey) || key === columnOrSubMenuKey,\n )\n .sort(([, a], [, b]) => a.order - b.order);\n\n return sortedGroupsForColumn.flatMap(([groupKey]) => {\n const groupItems = items\n .filter((item) => item.group === groupKey)\n .sort((a, b) => a.order - b.order)\n .map((item: Localized) => {\n return (\n \n \n {'command' in item ? (\n {\n // Since the item has a command, we know it is a MenuItemContainingCommand.\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n onSelectMenuItem(item as MenuItemContainingCommand);\n }}\n >\n {item.iconPathBefore && (\n \n )}\n {item.label}\n {item.iconPathAfter && (\n \n )}\n \n ) : (\n \n {item.label}\n\n \n \n {getGroupContent(\n groups,\n items,\n getSubMenuGroupKeyForMenuItemId(groups, item.id),\n onSelectMenuItem,\n )}\n \n \n \n )}\n \n {item.tooltip && {item.tooltip}}\n \n );\n });\n\n return groupItems;\n });\n};\n\nexport type TabDropdownMenuProps = {\n /** The handler to use for menu commands */\n onSelectMenuItem: SelectMenuItemHandler;\n\n /** The menu data to show on the dropdown menu */\n menuData: Localized;\n\n /** Defines a string value that labels the current element */\n tabLabel: string;\n\n /** Optional icon for the dropdown menu trigger. Defaults to hamburger icon. */\n icon?: ReactNode;\n\n /** Additional css class(es) to help with unique styling of the tab dropdown menu */\n className?: string;\n\n /** Style variant for the app menubar component. */\n variant?: 'default' | 'muted';\n\n buttonVariant?: 'default' | 'ghost' | 'outline' | 'secondary';\n\n /** Optional unique identifier */\n id?: string;\n};\n\n/**\n * Dropdown menu designed to be used with Platform.Bible menu data. Column headers are ignored.\n * Column data is separated by a horizontal divider, so groups are not distinguishable. Tooltips are\n * displayed on hovering over menu items, if a tooltip is defined for them.\n *\n * A child component can be passed in to show as an icon on the menu trigger button.\n */\nexport default function TabDropdownMenu({\n onSelectMenuItem,\n menuData,\n tabLabel,\n icon,\n className,\n variant,\n buttonVariant = 'ghost',\n id,\n}: TabDropdownMenuProps) {\n return (\n \n \n \n \n \n {Object.entries(menuData.columns)\n .filter(([, column]) => typeof column === 'object')\n .sort(([, a], [, b]) => {\n if (typeof a === 'boolean' || typeof b === 'boolean') return 0;\n return a.order - b.order;\n })\n .map(([columnKey], index, array) => (\n \n \n \n {getGroupContent(menuData.groups, menuData.items, columnKey, onSelectMenuItem)}\n \n \n\n {index < array.length - 1 && }\n \n ))}\n \n \n );\n}\n","import { Localized, MultiColumnMenu } from 'platform-bible-utils';\nimport React, { PropsWithChildren, ReactNode } from 'react';\nimport { SelectMenuItemHandler } from '../menus/platform-menubar.component';\n\nexport type TabToolbarCommonProps = {\n /**\n * The handler to use for toolbar item commands related to the project menu. Here is a basic\n * example of how to create this:\n *\n * @example\n *\n * ```tsx\n * const projectMenuCommandHandler: SelectMenuItemHandler = async (selectedMenuItem) => {\n * const commandName = selectedMenuItem.command;\n * try {\n * // Assert the more specific type. Assert the more specific type. The menu data should\n * // specify a valid command name here. If not, the error will be caught.\n * // eslint-disable-next-line no-type-assertion/no-type-assertion\n * await papi.commands.sendCommand(commandName as CommandNames);\n * } catch (e) {\n * throw new Error(\n * `handleMenuCommand error: command: ${commandName}. ${JSON.stringify(e)}`,\n * );\n * }\n * };\n * ```\n */\n onSelectProjectMenuItem: SelectMenuItemHandler;\n\n /**\n * Menu data that is used to populate the Menubar component for the project menu. In an extension,\n * the menu data comes from menus.json in the contributions folder. To access that info, use\n * useMemo to get the WebViewMenu.\n */\n projectMenuData?: Localized;\n\n /** Optional unique identifier */\n id?: string;\n\n /** Additional css classes to help with unique styling of the extensible toolbar */\n className?: string;\n\n /** Icon that will be displayed on the Menu Button. Defaults to the hamburger menu icon. */\n menuButtonIcon?: ReactNode;\n};\n\nexport type TabToolbarContainerProps = PropsWithChildren<{\n /** Optional unique identifier */\n id?: string;\n /** Additional css classes to help with unique styling of the extensible toolbar */\n className?: string;\n}>;\n\n/** Wrapper that allows consistent styling for both TabToolbar and TabFloatingMenu. */\nexport const TabToolbarContainer = React.forwardRef(\n ({ id, className, children }, ref) => (\n \n {children}\n \n ),\n);\n\nexport default TabToolbarContainer;\n","import { ReactNode } from 'react';\nimport { Localized, MultiColumnMenu } from 'platform-bible-utils';\nimport { Menu, EllipsisVertical } from 'lucide-react';\nimport TabDropdownMenu from '../menus/tab-dropdown-menu.component';\nimport { SelectMenuItemHandler } from '../menus/platform-menubar.component';\nimport { TabToolbarCommonProps, TabToolbarContainer } from './tab-toolbar-container.component';\n\nexport type TabToolbarProps = TabToolbarCommonProps & {\n /**\n * The handler to use for toolbar item commands related to the tab view menu. Here is a basic\n * example of how to create this from the hello-rock3 extension:\n *\n * @example\n *\n * ```tsx\n * const projectMenuCommandHandler: SelectMenuItemHandler = async (selectedMenuItem) => {\n * const commandName = selectedMenuItem.command;\n * try {\n * // Assert the more specific type. Assert the more specific type. The menu data should\n * // specify a valid command name here. If not, the error will be caught.\n * // eslint-disable-next-line no-type-assertion/no-type-assertion\n * await papi.commands.sendCommand(commandName as CommandNames);\n * } catch (e) {\n * throw new Error(\n * `handleMenuCommand error: command: ${commandName}. ${JSON.stringify(e)}`,\n * );\n * }\n * };\n * ```\n */\n onSelectViewInfoMenuItem: SelectMenuItemHandler;\n\n /** Menu data that is used to populate the Menubar component for the view info menu */\n tabViewMenuData?: Localized;\n\n /**\n * Toolbar children to be put at the start of the the toolbar after the project menu icon (left\n * side in ltr, right side in rtl). Recommended for inner navigation.\n */\n startAreaChildren?: ReactNode;\n\n /** Toolbar children to be put in the center area of the the toolbar. Recommended for tools. */\n centerAreaChildren?: ReactNode;\n\n /**\n * Toolbar children to be put at the end of the the toolbar before the tab view menu icon (right\n * side in ltr, left side in rtl). Recommended for secondary tools and view options.\n */\n endAreaChildren?: ReactNode;\n};\n\n/**\n * Toolbar that holds the project menu icon on one side followed by three different areas/categories\n * for toolbar icons followed by an optional view info menu icon. See the Tab Floating Menu Button\n * component for a menu component that takes up less screen real estate yet is always visible.\n */\nexport function TabToolbar({\n onSelectProjectMenuItem,\n onSelectViewInfoMenuItem,\n projectMenuData,\n tabViewMenuData,\n id,\n className,\n startAreaChildren,\n centerAreaChildren,\n endAreaChildren,\n menuButtonIcon,\n}: TabToolbarProps) {\n return (\n \n {projectMenuData && (\n }\n buttonVariant=\"ghost\"\n />\n )}\n {startAreaChildren && (\n
    \n {startAreaChildren}\n
    \n )}\n {centerAreaChildren && (\n
    \n {centerAreaChildren}\n
    \n )}\n
    \n {tabViewMenuData && (\n }\n className=\"tw-h-full\"\n />\n )}\n {endAreaChildren}\n
    \n
    \n );\n}\n\nexport default TabToolbar;\n","import TabDropdownMenu from '../menus/tab-dropdown-menu.component';\nimport { TabToolbarCommonProps, TabToolbarContainer } from './tab-toolbar-container.component';\n\n/**\n * Renders a TabDropdownMenu with a trigger button that looks like the menuButtonIcon or like the\n * default of three stacked horizontal lines (aka the hamburger). The menu \"floats\" over the content\n * so it is always visible. When clicked, it displays a dropdown menu with the projectMenuData.\n */\nexport function TabFloatingMenu({\n onSelectProjectMenuItem,\n projectMenuData,\n id,\n className,\n menuButtonIcon,\n}: TabToolbarCommonProps) {\n return (\n \n {projectMenuData && (\n \n )}\n \n );\n}\n\nexport default TabFloatingMenu;\n","// adapted from: https://github.com/shadcn-ui/ui/discussions/752\n\n'use client';\n\nimport { TabsContentProps, TabsListProps, TabsTriggerProps } from '@/components/shadcn-ui/tabs';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport * as TabsPrimitive from '@radix-ui/react-tabs';\nimport React from 'react';\n\nexport type VerticalTabsProps = React.ComponentPropsWithoutRef & {\n className?: string;\n};\n\nexport type LeftTabsTriggerProps = TabsTriggerProps & {\n value: string;\n ref?: React.Ref;\n};\n\n/**\n * Tabs components provide a set of layered sections of contentโ€”known as tab panelsโ€“that are\n * displayed one at a time. These components are built on Radix UI primitives and styled with Shadcn\n * UI. See Shadcn UI Documentation: https://ui.shadcn.com/docs/components/tabs See Radix UI\n * Documentation: https://www.radix-ui.com/primitives/docs/components/tabs\n */\nexport const VerticalTabs = React.forwardRef<\n React.ElementRef,\n VerticalTabsProps\n>(({ className, ...props }, ref) => {\n const dir: Direction = readDirection();\n return (\n \n );\n});\n\nVerticalTabs.displayName = TabsPrimitive.List.displayName;\n\n/** @inheritdoc VerticalTabs */\nexport const VerticalTabsList = React.forwardRef<\n React.ElementRef,\n TabsListProps\n>(({ className, ...props }, ref) => (\n \n));\nVerticalTabsList.displayName = TabsPrimitive.List.displayName;\n\n/** @inheritdoc VerticalTabs */\nexport const VerticalTabsTrigger = React.forwardRef<\n React.ElementRef,\n LeftTabsTriggerProps\n>(({ className, ...props }, ref) => (\n \n));\n\n/** @inheritdoc VerticalTabs */\nexport const VerticalTabsContent = React.forwardRef<\n React.ElementRef,\n TabsContentProps\n>(({ className, ...props }, ref) => (\n \n));\nVerticalTabsContent.displayName = TabsPrimitive.Content.displayName;\n","import { SearchBar } from '@/components/basics/search-bar.component';\nimport {\n VerticalTabs,\n VerticalTabsContent,\n VerticalTabsList,\n VerticalTabsTrigger,\n} from '@/components/basics/tabs-vertical';\nimport { ReactNode } from 'react';\n\nexport type TabKeyValueContent = {\n key: string;\n value: string;\n content: ReactNode;\n};\n\nexport type TabNavigationContentSearchProps = {\n /** List of values and keys for each tab this component should provide */\n tabList: TabKeyValueContent[];\n\n /** The search query in the search bar */\n searchValue: string;\n\n /** Handler to run when the value of the search bar changes */\n onSearch: (searchQuery: string) => void;\n\n /** Optional placeholder for the search bar */\n searchPlaceholder?: string;\n\n /** Optional title to include in the header */\n headerTitle?: string;\n\n /** Optional className to modify the search input */\n searchClassName?: string;\n\n /** Optional id for the root element */\n id?: string;\n};\n\n/**\n * TabNavigationContentSearch component provides a vertical tab navigation interface with a search\n * bar at the top. This component allows users to filter content within tabs based on a search\n * query.\n *\n * @param {TabNavigationContentSearchProps} props\n * @param {TabKeyValueContent[]} props.tabList - List of objects containing keys, values, and\n * content for each tab to be displayed.\n * @param {string} props.searchValue - The current value of the search input.\n * @param {function} props.onSearch - Callback function called when the search input changes;\n * receives the new search query as an argument.\n * @param {string} [props.searchPlaceholder] - Optional placeholder text for the search input.\n * @param {string} [props.headerTitle] - Optional title to display above the search input.\n * @param {string} [props.searchClassName] - Optional CSS class name to apply custom styles to the\n * search input.\n * @param {string} [props.id] - Optional id for the root element.\n */\nexport function TabNavigationContentSearch({\n tabList,\n searchValue,\n onSearch,\n searchPlaceholder,\n headerTitle,\n searchClassName,\n id,\n}: TabNavigationContentSearchProps) {\n return (\n
    \n
    \n {headerTitle ?

    {headerTitle}

    : ''}\n \n
    \n \n \n {tabList.map((tab) => (\n \n {tab.value}\n \n ))}\n \n {tabList.map((tab) => (\n \n {tab.content}\n \n ))}\n \n
    \n );\n}\n\nexport default TabNavigationContentSearch;\n","import {\n MenuContext,\n MenuContextProps,\n menuVariants,\n useMenuContext,\n} from '@/context/menu.context';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport * as MenubarPrimitive from '@radix-ui/react-menubar';\nimport { Check, ChevronRight, Circle } from 'lucide-react';\nimport React from 'react';\n\nfunction MenubarMenu({ ...props }: React.ComponentProps) {\n return ;\n}\n\nfunction MenubarGroup({ ...props }: React.ComponentProps) {\n return ;\n}\n\nfunction MenubarPortal({ ...props }: React.ComponentProps) {\n return ;\n}\n\nfunction MenubarRadioGroup({ ...props }: React.ComponentProps) {\n return ;\n}\n\nfunction MenubarSub({ ...props }: React.ComponentProps) {\n return ;\n}\n\nconst Menubar = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n variant?: MenuContextProps['variant'];\n }\n>(({ className, variant = 'default', ...props }, ref) => {\n /* #region CUSTOM provide context to add variants */\n const contextValue = React.useMemo(\n () => ({\n variant,\n }),\n [variant],\n );\n return (\n \n {/* #endregion CUSTOM */}\n \n \n );\n});\nMenubar.displayName = MenubarPrimitive.Root.displayName;\n\nconst MenubarTrigger = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n );\n});\nMenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;\n\nconst MenubarSubTrigger = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n inset?: boolean;\n }\n>(({ className, inset, children, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n {children}\n \n \n );\n});\nMenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;\n\nconst MenubarSubContent = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n );\n});\nMenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;\n\nconst MenubarContent = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, align = 'start', alignOffset = -4, sideOffset = 8, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n \n \n );\n});\nMenubarContent.displayName = MenubarPrimitive.Content.displayName;\n\nconst MenubarItem = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n inset?: boolean;\n }\n>(({ className, inset, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n );\n});\nMenubarItem.displayName = MenubarPrimitive.Item.displayName;\n\nconst MenubarCheckboxItem = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, checked, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n \n \n \n \n \n {children}\n \n );\n});\nMenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;\n\nconst MenubarRadioItem = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n \n \n \n \n \n {children}\n \n );\n});\nMenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;\n\nconst MenubarLabel = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n inset?: boolean;\n }\n>(({ className, inset, ...props }, ref) => (\n \n));\nMenubarLabel.displayName = MenubarPrimitive.Label.displayName;\n\nconst MenubarSeparator = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nMenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;\n\nfunction MenubarShortcut({ className, ...props }: React.HTMLAttributes) {\n return (\n \n );\n}\nMenubarShortcut.displayname = 'MenubarShortcut';\n\nexport {\n Menubar,\n MenubarCheckboxItem,\n MenubarContent,\n MenubarGroup,\n MenubarItem,\n MenubarLabel,\n MenubarMenu,\n MenubarPortal,\n MenubarRadioGroup,\n MenubarRadioItem,\n MenubarSeparator,\n MenubarShortcut,\n MenubarSub,\n MenubarSubContent,\n MenubarSubTrigger,\n MenubarTrigger,\n};\n","import {\n Menubar,\n MenubarContent,\n MenubarItem,\n MenubarMenu,\n MenubarSeparator,\n MenubarSub,\n MenubarSubContent,\n MenubarSubTrigger,\n MenubarTrigger,\n} from '@/components/shadcn-ui/menubar';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@/components/shadcn-ui/tooltip';\nimport {\n GroupsInMultiColumnMenu,\n Localized,\n MenuItemContainingCommand,\n MenuItemContainingSubmenu,\n MultiColumnMenu,\n} from 'platform-bible-utils';\nimport { RefObject, useEffect, useRef } from 'react';\nimport { useHotkeys } from 'react-hotkeys-hook';\nimport { getSubMenuGroupKeyForMenuItemId } from './menu.util';\nimport MenuItemIcon from './menu-icon.component';\n\n/**\n * Callback function that is invoked when a user selects a menu item. Receives the full\n * `MenuItemContainingCommand` object as an argument.\n */\nexport interface SelectMenuItemHandler {\n (selectedMenuItem: MenuItemContainingCommand): void;\n}\n\nconst simulateKeyPress = (ref: RefObject, keys: KeyboardEventInit[]) => {\n setTimeout(() => {\n keys.forEach((key) => {\n ref.current?.dispatchEvent(new KeyboardEvent('keydown', key));\n });\n }, 0);\n};\n\nconst getMenubarContent = (\n groups: Localized,\n items: Localized<(MenuItemContainingCommand | MenuItemContainingSubmenu)[]>,\n columnOrSubMenuKey: string | undefined,\n onSelectMenuItem: SelectMenuItemHandler,\n) => {\n if (!columnOrSubMenuKey) return undefined;\n\n const sortedGroupsForColumn = Object.entries(groups)\n .filter(\n ([key, group]) =>\n ('column' in group && group.column === columnOrSubMenuKey) || key === columnOrSubMenuKey,\n )\n .sort(([, a], [, b]) => a.order - b.order);\n\n return sortedGroupsForColumn.flatMap(([groupKey], index) => {\n const groupItems = items\n .filter((item) => item.group === groupKey)\n .sort((a, b) => a.order - b.order)\n .map((item: Localized) => {\n return (\n \n \n {'command' in item ? (\n {\n // Since the item has a command, we know it is a MenuItemContainingCommand.\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n onSelectMenuItem(item as MenuItemContainingCommand);\n }}\n >\n {item.iconPathBefore && (\n \n )}\n {item.label}\n {item.iconPathAfter && (\n \n )}\n \n ) : (\n \n {item.label}\n \n {getMenubarContent(\n groups,\n items,\n getSubMenuGroupKeyForMenuItemId(groups, item.id),\n onSelectMenuItem,\n )}\n \n \n )}\n \n {item.tooltip && {item.tooltip}}\n \n );\n });\n\n const itemsWithSeparator = [...groupItems];\n if (groupItems.length > 0 && index < sortedGroupsForColumn.length - 1) {\n itemsWithSeparator.push();\n }\n\n return itemsWithSeparator;\n });\n};\n\ntype PlatformMenubarProps = {\n /** Menu data that is used to populate the Menubar component. */\n menuData: Localized;\n\n /** The handler to use for menu commands. */\n onSelectMenuItem: SelectMenuItemHandler;\n\n /**\n * Optional callback function that is executed whenever a menu on the Menubar is opened or closed.\n * Helpful for handling updates to the menu, as changing menu data when the menu is opened is not\n * desirable.\n */\n onOpenChange?: (isOpen: boolean) => void;\n\n /** Style variant for the app menubar component. */\n variant?: 'default' | 'muted';\n};\n\n/** Menubar component tailored to work with Platform.Bible menu data */\nexport function PlatformMenubar({\n menuData,\n onSelectMenuItem,\n onOpenChange,\n variant,\n}: PlatformMenubarProps) {\n // These refs will always be defined\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const menubarRef = useRef(undefined!);\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const projectMenuRef = useRef(undefined!);\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const windowMenuRef = useRef(undefined!);\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const layoutMenuRef = useRef(undefined!);\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const helpMenuRef = useRef(undefined!);\n\n const getRefForColumn = (columnKey: string) => {\n switch (columnKey) {\n case 'platform.app':\n return projectMenuRef;\n case 'platform.window':\n return windowMenuRef;\n case 'platform.layout':\n return layoutMenuRef;\n case 'platform.help':\n return helpMenuRef;\n default:\n return undefined;\n }\n };\n\n // This is a quick and dirty way to implement some shortcuts by simulating key presses\n useHotkeys(['alt', 'alt+p', 'alt+l', 'alt+n', 'alt+h'], (event, handler) => {\n event.preventDefault();\n\n const escKey: KeyboardEventInit = { key: 'Escape', code: 'Escape', keyCode: 27, bubbles: true };\n const spaceKey: KeyboardEventInit = { key: ' ', code: 'Space', keyCode: 32, bubbles: true };\n\n switch (handler.hotkey) {\n case 'alt':\n simulateKeyPress(projectMenuRef, [escKey]);\n break;\n case 'alt+p':\n projectMenuRef.current?.focus();\n simulateKeyPress(projectMenuRef, [escKey, spaceKey]);\n break;\n case 'alt+l':\n windowMenuRef.current?.focus();\n simulateKeyPress(windowMenuRef, [escKey, spaceKey]);\n break;\n case 'alt+n':\n layoutMenuRef.current?.focus();\n simulateKeyPress(layoutMenuRef, [escKey, spaceKey]);\n break;\n case 'alt+h':\n helpMenuRef.current?.focus();\n simulateKeyPress(helpMenuRef, [escKey, spaceKey]);\n break;\n default:\n break;\n }\n });\n\n useEffect(() => {\n if (!onOpenChange || !menubarRef.current) return;\n\n const observer = new MutationObserver((mutations) => {\n mutations.forEach((mutation) => {\n if (mutation.attributeName === 'data-state' && mutation.target instanceof HTMLElement) {\n const state = mutation.target.getAttribute('data-state');\n\n if (state === 'open') {\n onOpenChange(true);\n } else {\n onOpenChange(false);\n }\n }\n });\n });\n\n const menubarElement = menubarRef.current;\n const dataStateAttributes = menubarElement.querySelectorAll('[data-state]');\n\n dataStateAttributes.forEach((element) => {\n observer.observe(element, { attributes: true });\n });\n\n return () => observer.disconnect();\n }, [onOpenChange]);\n\n if (!menuData) return undefined;\n\n return (\n \n {Object.entries(menuData.columns)\n .filter(([, column]) => typeof column === 'object')\n .sort(([, a], [, b]) => {\n if (typeof a === 'boolean' || typeof b === 'boolean') return 0;\n return a.order - b.order;\n })\n .map(([columnKey, column]) => (\n \n \n {typeof column === 'object' && 'label' in column && column.label}\n \n \n \n {getMenubarContent(menuData.groups, menuData.items, columnKey, onSelectMenuItem)}\n \n \n \n ))}\n \n );\n}\n\nexport default PlatformMenubar;\n","import {\n SelectMenuItemHandler,\n PlatformMenubar,\n} from '@/components/advanced/menus/platform-menubar.component';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Localized, MultiColumnMenu } from 'platform-bible-utils';\nimport { PropsWithChildren, ReactNode, useRef } from 'react';\n\nexport type ToolbarProps = PropsWithChildren<{\n /** The handler to use for menu commands (and eventually toolbar commands). */\n onSelectMenuItem: SelectMenuItemHandler;\n\n /**\n * Menu data that is used to populate the Menubar component. If empty object, no menus will be\n * shown on the App Menubar\n */\n menuData?: Localized;\n\n /**\n * Optional callback function that is executed whenever a menu on the App Menubar is opened or\n * closed. Helpful for handling updates to the menu, as changing menu data when the menu is opened\n * is not desirable.\n */\n onOpenChange?: (isOpen: boolean) => void;\n\n /** Optional unique identifier */\n id?: string;\n\n /** Additional css classes to help with unique styling of the toolbar */\n className?: string;\n\n /**\n * Whether the toolbar should be used as a draggable area for moving the application. This will\n * add an electron specific style `WebkitAppRegion: 'drag'` to the toolbar in order to make it\n * draggable. See:\n * https://www.electronjs.org/docs/latest/tutorial/custom-title-bar#create-a-custom-title-bar\n */\n shouldUseAsAppDragArea?: boolean;\n\n /** Toolbar children to be put at the start of the toolbar (left side in ltr, right side in rtl) */\n appMenuAreaChildren?: ReactNode;\n\n /** Toolbar children to be put at the end of the toolbar (right side in ltr, left side in rtl) */\n configAreaChildren?: ReactNode;\n\n /** Variant of the menubar */\n menubarVariant?: 'default' | 'muted';\n}>;\n\n/**\n * Get tailwind class for reserved space for the window controls / macos \"traffic lights\". Passing\n * 'darwin' will reserve the necessary space for macos traffic lights at the start, otherwise a\n * different amount of space at the end for the window controls.\n *\n * Apply to the toolbar like: `` or ``\n *\n * @param operatingSystem The os platform: 'darwin' (macos) | anything else\n * @returns The class name to apply to the toolbar if os specific space should be reserved\n */\nexport function getToolbarOSReservedSpaceClassName(\n operatingSystem: string | undefined,\n): string | undefined {\n switch (operatingSystem) {\n case undefined:\n return undefined;\n case 'darwin':\n return 'tw-ps-[85px]';\n default:\n return 'tw-pe-[calc(138px+1rem)]';\n }\n}\n\n/**\n * A customizable toolbar component with a menubar, content area, and configure area.\n *\n * This component is designed to be used in the window title bar of an electron application.\n *\n * @param {ToolbarProps} props - The props for the component.\n */\nexport function Toolbar({\n menuData,\n onOpenChange,\n onSelectMenuItem,\n className,\n id,\n children,\n appMenuAreaChildren,\n configAreaChildren,\n shouldUseAsAppDragArea,\n menubarVariant = 'default',\n}: ToolbarProps) {\n // This ref will always be defined\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const containerRef = useRef(undefined!);\n\n return (\n \n \n {/* App Menu area */}\n
    \n \n {appMenuAreaChildren}\n\n {menuData && (\n \n )}\n
    \n \n\n {/* Content area */}\n \n {children}\n \n\n {/* Configure area */}\n
    \n \n {configAreaChildren}\n
    \n \n \n \n );\n}\n\nexport default Toolbar;\n","import { useState } from 'react';\nimport { LocalizedStringValue, formatReplacementString } from 'platform-bible-utils';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../shadcn-ui/select';\nimport { Label } from '../shadcn-ui/label';\n\n/**\n * Immutable array containing all keys used for localization in this component. If you're using this\n * component in an extension, you can pass it into the useLocalizedStrings hook to easily obtain the\n * localized strings and pass them into the localizedStrings prop of this component\n */\nexport const UI_LANGUAGE_SELECTOR_STRING_KEYS = Object.freeze([\n '%settings_uiLanguageSelector_fallbackLanguages%',\n] as const);\n\nexport type UiLanguageSelectorLocalizedStrings = {\n [localizedUiLanguageSelectorKey in (typeof UI_LANGUAGE_SELECTOR_STRING_KEYS)[number]]?: LocalizedStringValue;\n};\n\n/**\n * Gets the localized value for the provided key\n *\n * @param strings Object containing localized string\n * @param key Key for a localized string\n * @returns The localized value for the provided key, if available. Returns the key if no localized\n * value is available\n */\nconst localizeString = (\n strings: UiLanguageSelectorLocalizedStrings,\n key: keyof UiLanguageSelectorLocalizedStrings,\n) => {\n return strings[key] ?? key;\n};\n\nexport type LanguageInfo = {\n /** The name of the language to be displayed (in its native script) */\n autonym: string;\n /**\n * The name of the language in other languages, so that the language can also be displayed in the\n * current UI language, if known.\n */\n uiNames?: Record;\n /**\n * Other known names of the language (for searching). This can include pejorative names and should\n * never be displayed unless typed by the user.\n */\n otherNames?: string[];\n};\n\nexport type UiLanguageSelectorProps = {\n /** Full set of known languages to display. The keys are valid BCP-47 tags. */\n knownUiLanguages: Record;\n /** IETF BCP-47 language tag of the current primary UI language. `undefined` => 'en' */\n primaryLanguage: string;\n /**\n * Ordered list of fallback language tags to use if the localization key can't be found in the\n * current primary UI language. This list never contains English ('en') because it is the ultimate\n * fallback.\n */\n fallbackLanguages: string[] | undefined;\n /**\n * Handler for when either the primary or the fallback languages change (or both). For this\n * handler, the primary UI language is the first one in the array, followed by the fallback\n * languages in order of decreasing preference.\n */\n onLanguagesChange?: (newUiLanguages: string[]) => void;\n /** Handler for the primary language changes. */\n onPrimaryLanguageChange?: (newPrimaryUiLanguage: string) => void;\n /**\n * Handler for when the fallback languages change. The array contains the fallback languages in\n * order of decreasing preference.\n */\n onFallbackLanguagesChange?: (newFallbackLanguages: string[]) => void;\n /**\n * Map whose keys are localized string keys as contained in UI_LANGUAGE_SELECTOR_STRING_KEYS and\n * whose values are the localized strings (in the current UI language).\n */\n localizedStrings: UiLanguageSelectorLocalizedStrings;\n /** Additional css classes to help with unique styling of the control */\n className?: string;\n /** Optional id for the root element */\n id?: string;\n};\n\n/**\n * A component for selecting the user interface language and managing fallback languages. Allows\n * users to choose a primary UI language and optionally select fallback languages.\n *\n * @param {UiLanguageSelectorProps} props - The props for the component.\n */\nexport function UiLanguageSelector({\n knownUiLanguages,\n primaryLanguage = 'en',\n fallbackLanguages = [],\n onLanguagesChange,\n onPrimaryLanguageChange,\n onFallbackLanguagesChange,\n localizedStrings,\n className,\n id,\n}: UiLanguageSelectorProps) {\n const fallbackLanguagesText = localizeString(\n localizedStrings,\n '%settings_uiLanguageSelector_fallbackLanguages%',\n );\n const [isOpen, setIsOpen] = useState(false);\n\n const handleLanguageChange = (code: string) => {\n if (onPrimaryLanguageChange) onPrimaryLanguageChange(code);\n // REVIEW: Should fallback languages be preserved when primary language changes?\n if (onLanguagesChange)\n onLanguagesChange([code, ...fallbackLanguages.filter((lang) => lang !== code)]);\n if (onFallbackLanguagesChange && fallbackLanguages.find((l) => l === code))\n onFallbackLanguagesChange([...fallbackLanguages.filter((lang) => lang !== code)]);\n setIsOpen(false); // Close the dropdown when a selection is made\n };\n\n /**\n * Gets the display name for the given language. This will typically include the autonym (in the\n * native script), along with the name of the language in the current UI locale if known, with a\n * fallback to the English name (if known).\n *\n * @param {string} lang - The BCP-47 code of the language whose display name is being requested.\n * @param {string} uiLang - The BCP-47 code of the current user-interface language used used to\n * try to look up the name of the language in a form that is likely to be helpful to the user if\n * they do not recognize the autonym.\n * @returns {string} The display name of the language.\n */\n const getLanguageDisplayName = (lang: string, uiLang: string) => {\n const altName =\n uiLang !== lang\n ? (knownUiLanguages[lang]?.uiNames?.[uiLang] ?? knownUiLanguages[lang]?.uiNames?.en)\n : undefined;\n\n return altName\n ? `${knownUiLanguages[lang]?.autonym} (${altName})`\n : knownUiLanguages[lang]?.autonym;\n };\n\n return (\n
    \n {/* Language Selector */}\n setIsOpen(open)}\n >\n \n \n \n \n {Object.keys(knownUiLanguages).map((key) => {\n return (\n \n {getLanguageDisplayName(key, primaryLanguage)}\n \n );\n })}\n \n \n\n {/* Fallback Language Button */}\n {primaryLanguage !== 'en' && (\n
    \n \n
    \n )}\n
    \n );\n}\n\nexport default UiLanguageSelector;\n","import { Label } from '@/components/shadcn-ui/label';\nimport { ReactNode } from 'react';\n\ntype SmartLabelProps = {\n item: string;\n createLabel?: (item: string) => string;\n createComplexLabel?: (item: string) => ReactNode;\n};\n\n/** Create labels with text, react elements (e.g. links), or text + react elements */\nfunction SmartLabel({ item, createLabel, createComplexLabel }: SmartLabelProps): ReactNode {\n if (createLabel) {\n return ;\n }\n if (createComplexLabel) {\n return ;\n }\n return ;\n}\n\nexport default SmartLabel;\n","import { Checkbox } from '@/components/shadcn-ui/checkbox';\nimport { ReactNode } from 'react';\nimport SmartLabel from './smart-label.component';\n\nexport type ChecklistProps = {\n /** Optional string representing the id attribute of the Checklist */\n id?: string;\n /** Optional string representing CSS class name(s) for styling */\n className?: string;\n /** Array of strings representing the checkable items */\n listItems: string[];\n /** Array of strings representing the checked items */\n selectedListItems: string[];\n /**\n * Function that is called when a checkbox item is selected or deselected\n *\n * @param item The string description for this item\n * @param selected True if selected, false if not selected\n */\n handleSelectListItem: (item: string, selected: boolean) => void;\n\n /**\n * Optional function creates a label for a provided checkable item\n *\n * @param item The item for which a label is to be created\n * @returns A string representing the label text for the checkbox associated with that item\n */\n createLabel?: (item: string) => string;\n\n /**\n * Optional function creates a label for a provided checkable item\n *\n * @param item The item for which a label is to be created, including text and any additional\n * elements (e.g. links)\n * @returns A react node representing the label text and any additional elements (e.g. links) for\n * the checkbox associated with that item\n */\n createComplexLabel?: (item: string) => ReactNode;\n};\n\n/** Renders a list of checkboxes. Each checkbox corresponds to an item from the `listItems` array. */\nexport function Checklist({\n id,\n className,\n listItems,\n selectedListItems,\n handleSelectListItem,\n createLabel,\n createComplexLabel,\n}: ChecklistProps) {\n return (\n
    \n {listItems.map((item) => (\n
    \n handleSelectListItem(item, value)}\n />\n \n
    \n ))}\n
    \n );\n}\n\nexport default Checklist;\n","import { cn } from '@/utils/shadcn-ui.util';\nimport { MoreHorizontal } from 'lucide-react';\nimport React, { ReactNode } from 'react';\nimport { Button } from '../shadcn-ui/button';\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '../shadcn-ui/dropdown-menu';\n\n/** Props interface for the ResultsCard base component */\nexport interface ResultsCardProps {\n /** Unique key for the card */\n cardKey: string;\n /** Whether this card is currently selected/focused */\n isSelected: boolean;\n /** Callback function called when the card is clicked */\n onSelect: () => void;\n /** Whether the content of this card are in a denied state */\n isDenied?: boolean;\n /** Whether the card should be hidden */\n isHidden?: boolean;\n /** Additional CSS classes to apply to the card */\n className?: string;\n /** Main content to display on the card */\n children: ReactNode;\n /** Content to show in the dropdown menu when selected */\n dropdownContent?: ReactNode;\n /** Additional content to show below the main content when selected */\n additionalSelectedContent?: ReactNode;\n /** Color to use for the card's accent border */\n accentColor?: string;\n}\n\n/**\n * ResultsCard is a base component for displaying scripture-related results in a card format, even\n * though it is not based on the Card component. It provides common functionality like selection\n * state, dropdown menus, and expandable content.\n */\nexport function ResultsCard({\n cardKey,\n isSelected,\n onSelect,\n isDenied,\n isHidden = false,\n className,\n children,\n dropdownContent,\n additionalSelectedContent,\n accentColor,\n}: ResultsCardProps) {\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n onSelect();\n }\n };\n\n return (\n
  • ,\n);\nSidebarMenuSubItem.displayName = 'SidebarMenuSubItem';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarMenuSubButton = React.forwardRef<\n HTMLAnchorElement,\n React.ComponentProps<'a'> & {\n asChild?: boolean;\n size?: 'sm' | 'md';\n isActive?: boolean;\n }\n>(({ asChild = false, size = 'md', isActive, className, ...props }, ref) => {\n const Comp = asChild ? Slot : 'a';\n\n return (\n span:last-child]:tw-truncate [&>svg]:tw-size-4 [&>svg]:tw-shrink-0 [&>svg]:tw-text-sidebar-accent-foreground',\n 'data-[active=true]:tw-bg-sidebar-accent data-[active=true]:tw-text-sidebar-accent-foreground',\n size === 'sm' && 'tw-text-xs',\n size === 'md' && 'tw-text-sm',\n 'group-data-[collapsible=icon]:tw-hidden',\n className,\n )}\n {...props}\n />\n );\n});\nSidebarMenuSubButton.displayName = 'SidebarMenuSubButton';\n\nexport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupAction,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarInput,\n SidebarInset,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuBadge,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSkeleton,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarProvider,\n SidebarRail,\n SidebarSeparator,\n SidebarTrigger,\n useSidebar,\n};\n","import { ComboBox } from '@/components/basics/combo-box.component';\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupLabel,\n SidebarGroupContent,\n SidebarMenu,\n SidebarMenuItem,\n SidebarMenuButton,\n} from '@/components/shadcn-ui/sidebar';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { ScrollText } from 'lucide-react';\nimport { useCallback } from 'react';\n\nexport type SelectedSettingsSidebarItem = {\n label: string;\n projectId?: string;\n};\n\nexport type ProjectInfo = { projectId: string; projectName: string };\n\nexport type SettingsSidebarProps = {\n /** Optional id for testing */\n id?: string;\n\n /** Extension labels from contribution */\n extensionLabels: Record;\n\n /** Project names and ids */\n projectInfo: ProjectInfo[];\n\n /** Handler for selecting a sidebar item */\n handleSelectSidebarItem: (key: string, projectId?: string) => void;\n\n /** The current selected value in the sidebar */\n selectedSidebarItem: SelectedSettingsSidebarItem;\n\n /** Label for the group of extensions setting groups */\n extensionsSidebarGroupLabel: string;\n\n /** Label for the group of projects settings */\n projectsSidebarGroupLabel: string;\n\n /** Placeholder text for the button */\n buttonPlaceholderText: string;\n\n /** Additional css classes to help with unique styling of the sidebar */\n className?: string;\n};\n\n/**\n * The SettingsSidebar component is a sidebar that displays a list of extension settings and project\n * settings. It can be used to navigate to different settings pages. Must be wrapped in a\n * SidebarProvider component otherwise produces errors.\n *\n * @param props - {@link SettingsSidebarProps} The props for the component.\n */\nexport function SettingsSidebar({\n id,\n extensionLabels,\n projectInfo,\n handleSelectSidebarItem,\n selectedSidebarItem,\n extensionsSidebarGroupLabel,\n projectsSidebarGroupLabel,\n buttonPlaceholderText,\n className,\n}: SettingsSidebarProps) {\n const handleSelectItem = useCallback(\n (item: string, projectId?: string) => {\n handleSelectSidebarItem(item, projectId);\n },\n [handleSelectSidebarItem],\n );\n\n const getProjectNameFromProjectId = useCallback(\n (projectId: string) => {\n const project = projectInfo.find((info) => info.projectId === projectId);\n return project ? project.projectName : projectId;\n },\n [projectInfo],\n );\n\n const getIsActive: (label: string) => boolean = useCallback(\n (label: string) => !selectedSidebarItem.projectId && label === selectedSidebarItem.label,\n [selectedSidebarItem],\n );\n\n return (\n \n \n \n \n {extensionsSidebarGroupLabel}\n \n \n \n {Object.entries(extensionLabels).map(([key, label]) => (\n \n handleSelectItem(key)}\n isActive={getIsActive(key)}\n >\n {label}\n \n \n ))}\n \n \n \n \n {projectsSidebarGroupLabel}\n \n info.projectId)}\n getOptionLabel={getProjectNameFromProjectId}\n buttonPlaceholder={buttonPlaceholderText}\n onChange={(projectId: string) => {\n const selectedProjectName = getProjectNameFromProjectId(projectId);\n handleSelectItem(selectedProjectName, projectId);\n }}\n value={selectedSidebarItem?.projectId ?? undefined}\n icon={}\n />\n \n \n \n \n );\n}\n\nexport default SettingsSidebar;\n","import { Button } from '@/components/shadcn-ui/button';\nimport { Input } from '@/components/shadcn-ui/input';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Search, X } from 'lucide-react';\nimport { forwardRef } from 'react';\n\n/** Props for the SearchBar component. */\nexport type SearchBarProps = {\n /** Search query for the search bar */\n value: string;\n /**\n * Callback fired to handle the search query is updated\n *\n * @param searchQuery\n */\n onSearch: (searchQuery: string) => void;\n\n /** Optional string that appears in the search bar without a search string */\n placeholder?: string;\n\n /** Optional boolean to set the input base to full width */\n isFullWidth?: boolean;\n\n /** Additional css classes to help with unique styling of the search bar */\n className?: string;\n\n /** Optional boolean to disable the search bar */\n isDisabled?: boolean;\n\n /** Optional id for the root element */\n id?: string;\n};\n\n/**\n * A search bar component with a search icon and a clear button when the search query is not empty.\n *\n * @param {SearchBarProps} props - The props for the component.\n * @param {string} props.value - The search query for the search bar\n * @param {(searchQuery: string) => void} props.onSearch - Callback fired to handle the search query\n * is updated\n * @param {string} [props.placeholder] - Optional string that appears in the search bar without a\n * search string\n * @param {boolean} [props.isFullWidth] - Optional boolean to set the input base to full width\n * @param {string} [props.className] - Additional css classes to help with unique styling of the\n * search bar\n * @param {boolean} [props.isDisabled] - Optional boolean to disable the search bar\n * @param {string} [props.id] - Optional id for the root element\n */\nexport const SearchBar = forwardRef(\n ({ value, onSearch, placeholder, isFullWidth, className, isDisabled = false, id }, inputRef) => {\n const dir: Direction = readDirection();\n\n return (\n
    \n \n onSearch(e.target.value)}\n disabled={isDisabled}\n />\n {value && (\n {\n onSearch('');\n }}\n >\n \n Clear\n \n )}\n
    \n );\n },\n);\n\nSearchBar.displayName = 'SearchBar';\n\nexport default SearchBar;\n","import { SidebarInset, SidebarProvider } from '@/components/shadcn-ui/sidebar';\nimport { PropsWithChildren } from 'react';\nimport { SearchBar } from '@/components/basics/search-bar.component';\nimport { SettingsSidebar, SettingsSidebarProps } from './settings-sidebar.component';\n\nexport type SettingsSidebarContentSearchProps = SettingsSidebarProps &\n PropsWithChildren & {\n /** The search query in the search bar */\n searchValue: string;\n\n /** Handler to run when the value of the search bar changes */\n onSearch: (searchQuery: string) => void;\n };\n\n/**\n * A component that wraps a search bar and a settings sidebar, providing a way to search and\n * navigate to different settings pages.\n *\n * @param {SettingsSidebarContentSearchProps} props - The props for the component.\n * @param {string} props.id - The id of the sidebar.\n */\nexport function SettingsSidebarContentSearch({\n id,\n extensionLabels,\n projectInfo,\n children,\n handleSelectSidebarItem,\n selectedSidebarItem,\n searchValue,\n onSearch,\n extensionsSidebarGroupLabel,\n projectsSidebarGroupLabel,\n buttonPlaceholderText,\n}: SettingsSidebarContentSearchProps) {\n return (\n
    \n
    \n \n
    \n \n \n {children}\n \n
    \n );\n}\n\nexport default SettingsSidebarContentSearch;\n","import { Button } from '@/components/shadcn-ui/button';\nimport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@/components/shadcn-ui/select';\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@/components/shadcn-ui/table';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Canon } from '@sillsdev/scripture';\nimport {\n Cell,\n ColumnDef,\n flexRender,\n getCoreRowModel,\n getExpandedRowModel,\n getGroupedRowModel,\n getSortedRowModel,\n GroupingState,\n Row,\n RowSelectionState,\n SortingState,\n useReactTable,\n} from '@tanstack/react-table';\nimport '@/components/advanced/scripture-results-viewer/scripture-results-viewer.component.css';\nimport {\n compareScrRefs,\n formatScrRef,\n ScriptureSelection,\n scrRefToBBBCCCVVV,\n} from 'platform-bible-utils';\nimport { MouseEvent, useEffect, useMemo, useState } from 'react';\nimport { ChevronDown, ChevronLeft, ChevronRight } from 'lucide-react';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\n\n/**\n * Information (e.g., a checking error or some other type of \"transient\" annotation) about something\n * noteworthy at a specific place in an instance of the Scriptures.\n */\nexport type ScriptureItemDetail = ScriptureSelection & {\n /**\n * Text of the error, note, etc. In the future, we might want to support something more than just\n * text so that a JSX element could be provided with a link or some other controls related to the\n * issue being reported.\n */\n detail: string;\n};\n\n/**\n * A uniquely identifiable source of results that can be displayed in the ScriptureResultsViewer.\n * Generally, the source will be a particular Scripture check, but there may be other types of\n * sources.\n */\nexport type ResultsSource = {\n /**\n * Uniquely identifies the source.\n *\n * @type {string}\n */\n id: string;\n\n /**\n * Name (potentially localized) of the source, suitable for display in the UI.\n *\n * @type {string}\n */\n displayName: string;\n};\n\nexport type ScriptureSrcItemDetail = ScriptureItemDetail & {\n /** Source/type of detail. Can be used for grouping. */\n source: ResultsSource;\n};\n\n/**\n * Represents a set of results keyed by Scripture reference. Generally, the source will be a\n * particular Scripture check, but this type also allows for other types of uniquely identifiable\n * sources.\n */\nexport type ResultsSet = {\n /**\n * The backing source associated with this set of results.\n *\n * @type {ResultsSource}\n */\n source: ResultsSource;\n\n /**\n * Array of Scripture item details (messages keyed by Scripture reference).\n *\n * @type {ScriptureItemDetail[]}\n */\n data: ScriptureItemDetail[];\n};\n\nconst scrBookColId = 'scrBook';\nconst scrRefColId = 'scrRef';\nconst typeColId = 'source';\nconst detailsColId = 'details';\n\nconst defaultScrRefColumnName = 'Scripture Reference';\nconst defaultScrBookGroupName = 'Scripture Book';\nconst defaultTypeColumnName = 'Type';\nconst defaultDetailsColumnName = 'Details';\n\nexport type ScriptureResultsViewerColumnInfo = {\n /** Optional header to display for the Reference column. Default value: 'Scripture Reference'. */\n scriptureReferenceColumnName?: string;\n\n /** Optional text to display to refer to the Scripture book group. Default value: 'Scripture Book'. */\n scriptureBookGroupName?: string;\n\n /** Optional header to display for the Type column. Default value: 'Type'. */\n typeColumnName?: string;\n\n /** Optional header to display for the Details column. Default value: 'Details' */\n detailsColumnName?: string;\n};\n\nexport type ScriptureResultsViewerProps = ScriptureResultsViewerColumnInfo & {\n /** Groups of ScriptureItemDetail objects from particular sources (e.g., Scripture checks) */\n sources: ResultsSet[];\n\n /** Flag indicating whether to display column headers. Default is false. */\n showColumnHeaders?: boolean;\n\n /** Flag indicating whether to display source column. Default is false. */\n showSourceColumn?: boolean;\n\n /** Callback function to notify when a row is selected */\n onRowSelected?: (selectedRow: ScriptureSrcItemDetail | undefined) => void;\n\n /** Optional id attribute for the outermost element */\n id?: string;\n};\n\nfunction getColumns(\n colInfo?: ScriptureResultsViewerColumnInfo,\n showSourceColumn?: boolean,\n): ColumnDef[] {\n const showSrcCol = showSourceColumn ?? false;\n return [\n {\n accessorFn: (row) => `${row.start.book} ${row.start.chapterNum}:${row.start.verseNum}`,\n id: scrBookColId,\n header: colInfo?.scriptureReferenceColumnName ?? defaultScrRefColumnName,\n cell: (info) => {\n const row = info.row.original;\n if (info.row.getIsGrouped()) {\n return Canon.bookIdToEnglishName(row.start.book);\n }\n return info.row.groupingColumnId === scrBookColId ? formatScrRef(row.start) : undefined;\n },\n getGroupingValue: (row) => Canon.bookIdToNumber(row.start.book),\n sortingFn: (a, b) => {\n return compareScrRefs(a.original.start, b.original.start);\n },\n enableGrouping: true,\n },\n {\n accessorFn: (row) => formatScrRef(row.start),\n id: scrRefColId,\n header: undefined,\n cell: (info) => {\n const row = info.row.original;\n return info.row.getIsGrouped() ? undefined : formatScrRef(row.start);\n },\n sortingFn: (a, b) => {\n return compareScrRefs(a.original.start, b.original.start);\n },\n enableGrouping: false,\n },\n {\n accessorFn: (row) => row.source.displayName,\n id: typeColId,\n header: showSrcCol ? (colInfo?.typeColumnName ?? defaultTypeColumnName) : undefined,\n cell: (info) => (showSrcCol || info.row.getIsGrouped() ? info.getValue() : undefined),\n getGroupingValue: (row) => row.source.id,\n sortingFn: (a, b) =>\n a.original.source.displayName.localeCompare(b.original.source.displayName),\n enableGrouping: true,\n },\n {\n accessorFn: (row) => row.detail,\n id: detailsColId,\n header: colInfo?.detailsColumnName ?? defaultDetailsColumnName,\n cell: (info) => info.getValue(),\n enableGrouping: false,\n },\n ];\n}\n\nconst toRefOrRange = (scriptureSelection: ScriptureSelection) => {\n if (!('offset' in scriptureSelection.start))\n throw new Error('No offset available in range start');\n if (scriptureSelection.end && !('offset' in scriptureSelection.end))\n throw new Error('No offset available in range end');\n const { offset: offsetStart } = scriptureSelection.start;\n let offsetEnd: number = 0;\n if (scriptureSelection.end) ({ offset: offsetEnd } = scriptureSelection.end);\n if (\n !scriptureSelection.end ||\n compareScrRefs(scriptureSelection.start, scriptureSelection.end) === 0\n )\n return `${scrRefToBBBCCCVVV(scriptureSelection.start)}+${offsetStart}`;\n return `${scrRefToBBBCCCVVV(scriptureSelection.start)}+${offsetStart}-${scrRefToBBBCCCVVV(scriptureSelection.end)}+${offsetEnd}`;\n};\n\nconst getRowKey = (row: ScriptureSrcItemDetail) =>\n `${toRefOrRange({ start: row.start, end: row.end })} ${row.source.displayName} ${row.detail}`;\n\n/**\n * Component to display a combined list of detailed items from one or more sources, where the items\n * are keyed primarily by Scripture reference. This is particularly useful for displaying a list of\n * results from Scripture checks, but more generally could be used to display any \"results\" from any\n * source(s). The component allows for grouping by Scripture book, source, or both. By default, it\n * displays somewhat \"tree-like\" which allows it to be more horizontally compact and intuitive. But\n * it also has the option of displaying as a traditional table with column headings (with or without\n * the source column showing).\n */\nexport function ScriptureResultsViewer({\n sources,\n showColumnHeaders = false,\n showSourceColumn = false,\n scriptureReferenceColumnName,\n scriptureBookGroupName,\n typeColumnName,\n detailsColumnName,\n onRowSelected,\n id,\n}: ScriptureResultsViewerProps) {\n const [grouping, setGrouping] = useState([]);\n const [sorting, setSorting] = useState([{ id: scrBookColId, desc: false }]);\n const [rowSelection, setRowSelection] = useState({});\n\n const scriptureResults = useMemo(\n () =>\n sources.flatMap((source) => {\n return source.data.map((item) => ({\n ...item,\n source: source.source,\n }));\n }),\n [sources],\n );\n\n const columns = useMemo(\n () =>\n getColumns(\n {\n scriptureReferenceColumnName,\n typeColumnName,\n detailsColumnName,\n },\n showSourceColumn,\n ),\n [scriptureReferenceColumnName, typeColumnName, detailsColumnName, showSourceColumn],\n );\n\n useEffect(() => {\n // Ensure sorting is applied correctly when grouped by type\n if (grouping.includes(typeColId)) {\n setSorting([\n { id: typeColId, desc: false },\n { id: scrBookColId, desc: false },\n ]);\n } else {\n setSorting([{ id: scrBookColId, desc: false }]);\n }\n }, [grouping]);\n\n const table = useReactTable({\n data: scriptureResults,\n columns,\n state: {\n grouping,\n sorting,\n rowSelection,\n },\n onGroupingChange: setGrouping,\n onSortingChange: setSorting,\n onRowSelectionChange: setRowSelection,\n getExpandedRowModel: getExpandedRowModel(),\n getGroupedRowModel: getGroupedRowModel(),\n getCoreRowModel: getCoreRowModel(),\n getSortedRowModel: getSortedRowModel(),\n getRowId: getRowKey,\n autoResetExpanded: false,\n enableMultiRowSelection: false,\n enableSubRowSelection: false,\n });\n\n useEffect(() => {\n if (onRowSelected) {\n const selectedRows = table.getSelectedRowModel().rowsById;\n const keys = Object.keys(selectedRows);\n if (keys.length === 1) {\n const selectedRow = scriptureResults.find((row) => getRowKey(row) === keys[0]) || undefined;\n if (selectedRow) onRowSelected(selectedRow);\n }\n }\n }, [rowSelection, scriptureResults, onRowSelected, table]);\n\n // Define possible grouping options\n const scrBookGroupName = scriptureBookGroupName ?? defaultScrBookGroupName;\n const typeGroupName = typeColumnName ?? defaultTypeColumnName;\n\n const groupingOptions = [\n { label: 'No Grouping', value: [] },\n { label: `Group by ${scrBookGroupName}`, value: [scrBookColId] },\n { label: `Group by ${typeGroupName}`, value: [typeColId] },\n {\n label: `Group by ${scrBookGroupName} and ${typeGroupName}`,\n value: [scrBookColId, typeColId],\n },\n {\n label: `Group by ${typeGroupName} and ${scrBookGroupName}`,\n value: [typeColId, scrBookColId],\n },\n ];\n\n const handleSelectChange = (selectedGrouping: string) => {\n setGrouping(JSON.parse(selectedGrouping));\n };\n\n const handleRowClick = (row: Row, event: MouseEvent) => {\n if (!row.getIsGrouped() && !row.getIsSelected()) {\n row.getToggleSelectedHandler()(event);\n }\n };\n\n const getEvenOrOddBandingStyle = (row: Row, index: number) => {\n if (row.getIsGrouped()) return '';\n // UX has now said they don't think they want banding. I'm leaving in the code to\n // set even and odd styles, but there's nothing in the CSS to style them differently.\n // The \"even\" style used to also have tw-bg-neutral-300 (along with even) to create\n // a visual banding effect. That could be added back in if UX changes the decision.\n return cn('banded-row', index % 2 === 0 ? 'even' : 'odd');\n };\n\n const getIndent = (\n groupingState: GroupingState,\n row: Row,\n cell: Cell,\n ) => {\n if (groupingState?.length === 0 || row.depth < cell.column.getGroupedIndex()) return undefined;\n if (row.getIsGrouped()) {\n switch (row.depth) {\n case 1:\n return 'tw-ps-4';\n default:\n return undefined;\n }\n }\n switch (row.depth) {\n case 1:\n return 'tw-ps-8';\n case 2:\n return 'tw-ps-12';\n default:\n return undefined;\n }\n };\n\n return (\n
    \n {!showColumnHeaders && (\n {\n handleSelectChange(value);\n }}\n >\n \n \n \n \n \n {groupingOptions.map((option) => (\n \n {option.label}\n \n ))}\n \n \n \n )}\n \n {showColumnHeaders && (\n \n {table.getHeaderGroups().map((headerGroup) => (\n \n {headerGroup.headers\n .filter((h) => h.column.columnDef.header)\n .map((header) => (\n /* For sticky column headers to work, we probably need to change the default definition of the shadcn Table component. See https://github.com/shadcn-ui/ui/issues/1151 */\n \n {header.isPlaceholder ? undefined : (\n
    \n {header.column.getCanGroup() ? (\n \n {header.column.getIsGrouped() ? `๐Ÿ›‘` : `๐Ÿ‘Š `}\n \n ) : undefined}{' '}\n {flexRender(header.column.columnDef.header, header.getContext())}\n
    \n )}\n
    \n ))}\n
    \n ))}\n
    \n )}\n \n {table.getRowModel().rows.map((row, rowIndex) => {\n const dir: Direction = readDirection();\n return (\n handleRowClick(row, event)}\n >\n {row.getVisibleCells().map((cell) => {\n if (\n cell.getIsPlaceholder() ||\n (cell.column.columnDef.enableGrouping &&\n !cell.getIsGrouped() &&\n (cell.column.columnDef.id !== typeColId || !showSourceColumn))\n )\n return undefined;\n return (\n \n {(() => {\n if (cell.getIsGrouped()) {\n return (\n \n {row.getIsExpanded() && }\n {!row.getIsExpanded() &&\n (dir === 'ltr' ? : )}{' '}\n {flexRender(cell.column.columnDef.cell, cell.getContext())} (\n {row.subRows.length})\n \n );\n }\n\n // if (cell.getIsAggregated()) {\n // flexRender(\n // cell.column.columnDef.aggregatedCell ?? cell.column.columnDef.cell,\n // cell.getContext(),\n // );\n // }\n\n return flexRender(cell.column.columnDef.cell, cell.getContext());\n })()}\n \n );\n })}\n \n );\n })}\n \n
    \n
    \n );\n}\n\nexport default ScriptureResultsViewer;\n","import { getSectionForBook, Section } from 'platform-bible-utils';\n\n/**\n * Filters an array of book IDs to only include books from a specific section\n *\n * @param bookIds Array of book IDs to filter\n * @param section The section to filter by\n * @returns Array of book IDs that belong to the specified section\n */\nexport const getBooksForSection = (bookIds: string[], section: Section) => {\n return bookIds.filter((bookId) => {\n try {\n return getSectionForBook(bookId) === section;\n } catch {\n return false;\n }\n });\n};\n\n/**\n * Checks if all books in a given section are included in the selectedBookIds array\n *\n * @param bookIds Array of all available book IDs\n * @param section The section to check\n * @param selectedBookIds Array of currently selected book IDs\n * @returns True if all books from the specified section are selected, false otherwise\n */\nexport const isSectionFullySelected = (\n bookIds: string[],\n section: Section,\n selectedBookIds: string[],\n) => getBooksForSection(bookIds, section).every((bookId) => selectedBookIds.includes(bookId));\n","import { Button } from '@/components/shadcn-ui/button';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { LanguageStrings, Section } from 'platform-bible-utils';\nimport { getSectionShortName } from '@/components/shared/book.utils';\nimport { getBooksForSection, isSectionFullySelected } from './scope-selector.utils';\n\n/**\n * A button component that represents a scripture section (testament) in the book selector. The\n * button shows a different state when all books in its section are selected and becomes disabled\n * when no books are available in its section.\n */\nfunction SectionButton({\n section,\n availableBookIds,\n selectedBookIds,\n onToggle,\n localizedStrings,\n}: {\n section: Section;\n availableBookIds: string[];\n selectedBookIds: string[];\n onToggle: (section: Section) => void;\n localizedStrings: LanguageStrings;\n}) {\n const isDisabled = getBooksForSection(availableBookIds, section).length === 0;\n\n const sectionOtShortText = localizedStrings['%scripture_section_ot_short%'];\n const sectionNtShortText = localizedStrings['%scripture_section_nt_short%'];\n const sectionDcShortText = localizedStrings['%scripture_section_dc_short%'];\n const sectionExtraShortText = localizedStrings['%scripture_section_extra_short%'];\n\n return (\n onToggle(section)}\n className={cn(\n isSectionFullySelected(availableBookIds, section, selectedBookIds) &&\n !isDisabled &&\n 'tw-bg-primary tw-text-primary-foreground hover:tw-bg-primary/70 hover:tw-text-primary-foreground',\n )}\n disabled={isDisabled}\n >\n {getSectionShortName(\n section,\n sectionOtShortText,\n sectionNtShortText,\n sectionDcShortText,\n sectionExtraShortText,\n )}\n \n );\n}\n\nexport default SectionButton;\n","import { BookItem } from '@/components/shared/book-item.component';\nimport { Badge } from '@/components/shadcn-ui/badge';\nimport { Button } from '@/components/shadcn-ui/button';\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandList,\n CommandSeparator,\n} from '@/components/shadcn-ui/command';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/shadcn-ui/popover';\nimport { Canon } from '@sillsdev/scripture';\nimport { ChevronsUpDown } from 'lucide-react';\nimport { getSectionForBook, LanguageStrings, Section } from 'platform-bible-utils';\nimport {\n getSectionLongName,\n getLocalizedBookName,\n doesBookMatchQuery,\n} from '@/components/shared/book.utils';\nimport { Fragment, MouseEvent, useCallback, useMemo, useRef, useState } from 'react';\nimport { generateCommandValue } from '@/components/shared/book-item.utils';\nimport { getBooksForSection, isSectionFullySelected } from './scope-selector.utils';\nimport SectionButton from './section-button.component';\n\n/** Maximum number of badges to show before collapsing into a \"+X more\" badge */\nconst VISIBLE_BADGES_COUNT = 5;\n/** Maximum number of badges that can be shown without triggering the collapse */\nconst MAX_VISIBLE_BADGES = 6;\n\ntype BookSelectorProps = {\n /**\n * Information about available books, formatted as a 123 character long string as defined in a\n * projects BooksPresent setting\n */\n availableBookInfo: string;\n /** Array of currently selected book IDs */\n selectedBookIds: string[];\n /** Callback function that is executed when the book selection changes */\n onChangeSelectedBookIds: (books: string[]) => void;\n /** Object containing the localized strings for the component */\n localizedStrings: LanguageStrings;\n /**\n * Optional map of localized book IDs/short names and full names. Key is the (English) book ID,\n * value contains localized versions of the ID and full book name\n */\n localizedBookNames?: Map;\n};\n\n/**\n * A component for selecting multiple books from the Bible canon. It provides:\n *\n * - Quick selection buttons for major sections (OT, NT, DC, Extra)\n * - A searchable dropdown with all available books\n * - Support for shift-click range selection\n * - Visual feedback with badges showing selected books\n */\nexport function BookSelector({\n availableBookInfo,\n selectedBookIds,\n onChangeSelectedBookIds,\n localizedStrings,\n localizedBookNames,\n}: BookSelectorProps) {\n const booksSelectedText = localizedStrings['%webView_book_selector_books_selected%'];\n const selectBooksText = localizedStrings['%webView_book_selector_select_books%'];\n const searchBooksText = localizedStrings['%webView_book_selector_search_books%'];\n const selectAllText = localizedStrings['%webView_book_selector_select_all%'];\n const clearAllText = localizedStrings['%webView_book_selector_clear_all%'];\n const noBookFoundText = localizedStrings['%webView_book_selector_no_book_found%'];\n const moreText = localizedStrings['%webView_book_selector_more%'];\n\n const { otLong, ntLong, dcLong, extraLong } = {\n otLong: localizedStrings?.['%scripture_section_ot_long%'],\n ntLong: localizedStrings?.['%scripture_section_nt_long%'],\n dcLong: localizedStrings?.['%scripture_section_dc_long%'],\n extraLong: localizedStrings?.['%scripture_section_extra_long%'],\n };\n\n const [isBooksSelectorOpen, setIsBooksSelectorOpen] = useState(false);\n const [inputValue, setInputValue] = useState('');\n const lastSelectedBookRef = useRef(undefined);\n const lastKeyEventShiftKey = useRef(false);\n\n if (availableBookInfo.length !== Canon.allBookIds.length) {\n throw new Error('availableBookInfo length must match Canon.allBookIds length');\n }\n\n const availableBooksIds = useMemo(() => {\n return Canon.allBookIds.filter(\n (bookId, index) =>\n availableBookInfo[index] === '1' && !Canon.isObsolete(Canon.bookIdToNumber(bookId)),\n );\n }, [availableBookInfo]);\n\n const filteredBooksBySection = useMemo(() => {\n if (!inputValue.trim()) {\n const allBooks: Record = {\n [Section.OT]: [],\n [Section.NT]: [],\n [Section.DC]: [],\n [Section.Extra]: [],\n };\n\n availableBooksIds.forEach((bookId) => {\n const section = getSectionForBook(bookId);\n allBooks[section].push(bookId);\n });\n\n return allBooks;\n }\n\n const filteredBooks = availableBooksIds.filter((bookId) =>\n doesBookMatchQuery(bookId, inputValue, localizedBookNames),\n );\n\n const matchingBooks: Record = {\n [Section.OT]: [],\n [Section.NT]: [],\n [Section.DC]: [],\n [Section.Extra]: [],\n };\n\n filteredBooks.forEach((bookId) => {\n const section = getSectionForBook(bookId);\n matchingBooks[section].push(bookId);\n });\n\n return matchingBooks;\n }, [availableBooksIds, inputValue, localizedBookNames]);\n\n const toggleBook = useCallback(\n (bookId: string, shiftKey = false) => {\n if (!shiftKey || !lastSelectedBookRef.current) {\n onChangeSelectedBookIds(\n selectedBookIds.includes(bookId)\n ? selectedBookIds.filter((id) => id !== bookId)\n : [...selectedBookIds, bookId],\n );\n lastSelectedBookRef.current = bookId;\n return;\n }\n\n const lastIndex = availableBooksIds.findIndex((id) => id === lastSelectedBookRef.current);\n const currentIndex = availableBooksIds.findIndex((id) => id === bookId);\n\n if (lastIndex === -1 || currentIndex === -1) return;\n\n const [startIndex, endIndex] = [\n Math.min(lastIndex, currentIndex),\n Math.max(lastIndex, currentIndex),\n ];\n const booksInRange = availableBooksIds.slice(startIndex, endIndex + 1).map((id) => id);\n\n onChangeSelectedBookIds(\n selectedBookIds.includes(bookId)\n ? selectedBookIds.filter((shortname) => !booksInRange.includes(shortname))\n : [...new Set([...selectedBookIds, ...booksInRange])],\n );\n },\n [selectedBookIds, onChangeSelectedBookIds, availableBooksIds],\n );\n\n const handleKeyboardSelect = (bookId: string) => {\n toggleBook(bookId, lastKeyEventShiftKey.current);\n lastKeyEventShiftKey.current = false;\n };\n\n const handleMouseDown = (event: MouseEvent, bookId: string) => {\n event.preventDefault();\n toggleBook(bookId, event.shiftKey);\n };\n\n const toggleSection = useCallback(\n (section: Section) => {\n const sectionBooks = getBooksForSection(availableBooksIds, section).map((bookId) => bookId);\n onChangeSelectedBookIds(\n isSectionFullySelected(availableBooksIds, section, selectedBookIds)\n ? selectedBookIds.filter((shortname) => !sectionBooks.includes(shortname))\n : [...new Set([...selectedBookIds, ...sectionBooks])],\n );\n },\n [selectedBookIds, onChangeSelectedBookIds, availableBooksIds],\n );\n\n const handleSelectAll = () => {\n onChangeSelectedBookIds(availableBooksIds.map((bookId) => bookId));\n };\n\n const handleClearAll = () => {\n onChangeSelectedBookIds([]);\n };\n\n return (\n
    \n
    \n {Object.values(Section).map((section) => {\n return (\n \n );\n })}\n
    \n\n {\n setIsBooksSelectorOpen(open);\n if (!open) {\n setInputValue(''); // Reset search when closing\n }\n }}\n >\n \n \n {selectedBookIds.length > 0\n ? `${booksSelectedText}: ${selectedBookIds.length}`\n : selectBooksText}\n \n \n \n \n {\n if (e.key === 'Enter') {\n // Store shift state in a ref that will be used by onSelect\n lastKeyEventShiftKey.current = e.shiftKey;\n }\n }}\n >\n \n
    \n \n \n
    \n \n {noBookFoundText}\n {Object.values(Section).map((section, index) => {\n const sectionBooks = filteredBooksBySection[section];\n\n if (sectionBooks.length === 0) return undefined;\n\n return (\n \n \n {sectionBooks.map((bookId) => (\n handleKeyboardSelect(bookId)}\n onMouseDown={(event) => handleMouseDown(event, bookId)}\n section={getSectionForBook(bookId)}\n showCheck\n localizedBookNames={localizedBookNames}\n commandValue={generateCommandValue(bookId, localizedBookNames)}\n className=\"tw-flex tw-items-center\"\n />\n ))}\n \n {index < Object.values(Section).length - 1 && }\n \n );\n })}\n \n \n
    \n \n\n {selectedBookIds.length > 0 && (\n
    \n {selectedBookIds\n .slice(\n 0,\n selectedBookIds.length === MAX_VISIBLE_BADGES\n ? MAX_VISIBLE_BADGES\n : VISIBLE_BADGES_COUNT,\n )\n .map((bookId) => (\n \n {getLocalizedBookName(bookId, localizedBookNames)}\n \n ))}\n {selectedBookIds.length > MAX_VISIBLE_BADGES && (\n {`+${selectedBookIds.length - VISIBLE_BADGES_COUNT} ${moreText}`}\n )}\n
    \n )}\n
    \n );\n}\n","import { BookSelector } from '@/components/advanced/scope-selector/book-selector.component';\nimport { Label } from '@/components/shadcn-ui/label';\nimport { RadioGroup, RadioGroupItem } from '@/components/shadcn-ui/radio-group';\nimport { Scope } from '@/components/utils/scripture.util';\nimport { LocalizedStringValue } from 'platform-bible-utils';\n\n/**\n * Object containing all keys used for localization in this component. If you're using this\n * component in an extension, you can pass it into the useLocalizedStrings hook to easily obtain the\n * localized strings and pass them into the localizedStrings prop of this component\n */\nexport const SCOPE_SELECTOR_STRING_KEYS = Object.freeze([\n '%webView_scope_selector_selected_text%',\n '%webView_scope_selector_current_verse%',\n '%webView_scope_selector_current_chapter%',\n '%webView_scope_selector_current_book%',\n '%webView_scope_selector_choose_books%',\n '%webView_scope_selector_scope%',\n '%webView_scope_selector_select_books%',\n '%webView_book_selector_books_selected%',\n '%webView_book_selector_select_books%',\n '%webView_book_selector_search_books%',\n '%webView_book_selector_select_all%',\n '%webView_book_selector_clear_all%',\n '%webView_book_selector_no_book_found%',\n '%webView_book_selector_more%',\n '%scripture_section_ot_long%',\n '%scripture_section_ot_short%',\n '%scripture_section_nt_long%',\n '%scripture_section_nt_short%',\n '%scripture_section_dc_long%',\n '%scripture_section_dc_short%',\n '%scripture_section_extra_long%',\n '%scripture_section_extra_short%',\n] as const);\n\n/** Type definition for the localized strings used in this component */\nexport type ScopeSelectorLocalizedStrings = {\n [localizedInventoryKey in (typeof SCOPE_SELECTOR_STRING_KEYS)[number]]?: LocalizedStringValue;\n};\n\n/**\n * Gets the localized value for the provided key\n *\n * @param strings Object containing localized string\n * @param key Key for a localized string\n * @returns The localized value for the provided key, if available. Returns the key if no localized\n * value is available\n */\nconst localizeString = (\n strings: ScopeSelectorLocalizedStrings,\n key: keyof ScopeSelectorLocalizedStrings,\n) => {\n return strings[key] ?? key;\n};\n\n/** Props for configuring the ScopeSelector component */\ninterface ScopeSelectorProps {\n /** The current scope selection */\n scope: Scope;\n\n /**\n * Optional array of scopes that should be available in the selector. If not provided, all scopes\n * will be shown as defined in the Scope type\n */\n availableScopes?: Scope[];\n\n /** Callback function that is executed when the user changes the scope selection */\n onScopeChange: (scope: Scope) => void;\n\n /**\n * Information about available books, formatted as a 123 character long string as defined in a\n * projects BooksPresent setting\n */\n availableBookInfo: string;\n\n /** Array of currently selected book IDs */\n selectedBookIds: string[];\n\n /** Callback function that is executed when the user changes the book selection */\n onSelectedBookIdsChange: (books: string[]) => void;\n\n /**\n * Object with all localized strings that the component needs to work well across multiple\n * languages. When using this component with Platform.Bible, you can import\n * `SCOPE_SELECTOR_STRING_KEYS` from this library, pass it in to the Platform's localization hook,\n * and pass the localized keys that are returned by the hook into this prop.\n */\n localizedStrings: ScopeSelectorLocalizedStrings;\n /**\n * Optional map of localized book IDs/short names and full names. Key is the (English) book ID,\n * value contains localized versions of the ID and full book name\n */\n localizedBookNames?: Map;\n /** Optional ID that is applied to the root element of this component */\n id?: string;\n}\n\n/**\n * A component that allows users to select the scope of their search or operation. Available scopes\n * are defined in the Scope type. When 'selectedBooks' is chosen as the scope, a BookSelector\n * component is displayed to allow users to choose specific books.\n */\nexport function ScopeSelector({\n scope,\n availableScopes,\n onScopeChange,\n availableBookInfo,\n selectedBookIds,\n onSelectedBookIdsChange,\n localizedStrings,\n localizedBookNames,\n id,\n}: ScopeSelectorProps) {\n const selectedTextText = localizeString(\n localizedStrings,\n '%webView_scope_selector_selected_text%',\n );\n const currentVerseText = localizeString(\n localizedStrings,\n '%webView_scope_selector_current_verse%',\n );\n const currentChapterText = localizeString(\n localizedStrings,\n '%webView_scope_selector_current_chapter%',\n );\n const currentBookText = localizeString(localizedStrings, '%webView_scope_selector_current_book%');\n const chooseBooksText = localizeString(localizedStrings, '%webView_scope_selector_choose_books%');\n const scopeText = localizeString(localizedStrings, '%webView_scope_selector_scope%');\n const selectBooksText = localizeString(localizedStrings, '%webView_scope_selector_select_books%');\n\n const SCOPE_OPTIONS: Array<{ value: Scope; label: string; id: string }> = [\n { value: 'selectedText', label: selectedTextText, id: 'scope-selected-text' },\n { value: 'verse', label: currentVerseText, id: 'scope-verse' },\n { value: 'chapter', label: currentChapterText, id: 'scope-chapter' },\n { value: 'book', label: currentBookText, id: 'scope-book' },\n { value: 'selectedBooks', label: chooseBooksText, id: 'scope-selected' },\n ];\n\n const displayedScopes = availableScopes\n ? SCOPE_OPTIONS.filter((option) => availableScopes.includes(option.value))\n : SCOPE_OPTIONS;\n\n return (\n
    \n
    \n \n \n {displayedScopes.map(({ value, label, id: scopeId }) => (\n
    \n \n \n
    \n ))}\n \n
    \n\n {scope === 'selectedBooks' && (\n
    \n \n \n
    \n )}\n
    \n );\n}\n\nexport default ScopeSelector;\n","import {\n getLocalizeKeyForScrollGroupId,\n LanguageStrings,\n ScrollGroupId,\n} from 'platform-bible-utils';\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@/components/shadcn-ui/select';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\nimport { cn } from '@/utils/shadcn-ui.util';\n\nconst DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS = {\n [getLocalizeKeyForScrollGroupId('undefined')]: 'ร˜',\n [getLocalizeKeyForScrollGroupId(0)]: 'A',\n [getLocalizeKeyForScrollGroupId(1)]: 'B',\n [getLocalizeKeyForScrollGroupId(2)]: 'C',\n [getLocalizeKeyForScrollGroupId(3)]: 'D',\n [getLocalizeKeyForScrollGroupId(4)]: 'E',\n [getLocalizeKeyForScrollGroupId(5)]: 'F',\n [getLocalizeKeyForScrollGroupId(6)]: 'G',\n [getLocalizeKeyForScrollGroupId(7)]: 'H',\n [getLocalizeKeyForScrollGroupId(8)]: 'I',\n [getLocalizeKeyForScrollGroupId(9)]: 'J',\n [getLocalizeKeyForScrollGroupId(10)]: 'K',\n [getLocalizeKeyForScrollGroupId(11)]: 'L',\n [getLocalizeKeyForScrollGroupId(12)]: 'M',\n [getLocalizeKeyForScrollGroupId(13)]: 'N',\n [getLocalizeKeyForScrollGroupId(14)]: 'O',\n [getLocalizeKeyForScrollGroupId(15)]: 'P',\n [getLocalizeKeyForScrollGroupId(16)]: 'Q',\n [getLocalizeKeyForScrollGroupId(17)]: 'R',\n [getLocalizeKeyForScrollGroupId(18)]: 'S',\n [getLocalizeKeyForScrollGroupId(19)]: 'T',\n [getLocalizeKeyForScrollGroupId(20)]: 'U',\n [getLocalizeKeyForScrollGroupId(21)]: 'V',\n [getLocalizeKeyForScrollGroupId(22)]: 'W',\n [getLocalizeKeyForScrollGroupId(23)]: 'X',\n [getLocalizeKeyForScrollGroupId(24)]: 'Y',\n [getLocalizeKeyForScrollGroupId(25)]: 'Z',\n};\n\nexport type ScrollGroupSelectorProps = {\n /**\n * List of scroll group ids to show to the user. Either a `ScrollGroupId` or `undefined` for no\n * scroll group\n */\n availableScrollGroupIds: (ScrollGroupId | undefined)[];\n /** Currently selected scroll group id. `undefined` for no scroll group */\n scrollGroupId: ScrollGroupId | undefined;\n /** Callback function run when the user tries to change the scroll group id */\n onChangeScrollGroupId: (newScrollGroupId: ScrollGroupId | undefined) => void;\n /**\n * Localized strings to use for displaying scroll group ids. Must be an object whose keys are\n * `getLocalizeKeyForScrollGroupId(scrollGroupId)` for all scroll group ids (and `undefined` if\n * included) in {@link ScrollGroupSelectorProps.availableScrollGroupIds} and whose values are the\n * localized strings to use for those scroll group ids.\n *\n * Defaults to English localizations of English alphabet for scroll groups 0-25 (e.g. 0 is A) and\n * ร˜ for `undefined`. Will fill in any that are not provided with these English localizations.\n * Also, if any values match the keys, the English localization will be used. This is useful in\n * case you want to pass in a temporary version of the localized strings while your localized\n * strings load.\n *\n * @example\n *\n * ```typescript\n * const myScrollGroupIdLocalizedStrings = {\n * [getLocalizeKeyForScrollGroupId('undefined')]: 'ร˜',\n * [getLocalizeKeyForScrollGroupId(0)]: 'A',\n * [getLocalizeKeyForScrollGroupId(1)]: 'B',\n * [getLocalizeKeyForScrollGroupId(2)]: 'C',\n * [getLocalizeKeyForScrollGroupId(3)]: 'D',\n * [getLocalizeKeyForScrollGroupId(4)]: 'E',\n * };\n * ```\n *\n * @example\n *\n * ```tsx\n * const availableScrollGroupIds = [undefined, 0, 1, 2, 3, 4];\n *\n * const localizeKeys = getLocalizeKeysForScrollGroupIds();\n *\n * const [localizedStrings] = useLocalizedStrings(localizeKeys);\n *\n * ...\n *\n * \n * ```\n */\n localizedStrings?: LanguageStrings;\n\n /** Size of the scroll group dropdown button. Defaults to 'sm' */\n size?: 'default' | 'sm' | 'lg' | 'icon';\n\n /** Additional css classes to help with unique styling */\n className?: string;\n\n /** Optional id for the select element */\n id?: string;\n};\n\n/** Selector component for choosing a scroll group */\nexport function ScrollGroupSelector({\n availableScrollGroupIds,\n scrollGroupId,\n onChangeScrollGroupId,\n localizedStrings = {},\n size = 'sm',\n className,\n id,\n}: ScrollGroupSelectorProps) {\n const localizedStringsDefaulted = {\n ...DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS,\n ...Object.fromEntries(\n Object.entries(localizedStrings).map(\n ([localizedStringKey, localizedStringValue]: [string, string]) => [\n localizedStringKey,\n localizedStringKey === localizedStringValue &&\n localizedStringKey in DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS\n ? DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS[localizedStringKey]\n : localizedStringValue,\n ],\n ),\n ),\n };\n\n const dir: Direction = readDirection();\n\n return (\n \n onChangeScrollGroupId(\n newScrollGroupString === 'undefined' ? undefined : parseInt(newScrollGroupString, 10),\n )\n }\n >\n \n \n \n \n {availableScrollGroupIds.map((scrollGroupOptionId) => (\n \n {localizedStringsDefaulted[getLocalizeKeyForScrollGroupId(scrollGroupOptionId)]}\n \n ))}\n \n \n );\n}\n\nexport default ScrollGroupSelector;\n","import { PropsWithChildren } from 'react';\nimport { Separator } from '@/components/shadcn-ui/separator';\n\n/** Props for the SettingsList component, currently just children */\ntype SettingsListProps = PropsWithChildren;\n\n/**\n * SettingsList component is a wrapper for list items. Rendered with a formatted div\n *\n * @deprecated Jul 18 2025. This component is no longer supported or tested. Use of this component\n * is discouraged and it may be removed in the future.\n * @param children To populate the list with\n * @returns Formatted div encompassing the children\n */\nexport function SettingsList({ children }: SettingsListProps) {\n return
    {children}
    ;\n}\n\n/** Props for SettingsListItem component */\ntype SettingsListItemProps = PropsWithChildren & {\n /** Primary text of the list item */\n primary: string;\n\n /** Optional text of the list item */\n secondary?: string | undefined;\n\n /** Optional boolean to display a message if the children aren't loaded yet. Defaults to false */\n isLoading?: boolean;\n\n /** Optional message to display if isLoading */\n loadingMessage?: string;\n};\n\n/**\n * SettingsListItem component is a common list item. Rendered with a formatted div\n *\n * @deprecated Jul 18 2025. This component is no longer supported or tested. Use of this component\n * is discouraged and it may be removed in the future.\n * @param SettingsListItemProps\n * @returns Formatted div encompassing the list item content\n */\nexport function SettingsListItem({\n primary,\n secondary,\n children,\n isLoading = false,\n loadingMessage,\n}: SettingsListItemProps) {\n return (\n
    \n
    \n

    {primary}

    \n

    \n {secondary}\n

    \n
    \n\n {isLoading ? (\n

    {loadingMessage}

    \n ) : (\n
    {children}
    \n )}\n
    \n );\n}\n\n/** Props for SettingsListHeader component */\ntype SettingsListHeaderProps = {\n /** The primary text of the list header */\n primary: string;\n\n /** Optional secondary text of the list header */\n secondary?: string | undefined;\n\n /** Optional boolean to include a separator underneath the secondary text. Defaults to false */\n includeSeparator?: boolean;\n};\n\n/**\n * SettingsListHeader component displays text above the list\n *\n * @deprecated Jul 18 2025. This component is no longer supported or tested. Use of this component\n * is discouraged and it may be removed in the future.\n * @param SettingsListHeaderProps\n * @returns Formatted div with list header content\n */\nexport function SettingsListHeader({\n primary,\n secondary,\n includeSeparator = false,\n}: SettingsListHeaderProps) {\n return (\n
    \n
    \n

    {primary}

    \n

    {secondary}

    \n
    \n {includeSeparator ? : ''}\n
    \n );\n}\n","import { GroupsInMultiColumnMenu, Localized } from 'platform-bible-utils';\n\n/**\n * Function that looks up the key of a sub-menu group using the value of it's `menuItem` property.\n *\n * @example\n *\n * ```ts\n * const groups = {\n * 'platform.subMenu': { menuItem: 'platform.subMenuId', order: 1 },\n * 'platform.subSubMenu': { menuItem: 'platform.subSubMenuId', order: 2 },\n * };\n * const id = 'platform.subMenuId';\n * const groupKey = getSubMenuGroupKeyForMenuItemId(groups, id);\n * console.log(groupKey); // Output: 'platform.subMenu'\n * ```\n *\n * @param groups The JSON Object containing the group definitions\n * @param id The value of the `menuItem` property of the group to look up\n * @returns The key of the group that has the `menuItem` property with the value of `id` or\n * `undefined` if no such group exists.\n */\nexport function getSubMenuGroupKeyForMenuItemId(\n groups: Localized,\n id: string,\n): string | undefined {\n return Object.entries(groups).find(\n ([, value]) => 'menuItem' in value && value.menuItem === id,\n )?.[0];\n}\n","import { cn } from '@/utils/shadcn-ui.util';\n\ntype MenuItemIconProps = {\n /** The icon to display */\n icon: string;\n /** The label of the menu item */\n menuLabel: string;\n /** Whether the icon is leading or trailing */\n leading?: boolean;\n};\n\nfunction MenuItemIcon({ icon, menuLabel, leading }: MenuItemIconProps) {\n return icon ? (\n \n ) : undefined;\n}\n\nexport default MenuItemIcon;\n","import {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuPortal,\n DropdownMenuSeparator,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n} from '@/components/shadcn-ui/dropdown-menu';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@/components/shadcn-ui/tooltip';\nimport { MenuIcon } from 'lucide-react';\nimport {\n GroupsInMultiColumnMenu,\n Localized,\n MenuItemContainingCommand,\n MenuItemContainingSubmenu,\n MultiColumnMenu,\n} from 'platform-bible-utils';\nimport { Fragment, ReactNode } from 'react';\nimport { Button } from '@/components/shadcn-ui/button';\nimport { getSubMenuGroupKeyForMenuItemId } from './menu.util';\nimport { SelectMenuItemHandler } from './platform-menubar.component';\nimport MenuItemIcon from './menu-icon.component';\n\nconst getGroupContent = (\n groups: Localized,\n items: Localized<(MenuItemContainingCommand | MenuItemContainingSubmenu)[]>,\n columnOrSubMenuKey: string | undefined,\n onSelectMenuItem: SelectMenuItemHandler,\n) => {\n if (!columnOrSubMenuKey) return undefined;\n\n const sortedGroupsForColumn = Object.entries(groups)\n .filter(\n ([key, group]) =>\n ('column' in group && group.column === columnOrSubMenuKey) || key === columnOrSubMenuKey,\n )\n .sort(([, a], [, b]) => a.order - b.order);\n\n return sortedGroupsForColumn.flatMap(([groupKey]) => {\n const groupItems = items\n .filter((item) => item.group === groupKey)\n .sort((a, b) => a.order - b.order)\n .map((item: Localized) => {\n return (\n \n \n {'command' in item ? (\n {\n // Since the item has a command, we know it is a MenuItemContainingCommand.\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n onSelectMenuItem(item as MenuItemContainingCommand);\n }}\n >\n {item.iconPathBefore && (\n \n )}\n {item.label}\n {item.iconPathAfter && (\n \n )}\n \n ) : (\n \n {item.label}\n\n \n \n {getGroupContent(\n groups,\n items,\n getSubMenuGroupKeyForMenuItemId(groups, item.id),\n onSelectMenuItem,\n )}\n \n \n \n )}\n \n {item.tooltip && {item.tooltip}}\n \n );\n });\n\n return groupItems;\n });\n};\n\nexport type TabDropdownMenuProps = {\n /** The handler to use for menu commands */\n onSelectMenuItem: SelectMenuItemHandler;\n\n /** The menu data to show on the dropdown menu */\n menuData: Localized;\n\n /** Defines a string value that labels the current element */\n tabLabel: string;\n\n /** Optional icon for the dropdown menu trigger. Defaults to hamburger icon. */\n icon?: ReactNode;\n\n /** Additional css class(es) to help with unique styling of the tab dropdown menu */\n className?: string;\n\n /** Style variant for the app menubar component. */\n variant?: 'default' | 'muted';\n\n buttonVariant?: 'default' | 'ghost' | 'outline' | 'secondary';\n\n /** Optional unique identifier */\n id?: string;\n};\n\n/**\n * Dropdown menu designed to be used with Platform.Bible menu data. Column headers are ignored.\n * Column data is separated by a horizontal divider, so groups are not distinguishable. Tooltips are\n * displayed on hovering over menu items, if a tooltip is defined for them.\n *\n * A child component can be passed in to show as an icon on the menu trigger button.\n */\nexport default function TabDropdownMenu({\n onSelectMenuItem,\n menuData,\n tabLabel,\n icon,\n className,\n variant,\n buttonVariant = 'ghost',\n id,\n}: TabDropdownMenuProps) {\n return (\n \n \n \n \n \n {Object.entries(menuData.columns)\n .filter(([, column]) => typeof column === 'object')\n .sort(([, a], [, b]) => {\n if (typeof a === 'boolean' || typeof b === 'boolean') return 0;\n return a.order - b.order;\n })\n .map(([columnKey], index, array) => (\n \n \n \n {getGroupContent(menuData.groups, menuData.items, columnKey, onSelectMenuItem)}\n \n \n\n {index < array.length - 1 && }\n \n ))}\n \n \n );\n}\n","import { Localized, MultiColumnMenu } from 'platform-bible-utils';\nimport React, { PropsWithChildren, ReactNode } from 'react';\nimport { SelectMenuItemHandler } from '../menus/platform-menubar.component';\n\nexport type TabToolbarCommonProps = {\n /**\n * The handler to use for toolbar item commands related to the project menu. Here is a basic\n * example of how to create this:\n *\n * @example\n *\n * ```tsx\n * const projectMenuCommandHandler: SelectMenuItemHandler = async (selectedMenuItem) => {\n * const commandName = selectedMenuItem.command;\n * try {\n * // Assert the more specific type. Assert the more specific type. The menu data should\n * // specify a valid command name here. If not, the error will be caught.\n * // eslint-disable-next-line no-type-assertion/no-type-assertion\n * await papi.commands.sendCommand(commandName as CommandNames);\n * } catch (e) {\n * throw new Error(\n * `handleMenuCommand error: command: ${commandName}. ${JSON.stringify(e)}`,\n * );\n * }\n * };\n * ```\n */\n onSelectProjectMenuItem: SelectMenuItemHandler;\n\n /**\n * Menu data that is used to populate the Menubar component for the project menu. In an extension,\n * the menu data comes from menus.json in the contributions folder. To access that info, use\n * useMemo to get the WebViewMenu.\n */\n projectMenuData?: Localized;\n\n /** Optional unique identifier */\n id?: string;\n\n /** Additional css classes to help with unique styling of the extensible toolbar */\n className?: string;\n\n /** Icon that will be displayed on the Menu Button. Defaults to the hamburger menu icon. */\n menuButtonIcon?: ReactNode;\n};\n\nexport type TabToolbarContainerProps = PropsWithChildren<{\n /** Optional unique identifier */\n id?: string;\n /** Additional css classes to help with unique styling of the extensible toolbar */\n className?: string;\n}>;\n\n/** Wrapper that allows consistent styling for both TabToolbar and TabFloatingMenu. */\nexport const TabToolbarContainer = React.forwardRef(\n ({ id, className, children }, ref) => (\n \n {children}\n \n ),\n);\n\nexport default TabToolbarContainer;\n","import { ReactNode } from 'react';\nimport { Localized, MultiColumnMenu } from 'platform-bible-utils';\nimport { Menu, EllipsisVertical } from 'lucide-react';\nimport TabDropdownMenu from '../menus/tab-dropdown-menu.component';\nimport { SelectMenuItemHandler } from '../menus/platform-menubar.component';\nimport { TabToolbarCommonProps, TabToolbarContainer } from './tab-toolbar-container.component';\n\nexport type TabToolbarProps = TabToolbarCommonProps & {\n /**\n * The handler to use for toolbar item commands related to the tab view menu. Here is a basic\n * example of how to create this from the hello-rock3 extension:\n *\n * @example\n *\n * ```tsx\n * const projectMenuCommandHandler: SelectMenuItemHandler = async (selectedMenuItem) => {\n * const commandName = selectedMenuItem.command;\n * try {\n * // Assert the more specific type. Assert the more specific type. The menu data should\n * // specify a valid command name here. If not, the error will be caught.\n * // eslint-disable-next-line no-type-assertion/no-type-assertion\n * await papi.commands.sendCommand(commandName as CommandNames);\n * } catch (e) {\n * throw new Error(\n * `handleMenuCommand error: command: ${commandName}. ${JSON.stringify(e)}`,\n * );\n * }\n * };\n * ```\n */\n onSelectViewInfoMenuItem: SelectMenuItemHandler;\n\n /** Menu data that is used to populate the Menubar component for the view info menu */\n tabViewMenuData?: Localized;\n\n /**\n * Toolbar children to be put at the start of the the toolbar after the project menu icon (left\n * side in ltr, right side in rtl). Recommended for inner navigation.\n */\n startAreaChildren?: ReactNode;\n\n /** Toolbar children to be put in the center area of the the toolbar. Recommended for tools. */\n centerAreaChildren?: ReactNode;\n\n /**\n * Toolbar children to be put at the end of the the toolbar before the tab view menu icon (right\n * side in ltr, left side in rtl). Recommended for secondary tools and view options.\n */\n endAreaChildren?: ReactNode;\n};\n\n/**\n * Toolbar that holds the project menu icon on one side followed by three different areas/categories\n * for toolbar icons followed by an optional view info menu icon. See the Tab Floating Menu Button\n * component for a menu component that takes up less screen real estate yet is always visible.\n */\nexport function TabToolbar({\n onSelectProjectMenuItem,\n onSelectViewInfoMenuItem,\n projectMenuData,\n tabViewMenuData,\n id,\n className,\n startAreaChildren,\n centerAreaChildren,\n endAreaChildren,\n menuButtonIcon,\n}: TabToolbarProps) {\n return (\n \n {projectMenuData && (\n }\n buttonVariant=\"ghost\"\n />\n )}\n {startAreaChildren && (\n
    \n {startAreaChildren}\n
    \n )}\n {centerAreaChildren && (\n
    \n {centerAreaChildren}\n
    \n )}\n
    \n {tabViewMenuData && (\n }\n className=\"tw-h-full\"\n />\n )}\n {endAreaChildren}\n
    \n
    \n );\n}\n\nexport default TabToolbar;\n","import TabDropdownMenu from '../menus/tab-dropdown-menu.component';\nimport { TabToolbarCommonProps, TabToolbarContainer } from './tab-toolbar-container.component';\n\n/**\n * Renders a TabDropdownMenu with a trigger button that looks like the menuButtonIcon or like the\n * default of three stacked horizontal lines (aka the hamburger). The menu \"floats\" over the content\n * so it is always visible. When clicked, it displays a dropdown menu with the projectMenuData.\n */\nexport function TabFloatingMenu({\n onSelectProjectMenuItem,\n projectMenuData,\n id,\n className,\n menuButtonIcon,\n}: TabToolbarCommonProps) {\n return (\n \n {projectMenuData && (\n \n )}\n \n );\n}\n\nexport default TabFloatingMenu;\n","// adapted from: https://github.com/shadcn-ui/ui/discussions/752\n\n'use client';\n\nimport { TabsContentProps, TabsListProps, TabsTriggerProps } from '@/components/shadcn-ui/tabs';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport * as TabsPrimitive from '@radix-ui/react-tabs';\nimport React from 'react';\n\nexport type VerticalTabsProps = React.ComponentPropsWithoutRef & {\n className?: string;\n};\n\nexport type LeftTabsTriggerProps = TabsTriggerProps & {\n value: string;\n ref?: React.Ref;\n};\n\n/**\n * Tabs components provide a set of layered sections of contentโ€”known as tab panelsโ€“that are\n * displayed one at a time. These components are built on Radix UI primitives and styled with Shadcn\n * UI. See Shadcn UI Documentation: https://ui.shadcn.com/docs/components/tabs See Radix UI\n * Documentation: https://www.radix-ui.com/primitives/docs/components/tabs\n */\nexport const VerticalTabs = React.forwardRef<\n React.ElementRef,\n VerticalTabsProps\n>(({ className, ...props }, ref) => {\n const dir: Direction = readDirection();\n return (\n \n );\n});\n\nVerticalTabs.displayName = TabsPrimitive.List.displayName;\n\n/** @inheritdoc VerticalTabs */\nexport const VerticalTabsList = React.forwardRef<\n React.ElementRef,\n TabsListProps\n>(({ className, ...props }, ref) => (\n \n));\nVerticalTabsList.displayName = TabsPrimitive.List.displayName;\n\n/** @inheritdoc VerticalTabs */\nexport const VerticalTabsTrigger = React.forwardRef<\n React.ElementRef,\n LeftTabsTriggerProps\n>(({ className, ...props }, ref) => (\n \n));\n\n/** @inheritdoc VerticalTabs */\nexport const VerticalTabsContent = React.forwardRef<\n React.ElementRef,\n TabsContentProps\n>(({ className, ...props }, ref) => (\n \n));\nVerticalTabsContent.displayName = TabsPrimitive.Content.displayName;\n","import { SearchBar } from '@/components/basics/search-bar.component';\nimport {\n VerticalTabs,\n VerticalTabsContent,\n VerticalTabsList,\n VerticalTabsTrigger,\n} from '@/components/basics/tabs-vertical';\nimport { ReactNode } from 'react';\n\nexport type TabKeyValueContent = {\n key: string;\n value: string;\n content: ReactNode;\n};\n\nexport type TabNavigationContentSearchProps = {\n /** List of values and keys for each tab this component should provide */\n tabList: TabKeyValueContent[];\n\n /** The search query in the search bar */\n searchValue: string;\n\n /** Handler to run when the value of the search bar changes */\n onSearch: (searchQuery: string) => void;\n\n /** Optional placeholder for the search bar */\n searchPlaceholder?: string;\n\n /** Optional title to include in the header */\n headerTitle?: string;\n\n /** Optional className to modify the search input */\n searchClassName?: string;\n\n /** Optional id for the root element */\n id?: string;\n};\n\n/**\n * TabNavigationContentSearch component provides a vertical tab navigation interface with a search\n * bar at the top. This component allows users to filter content within tabs based on a search\n * query.\n *\n * @param {TabNavigationContentSearchProps} props\n * @param {TabKeyValueContent[]} props.tabList - List of objects containing keys, values, and\n * content for each tab to be displayed.\n * @param {string} props.searchValue - The current value of the search input.\n * @param {function} props.onSearch - Callback function called when the search input changes;\n * receives the new search query as an argument.\n * @param {string} [props.searchPlaceholder] - Optional placeholder text for the search input.\n * @param {string} [props.headerTitle] - Optional title to display above the search input.\n * @param {string} [props.searchClassName] - Optional CSS class name to apply custom styles to the\n * search input.\n * @param {string} [props.id] - Optional id for the root element.\n */\nexport function TabNavigationContentSearch({\n tabList,\n searchValue,\n onSearch,\n searchPlaceholder,\n headerTitle,\n searchClassName,\n id,\n}: TabNavigationContentSearchProps) {\n return (\n
    \n
    \n {headerTitle ?

    {headerTitle}

    : ''}\n \n
    \n \n \n {tabList.map((tab) => (\n \n {tab.value}\n \n ))}\n \n {tabList.map((tab) => (\n \n {tab.content}\n \n ))}\n \n
    \n );\n}\n\nexport default TabNavigationContentSearch;\n","import {\n MenuContext,\n MenuContextProps,\n menuVariants,\n useMenuContext,\n} from '@/context/menu.context';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport * as MenubarPrimitive from '@radix-ui/react-menubar';\nimport { Check, ChevronRight, Circle } from 'lucide-react';\nimport React from 'react';\n\nfunction MenubarMenu({ ...props }: React.ComponentProps) {\n return ;\n}\n\nfunction MenubarGroup({ ...props }: React.ComponentProps) {\n return ;\n}\n\nfunction MenubarPortal({ ...props }: React.ComponentProps) {\n return ;\n}\n\nfunction MenubarRadioGroup({ ...props }: React.ComponentProps) {\n return ;\n}\n\nfunction MenubarSub({ ...props }: React.ComponentProps) {\n return ;\n}\n\nconst Menubar = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n variant?: MenuContextProps['variant'];\n }\n>(({ className, variant = 'default', ...props }, ref) => {\n /* #region CUSTOM provide context to add variants */\n const contextValue = React.useMemo(\n () => ({\n variant,\n }),\n [variant],\n );\n return (\n \n {/* #endregion CUSTOM */}\n \n \n );\n});\nMenubar.displayName = MenubarPrimitive.Root.displayName;\n\nconst MenubarTrigger = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n );\n});\nMenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;\n\nconst MenubarSubTrigger = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n inset?: boolean;\n }\n>(({ className, inset, children, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n {children}\n \n \n );\n});\nMenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;\n\nconst MenubarSubContent = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n );\n});\nMenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;\n\nconst MenubarContent = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, align = 'start', alignOffset = -4, sideOffset = 8, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n \n \n );\n});\nMenubarContent.displayName = MenubarPrimitive.Content.displayName;\n\nconst MenubarItem = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n inset?: boolean;\n }\n>(({ className, inset, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n );\n});\nMenubarItem.displayName = MenubarPrimitive.Item.displayName;\n\nconst MenubarCheckboxItem = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, checked, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n \n \n \n \n \n {children}\n \n );\n});\nMenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;\n\nconst MenubarRadioItem = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n \n \n \n \n \n {children}\n \n );\n});\nMenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;\n\nconst MenubarLabel = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n inset?: boolean;\n }\n>(({ className, inset, ...props }, ref) => (\n \n));\nMenubarLabel.displayName = MenubarPrimitive.Label.displayName;\n\nconst MenubarSeparator = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nMenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;\n\nfunction MenubarShortcut({ className, ...props }: React.HTMLAttributes) {\n return (\n \n );\n}\nMenubarShortcut.displayname = 'MenubarShortcut';\n\nexport {\n Menubar,\n MenubarCheckboxItem,\n MenubarContent,\n MenubarGroup,\n MenubarItem,\n MenubarLabel,\n MenubarMenu,\n MenubarPortal,\n MenubarRadioGroup,\n MenubarRadioItem,\n MenubarSeparator,\n MenubarShortcut,\n MenubarSub,\n MenubarSubContent,\n MenubarSubTrigger,\n MenubarTrigger,\n};\n","import {\n Menubar,\n MenubarContent,\n MenubarItem,\n MenubarMenu,\n MenubarSeparator,\n MenubarSub,\n MenubarSubContent,\n MenubarSubTrigger,\n MenubarTrigger,\n} from '@/components/shadcn-ui/menubar';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@/components/shadcn-ui/tooltip';\nimport {\n GroupsInMultiColumnMenu,\n Localized,\n MenuItemContainingCommand,\n MenuItemContainingSubmenu,\n MultiColumnMenu,\n} from 'platform-bible-utils';\nimport { RefObject, useEffect, useRef } from 'react';\nimport { useHotkeys } from 'react-hotkeys-hook';\nimport { getSubMenuGroupKeyForMenuItemId } from './menu.util';\nimport MenuItemIcon from './menu-icon.component';\n\n/**\n * Callback function that is invoked when a user selects a menu item. Receives the full\n * `MenuItemContainingCommand` object as an argument.\n */\nexport interface SelectMenuItemHandler {\n (selectedMenuItem: MenuItemContainingCommand): void;\n}\n\nconst simulateKeyPress = (ref: RefObject, keys: KeyboardEventInit[]) => {\n setTimeout(() => {\n keys.forEach((key) => {\n ref.current?.dispatchEvent(new KeyboardEvent('keydown', key));\n });\n }, 0);\n};\n\nconst getMenubarContent = (\n groups: Localized,\n items: Localized<(MenuItemContainingCommand | MenuItemContainingSubmenu)[]>,\n columnOrSubMenuKey: string | undefined,\n onSelectMenuItem: SelectMenuItemHandler,\n) => {\n if (!columnOrSubMenuKey) return undefined;\n\n const sortedGroupsForColumn = Object.entries(groups)\n .filter(\n ([key, group]) =>\n ('column' in group && group.column === columnOrSubMenuKey) || key === columnOrSubMenuKey,\n )\n .sort(([, a], [, b]) => a.order - b.order);\n\n return sortedGroupsForColumn.flatMap(([groupKey], index) => {\n const groupItems = items\n .filter((item) => item.group === groupKey)\n .sort((a, b) => a.order - b.order)\n .map((item: Localized) => {\n return (\n \n \n {'command' in item ? (\n {\n // Since the item has a command, we know it is a MenuItemContainingCommand.\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n onSelectMenuItem(item as MenuItemContainingCommand);\n }}\n >\n {item.iconPathBefore && (\n \n )}\n {item.label}\n {item.iconPathAfter && (\n \n )}\n \n ) : (\n \n {item.label}\n \n {getMenubarContent(\n groups,\n items,\n getSubMenuGroupKeyForMenuItemId(groups, item.id),\n onSelectMenuItem,\n )}\n \n \n )}\n \n {item.tooltip && {item.tooltip}}\n \n );\n });\n\n const itemsWithSeparator = [...groupItems];\n if (groupItems.length > 0 && index < sortedGroupsForColumn.length - 1) {\n itemsWithSeparator.push();\n }\n\n return itemsWithSeparator;\n });\n};\n\ntype PlatformMenubarProps = {\n /** Menu data that is used to populate the Menubar component. */\n menuData: Localized;\n\n /** The handler to use for menu commands. */\n onSelectMenuItem: SelectMenuItemHandler;\n\n /**\n * Optional callback function that is executed whenever a menu on the Menubar is opened or closed.\n * Helpful for handling updates to the menu, as changing menu data when the menu is opened is not\n * desirable.\n */\n onOpenChange?: (isOpen: boolean) => void;\n\n /** Style variant for the app menubar component. */\n variant?: 'default' | 'muted';\n};\n\n/** Menubar component tailored to work with Platform.Bible menu data */\nexport function PlatformMenubar({\n menuData,\n onSelectMenuItem,\n onOpenChange,\n variant,\n}: PlatformMenubarProps) {\n // These refs will always be defined\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const menubarRef = useRef(undefined!);\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const projectMenuRef = useRef(undefined!);\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const windowMenuRef = useRef(undefined!);\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const layoutMenuRef = useRef(undefined!);\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const helpMenuRef = useRef(undefined!);\n\n const getRefForColumn = (columnKey: string) => {\n switch (columnKey) {\n case 'platform.app':\n return projectMenuRef;\n case 'platform.window':\n return windowMenuRef;\n case 'platform.layout':\n return layoutMenuRef;\n case 'platform.help':\n return helpMenuRef;\n default:\n return undefined;\n }\n };\n\n // This is a quick and dirty way to implement some shortcuts by simulating key presses\n useHotkeys(['alt', 'alt+p', 'alt+l', 'alt+n', 'alt+h'], (event, handler) => {\n event.preventDefault();\n\n const escKey: KeyboardEventInit = { key: 'Escape', code: 'Escape', keyCode: 27, bubbles: true };\n const spaceKey: KeyboardEventInit = { key: ' ', code: 'Space', keyCode: 32, bubbles: true };\n\n switch (handler.hotkey) {\n case 'alt':\n simulateKeyPress(projectMenuRef, [escKey]);\n break;\n case 'alt+p':\n projectMenuRef.current?.focus();\n simulateKeyPress(projectMenuRef, [escKey, spaceKey]);\n break;\n case 'alt+l':\n windowMenuRef.current?.focus();\n simulateKeyPress(windowMenuRef, [escKey, spaceKey]);\n break;\n case 'alt+n':\n layoutMenuRef.current?.focus();\n simulateKeyPress(layoutMenuRef, [escKey, spaceKey]);\n break;\n case 'alt+h':\n helpMenuRef.current?.focus();\n simulateKeyPress(helpMenuRef, [escKey, spaceKey]);\n break;\n default:\n break;\n }\n });\n\n useEffect(() => {\n if (!onOpenChange || !menubarRef.current) return;\n\n const observer = new MutationObserver((mutations) => {\n mutations.forEach((mutation) => {\n if (mutation.attributeName === 'data-state' && mutation.target instanceof HTMLElement) {\n const state = mutation.target.getAttribute('data-state');\n\n if (state === 'open') {\n onOpenChange(true);\n } else {\n onOpenChange(false);\n }\n }\n });\n });\n\n const menubarElement = menubarRef.current;\n const dataStateAttributes = menubarElement.querySelectorAll('[data-state]');\n\n dataStateAttributes.forEach((element) => {\n observer.observe(element, { attributes: true });\n });\n\n return () => observer.disconnect();\n }, [onOpenChange]);\n\n if (!menuData) return undefined;\n\n return (\n \n {Object.entries(menuData.columns)\n .filter(([, column]) => typeof column === 'object')\n .sort(([, a], [, b]) => {\n if (typeof a === 'boolean' || typeof b === 'boolean') return 0;\n return a.order - b.order;\n })\n .map(([columnKey, column]) => (\n \n \n {typeof column === 'object' && 'label' in column && column.label}\n \n \n \n {getMenubarContent(menuData.groups, menuData.items, columnKey, onSelectMenuItem)}\n \n \n \n ))}\n \n );\n}\n\nexport default PlatformMenubar;\n","import {\n SelectMenuItemHandler,\n PlatformMenubar,\n} from '@/components/advanced/menus/platform-menubar.component';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Localized, MultiColumnMenu } from 'platform-bible-utils';\nimport { PropsWithChildren, ReactNode, useRef } from 'react';\n\nexport type ToolbarProps = PropsWithChildren<{\n /** The handler to use for menu commands (and eventually toolbar commands). */\n onSelectMenuItem: SelectMenuItemHandler;\n\n /**\n * Menu data that is used to populate the Menubar component. If empty object, no menus will be\n * shown on the App Menubar\n */\n menuData?: Localized;\n\n /**\n * Optional callback function that is executed whenever a menu on the App Menubar is opened or\n * closed. Helpful for handling updates to the menu, as changing menu data when the menu is opened\n * is not desirable.\n */\n onOpenChange?: (isOpen: boolean) => void;\n\n /** Optional unique identifier */\n id?: string;\n\n /** Additional css classes to help with unique styling of the toolbar */\n className?: string;\n\n /**\n * Whether the toolbar should be used as a draggable area for moving the application. This will\n * add an electron specific style `WebkitAppRegion: 'drag'` to the toolbar in order to make it\n * draggable. See:\n * https://www.electronjs.org/docs/latest/tutorial/custom-title-bar#create-a-custom-title-bar\n */\n shouldUseAsAppDragArea?: boolean;\n\n /** Toolbar children to be put at the start of the toolbar (left side in ltr, right side in rtl) */\n appMenuAreaChildren?: ReactNode;\n\n /** Toolbar children to be put at the end of the toolbar (right side in ltr, left side in rtl) */\n configAreaChildren?: ReactNode;\n\n /** Variant of the menubar */\n menubarVariant?: 'default' | 'muted';\n}>;\n\n/**\n * Get tailwind class for reserved space for the window controls / macos \"traffic lights\". Passing\n * 'darwin' will reserve the necessary space for macos traffic lights at the start, otherwise a\n * different amount of space at the end for the window controls.\n *\n * Apply to the toolbar like: `` or ``\n *\n * @param operatingSystem The os platform: 'darwin' (macos) | anything else\n * @returns The class name to apply to the toolbar if os specific space should be reserved\n */\nexport function getToolbarOSReservedSpaceClassName(\n operatingSystem: string | undefined,\n): string | undefined {\n switch (operatingSystem) {\n case undefined:\n return undefined;\n case 'darwin':\n return 'tw-ps-[85px]';\n default:\n return 'tw-pe-[calc(138px+1rem)]';\n }\n}\n\n/**\n * A customizable toolbar component with a menubar, content area, and configure area.\n *\n * This component is designed to be used in the window title bar of an electron application.\n *\n * @param {ToolbarProps} props - The props for the component.\n */\nexport function Toolbar({\n menuData,\n onOpenChange,\n onSelectMenuItem,\n className,\n id,\n children,\n appMenuAreaChildren,\n configAreaChildren,\n shouldUseAsAppDragArea,\n menubarVariant = 'default',\n}: ToolbarProps) {\n // This ref will always be defined\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const containerRef = useRef(undefined!);\n\n return (\n \n \n {/* App Menu area */}\n
    \n \n {appMenuAreaChildren}\n\n {menuData && (\n \n )}\n
    \n \n\n {/* Content area */}\n \n {children}\n \n\n {/* Configure area */}\n
    \n \n {configAreaChildren}\n
    \n \n \n \n );\n}\n\nexport default Toolbar;\n","import { useState } from 'react';\nimport { LocalizedStringValue, formatReplacementString } from 'platform-bible-utils';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../shadcn-ui/select';\nimport { Label } from '../shadcn-ui/label';\n\n/**\n * Immutable array containing all keys used for localization in this component. If you're using this\n * component in an extension, you can pass it into the useLocalizedStrings hook to easily obtain the\n * localized strings and pass them into the localizedStrings prop of this component\n */\nexport const UI_LANGUAGE_SELECTOR_STRING_KEYS = Object.freeze([\n '%settings_uiLanguageSelector_fallbackLanguages%',\n] as const);\n\nexport type UiLanguageSelectorLocalizedStrings = {\n [localizedUiLanguageSelectorKey in (typeof UI_LANGUAGE_SELECTOR_STRING_KEYS)[number]]?: LocalizedStringValue;\n};\n\n/**\n * Gets the localized value for the provided key\n *\n * @param strings Object containing localized string\n * @param key Key for a localized string\n * @returns The localized value for the provided key, if available. Returns the key if no localized\n * value is available\n */\nconst localizeString = (\n strings: UiLanguageSelectorLocalizedStrings,\n key: keyof UiLanguageSelectorLocalizedStrings,\n) => {\n return strings[key] ?? key;\n};\n\nexport type LanguageInfo = {\n /** The name of the language to be displayed (in its native script) */\n autonym: string;\n /**\n * The name of the language in other languages, so that the language can also be displayed in the\n * current UI language, if known.\n */\n uiNames?: Record;\n /**\n * Other known names of the language (for searching). This can include pejorative names and should\n * never be displayed unless typed by the user.\n */\n otherNames?: string[];\n};\n\nexport type UiLanguageSelectorProps = {\n /** Full set of known languages to display. The keys are valid BCP-47 tags. */\n knownUiLanguages: Record;\n /** IETF BCP-47 language tag of the current primary UI language. `undefined` => 'en' */\n primaryLanguage: string;\n /**\n * Ordered list of fallback language tags to use if the localization key can't be found in the\n * current primary UI language. This list never contains English ('en') because it is the ultimate\n * fallback.\n */\n fallbackLanguages: string[] | undefined;\n /**\n * Handler for when either the primary or the fallback languages change (or both). For this\n * handler, the primary UI language is the first one in the array, followed by the fallback\n * languages in order of decreasing preference.\n */\n onLanguagesChange?: (newUiLanguages: string[]) => void;\n /** Handler for the primary language changes. */\n onPrimaryLanguageChange?: (newPrimaryUiLanguage: string) => void;\n /**\n * Handler for when the fallback languages change. The array contains the fallback languages in\n * order of decreasing preference.\n */\n onFallbackLanguagesChange?: (newFallbackLanguages: string[]) => void;\n /**\n * Map whose keys are localized string keys as contained in UI_LANGUAGE_SELECTOR_STRING_KEYS and\n * whose values are the localized strings (in the current UI language).\n */\n localizedStrings: UiLanguageSelectorLocalizedStrings;\n /** Additional css classes to help with unique styling of the control */\n className?: string;\n /** Optional id for the root element */\n id?: string;\n};\n\n/**\n * A component for selecting the user interface language and managing fallback languages. Allows\n * users to choose a primary UI language and optionally select fallback languages.\n *\n * @param {UiLanguageSelectorProps} props - The props for the component.\n */\nexport function UiLanguageSelector({\n knownUiLanguages,\n primaryLanguage = 'en',\n fallbackLanguages = [],\n onLanguagesChange,\n onPrimaryLanguageChange,\n onFallbackLanguagesChange,\n localizedStrings,\n className,\n id,\n}: UiLanguageSelectorProps) {\n const fallbackLanguagesText = localizeString(\n localizedStrings,\n '%settings_uiLanguageSelector_fallbackLanguages%',\n );\n const [isOpen, setIsOpen] = useState(false);\n\n const handleLanguageChange = (code: string) => {\n if (onPrimaryLanguageChange) onPrimaryLanguageChange(code);\n // REVIEW: Should fallback languages be preserved when primary language changes?\n if (onLanguagesChange)\n onLanguagesChange([code, ...fallbackLanguages.filter((lang) => lang !== code)]);\n if (onFallbackLanguagesChange && fallbackLanguages.find((l) => l === code))\n onFallbackLanguagesChange([...fallbackLanguages.filter((lang) => lang !== code)]);\n setIsOpen(false); // Close the dropdown when a selection is made\n };\n\n /**\n * Gets the display name for the given language. This will typically include the autonym (in the\n * native script), along with the name of the language in the current UI locale if known, with a\n * fallback to the English name (if known).\n *\n * @param {string} lang - The BCP-47 code of the language whose display name is being requested.\n * @param {string} uiLang - The BCP-47 code of the current user-interface language used used to\n * try to look up the name of the language in a form that is likely to be helpful to the user if\n * they do not recognize the autonym.\n * @returns {string} The display name of the language.\n */\n const getLanguageDisplayName = (lang: string, uiLang: string) => {\n const altName =\n uiLang !== lang\n ? (knownUiLanguages[lang]?.uiNames?.[uiLang] ?? knownUiLanguages[lang]?.uiNames?.en)\n : undefined;\n\n return altName\n ? `${knownUiLanguages[lang]?.autonym} (${altName})`\n : knownUiLanguages[lang]?.autonym;\n };\n\n return (\n
    \n {/* Language Selector */}\n setIsOpen(open)}\n >\n \n \n \n \n {Object.keys(knownUiLanguages).map((key) => {\n return (\n \n {getLanguageDisplayName(key, primaryLanguage)}\n \n );\n })}\n \n \n\n {/* Fallback Language Button */}\n {primaryLanguage !== 'en' && (\n
    \n \n
    \n )}\n
    \n );\n}\n\nexport default UiLanguageSelector;\n","import { Label } from '@/components/shadcn-ui/label';\nimport { ReactNode } from 'react';\n\ntype SmartLabelProps = {\n item: string;\n createLabel?: (item: string) => string;\n createComplexLabel?: (item: string) => ReactNode;\n};\n\n/** Create labels with text, react elements (e.g. links), or text + react elements */\nfunction SmartLabel({ item, createLabel, createComplexLabel }: SmartLabelProps): ReactNode {\n if (createLabel) {\n return ;\n }\n if (createComplexLabel) {\n return ;\n }\n return ;\n}\n\nexport default SmartLabel;\n","import { Checkbox } from '@/components/shadcn-ui/checkbox';\nimport { ReactNode } from 'react';\nimport SmartLabel from './smart-label.component';\n\nexport type ChecklistProps = {\n /** Optional string representing the id attribute of the Checklist */\n id?: string;\n /** Optional string representing CSS class name(s) for styling */\n className?: string;\n /** Array of strings representing the checkable items */\n listItems: string[];\n /** Array of strings representing the checked items */\n selectedListItems: string[];\n /**\n * Function that is called when a checkbox item is selected or deselected\n *\n * @param item The string description for this item\n * @param selected True if selected, false if not selected\n */\n handleSelectListItem: (item: string, selected: boolean) => void;\n\n /**\n * Optional function creates a label for a provided checkable item\n *\n * @param item The item for which a label is to be created\n * @returns A string representing the label text for the checkbox associated with that item\n */\n createLabel?: (item: string) => string;\n\n /**\n * Optional function creates a label for a provided checkable item\n *\n * @param item The item for which a label is to be created, including text and any additional\n * elements (e.g. links)\n * @returns A react node representing the label text and any additional elements (e.g. links) for\n * the checkbox associated with that item\n */\n createComplexLabel?: (item: string) => ReactNode;\n};\n\n/** Renders a list of checkboxes. Each checkbox corresponds to an item from the `listItems` array. */\nexport function Checklist({\n id,\n className,\n listItems,\n selectedListItems,\n handleSelectListItem,\n createLabel,\n createComplexLabel,\n}: ChecklistProps) {\n return (\n
    \n {listItems.map((item) => (\n
    \n handleSelectListItem(item, value)}\n />\n \n
    \n ))}\n
    \n );\n}\n\nexport default Checklist;\n","import { cn } from '@/utils/shadcn-ui.util';\nimport { MoreHorizontal } from 'lucide-react';\nimport React, { ReactNode } from 'react';\nimport { Button } from '../shadcn-ui/button';\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '../shadcn-ui/dropdown-menu';\n\n/** Props interface for the ResultsCard base component */\nexport interface ResultsCardProps {\n /** Unique key for the card */\n cardKey: string;\n /** Whether this card is currently selected/focused */\n isSelected: boolean;\n /** Callback function called when the card is clicked */\n onSelect: () => void;\n /** Whether the content of this card are in a denied state */\n isDenied?: boolean;\n /** Whether the card should be hidden */\n isHidden?: boolean;\n /** Additional CSS classes to apply to the card */\n className?: string;\n /** Main content to display on the card */\n children: ReactNode;\n /** Content to show in the dropdown menu when selected */\n dropdownContent?: ReactNode;\n /** Additional content to show below the main content when selected */\n additionalSelectedContent?: ReactNode;\n /** Color to use for the card's accent border */\n accentColor?: string;\n}\n\n/**\n * ResultsCard is a base component for displaying scripture-related results in a card format, even\n * though it is not based on the Card component. It provides common functionality like selection\n * state, dropdown menus, and expandable content.\n */\nexport function ResultsCard({\n cardKey,\n isSelected,\n onSelect,\n isDenied,\n isHidden = false,\n className,\n children,\n dropdownContent,\n additionalSelectedContent,\n accentColor,\n}: ResultsCardProps) {\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n onSelect();\n }\n };\n\n return (\n
  • ,\n);\nSidebarMenuSubItem.displayName = 'SidebarMenuSubItem';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarMenuSubButton = React.forwardRef<\n HTMLAnchorElement,\n React.ComponentProps<'a'> & {\n asChild?: boolean;\n size?: 'sm' | 'md';\n isActive?: boolean;\n }\n>(({ asChild = false, size = 'md', isActive, className, ...props }, ref) => {\n const Comp = asChild ? Slot : 'a';\n\n return (\n span:last-child]:tw-truncate [&>svg]:tw-size-4 [&>svg]:tw-shrink-0 [&>svg]:tw-text-sidebar-accent-foreground',\n 'data-[active=true]:tw-bg-sidebar-accent data-[active=true]:tw-text-sidebar-accent-foreground',\n size === 'sm' && 'tw-text-xs',\n size === 'md' && 'tw-text-sm',\n 'group-data-[collapsible=icon]:tw-hidden',\n className,\n )}\n {...props}\n />\n );\n});\nSidebarMenuSubButton.displayName = 'SidebarMenuSubButton';\n\nexport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupAction,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarInput,\n SidebarInset,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuBadge,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSkeleton,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarProvider,\n SidebarRail,\n SidebarSeparator,\n SidebarTrigger,\n useSidebar,\n};\n","import { ComboBox } from '@/components/basics/combo-box.component';\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupLabel,\n SidebarGroupContent,\n SidebarMenu,\n SidebarMenuItem,\n SidebarMenuButton,\n} from '@/components/shadcn-ui/sidebar';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { ScrollText } from 'lucide-react';\nimport { useCallback } from 'react';\n\nexport type SelectedSettingsSidebarItem = {\n label: string;\n projectId?: string;\n};\n\nexport type ProjectInfo = { projectId: string; projectName: string };\n\nexport type SettingsSidebarProps = {\n /** Optional id for testing */\n id?: string;\n\n /** Extension labels from contribution */\n extensionLabels: Record;\n\n /** Project names and ids */\n projectInfo: ProjectInfo[];\n\n /** Handler for selecting a sidebar item */\n handleSelectSidebarItem: (key: string, projectId?: string) => void;\n\n /** The current selected value in the sidebar */\n selectedSidebarItem: SelectedSettingsSidebarItem;\n\n /** Label for the group of extensions setting groups */\n extensionsSidebarGroupLabel: string;\n\n /** Label for the group of projects settings */\n projectsSidebarGroupLabel: string;\n\n /** Placeholder text for the button */\n buttonPlaceholderText: string;\n\n /** Additional css classes to help with unique styling of the sidebar */\n className?: string;\n};\n\n/**\n * The SettingsSidebar component is a sidebar that displays a list of extension settings and project\n * settings. It can be used to navigate to different settings pages. Must be wrapped in a\n * SidebarProvider component otherwise produces errors.\n *\n * @param props - {@link SettingsSidebarProps} The props for the component.\n */\nexport function SettingsSidebar({\n id,\n extensionLabels,\n projectInfo,\n handleSelectSidebarItem,\n selectedSidebarItem,\n extensionsSidebarGroupLabel,\n projectsSidebarGroupLabel,\n buttonPlaceholderText,\n className,\n}: SettingsSidebarProps) {\n const handleSelectItem = useCallback(\n (item: string, projectId?: string) => {\n handleSelectSidebarItem(item, projectId);\n },\n [handleSelectSidebarItem],\n );\n\n const getProjectNameFromProjectId = useCallback(\n (projectId: string) => {\n const project = projectInfo.find((info) => info.projectId === projectId);\n return project ? project.projectName : projectId;\n },\n [projectInfo],\n );\n\n const getIsActive: (label: string) => boolean = useCallback(\n (label: string) => !selectedSidebarItem.projectId && label === selectedSidebarItem.label,\n [selectedSidebarItem],\n );\n\n return (\n \n \n \n \n {extensionsSidebarGroupLabel}\n \n \n \n {Object.entries(extensionLabels).map(([key, label]) => (\n \n handleSelectItem(key)}\n isActive={getIsActive(key)}\n >\n {label}\n \n \n ))}\n \n \n \n \n {projectsSidebarGroupLabel}\n \n info.projectId)}\n getOptionLabel={getProjectNameFromProjectId}\n buttonPlaceholder={buttonPlaceholderText}\n onChange={(projectId: string) => {\n const selectedProjectName = getProjectNameFromProjectId(projectId);\n handleSelectItem(selectedProjectName, projectId);\n }}\n value={selectedSidebarItem?.projectId ?? undefined}\n icon={}\n />\n \n \n \n \n );\n}\n\nexport default SettingsSidebar;\n","import { Button } from '@/components/shadcn-ui/button';\nimport { Input } from '@/components/shadcn-ui/input';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Search, X } from 'lucide-react';\nimport { forwardRef } from 'react';\n\n/** Props for the SearchBar component. */\nexport type SearchBarProps = {\n /** Search query for the search bar */\n value: string;\n /**\n * Callback fired to handle the search query is updated\n *\n * @param searchQuery\n */\n onSearch: (searchQuery: string) => void;\n\n /** Optional string that appears in the search bar without a search string */\n placeholder?: string;\n\n /** Optional boolean to set the input base to full width */\n isFullWidth?: boolean;\n\n /** Additional css classes to help with unique styling of the search bar */\n className?: string;\n\n /** Optional boolean to disable the search bar */\n isDisabled?: boolean;\n\n /** Optional id for the root element */\n id?: string;\n};\n\n/**\n * A search bar component with a search icon and a clear button when the search query is not empty.\n *\n * @param {SearchBarProps} props - The props for the component.\n * @param {string} props.value - The search query for the search bar\n * @param {(searchQuery: string) => void} props.onSearch - Callback fired to handle the search query\n * is updated\n * @param {string} [props.placeholder] - Optional string that appears in the search bar without a\n * search string\n * @param {boolean} [props.isFullWidth] - Optional boolean to set the input base to full width\n * @param {string} [props.className] - Additional css classes to help with unique styling of the\n * search bar\n * @param {boolean} [props.isDisabled] - Optional boolean to disable the search bar\n * @param {string} [props.id] - Optional id for the root element\n */\nexport const SearchBar = forwardRef(\n ({ value, onSearch, placeholder, isFullWidth, className, isDisabled = false, id }, inputRef) => {\n const dir: Direction = readDirection();\n\n return (\n
    \n \n onSearch(e.target.value)}\n disabled={isDisabled}\n />\n {value && (\n {\n onSearch('');\n }}\n >\n \n Clear\n \n )}\n
    \n );\n },\n);\n\nSearchBar.displayName = 'SearchBar';\n\nexport default SearchBar;\n","import { SidebarInset, SidebarProvider } from '@/components/shadcn-ui/sidebar';\nimport { PropsWithChildren } from 'react';\nimport { SearchBar } from '@/components/basics/search-bar.component';\nimport { SettingsSidebar, SettingsSidebarProps } from './settings-sidebar.component';\n\nexport type SettingsSidebarContentSearchProps = SettingsSidebarProps &\n PropsWithChildren & {\n /** The search query in the search bar */\n searchValue: string;\n\n /** Handler to run when the value of the search bar changes */\n onSearch: (searchQuery: string) => void;\n };\n\n/**\n * A component that wraps a search bar and a settings sidebar, providing a way to search and\n * navigate to different settings pages.\n *\n * @param {SettingsSidebarContentSearchProps} props - The props for the component.\n * @param {string} props.id - The id of the sidebar.\n */\nexport function SettingsSidebarContentSearch({\n id,\n extensionLabels,\n projectInfo,\n children,\n handleSelectSidebarItem,\n selectedSidebarItem,\n searchValue,\n onSearch,\n extensionsSidebarGroupLabel,\n projectsSidebarGroupLabel,\n buttonPlaceholderText,\n}: SettingsSidebarContentSearchProps) {\n return (\n
    \n
    \n \n
    \n \n \n {children}\n \n
    \n );\n}\n\nexport default SettingsSidebarContentSearch;\n","import { Button } from '@/components/shadcn-ui/button';\nimport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@/components/shadcn-ui/select';\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@/components/shadcn-ui/table';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Canon } from '@sillsdev/scripture';\nimport {\n Cell,\n ColumnDef,\n flexRender,\n getCoreRowModel,\n getExpandedRowModel,\n getGroupedRowModel,\n getSortedRowModel,\n GroupingState,\n Row,\n RowSelectionState,\n SortingState,\n useReactTable,\n} from '@tanstack/react-table';\nimport '@/components/advanced/scripture-results-viewer/scripture-results-viewer.component.css';\nimport {\n compareScrRefs,\n formatScrRef,\n ScriptureSelection,\n scrRefToBBBCCCVVV,\n} from 'platform-bible-utils';\nimport { MouseEvent, useEffect, useMemo, useState } from 'react';\nimport { ChevronDown, ChevronLeft, ChevronRight } from 'lucide-react';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\n\n/**\n * Information (e.g., a checking error or some other type of \"transient\" annotation) about something\n * noteworthy at a specific place in an instance of the Scriptures.\n */\nexport type ScriptureItemDetail = ScriptureSelection & {\n /**\n * Text of the error, note, etc. In the future, we might want to support something more than just\n * text so that a JSX element could be provided with a link or some other controls related to the\n * issue being reported.\n */\n detail: string;\n};\n\n/**\n * A uniquely identifiable source of results that can be displayed in the ScriptureResultsViewer.\n * Generally, the source will be a particular Scripture check, but there may be other types of\n * sources.\n */\nexport type ResultsSource = {\n /**\n * Uniquely identifies the source.\n *\n * @type {string}\n */\n id: string;\n\n /**\n * Name (potentially localized) of the source, suitable for display in the UI.\n *\n * @type {string}\n */\n displayName: string;\n};\n\nexport type ScriptureSrcItemDetail = ScriptureItemDetail & {\n /** Source/type of detail. Can be used for grouping. */\n source: ResultsSource;\n};\n\n/**\n * Represents a set of results keyed by Scripture reference. Generally, the source will be a\n * particular Scripture check, but this type also allows for other types of uniquely identifiable\n * sources.\n */\nexport type ResultsSet = {\n /**\n * The backing source associated with this set of results.\n *\n * @type {ResultsSource}\n */\n source: ResultsSource;\n\n /**\n * Array of Scripture item details (messages keyed by Scripture reference).\n *\n * @type {ScriptureItemDetail[]}\n */\n data: ScriptureItemDetail[];\n};\n\nconst scrBookColId = 'scrBook';\nconst scrRefColId = 'scrRef';\nconst typeColId = 'source';\nconst detailsColId = 'details';\n\nconst defaultScrRefColumnName = 'Scripture Reference';\nconst defaultScrBookGroupName = 'Scripture Book';\nconst defaultTypeColumnName = 'Type';\nconst defaultDetailsColumnName = 'Details';\n\nexport type ScriptureResultsViewerColumnInfo = {\n /** Optional header to display for the Reference column. Default value: 'Scripture Reference'. */\n scriptureReferenceColumnName?: string;\n\n /** Optional text to display to refer to the Scripture book group. Default value: 'Scripture Book'. */\n scriptureBookGroupName?: string;\n\n /** Optional header to display for the Type column. Default value: 'Type'. */\n typeColumnName?: string;\n\n /** Optional header to display for the Details column. Default value: 'Details' */\n detailsColumnName?: string;\n};\n\nexport type ScriptureResultsViewerProps = ScriptureResultsViewerColumnInfo & {\n /** Groups of ScriptureItemDetail objects from particular sources (e.g., Scripture checks) */\n sources: ResultsSet[];\n\n /** Flag indicating whether to display column headers. Default is false. */\n showColumnHeaders?: boolean;\n\n /** Flag indicating whether to display source column. Default is false. */\n showSourceColumn?: boolean;\n\n /** Callback function to notify when a row is selected */\n onRowSelected?: (selectedRow: ScriptureSrcItemDetail | undefined) => void;\n\n /** Optional id attribute for the outermost element */\n id?: string;\n};\n\nfunction getColumns(\n colInfo?: ScriptureResultsViewerColumnInfo,\n showSourceColumn?: boolean,\n): ColumnDef[] {\n const showSrcCol = showSourceColumn ?? false;\n return [\n {\n accessorFn: (row) => `${row.start.book} ${row.start.chapterNum}:${row.start.verseNum}`,\n id: scrBookColId,\n header: colInfo?.scriptureReferenceColumnName ?? defaultScrRefColumnName,\n cell: (info) => {\n const row = info.row.original;\n if (info.row.getIsGrouped()) {\n return Canon.bookIdToEnglishName(row.start.book);\n }\n return info.row.groupingColumnId === scrBookColId ? formatScrRef(row.start) : undefined;\n },\n getGroupingValue: (row) => Canon.bookIdToNumber(row.start.book),\n sortingFn: (a, b) => {\n return compareScrRefs(a.original.start, b.original.start);\n },\n enableGrouping: true,\n },\n {\n accessorFn: (row) => formatScrRef(row.start),\n id: scrRefColId,\n header: undefined,\n cell: (info) => {\n const row = info.row.original;\n return info.row.getIsGrouped() ? undefined : formatScrRef(row.start);\n },\n sortingFn: (a, b) => {\n return compareScrRefs(a.original.start, b.original.start);\n },\n enableGrouping: false,\n },\n {\n accessorFn: (row) => row.source.displayName,\n id: typeColId,\n header: showSrcCol ? (colInfo?.typeColumnName ?? defaultTypeColumnName) : undefined,\n cell: (info) => (showSrcCol || info.row.getIsGrouped() ? info.getValue() : undefined),\n getGroupingValue: (row) => row.source.id,\n sortingFn: (a, b) =>\n a.original.source.displayName.localeCompare(b.original.source.displayName),\n enableGrouping: true,\n },\n {\n accessorFn: (row) => row.detail,\n id: detailsColId,\n header: colInfo?.detailsColumnName ?? defaultDetailsColumnName,\n cell: (info) => info.getValue(),\n enableGrouping: false,\n },\n ];\n}\n\nconst toRefOrRange = (scriptureSelection: ScriptureSelection) => {\n if (!('offset' in scriptureSelection.start))\n throw new Error('No offset available in range start');\n if (scriptureSelection.end && !('offset' in scriptureSelection.end))\n throw new Error('No offset available in range end');\n const { offset: offsetStart } = scriptureSelection.start;\n let offsetEnd: number = 0;\n if (scriptureSelection.end) ({ offset: offsetEnd } = scriptureSelection.end);\n if (\n !scriptureSelection.end ||\n compareScrRefs(scriptureSelection.start, scriptureSelection.end) === 0\n )\n return `${scrRefToBBBCCCVVV(scriptureSelection.start)}+${offsetStart}`;\n return `${scrRefToBBBCCCVVV(scriptureSelection.start)}+${offsetStart}-${scrRefToBBBCCCVVV(scriptureSelection.end)}+${offsetEnd}`;\n};\n\nconst getRowKey = (row: ScriptureSrcItemDetail) =>\n `${toRefOrRange({ start: row.start, end: row.end })} ${row.source.displayName} ${row.detail}`;\n\n/**\n * Component to display a combined list of detailed items from one or more sources, where the items\n * are keyed primarily by Scripture reference. This is particularly useful for displaying a list of\n * results from Scripture checks, but more generally could be used to display any \"results\" from any\n * source(s). The component allows for grouping by Scripture book, source, or both. By default, it\n * displays somewhat \"tree-like\" which allows it to be more horizontally compact and intuitive. But\n * it also has the option of displaying as a traditional table with column headings (with or without\n * the source column showing).\n */\nexport function ScriptureResultsViewer({\n sources,\n showColumnHeaders = false,\n showSourceColumn = false,\n scriptureReferenceColumnName,\n scriptureBookGroupName,\n typeColumnName,\n detailsColumnName,\n onRowSelected,\n id,\n}: ScriptureResultsViewerProps) {\n const [grouping, setGrouping] = useState([]);\n const [sorting, setSorting] = useState([{ id: scrBookColId, desc: false }]);\n const [rowSelection, setRowSelection] = useState({});\n\n const scriptureResults = useMemo(\n () =>\n sources.flatMap((source) => {\n return source.data.map((item) => ({\n ...item,\n source: source.source,\n }));\n }),\n [sources],\n );\n\n const columns = useMemo(\n () =>\n getColumns(\n {\n scriptureReferenceColumnName,\n typeColumnName,\n detailsColumnName,\n },\n showSourceColumn,\n ),\n [scriptureReferenceColumnName, typeColumnName, detailsColumnName, showSourceColumn],\n );\n\n useEffect(() => {\n // Ensure sorting is applied correctly when grouped by type\n if (grouping.includes(typeColId)) {\n setSorting([\n { id: typeColId, desc: false },\n { id: scrBookColId, desc: false },\n ]);\n } else {\n setSorting([{ id: scrBookColId, desc: false }]);\n }\n }, [grouping]);\n\n const table = useReactTable({\n data: scriptureResults,\n columns,\n state: {\n grouping,\n sorting,\n rowSelection,\n },\n onGroupingChange: setGrouping,\n onSortingChange: setSorting,\n onRowSelectionChange: setRowSelection,\n getExpandedRowModel: getExpandedRowModel(),\n getGroupedRowModel: getGroupedRowModel(),\n getCoreRowModel: getCoreRowModel(),\n getSortedRowModel: getSortedRowModel(),\n getRowId: getRowKey,\n autoResetExpanded: false,\n enableMultiRowSelection: false,\n enableSubRowSelection: false,\n });\n\n useEffect(() => {\n if (onRowSelected) {\n const selectedRows = table.getSelectedRowModel().rowsById;\n const keys = Object.keys(selectedRows);\n if (keys.length === 1) {\n const selectedRow = scriptureResults.find((row) => getRowKey(row) === keys[0]) || undefined;\n if (selectedRow) onRowSelected(selectedRow);\n }\n }\n }, [rowSelection, scriptureResults, onRowSelected, table]);\n\n // Define possible grouping options\n const scrBookGroupName = scriptureBookGroupName ?? defaultScrBookGroupName;\n const typeGroupName = typeColumnName ?? defaultTypeColumnName;\n\n const groupingOptions = [\n { label: 'No Grouping', value: [] },\n { label: `Group by ${scrBookGroupName}`, value: [scrBookColId] },\n { label: `Group by ${typeGroupName}`, value: [typeColId] },\n {\n label: `Group by ${scrBookGroupName} and ${typeGroupName}`,\n value: [scrBookColId, typeColId],\n },\n {\n label: `Group by ${typeGroupName} and ${scrBookGroupName}`,\n value: [typeColId, scrBookColId],\n },\n ];\n\n const handleSelectChange = (selectedGrouping: string) => {\n setGrouping(JSON.parse(selectedGrouping));\n };\n\n const handleRowClick = (row: Row, event: MouseEvent) => {\n if (!row.getIsGrouped() && !row.getIsSelected()) {\n row.getToggleSelectedHandler()(event);\n }\n };\n\n const getEvenOrOddBandingStyle = (row: Row, index: number) => {\n if (row.getIsGrouped()) return '';\n // UX has now said they don't think they want banding. I'm leaving in the code to\n // set even and odd styles, but there's nothing in the CSS to style them differently.\n // The \"even\" style used to also have tw-bg-neutral-300 (along with even) to create\n // a visual banding effect. That could be added back in if UX changes the decision.\n return cn('banded-row', index % 2 === 0 ? 'even' : 'odd');\n };\n\n const getIndent = (\n groupingState: GroupingState,\n row: Row,\n cell: Cell,\n ) => {\n if (groupingState?.length === 0 || row.depth < cell.column.getGroupedIndex()) return undefined;\n if (row.getIsGrouped()) {\n switch (row.depth) {\n case 1:\n return 'tw-ps-4';\n default:\n return undefined;\n }\n }\n switch (row.depth) {\n case 1:\n return 'tw-ps-8';\n case 2:\n return 'tw-ps-12';\n default:\n return undefined;\n }\n };\n\n return (\n
    \n {!showColumnHeaders && (\n {\n handleSelectChange(value);\n }}\n >\n \n \n \n \n \n {groupingOptions.map((option) => (\n \n {option.label}\n \n ))}\n \n \n \n )}\n \n {showColumnHeaders && (\n \n {table.getHeaderGroups().map((headerGroup) => (\n \n {headerGroup.headers\n .filter((h) => h.column.columnDef.header)\n .map((header) => (\n /* For sticky column headers to work, we probably need to change the default definition of the shadcn Table component. See https://github.com/shadcn-ui/ui/issues/1151 */\n \n {header.isPlaceholder ? undefined : (\n
    \n {header.column.getCanGroup() ? (\n \n {header.column.getIsGrouped() ? `๐Ÿ›‘` : `๐Ÿ‘Š `}\n \n ) : undefined}{' '}\n {flexRender(header.column.columnDef.header, header.getContext())}\n
    \n )}\n
    \n ))}\n
    \n ))}\n
    \n )}\n \n {table.getRowModel().rows.map((row, rowIndex) => {\n const dir: Direction = readDirection();\n return (\n handleRowClick(row, event)}\n >\n {row.getVisibleCells().map((cell) => {\n if (\n cell.getIsPlaceholder() ||\n (cell.column.columnDef.enableGrouping &&\n !cell.getIsGrouped() &&\n (cell.column.columnDef.id !== typeColId || !showSourceColumn))\n )\n return undefined;\n return (\n \n {(() => {\n if (cell.getIsGrouped()) {\n return (\n \n {row.getIsExpanded() && }\n {!row.getIsExpanded() &&\n (dir === 'ltr' ? : )}{' '}\n {flexRender(cell.column.columnDef.cell, cell.getContext())} (\n {row.subRows.length})\n \n );\n }\n\n // if (cell.getIsAggregated()) {\n // flexRender(\n // cell.column.columnDef.aggregatedCell ?? cell.column.columnDef.cell,\n // cell.getContext(),\n // );\n // }\n\n return flexRender(cell.column.columnDef.cell, cell.getContext());\n })()}\n \n );\n })}\n \n );\n })}\n \n
    \n
    \n );\n}\n\nexport default ScriptureResultsViewer;\n","import { getSectionForBook, Section } from 'platform-bible-utils';\n\n/**\n * Filters an array of book IDs to only include books from a specific section\n *\n * @param bookIds Array of book IDs to filter\n * @param section The section to filter by\n * @returns Array of book IDs that belong to the specified section\n */\nexport const getBooksForSection = (bookIds: string[], section: Section) => {\n return bookIds.filter((bookId) => {\n try {\n return getSectionForBook(bookId) === section;\n } catch {\n return false;\n }\n });\n};\n\n/**\n * Checks if all books in a given section are included in the selectedBookIds array\n *\n * @param bookIds Array of all available book IDs\n * @param section The section to check\n * @param selectedBookIds Array of currently selected book IDs\n * @returns True if all books from the specified section are selected, false otherwise\n */\nexport const isSectionFullySelected = (\n bookIds: string[],\n section: Section,\n selectedBookIds: string[],\n) => getBooksForSection(bookIds, section).every((bookId) => selectedBookIds.includes(bookId));\n","import { Button } from '@/components/shadcn-ui/button';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { LanguageStrings, Section } from 'platform-bible-utils';\nimport { getSectionShortName } from '@/components/shared/book.utils';\nimport { getBooksForSection, isSectionFullySelected } from './scope-selector.utils';\n\n/**\n * A button component that represents a scripture section (testament) in the book selector. The\n * button shows a different state when all books in its section are selected and becomes disabled\n * when no books are available in its section.\n */\nfunction SectionButton({\n section,\n availableBookIds,\n selectedBookIds,\n onToggle,\n localizedStrings,\n}: {\n section: Section;\n availableBookIds: string[];\n selectedBookIds: string[];\n onToggle: (section: Section) => void;\n localizedStrings: LanguageStrings;\n}) {\n const isDisabled = getBooksForSection(availableBookIds, section).length === 0;\n\n const sectionOtShortText = localizedStrings['%scripture_section_ot_short%'];\n const sectionNtShortText = localizedStrings['%scripture_section_nt_short%'];\n const sectionDcShortText = localizedStrings['%scripture_section_dc_short%'];\n const sectionExtraShortText = localizedStrings['%scripture_section_extra_short%'];\n\n return (\n onToggle(section)}\n className={cn(\n isSectionFullySelected(availableBookIds, section, selectedBookIds) &&\n !isDisabled &&\n 'tw-bg-primary tw-text-primary-foreground hover:tw-bg-primary/70 hover:tw-text-primary-foreground',\n )}\n disabled={isDisabled}\n >\n {getSectionShortName(\n section,\n sectionOtShortText,\n sectionNtShortText,\n sectionDcShortText,\n sectionExtraShortText,\n )}\n \n );\n}\n\nexport default SectionButton;\n","import { BookItem } from '@/components/shared/book-item.component';\nimport { Badge } from '@/components/shadcn-ui/badge';\nimport { Button } from '@/components/shadcn-ui/button';\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandList,\n CommandSeparator,\n} from '@/components/shadcn-ui/command';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/shadcn-ui/popover';\nimport { Canon } from '@sillsdev/scripture';\nimport { ChevronsUpDown } from 'lucide-react';\nimport { getSectionForBook, LanguageStrings, Section } from 'platform-bible-utils';\nimport {\n getSectionLongName,\n getLocalizedBookName,\n doesBookMatchQuery,\n} from '@/components/shared/book.utils';\nimport { Fragment, MouseEvent, useCallback, useMemo, useRef, useState } from 'react';\nimport { generateCommandValue } from '@/components/shared/book-item.utils';\nimport { getBooksForSection, isSectionFullySelected } from './scope-selector.utils';\nimport SectionButton from './section-button.component';\n\n/** Maximum number of badges to show before collapsing into a \"+X more\" badge */\nconst VISIBLE_BADGES_COUNT = 5;\n/** Maximum number of badges that can be shown without triggering the collapse */\nconst MAX_VISIBLE_BADGES = 6;\n\ntype BookSelectorProps = {\n /**\n * Information about available books, formatted as a 123 character long string as defined in a\n * projects BooksPresent setting\n */\n availableBookInfo: string;\n /** Array of currently selected book IDs */\n selectedBookIds: string[];\n /** Callback function that is executed when the book selection changes */\n onChangeSelectedBookIds: (books: string[]) => void;\n /** Object containing the localized strings for the component */\n localizedStrings: LanguageStrings;\n /**\n * Optional map of localized book IDs/short names and full names. Key is the (English) book ID,\n * value contains localized versions of the ID and full book name\n */\n localizedBookNames?: Map;\n};\n\n/**\n * A component for selecting multiple books from the Bible canon. It provides:\n *\n * - Quick selection buttons for major sections (OT, NT, DC, Extra)\n * - A searchable dropdown with all available books\n * - Support for shift-click range selection\n * - Visual feedback with badges showing selected books\n */\nexport function BookSelector({\n availableBookInfo,\n selectedBookIds,\n onChangeSelectedBookIds,\n localizedStrings,\n localizedBookNames,\n}: BookSelectorProps) {\n const booksSelectedText = localizedStrings['%webView_book_selector_books_selected%'];\n const selectBooksText = localizedStrings['%webView_book_selector_select_books%'];\n const searchBooksText = localizedStrings['%webView_book_selector_search_books%'];\n const selectAllText = localizedStrings['%webView_book_selector_select_all%'];\n const clearAllText = localizedStrings['%webView_book_selector_clear_all%'];\n const noBookFoundText = localizedStrings['%webView_book_selector_no_book_found%'];\n const moreText = localizedStrings['%webView_book_selector_more%'];\n\n const { otLong, ntLong, dcLong, extraLong } = {\n otLong: localizedStrings?.['%scripture_section_ot_long%'],\n ntLong: localizedStrings?.['%scripture_section_nt_long%'],\n dcLong: localizedStrings?.['%scripture_section_dc_long%'],\n extraLong: localizedStrings?.['%scripture_section_extra_long%'],\n };\n\n const [isBooksSelectorOpen, setIsBooksSelectorOpen] = useState(false);\n const [inputValue, setInputValue] = useState('');\n const lastSelectedBookRef = useRef(undefined);\n const lastKeyEventShiftKey = useRef(false);\n\n if (availableBookInfo.length !== Canon.allBookIds.length) {\n throw new Error('availableBookInfo length must match Canon.allBookIds length');\n }\n\n const availableBooksIds = useMemo(() => {\n return Canon.allBookIds.filter(\n (bookId, index) =>\n availableBookInfo[index] === '1' && !Canon.isObsolete(Canon.bookIdToNumber(bookId)),\n );\n }, [availableBookInfo]);\n\n const filteredBooksBySection = useMemo(() => {\n if (!inputValue.trim()) {\n const allBooks: Record = {\n [Section.OT]: [],\n [Section.NT]: [],\n [Section.DC]: [],\n [Section.Extra]: [],\n };\n\n availableBooksIds.forEach((bookId) => {\n const section = getSectionForBook(bookId);\n allBooks[section].push(bookId);\n });\n\n return allBooks;\n }\n\n const filteredBooks = availableBooksIds.filter((bookId) =>\n doesBookMatchQuery(bookId, inputValue, localizedBookNames),\n );\n\n const matchingBooks: Record = {\n [Section.OT]: [],\n [Section.NT]: [],\n [Section.DC]: [],\n [Section.Extra]: [],\n };\n\n filteredBooks.forEach((bookId) => {\n const section = getSectionForBook(bookId);\n matchingBooks[section].push(bookId);\n });\n\n return matchingBooks;\n }, [availableBooksIds, inputValue, localizedBookNames]);\n\n const toggleBook = useCallback(\n (bookId: string, shiftKey = false) => {\n if (!shiftKey || !lastSelectedBookRef.current) {\n onChangeSelectedBookIds(\n selectedBookIds.includes(bookId)\n ? selectedBookIds.filter((id) => id !== bookId)\n : [...selectedBookIds, bookId],\n );\n lastSelectedBookRef.current = bookId;\n return;\n }\n\n const lastIndex = availableBooksIds.findIndex((id) => id === lastSelectedBookRef.current);\n const currentIndex = availableBooksIds.findIndex((id) => id === bookId);\n\n if (lastIndex === -1 || currentIndex === -1) return;\n\n const [startIndex, endIndex] = [\n Math.min(lastIndex, currentIndex),\n Math.max(lastIndex, currentIndex),\n ];\n const booksInRange = availableBooksIds.slice(startIndex, endIndex + 1).map((id) => id);\n\n onChangeSelectedBookIds(\n selectedBookIds.includes(bookId)\n ? selectedBookIds.filter((shortname) => !booksInRange.includes(shortname))\n : [...new Set([...selectedBookIds, ...booksInRange])],\n );\n },\n [selectedBookIds, onChangeSelectedBookIds, availableBooksIds],\n );\n\n const handleKeyboardSelect = (bookId: string) => {\n toggleBook(bookId, lastKeyEventShiftKey.current);\n lastKeyEventShiftKey.current = false;\n };\n\n const handleMouseDown = (event: MouseEvent, bookId: string) => {\n event.preventDefault();\n toggleBook(bookId, event.shiftKey);\n };\n\n const toggleSection = useCallback(\n (section: Section) => {\n const sectionBooks = getBooksForSection(availableBooksIds, section).map((bookId) => bookId);\n onChangeSelectedBookIds(\n isSectionFullySelected(availableBooksIds, section, selectedBookIds)\n ? selectedBookIds.filter((shortname) => !sectionBooks.includes(shortname))\n : [...new Set([...selectedBookIds, ...sectionBooks])],\n );\n },\n [selectedBookIds, onChangeSelectedBookIds, availableBooksIds],\n );\n\n const handleSelectAll = () => {\n onChangeSelectedBookIds(availableBooksIds.map((bookId) => bookId));\n };\n\n const handleClearAll = () => {\n onChangeSelectedBookIds([]);\n };\n\n return (\n
    \n
    \n {Object.values(Section).map((section) => {\n return (\n \n );\n })}\n
    \n\n {\n setIsBooksSelectorOpen(open);\n if (!open) {\n setInputValue(''); // Reset search when closing\n }\n }}\n >\n \n \n {selectedBookIds.length > 0\n ? `${booksSelectedText}: ${selectedBookIds.length}`\n : selectBooksText}\n \n \n \n \n {\n if (e.key === 'Enter') {\n // Store shift state in a ref that will be used by onSelect\n lastKeyEventShiftKey.current = e.shiftKey;\n }\n }}\n >\n \n
    \n \n \n
    \n \n {noBookFoundText}\n {Object.values(Section).map((section, index) => {\n const sectionBooks = filteredBooksBySection[section];\n\n if (sectionBooks.length === 0) return undefined;\n\n return (\n \n \n {sectionBooks.map((bookId) => (\n handleKeyboardSelect(bookId)}\n onMouseDown={(event) => handleMouseDown(event, bookId)}\n section={getSectionForBook(bookId)}\n showCheck\n localizedBookNames={localizedBookNames}\n commandValue={generateCommandValue(bookId, localizedBookNames)}\n className=\"tw-flex tw-items-center\"\n />\n ))}\n \n {index < Object.values(Section).length - 1 && }\n \n );\n })}\n \n \n
    \n \n\n {selectedBookIds.length > 0 && (\n
    \n {selectedBookIds\n .slice(\n 0,\n selectedBookIds.length === MAX_VISIBLE_BADGES\n ? MAX_VISIBLE_BADGES\n : VISIBLE_BADGES_COUNT,\n )\n .map((bookId) => (\n \n {getLocalizedBookName(bookId, localizedBookNames)}\n \n ))}\n {selectedBookIds.length > MAX_VISIBLE_BADGES && (\n {`+${selectedBookIds.length - VISIBLE_BADGES_COUNT} ${moreText}`}\n )}\n
    \n )}\n
    \n );\n}\n","import { BookSelector } from '@/components/advanced/scope-selector/book-selector.component';\nimport { Label } from '@/components/shadcn-ui/label';\nimport { RadioGroup, RadioGroupItem } from '@/components/shadcn-ui/radio-group';\nimport { Scope } from '@/components/utils/scripture.util';\nimport { LocalizedStringValue } from 'platform-bible-utils';\n\n/**\n * Object containing all keys used for localization in this component. If you're using this\n * component in an extension, you can pass it into the useLocalizedStrings hook to easily obtain the\n * localized strings and pass them into the localizedStrings prop of this component\n */\nexport const SCOPE_SELECTOR_STRING_KEYS = Object.freeze([\n '%webView_scope_selector_selected_text%',\n '%webView_scope_selector_current_verse%',\n '%webView_scope_selector_current_chapter%',\n '%webView_scope_selector_current_book%',\n '%webView_scope_selector_choose_books%',\n '%webView_scope_selector_scope%',\n '%webView_scope_selector_select_books%',\n '%webView_book_selector_books_selected%',\n '%webView_book_selector_select_books%',\n '%webView_book_selector_search_books%',\n '%webView_book_selector_select_all%',\n '%webView_book_selector_clear_all%',\n '%webView_book_selector_no_book_found%',\n '%webView_book_selector_more%',\n '%scripture_section_ot_long%',\n '%scripture_section_ot_short%',\n '%scripture_section_nt_long%',\n '%scripture_section_nt_short%',\n '%scripture_section_dc_long%',\n '%scripture_section_dc_short%',\n '%scripture_section_extra_long%',\n '%scripture_section_extra_short%',\n] as const);\n\n/** Type definition for the localized strings used in this component */\nexport type ScopeSelectorLocalizedStrings = {\n [localizedInventoryKey in (typeof SCOPE_SELECTOR_STRING_KEYS)[number]]?: LocalizedStringValue;\n};\n\n/**\n * Gets the localized value for the provided key\n *\n * @param strings Object containing localized string\n * @param key Key for a localized string\n * @returns The localized value for the provided key, if available. Returns the key if no localized\n * value is available\n */\nconst localizeString = (\n strings: ScopeSelectorLocalizedStrings,\n key: keyof ScopeSelectorLocalizedStrings,\n) => {\n return strings[key] ?? key;\n};\n\n/** Props for configuring the ScopeSelector component */\ninterface ScopeSelectorProps {\n /** The current scope selection */\n scope: Scope;\n\n /**\n * Optional array of scopes that should be available in the selector. If not provided, all scopes\n * will be shown as defined in the Scope type\n */\n availableScopes?: Scope[];\n\n /** Callback function that is executed when the user changes the scope selection */\n onScopeChange: (scope: Scope) => void;\n\n /**\n * Information about available books, formatted as a 123 character long string as defined in a\n * projects BooksPresent setting\n */\n availableBookInfo: string;\n\n /** Array of currently selected book IDs */\n selectedBookIds: string[];\n\n /** Callback function that is executed when the user changes the book selection */\n onSelectedBookIdsChange: (books: string[]) => void;\n\n /**\n * Object with all localized strings that the component needs to work well across multiple\n * languages. When using this component with Platform.Bible, you can import\n * `SCOPE_SELECTOR_STRING_KEYS` from this library, pass it in to the Platform's localization hook,\n * and pass the localized keys that are returned by the hook into this prop.\n */\n localizedStrings: ScopeSelectorLocalizedStrings;\n /**\n * Optional map of localized book IDs/short names and full names. Key is the (English) book ID,\n * value contains localized versions of the ID and full book name\n */\n localizedBookNames?: Map;\n /** Optional ID that is applied to the root element of this component */\n id?: string;\n}\n\n/**\n * A component that allows users to select the scope of their search or operation. Available scopes\n * are defined in the Scope type. When 'selectedBooks' is chosen as the scope, a BookSelector\n * component is displayed to allow users to choose specific books.\n */\nexport function ScopeSelector({\n scope,\n availableScopes,\n onScopeChange,\n availableBookInfo,\n selectedBookIds,\n onSelectedBookIdsChange,\n localizedStrings,\n localizedBookNames,\n id,\n}: ScopeSelectorProps) {\n const selectedTextText = localizeString(\n localizedStrings,\n '%webView_scope_selector_selected_text%',\n );\n const currentVerseText = localizeString(\n localizedStrings,\n '%webView_scope_selector_current_verse%',\n );\n const currentChapterText = localizeString(\n localizedStrings,\n '%webView_scope_selector_current_chapter%',\n );\n const currentBookText = localizeString(localizedStrings, '%webView_scope_selector_current_book%');\n const chooseBooksText = localizeString(localizedStrings, '%webView_scope_selector_choose_books%');\n const scopeText = localizeString(localizedStrings, '%webView_scope_selector_scope%');\n const selectBooksText = localizeString(localizedStrings, '%webView_scope_selector_select_books%');\n\n const SCOPE_OPTIONS: Array<{ value: Scope; label: string; id: string }> = [\n { value: 'selectedText', label: selectedTextText, id: 'scope-selected-text' },\n { value: 'verse', label: currentVerseText, id: 'scope-verse' },\n { value: 'chapter', label: currentChapterText, id: 'scope-chapter' },\n { value: 'book', label: currentBookText, id: 'scope-book' },\n { value: 'selectedBooks', label: chooseBooksText, id: 'scope-selected' },\n ];\n\n const displayedScopes = availableScopes\n ? SCOPE_OPTIONS.filter((option) => availableScopes.includes(option.value))\n : SCOPE_OPTIONS;\n\n return (\n
    \n
    \n \n \n {displayedScopes.map(({ value, label, id: scopeId }) => (\n
    \n \n \n
    \n ))}\n \n
    \n\n {scope === 'selectedBooks' && (\n
    \n \n \n
    \n )}\n
    \n );\n}\n\nexport default ScopeSelector;\n","import {\n getLocalizeKeyForScrollGroupId,\n LanguageStrings,\n ScrollGroupId,\n} from 'platform-bible-utils';\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@/components/shadcn-ui/select';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\nimport { cn } from '@/utils/shadcn-ui.util';\n\nconst DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS = {\n [getLocalizeKeyForScrollGroupId('undefined')]: 'ร˜',\n [getLocalizeKeyForScrollGroupId(0)]: 'A',\n [getLocalizeKeyForScrollGroupId(1)]: 'B',\n [getLocalizeKeyForScrollGroupId(2)]: 'C',\n [getLocalizeKeyForScrollGroupId(3)]: 'D',\n [getLocalizeKeyForScrollGroupId(4)]: 'E',\n [getLocalizeKeyForScrollGroupId(5)]: 'F',\n [getLocalizeKeyForScrollGroupId(6)]: 'G',\n [getLocalizeKeyForScrollGroupId(7)]: 'H',\n [getLocalizeKeyForScrollGroupId(8)]: 'I',\n [getLocalizeKeyForScrollGroupId(9)]: 'J',\n [getLocalizeKeyForScrollGroupId(10)]: 'K',\n [getLocalizeKeyForScrollGroupId(11)]: 'L',\n [getLocalizeKeyForScrollGroupId(12)]: 'M',\n [getLocalizeKeyForScrollGroupId(13)]: 'N',\n [getLocalizeKeyForScrollGroupId(14)]: 'O',\n [getLocalizeKeyForScrollGroupId(15)]: 'P',\n [getLocalizeKeyForScrollGroupId(16)]: 'Q',\n [getLocalizeKeyForScrollGroupId(17)]: 'R',\n [getLocalizeKeyForScrollGroupId(18)]: 'S',\n [getLocalizeKeyForScrollGroupId(19)]: 'T',\n [getLocalizeKeyForScrollGroupId(20)]: 'U',\n [getLocalizeKeyForScrollGroupId(21)]: 'V',\n [getLocalizeKeyForScrollGroupId(22)]: 'W',\n [getLocalizeKeyForScrollGroupId(23)]: 'X',\n [getLocalizeKeyForScrollGroupId(24)]: 'Y',\n [getLocalizeKeyForScrollGroupId(25)]: 'Z',\n};\n\nexport type ScrollGroupSelectorProps = {\n /**\n * List of scroll group ids to show to the user. Either a `ScrollGroupId` or `undefined` for no\n * scroll group\n */\n availableScrollGroupIds: (ScrollGroupId | undefined)[];\n /** Currently selected scroll group id. `undefined` for no scroll group */\n scrollGroupId: ScrollGroupId | undefined;\n /** Callback function run when the user tries to change the scroll group id */\n onChangeScrollGroupId: (newScrollGroupId: ScrollGroupId | undefined) => void;\n /**\n * Localized strings to use for displaying scroll group ids. Must be an object whose keys are\n * `getLocalizeKeyForScrollGroupId(scrollGroupId)` for all scroll group ids (and `undefined` if\n * included) in {@link ScrollGroupSelectorProps.availableScrollGroupIds} and whose values are the\n * localized strings to use for those scroll group ids.\n *\n * Defaults to English localizations of English alphabet for scroll groups 0-25 (e.g. 0 is A) and\n * ร˜ for `undefined`. Will fill in any that are not provided with these English localizations.\n * Also, if any values match the keys, the English localization will be used. This is useful in\n * case you want to pass in a temporary version of the localized strings while your localized\n * strings load.\n *\n * @example\n *\n * ```typescript\n * const myScrollGroupIdLocalizedStrings = {\n * [getLocalizeKeyForScrollGroupId('undefined')]: 'ร˜',\n * [getLocalizeKeyForScrollGroupId(0)]: 'A',\n * [getLocalizeKeyForScrollGroupId(1)]: 'B',\n * [getLocalizeKeyForScrollGroupId(2)]: 'C',\n * [getLocalizeKeyForScrollGroupId(3)]: 'D',\n * [getLocalizeKeyForScrollGroupId(4)]: 'E',\n * };\n * ```\n *\n * @example\n *\n * ```tsx\n * const availableScrollGroupIds = [undefined, 0, 1, 2, 3, 4];\n *\n * const localizeKeys = getLocalizeKeysForScrollGroupIds();\n *\n * const [localizedStrings] = useLocalizedStrings(localizeKeys);\n *\n * ...\n *\n * \n * ```\n */\n localizedStrings?: LanguageStrings;\n\n /** Size of the scroll group dropdown button. Defaults to 'sm' */\n size?: 'default' | 'sm' | 'lg' | 'icon';\n\n /** Additional css classes to help with unique styling */\n className?: string;\n\n /** Optional id for the select element */\n id?: string;\n};\n\n/** Selector component for choosing a scroll group */\nexport function ScrollGroupSelector({\n availableScrollGroupIds,\n scrollGroupId,\n onChangeScrollGroupId,\n localizedStrings = {},\n size = 'sm',\n className,\n id,\n}: ScrollGroupSelectorProps) {\n const localizedStringsDefaulted = {\n ...DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS,\n ...Object.fromEntries(\n Object.entries(localizedStrings).map(\n ([localizedStringKey, localizedStringValue]: [string, string]) => [\n localizedStringKey,\n localizedStringKey === localizedStringValue &&\n localizedStringKey in DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS\n ? DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS[localizedStringKey]\n : localizedStringValue,\n ],\n ),\n ),\n };\n\n const dir: Direction = readDirection();\n\n return (\n \n onChangeScrollGroupId(\n newScrollGroupString === 'undefined' ? undefined : parseInt(newScrollGroupString, 10),\n )\n }\n >\n \n \n \n \n {availableScrollGroupIds.map((scrollGroupOptionId) => (\n \n {localizedStringsDefaulted[getLocalizeKeyForScrollGroupId(scrollGroupOptionId)]}\n \n ))}\n \n \n );\n}\n\nexport default ScrollGroupSelector;\n","import { PropsWithChildren } from 'react';\nimport { Separator } from '@/components/shadcn-ui/separator';\n\n/** Props for the SettingsList component, currently just children */\ntype SettingsListProps = PropsWithChildren;\n\n/**\n * SettingsList component is a wrapper for list items. Rendered with a formatted div\n *\n * @deprecated Jul 18 2025. This component is no longer supported or tested. Use of this component\n * is discouraged and it may be removed in the future.\n * @param children To populate the list with\n * @returns Formatted div encompassing the children\n */\nexport function SettingsList({ children }: SettingsListProps) {\n return
    {children}
    ;\n}\n\n/** Props for SettingsListItem component */\ntype SettingsListItemProps = PropsWithChildren & {\n /** Primary text of the list item */\n primary: string;\n\n /** Optional text of the list item */\n secondary?: string | undefined;\n\n /** Optional boolean to display a message if the children aren't loaded yet. Defaults to false */\n isLoading?: boolean;\n\n /** Optional message to display if isLoading */\n loadingMessage?: string;\n};\n\n/**\n * SettingsListItem component is a common list item. Rendered with a formatted div\n *\n * @deprecated Jul 18 2025. This component is no longer supported or tested. Use of this component\n * is discouraged and it may be removed in the future.\n * @param SettingsListItemProps\n * @returns Formatted div encompassing the list item content\n */\nexport function SettingsListItem({\n primary,\n secondary,\n children,\n isLoading = false,\n loadingMessage,\n}: SettingsListItemProps) {\n return (\n
    \n
    \n

    {primary}

    \n

    \n {secondary}\n

    \n
    \n\n {isLoading ? (\n

    {loadingMessage}

    \n ) : (\n
    {children}
    \n )}\n
    \n );\n}\n\n/** Props for SettingsListHeader component */\ntype SettingsListHeaderProps = {\n /** The primary text of the list header */\n primary: string;\n\n /** Optional secondary text of the list header */\n secondary?: string | undefined;\n\n /** Optional boolean to include a separator underneath the secondary text. Defaults to false */\n includeSeparator?: boolean;\n};\n\n/**\n * SettingsListHeader component displays text above the list\n *\n * @deprecated Jul 18 2025. This component is no longer supported or tested. Use of this component\n * is discouraged and it may be removed in the future.\n * @param SettingsListHeaderProps\n * @returns Formatted div with list header content\n */\nexport function SettingsListHeader({\n primary,\n secondary,\n includeSeparator = false,\n}: SettingsListHeaderProps) {\n return (\n
    \n
    \n

    {primary}

    \n

    {secondary}

    \n
    \n {includeSeparator ? : ''}\n
    \n );\n}\n","import { GroupsInMultiColumnMenu, Localized } from 'platform-bible-utils';\n\n/**\n * Function that looks up the key of a sub-menu group using the value of it's `menuItem` property.\n *\n * @example\n *\n * ```ts\n * const groups = {\n * 'platform.subMenu': { menuItem: 'platform.subMenuId', order: 1 },\n * 'platform.subSubMenu': { menuItem: 'platform.subSubMenuId', order: 2 },\n * };\n * const id = 'platform.subMenuId';\n * const groupKey = getSubMenuGroupKeyForMenuItemId(groups, id);\n * console.log(groupKey); // Output: 'platform.subMenu'\n * ```\n *\n * @param groups The JSON Object containing the group definitions\n * @param id The value of the `menuItem` property of the group to look up\n * @returns The key of the group that has the `menuItem` property with the value of `id` or\n * `undefined` if no such group exists.\n */\nexport function getSubMenuGroupKeyForMenuItemId(\n groups: Localized,\n id: string,\n): string | undefined {\n return Object.entries(groups).find(\n ([, value]) => 'menuItem' in value && value.menuItem === id,\n )?.[0];\n}\n","import { cn } from '@/utils/shadcn-ui.util';\n\ntype MenuItemIconProps = {\n /** The icon to display */\n icon: string;\n /** The label of the menu item */\n menuLabel: string;\n /** Whether the icon is leading or trailing */\n leading?: boolean;\n};\n\nfunction MenuItemIcon({ icon, menuLabel, leading }: MenuItemIconProps) {\n return icon ? (\n \n ) : undefined;\n}\n\nexport default MenuItemIcon;\n","import {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuPortal,\n DropdownMenuSeparator,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n} from '@/components/shadcn-ui/dropdown-menu';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@/components/shadcn-ui/tooltip';\nimport { MenuIcon } from 'lucide-react';\nimport {\n GroupsInMultiColumnMenu,\n Localized,\n MenuItemContainingCommand,\n MenuItemContainingSubmenu,\n MultiColumnMenu,\n} from 'platform-bible-utils';\nimport { Fragment, ReactNode } from 'react';\nimport { Button } from '@/components/shadcn-ui/button';\nimport { getSubMenuGroupKeyForMenuItemId } from './menu.util';\nimport { SelectMenuItemHandler } from './platform-menubar.component';\nimport MenuItemIcon from './menu-icon.component';\n\nconst getGroupContent = (\n groups: Localized,\n items: Localized<(MenuItemContainingCommand | MenuItemContainingSubmenu)[]>,\n columnOrSubMenuKey: string | undefined,\n onSelectMenuItem: SelectMenuItemHandler,\n) => {\n if (!columnOrSubMenuKey) return undefined;\n\n const sortedGroupsForColumn = Object.entries(groups)\n .filter(\n ([key, group]) =>\n ('column' in group && group.column === columnOrSubMenuKey) || key === columnOrSubMenuKey,\n )\n .sort(([, a], [, b]) => a.order - b.order);\n\n return sortedGroupsForColumn.flatMap(([groupKey]) => {\n const groupItems = items\n .filter((item) => item.group === groupKey)\n .sort((a, b) => a.order - b.order)\n .map((item: Localized) => {\n return (\n \n \n {'command' in item ? (\n {\n // Since the item has a command, we know it is a MenuItemContainingCommand.\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n onSelectMenuItem(item as MenuItemContainingCommand);\n }}\n >\n {item.iconPathBefore && (\n \n )}\n {item.label}\n {item.iconPathAfter && (\n \n )}\n \n ) : (\n \n {item.label}\n\n \n \n {getGroupContent(\n groups,\n items,\n getSubMenuGroupKeyForMenuItemId(groups, item.id),\n onSelectMenuItem,\n )}\n \n \n \n )}\n \n {item.tooltip && {item.tooltip}}\n \n );\n });\n\n return groupItems;\n });\n};\n\nexport type TabDropdownMenuProps = {\n /** The handler to use for menu commands */\n onSelectMenuItem: SelectMenuItemHandler;\n\n /** The menu data to show on the dropdown menu */\n menuData: Localized;\n\n /** Defines a string value that labels the current element */\n tabLabel: string;\n\n /** Optional icon for the dropdown menu trigger. Defaults to hamburger icon. */\n icon?: ReactNode;\n\n /** Additional css class(es) to help with unique styling of the tab dropdown menu */\n className?: string;\n\n /** Style variant for the app menubar component. */\n variant?: 'default' | 'muted';\n\n buttonVariant?: 'default' | 'ghost' | 'outline' | 'secondary';\n\n /** Optional unique identifier */\n id?: string;\n};\n\n/**\n * Dropdown menu designed to be used with Platform.Bible menu data. Column headers are ignored.\n * Column data is separated by a horizontal divider, so groups are not distinguishable. Tooltips are\n * displayed on hovering over menu items, if a tooltip is defined for them.\n *\n * A child component can be passed in to show as an icon on the menu trigger button.\n */\nexport default function TabDropdownMenu({\n onSelectMenuItem,\n menuData,\n tabLabel,\n icon,\n className,\n variant,\n buttonVariant = 'ghost',\n id,\n}: TabDropdownMenuProps) {\n return (\n \n \n \n \n \n {Object.entries(menuData.columns)\n .filter(([, column]) => typeof column === 'object')\n .sort(([, a], [, b]) => {\n if (typeof a === 'boolean' || typeof b === 'boolean') return 0;\n return a.order - b.order;\n })\n .map(([columnKey], index, array) => (\n \n \n \n {getGroupContent(menuData.groups, menuData.items, columnKey, onSelectMenuItem)}\n \n \n\n {index < array.length - 1 && }\n \n ))}\n \n \n );\n}\n","import { Localized, MultiColumnMenu } from 'platform-bible-utils';\nimport React, { PropsWithChildren, ReactNode } from 'react';\nimport { SelectMenuItemHandler } from '../menus/platform-menubar.component';\n\nexport type TabToolbarCommonProps = {\n /**\n * The handler to use for toolbar item commands related to the project menu. Here is a basic\n * example of how to create this:\n *\n * @example\n *\n * ```tsx\n * const projectMenuCommandHandler: SelectMenuItemHandler = async (selectedMenuItem) => {\n * const commandName = selectedMenuItem.command;\n * try {\n * // Assert the more specific type. Assert the more specific type. The menu data should\n * // specify a valid command name here. If not, the error will be caught.\n * // eslint-disable-next-line no-type-assertion/no-type-assertion\n * await papi.commands.sendCommand(commandName as CommandNames);\n * } catch (e) {\n * throw new Error(\n * `handleMenuCommand error: command: ${commandName}. ${JSON.stringify(e)}`,\n * );\n * }\n * };\n * ```\n */\n onSelectProjectMenuItem: SelectMenuItemHandler;\n\n /**\n * Menu data that is used to populate the Menubar component for the project menu. In an extension,\n * the menu data comes from menus.json in the contributions folder. To access that info, use\n * useMemo to get the WebViewMenu.\n */\n projectMenuData?: Localized;\n\n /** Optional unique identifier */\n id?: string;\n\n /** Additional css classes to help with unique styling of the extensible toolbar */\n className?: string;\n\n /** Icon that will be displayed on the Menu Button. Defaults to the hamburger menu icon. */\n menuButtonIcon?: ReactNode;\n};\n\nexport type TabToolbarContainerProps = PropsWithChildren<{\n /** Optional unique identifier */\n id?: string;\n /** Additional css classes to help with unique styling of the extensible toolbar */\n className?: string;\n}>;\n\n/** Wrapper that allows consistent styling for both TabToolbar and TabFloatingMenu. */\nexport const TabToolbarContainer = React.forwardRef(\n ({ id, className, children }, ref) => (\n \n {children}\n \n ),\n);\n\nexport default TabToolbarContainer;\n","import { ReactNode } from 'react';\nimport { Localized, MultiColumnMenu } from 'platform-bible-utils';\nimport { Menu, EllipsisVertical } from 'lucide-react';\nimport TabDropdownMenu from '../menus/tab-dropdown-menu.component';\nimport { SelectMenuItemHandler } from '../menus/platform-menubar.component';\nimport { TabToolbarCommonProps, TabToolbarContainer } from './tab-toolbar-container.component';\n\nexport type TabToolbarProps = TabToolbarCommonProps & {\n /**\n * The handler to use for toolbar item commands related to the tab view menu. Here is a basic\n * example of how to create this from the hello-rock3 extension:\n *\n * @example\n *\n * ```tsx\n * const projectMenuCommandHandler: SelectMenuItemHandler = async (selectedMenuItem) => {\n * const commandName = selectedMenuItem.command;\n * try {\n * // Assert the more specific type. Assert the more specific type. The menu data should\n * // specify a valid command name here. If not, the error will be caught.\n * // eslint-disable-next-line no-type-assertion/no-type-assertion\n * await papi.commands.sendCommand(commandName as CommandNames);\n * } catch (e) {\n * throw new Error(\n * `handleMenuCommand error: command: ${commandName}. ${JSON.stringify(e)}`,\n * );\n * }\n * };\n * ```\n */\n onSelectViewInfoMenuItem: SelectMenuItemHandler;\n\n /** Menu data that is used to populate the Menubar component for the view info menu */\n tabViewMenuData?: Localized;\n\n /**\n * Toolbar children to be put at the start of the the toolbar after the project menu icon (left\n * side in ltr, right side in rtl). Recommended for inner navigation.\n */\n startAreaChildren?: ReactNode;\n\n /** Toolbar children to be put in the center area of the the toolbar. Recommended for tools. */\n centerAreaChildren?: ReactNode;\n\n /**\n * Toolbar children to be put at the end of the the toolbar before the tab view menu icon (right\n * side in ltr, left side in rtl). Recommended for secondary tools and view options.\n */\n endAreaChildren?: ReactNode;\n};\n\n/**\n * Toolbar that holds the project menu icon on one side followed by three different areas/categories\n * for toolbar icons followed by an optional view info menu icon. See the Tab Floating Menu Button\n * component for a menu component that takes up less screen real estate yet is always visible.\n */\nexport function TabToolbar({\n onSelectProjectMenuItem,\n onSelectViewInfoMenuItem,\n projectMenuData,\n tabViewMenuData,\n id,\n className,\n startAreaChildren,\n centerAreaChildren,\n endAreaChildren,\n menuButtonIcon,\n}: TabToolbarProps) {\n return (\n \n {projectMenuData && (\n }\n buttonVariant=\"ghost\"\n />\n )}\n {startAreaChildren && (\n
    \n {startAreaChildren}\n
    \n )}\n {centerAreaChildren && (\n
    \n {centerAreaChildren}\n
    \n )}\n
    \n {tabViewMenuData && (\n }\n className=\"tw-h-full\"\n />\n )}\n {endAreaChildren}\n
    \n
    \n );\n}\n\nexport default TabToolbar;\n","import TabDropdownMenu from '../menus/tab-dropdown-menu.component';\nimport { TabToolbarCommonProps, TabToolbarContainer } from './tab-toolbar-container.component';\n\n/**\n * Renders a TabDropdownMenu with a trigger button that looks like the menuButtonIcon or like the\n * default of three stacked horizontal lines (aka the hamburger). The menu \"floats\" over the content\n * so it is always visible. When clicked, it displays a dropdown menu with the projectMenuData.\n */\nexport function TabFloatingMenu({\n onSelectProjectMenuItem,\n projectMenuData,\n id,\n className,\n menuButtonIcon,\n}: TabToolbarCommonProps) {\n return (\n \n {projectMenuData && (\n \n )}\n \n );\n}\n\nexport default TabFloatingMenu;\n","// adapted from: https://github.com/shadcn-ui/ui/discussions/752\n\n'use client';\n\nimport { TabsContentProps, TabsListProps, TabsTriggerProps } from '@/components/shadcn-ui/tabs';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport * as TabsPrimitive from '@radix-ui/react-tabs';\nimport React from 'react';\n\nexport type VerticalTabsProps = React.ComponentPropsWithoutRef & {\n className?: string;\n};\n\nexport type LeftTabsTriggerProps = TabsTriggerProps & {\n value: string;\n ref?: React.Ref;\n};\n\n/**\n * Tabs components provide a set of layered sections of contentโ€”known as tab panelsโ€“that are\n * displayed one at a time. These components are built on Radix UI primitives and styled with Shadcn\n * UI. See Shadcn UI Documentation: https://ui.shadcn.com/docs/components/tabs See Radix UI\n * Documentation: https://www.radix-ui.com/primitives/docs/components/tabs\n */\nexport const VerticalTabs = React.forwardRef<\n React.ElementRef,\n VerticalTabsProps\n>(({ className, ...props }, ref) => {\n const dir: Direction = readDirection();\n return (\n \n );\n});\n\nVerticalTabs.displayName = TabsPrimitive.List.displayName;\n\n/** @inheritdoc VerticalTabs */\nexport const VerticalTabsList = React.forwardRef<\n React.ElementRef,\n TabsListProps\n>(({ className, ...props }, ref) => (\n \n));\nVerticalTabsList.displayName = TabsPrimitive.List.displayName;\n\n/** @inheritdoc VerticalTabs */\nexport const VerticalTabsTrigger = React.forwardRef<\n React.ElementRef,\n LeftTabsTriggerProps\n>(({ className, ...props }, ref) => (\n \n));\n\n/** @inheritdoc VerticalTabs */\nexport const VerticalTabsContent = React.forwardRef<\n React.ElementRef,\n TabsContentProps\n>(({ className, ...props }, ref) => (\n \n));\nVerticalTabsContent.displayName = TabsPrimitive.Content.displayName;\n","import { SearchBar } from '@/components/basics/search-bar.component';\nimport {\n VerticalTabs,\n VerticalTabsContent,\n VerticalTabsList,\n VerticalTabsTrigger,\n} from '@/components/basics/tabs-vertical';\nimport { ReactNode } from 'react';\n\nexport type TabKeyValueContent = {\n key: string;\n value: string;\n content: ReactNode;\n};\n\nexport type TabNavigationContentSearchProps = {\n /** List of values and keys for each tab this component should provide */\n tabList: TabKeyValueContent[];\n\n /** The search query in the search bar */\n searchValue: string;\n\n /** Handler to run when the value of the search bar changes */\n onSearch: (searchQuery: string) => void;\n\n /** Optional placeholder for the search bar */\n searchPlaceholder?: string;\n\n /** Optional title to include in the header */\n headerTitle?: string;\n\n /** Optional className to modify the search input */\n searchClassName?: string;\n\n /** Optional id for the root element */\n id?: string;\n};\n\n/**\n * TabNavigationContentSearch component provides a vertical tab navigation interface with a search\n * bar at the top. This component allows users to filter content within tabs based on a search\n * query.\n *\n * @param {TabNavigationContentSearchProps} props\n * @param {TabKeyValueContent[]} props.tabList - List of objects containing keys, values, and\n * content for each tab to be displayed.\n * @param {string} props.searchValue - The current value of the search input.\n * @param {function} props.onSearch - Callback function called when the search input changes;\n * receives the new search query as an argument.\n * @param {string} [props.searchPlaceholder] - Optional placeholder text for the search input.\n * @param {string} [props.headerTitle] - Optional title to display above the search input.\n * @param {string} [props.searchClassName] - Optional CSS class name to apply custom styles to the\n * search input.\n * @param {string} [props.id] - Optional id for the root element.\n */\nexport function TabNavigationContentSearch({\n tabList,\n searchValue,\n onSearch,\n searchPlaceholder,\n headerTitle,\n searchClassName,\n id,\n}: TabNavigationContentSearchProps) {\n return (\n
    \n
    \n {headerTitle ?

    {headerTitle}

    : ''}\n \n
    \n \n \n {tabList.map((tab) => (\n \n {tab.value}\n \n ))}\n \n {tabList.map((tab) => (\n \n {tab.content}\n \n ))}\n \n
    \n );\n}\n\nexport default TabNavigationContentSearch;\n","import {\n MenuContext,\n MenuContextProps,\n menuVariants,\n useMenuContext,\n} from '@/context/menu.context';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport * as MenubarPrimitive from '@radix-ui/react-menubar';\nimport { Check, ChevronRight, Circle } from 'lucide-react';\nimport React from 'react';\n\nfunction MenubarMenu({ ...props }: React.ComponentProps) {\n return ;\n}\n\nfunction MenubarGroup({ ...props }: React.ComponentProps) {\n return ;\n}\n\nfunction MenubarPortal({ ...props }: React.ComponentProps) {\n return ;\n}\n\nfunction MenubarRadioGroup({ ...props }: React.ComponentProps) {\n return ;\n}\n\nfunction MenubarSub({ ...props }: React.ComponentProps) {\n return ;\n}\n\nconst Menubar = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n variant?: MenuContextProps['variant'];\n }\n>(({ className, variant = 'default', ...props }, ref) => {\n /* #region CUSTOM provide context to add variants */\n const contextValue = React.useMemo(\n () => ({\n variant,\n }),\n [variant],\n );\n return (\n \n {/* #endregion CUSTOM */}\n \n \n );\n});\nMenubar.displayName = MenubarPrimitive.Root.displayName;\n\nconst MenubarTrigger = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n );\n});\nMenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;\n\nconst MenubarSubTrigger = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n inset?: boolean;\n }\n>(({ className, inset, children, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n {children}\n \n \n );\n});\nMenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;\n\nconst MenubarSubContent = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n );\n});\nMenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;\n\nconst MenubarContent = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, align = 'start', alignOffset = -4, sideOffset = 8, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n \n \n );\n});\nMenubarContent.displayName = MenubarPrimitive.Content.displayName;\n\nconst MenubarItem = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n inset?: boolean;\n }\n>(({ className, inset, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n );\n});\nMenubarItem.displayName = MenubarPrimitive.Item.displayName;\n\nconst MenubarCheckboxItem = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, checked, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n \n \n \n \n \n {children}\n \n );\n});\nMenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;\n\nconst MenubarRadioItem = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n \n \n \n \n \n {children}\n \n );\n});\nMenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;\n\nconst MenubarLabel = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n inset?: boolean;\n }\n>(({ className, inset, ...props }, ref) => (\n \n));\nMenubarLabel.displayName = MenubarPrimitive.Label.displayName;\n\nconst MenubarSeparator = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nMenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;\n\nfunction MenubarShortcut({ className, ...props }: React.HTMLAttributes) {\n return (\n \n );\n}\nMenubarShortcut.displayname = 'MenubarShortcut';\n\nexport {\n Menubar,\n MenubarCheckboxItem,\n MenubarContent,\n MenubarGroup,\n MenubarItem,\n MenubarLabel,\n MenubarMenu,\n MenubarPortal,\n MenubarRadioGroup,\n MenubarRadioItem,\n MenubarSeparator,\n MenubarShortcut,\n MenubarSub,\n MenubarSubContent,\n MenubarSubTrigger,\n MenubarTrigger,\n};\n","import {\n Menubar,\n MenubarContent,\n MenubarItem,\n MenubarMenu,\n MenubarSeparator,\n MenubarSub,\n MenubarSubContent,\n MenubarSubTrigger,\n MenubarTrigger,\n} from '@/components/shadcn-ui/menubar';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@/components/shadcn-ui/tooltip';\nimport {\n GroupsInMultiColumnMenu,\n Localized,\n MenuItemContainingCommand,\n MenuItemContainingSubmenu,\n MultiColumnMenu,\n} from 'platform-bible-utils';\nimport { RefObject, useEffect, useRef } from 'react';\nimport { useHotkeys } from 'react-hotkeys-hook';\nimport { getSubMenuGroupKeyForMenuItemId } from './menu.util';\nimport MenuItemIcon from './menu-icon.component';\n\n/**\n * Callback function that is invoked when a user selects a menu item. Receives the full\n * `MenuItemContainingCommand` object as an argument.\n */\nexport interface SelectMenuItemHandler {\n (selectedMenuItem: MenuItemContainingCommand): void;\n}\n\nconst simulateKeyPress = (ref: RefObject, keys: KeyboardEventInit[]) => {\n setTimeout(() => {\n keys.forEach((key) => {\n ref.current?.dispatchEvent(new KeyboardEvent('keydown', key));\n });\n }, 0);\n};\n\nconst getMenubarContent = (\n groups: Localized,\n items: Localized<(MenuItemContainingCommand | MenuItemContainingSubmenu)[]>,\n columnOrSubMenuKey: string | undefined,\n onSelectMenuItem: SelectMenuItemHandler,\n) => {\n if (!columnOrSubMenuKey) return undefined;\n\n const sortedGroupsForColumn = Object.entries(groups)\n .filter(\n ([key, group]) =>\n ('column' in group && group.column === columnOrSubMenuKey) || key === columnOrSubMenuKey,\n )\n .sort(([, a], [, b]) => a.order - b.order);\n\n return sortedGroupsForColumn.flatMap(([groupKey], index) => {\n const groupItems = items\n .filter((item) => item.group === groupKey)\n .sort((a, b) => a.order - b.order)\n .map((item: Localized) => {\n return (\n \n \n {'command' in item ? (\n {\n // Since the item has a command, we know it is a MenuItemContainingCommand.\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n onSelectMenuItem(item as MenuItemContainingCommand);\n }}\n >\n {item.iconPathBefore && (\n \n )}\n {item.label}\n {item.iconPathAfter && (\n \n )}\n \n ) : (\n \n {item.label}\n \n {getMenubarContent(\n groups,\n items,\n getSubMenuGroupKeyForMenuItemId(groups, item.id),\n onSelectMenuItem,\n )}\n \n \n )}\n \n {item.tooltip && {item.tooltip}}\n \n );\n });\n\n const itemsWithSeparator = [...groupItems];\n if (groupItems.length > 0 && index < sortedGroupsForColumn.length - 1) {\n itemsWithSeparator.push();\n }\n\n return itemsWithSeparator;\n });\n};\n\ntype PlatformMenubarProps = {\n /** Menu data that is used to populate the Menubar component. */\n menuData: Localized;\n\n /** The handler to use for menu commands. */\n onSelectMenuItem: SelectMenuItemHandler;\n\n /**\n * Optional callback function that is executed whenever a menu on the Menubar is opened or closed.\n * Helpful for handling updates to the menu, as changing menu data when the menu is opened is not\n * desirable.\n */\n onOpenChange?: (isOpen: boolean) => void;\n\n /** Style variant for the app menubar component. */\n variant?: 'default' | 'muted';\n};\n\n/** Menubar component tailored to work with Platform.Bible menu data */\nexport function PlatformMenubar({\n menuData,\n onSelectMenuItem,\n onOpenChange,\n variant,\n}: PlatformMenubarProps) {\n // These refs will always be defined\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const menubarRef = useRef(undefined!);\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const projectMenuRef = useRef(undefined!);\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const windowMenuRef = useRef(undefined!);\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const layoutMenuRef = useRef(undefined!);\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const helpMenuRef = useRef(undefined!);\n\n const getRefForColumn = (columnKey: string) => {\n switch (columnKey) {\n case 'platform.app':\n return projectMenuRef;\n case 'platform.window':\n return windowMenuRef;\n case 'platform.layout':\n return layoutMenuRef;\n case 'platform.help':\n return helpMenuRef;\n default:\n return undefined;\n }\n };\n\n // This is a quick and dirty way to implement some shortcuts by simulating key presses\n useHotkeys(['alt', 'alt+p', 'alt+l', 'alt+n', 'alt+h'], (event, handler) => {\n event.preventDefault();\n\n const escKey: KeyboardEventInit = { key: 'Escape', code: 'Escape', keyCode: 27, bubbles: true };\n const spaceKey: KeyboardEventInit = { key: ' ', code: 'Space', keyCode: 32, bubbles: true };\n\n switch (handler.hotkey) {\n case 'alt':\n simulateKeyPress(projectMenuRef, [escKey]);\n break;\n case 'alt+p':\n projectMenuRef.current?.focus();\n simulateKeyPress(projectMenuRef, [escKey, spaceKey]);\n break;\n case 'alt+l':\n windowMenuRef.current?.focus();\n simulateKeyPress(windowMenuRef, [escKey, spaceKey]);\n break;\n case 'alt+n':\n layoutMenuRef.current?.focus();\n simulateKeyPress(layoutMenuRef, [escKey, spaceKey]);\n break;\n case 'alt+h':\n helpMenuRef.current?.focus();\n simulateKeyPress(helpMenuRef, [escKey, spaceKey]);\n break;\n default:\n break;\n }\n });\n\n useEffect(() => {\n if (!onOpenChange || !menubarRef.current) return;\n\n const observer = new MutationObserver((mutations) => {\n mutations.forEach((mutation) => {\n if (mutation.attributeName === 'data-state' && mutation.target instanceof HTMLElement) {\n const state = mutation.target.getAttribute('data-state');\n\n if (state === 'open') {\n onOpenChange(true);\n } else {\n onOpenChange(false);\n }\n }\n });\n });\n\n const menubarElement = menubarRef.current;\n const dataStateAttributes = menubarElement.querySelectorAll('[data-state]');\n\n dataStateAttributes.forEach((element) => {\n observer.observe(element, { attributes: true });\n });\n\n return () => observer.disconnect();\n }, [onOpenChange]);\n\n if (!menuData) return undefined;\n\n return (\n \n {Object.entries(menuData.columns)\n .filter(([, column]) => typeof column === 'object')\n .sort(([, a], [, b]) => {\n if (typeof a === 'boolean' || typeof b === 'boolean') return 0;\n return a.order - b.order;\n })\n .map(([columnKey, column]) => (\n \n \n {typeof column === 'object' && 'label' in column && column.label}\n \n \n \n {getMenubarContent(menuData.groups, menuData.items, columnKey, onSelectMenuItem)}\n \n \n \n ))}\n \n );\n}\n\nexport default PlatformMenubar;\n","import {\n SelectMenuItemHandler,\n PlatformMenubar,\n} from '@/components/advanced/menus/platform-menubar.component';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Localized, MultiColumnMenu } from 'platform-bible-utils';\nimport { PropsWithChildren, ReactNode, useRef } from 'react';\n\nexport type ToolbarProps = PropsWithChildren<{\n /** The handler to use for menu commands (and eventually toolbar commands). */\n onSelectMenuItem: SelectMenuItemHandler;\n\n /**\n * Menu data that is used to populate the Menubar component. If empty object, no menus will be\n * shown on the App Menubar\n */\n menuData?: Localized;\n\n /**\n * Optional callback function that is executed whenever a menu on the App Menubar is opened or\n * closed. Helpful for handling updates to the menu, as changing menu data when the menu is opened\n * is not desirable.\n */\n onOpenChange?: (isOpen: boolean) => void;\n\n /** Optional unique identifier */\n id?: string;\n\n /** Additional css classes to help with unique styling of the toolbar */\n className?: string;\n\n /**\n * Whether the toolbar should be used as a draggable area for moving the application. This will\n * add an electron specific style `WebkitAppRegion: 'drag'` to the toolbar in order to make it\n * draggable. See:\n * https://www.electronjs.org/docs/latest/tutorial/custom-title-bar#create-a-custom-title-bar\n */\n shouldUseAsAppDragArea?: boolean;\n\n /** Toolbar children to be put at the start of the toolbar (left side in ltr, right side in rtl) */\n appMenuAreaChildren?: ReactNode;\n\n /** Toolbar children to be put at the end of the toolbar (right side in ltr, left side in rtl) */\n configAreaChildren?: ReactNode;\n\n /** Variant of the menubar */\n menubarVariant?: 'default' | 'muted';\n}>;\n\n/**\n * Get tailwind class for reserved space for the window controls / macos \"traffic lights\". Passing\n * 'darwin' will reserve the necessary space for macos traffic lights at the start, otherwise a\n * different amount of space at the end for the window controls.\n *\n * Apply to the toolbar like: `` or ``\n *\n * @param operatingSystem The os platform: 'darwin' (macos) | anything else\n * @returns The class name to apply to the toolbar if os specific space should be reserved\n */\nexport function getToolbarOSReservedSpaceClassName(\n operatingSystem: string | undefined,\n): string | undefined {\n switch (operatingSystem) {\n case undefined:\n return undefined;\n case 'darwin':\n return 'tw-ps-[85px]';\n default:\n return 'tw-pe-[calc(138px+1rem)]';\n }\n}\n\n/**\n * A customizable toolbar component with a menubar, content area, and configure area.\n *\n * This component is designed to be used in the window title bar of an electron application.\n *\n * @param {ToolbarProps} props - The props for the component.\n */\nexport function Toolbar({\n menuData,\n onOpenChange,\n onSelectMenuItem,\n className,\n id,\n children,\n appMenuAreaChildren,\n configAreaChildren,\n shouldUseAsAppDragArea,\n menubarVariant = 'default',\n}: ToolbarProps) {\n // This ref will always be defined\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const containerRef = useRef(undefined!);\n\n return (\n \n \n {/* App Menu area */}\n
    \n \n {appMenuAreaChildren}\n\n {menuData && (\n \n )}\n
    \n \n\n {/* Content area */}\n \n {children}\n \n\n {/* Configure area */}\n
    \n \n {configAreaChildren}\n
    \n \n \n \n );\n}\n\nexport default Toolbar;\n","import { useState } from 'react';\nimport { LocalizedStringValue, formatReplacementString } from 'platform-bible-utils';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../shadcn-ui/select';\nimport { Label } from '../shadcn-ui/label';\n\n/**\n * Immutable array containing all keys used for localization in this component. If you're using this\n * component in an extension, you can pass it into the useLocalizedStrings hook to easily obtain the\n * localized strings and pass them into the localizedStrings prop of this component\n */\nexport const UI_LANGUAGE_SELECTOR_STRING_KEYS = Object.freeze([\n '%settings_uiLanguageSelector_fallbackLanguages%',\n] as const);\n\nexport type UiLanguageSelectorLocalizedStrings = {\n [localizedUiLanguageSelectorKey in (typeof UI_LANGUAGE_SELECTOR_STRING_KEYS)[number]]?: LocalizedStringValue;\n};\n\n/**\n * Gets the localized value for the provided key\n *\n * @param strings Object containing localized string\n * @param key Key for a localized string\n * @returns The localized value for the provided key, if available. Returns the key if no localized\n * value is available\n */\nconst localizeString = (\n strings: UiLanguageSelectorLocalizedStrings,\n key: keyof UiLanguageSelectorLocalizedStrings,\n) => {\n return strings[key] ?? key;\n};\n\nexport type LanguageInfo = {\n /** The name of the language to be displayed (in its native script) */\n autonym: string;\n /**\n * The name of the language in other languages, so that the language can also be displayed in the\n * current UI language, if known.\n */\n uiNames?: Record;\n /**\n * Other known names of the language (for searching). This can include pejorative names and should\n * never be displayed unless typed by the user.\n */\n otherNames?: string[];\n};\n\nexport type UiLanguageSelectorProps = {\n /** Full set of known languages to display. The keys are valid BCP-47 tags. */\n knownUiLanguages: Record;\n /** IETF BCP-47 language tag of the current primary UI language. `undefined` => 'en' */\n primaryLanguage: string;\n /**\n * Ordered list of fallback language tags to use if the localization key can't be found in the\n * current primary UI language. This list never contains English ('en') because it is the ultimate\n * fallback.\n */\n fallbackLanguages: string[] | undefined;\n /**\n * Handler for when either the primary or the fallback languages change (or both). For this\n * handler, the primary UI language is the first one in the array, followed by the fallback\n * languages in order of decreasing preference.\n */\n onLanguagesChange?: (newUiLanguages: string[]) => void;\n /** Handler for the primary language changes. */\n onPrimaryLanguageChange?: (newPrimaryUiLanguage: string) => void;\n /**\n * Handler for when the fallback languages change. The array contains the fallback languages in\n * order of decreasing preference.\n */\n onFallbackLanguagesChange?: (newFallbackLanguages: string[]) => void;\n /**\n * Map whose keys are localized string keys as contained in UI_LANGUAGE_SELECTOR_STRING_KEYS and\n * whose values are the localized strings (in the current UI language).\n */\n localizedStrings: UiLanguageSelectorLocalizedStrings;\n /** Additional css classes to help with unique styling of the control */\n className?: string;\n /** Optional id for the root element */\n id?: string;\n};\n\n/**\n * A component for selecting the user interface language and managing fallback languages. Allows\n * users to choose a primary UI language and optionally select fallback languages.\n *\n * @param {UiLanguageSelectorProps} props - The props for the component.\n */\nexport function UiLanguageSelector({\n knownUiLanguages,\n primaryLanguage = 'en',\n fallbackLanguages = [],\n onLanguagesChange,\n onPrimaryLanguageChange,\n onFallbackLanguagesChange,\n localizedStrings,\n className,\n id,\n}: UiLanguageSelectorProps) {\n const fallbackLanguagesText = localizeString(\n localizedStrings,\n '%settings_uiLanguageSelector_fallbackLanguages%',\n );\n const [isOpen, setIsOpen] = useState(false);\n\n const handleLanguageChange = (code: string) => {\n if (onPrimaryLanguageChange) onPrimaryLanguageChange(code);\n // REVIEW: Should fallback languages be preserved when primary language changes?\n if (onLanguagesChange)\n onLanguagesChange([code, ...fallbackLanguages.filter((lang) => lang !== code)]);\n if (onFallbackLanguagesChange && fallbackLanguages.find((l) => l === code))\n onFallbackLanguagesChange([...fallbackLanguages.filter((lang) => lang !== code)]);\n setIsOpen(false); // Close the dropdown when a selection is made\n };\n\n /**\n * Gets the display name for the given language. This will typically include the autonym (in the\n * native script), along with the name of the language in the current UI locale if known, with a\n * fallback to the English name (if known).\n *\n * @param {string} lang - The BCP-47 code of the language whose display name is being requested.\n * @param {string} uiLang - The BCP-47 code of the current user-interface language used used to\n * try to look up the name of the language in a form that is likely to be helpful to the user if\n * they do not recognize the autonym.\n * @returns {string} The display name of the language.\n */\n const getLanguageDisplayName = (lang: string, uiLang: string) => {\n const altName =\n uiLang !== lang\n ? (knownUiLanguages[lang]?.uiNames?.[uiLang] ?? knownUiLanguages[lang]?.uiNames?.en)\n : undefined;\n\n return altName\n ? `${knownUiLanguages[lang]?.autonym} (${altName})`\n : knownUiLanguages[lang]?.autonym;\n };\n\n return (\n
    \n {/* Language Selector */}\n setIsOpen(open)}\n >\n \n \n \n \n {Object.keys(knownUiLanguages).map((key) => {\n return (\n \n {getLanguageDisplayName(key, primaryLanguage)}\n \n );\n })}\n \n \n\n {/* Fallback Language Button */}\n {primaryLanguage !== 'en' && (\n
    \n \n
    \n )}\n
    \n );\n}\n\nexport default UiLanguageSelector;\n","import { Label } from '@/components/shadcn-ui/label';\nimport { ReactNode } from 'react';\n\ntype SmartLabelProps = {\n item: string;\n createLabel?: (item: string) => string;\n createComplexLabel?: (item: string) => ReactNode;\n};\n\n/** Create labels with text, react elements (e.g. links), or text + react elements */\nfunction SmartLabel({ item, createLabel, createComplexLabel }: SmartLabelProps): ReactNode {\n if (createLabel) {\n return ;\n }\n if (createComplexLabel) {\n return ;\n }\n return ;\n}\n\nexport default SmartLabel;\n","import { Checkbox } from '@/components/shadcn-ui/checkbox';\nimport { ReactNode } from 'react';\nimport SmartLabel from './smart-label.component';\n\nexport type ChecklistProps = {\n /** Optional string representing the id attribute of the Checklist */\n id?: string;\n /** Optional string representing CSS class name(s) for styling */\n className?: string;\n /** Array of strings representing the checkable items */\n listItems: string[];\n /** Array of strings representing the checked items */\n selectedListItems: string[];\n /**\n * Function that is called when a checkbox item is selected or deselected\n *\n * @param item The string description for this item\n * @param selected True if selected, false if not selected\n */\n handleSelectListItem: (item: string, selected: boolean) => void;\n\n /**\n * Optional function creates a label for a provided checkable item\n *\n * @param item The item for which a label is to be created\n * @returns A string representing the label text for the checkbox associated with that item\n */\n createLabel?: (item: string) => string;\n\n /**\n * Optional function creates a label for a provided checkable item\n *\n * @param item The item for which a label is to be created, including text and any additional\n * elements (e.g. links)\n * @returns A react node representing the label text and any additional elements (e.g. links) for\n * the checkbox associated with that item\n */\n createComplexLabel?: (item: string) => ReactNode;\n};\n\n/** Renders a list of checkboxes. Each checkbox corresponds to an item from the `listItems` array. */\nexport function Checklist({\n id,\n className,\n listItems,\n selectedListItems,\n handleSelectListItem,\n createLabel,\n createComplexLabel,\n}: ChecklistProps) {\n return (\n
    \n {listItems.map((item) => (\n
    \n handleSelectListItem(item, value)}\n />\n \n
    \n ))}\n
    \n );\n}\n\nexport default Checklist;\n","import { cn } from '@/utils/shadcn-ui.util';\nimport { MoreHorizontal } from 'lucide-react';\nimport React, { ReactNode } from 'react';\nimport { Button } from '../shadcn-ui/button';\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '../shadcn-ui/dropdown-menu';\n\n/** Props interface for the ResultsCard base component */\nexport interface ResultsCardProps {\n /** Unique key for the card */\n cardKey: string;\n /** Whether this card is currently selected/focused */\n isSelected: boolean;\n /** Callback function called when the card is clicked */\n onSelect: () => void;\n /** Whether the content of this card are in a denied state */\n isDenied?: boolean;\n /** Whether the card should be hidden */\n isHidden?: boolean;\n /** Additional CSS classes to apply to the card */\n className?: string;\n /** Main content to display on the card */\n children: ReactNode;\n /** Content to show in the dropdown menu when selected */\n dropdownContent?: ReactNode;\n /** Additional content to show below the main content when selected */\n additionalSelectedContent?: ReactNode;\n /** Color to use for the card's accent border */\n accentColor?: string;\n}\n\n/**\n * ResultsCard is a base component for displaying scripture-related results in a card format, even\n * though it is not based on the Card component. It provides common functionality like selection\n * state, dropdown menus, and expandable content.\n */\nexport function ResultsCard({\n cardKey,\n isSelected,\n onSelect,\n isDenied,\n isHidden = false,\n className,\n children,\n dropdownContent,\n additionalSelectedContent,\n accentColor,\n}: ResultsCardProps) {\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n onSelect();\n }\n };\n\n return (\n
  • ,\n);\nSidebarMenuSubItem.displayName = 'SidebarMenuSubItem';\n\n/** @inheritdoc SidebarProvider */\nconst SidebarMenuSubButton = React.forwardRef<\n HTMLAnchorElement,\n React.ComponentProps<'a'> & {\n asChild?: boolean;\n size?: 'sm' | 'md';\n isActive?: boolean;\n }\n>(({ asChild = false, size = 'md', isActive, className, ...props }, ref) => {\n const Comp = asChild ? Slot : 'a';\n\n return (\n span:last-child]:tw-truncate [&>svg]:tw-size-4 [&>svg]:tw-shrink-0 [&>svg]:tw-text-sidebar-accent-foreground',\n 'data-[active=true]:tw-bg-sidebar-accent data-[active=true]:tw-text-sidebar-accent-foreground',\n size === 'sm' && 'tw-text-xs',\n size === 'md' && 'tw-text-sm',\n 'group-data-[collapsible=icon]:tw-hidden',\n className,\n )}\n {...props}\n />\n );\n});\nSidebarMenuSubButton.displayName = 'SidebarMenuSubButton';\n\nexport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupAction,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarInput,\n SidebarInset,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuBadge,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSkeleton,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarProvider,\n SidebarRail,\n SidebarSeparator,\n SidebarTrigger,\n useSidebar,\n};\n","import { ComboBox } from '@/components/basics/combo-box.component';\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupLabel,\n SidebarGroupContent,\n SidebarMenu,\n SidebarMenuItem,\n SidebarMenuButton,\n} from '@/components/shadcn-ui/sidebar';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { ScrollText } from 'lucide-react';\nimport { useCallback } from 'react';\n\nexport type SelectedSettingsSidebarItem = {\n label: string;\n projectId?: string;\n};\n\nexport type ProjectInfo = { projectId: string; projectName: string };\n\nexport type SettingsSidebarProps = {\n /** Optional id for testing */\n id?: string;\n\n /** Extension labels from contribution */\n extensionLabels: Record;\n\n /** Project names and ids */\n projectInfo: ProjectInfo[];\n\n /** Handler for selecting a sidebar item */\n handleSelectSidebarItem: (key: string, projectId?: string) => void;\n\n /** The current selected value in the sidebar */\n selectedSidebarItem: SelectedSettingsSidebarItem;\n\n /** Label for the group of extensions setting groups */\n extensionsSidebarGroupLabel: string;\n\n /** Label for the group of projects settings */\n projectsSidebarGroupLabel: string;\n\n /** Placeholder text for the button */\n buttonPlaceholderText: string;\n\n /** Additional css classes to help with unique styling of the sidebar */\n className?: string;\n};\n\n/**\n * The SettingsSidebar component is a sidebar that displays a list of extension settings and project\n * settings. It can be used to navigate to different settings pages. Must be wrapped in a\n * SidebarProvider component otherwise produces errors.\n *\n * @param props - {@link SettingsSidebarProps} The props for the component.\n */\nexport function SettingsSidebar({\n id,\n extensionLabels,\n projectInfo,\n handleSelectSidebarItem,\n selectedSidebarItem,\n extensionsSidebarGroupLabel,\n projectsSidebarGroupLabel,\n buttonPlaceholderText,\n className,\n}: SettingsSidebarProps) {\n const handleSelectItem = useCallback(\n (item: string, projectId?: string) => {\n handleSelectSidebarItem(item, projectId);\n },\n [handleSelectSidebarItem],\n );\n\n const getProjectNameFromProjectId = useCallback(\n (projectId: string) => {\n const project = projectInfo.find((info) => info.projectId === projectId);\n return project ? project.projectName : projectId;\n },\n [projectInfo],\n );\n\n const getIsActive: (label: string) => boolean = useCallback(\n (label: string) => !selectedSidebarItem.projectId && label === selectedSidebarItem.label,\n [selectedSidebarItem],\n );\n\n return (\n \n \n \n \n {extensionsSidebarGroupLabel}\n \n \n \n {Object.entries(extensionLabels).map(([key, label]) => (\n \n handleSelectItem(key)}\n isActive={getIsActive(key)}\n >\n {label}\n \n \n ))}\n \n \n \n \n {projectsSidebarGroupLabel}\n \n info.projectId)}\n getOptionLabel={getProjectNameFromProjectId}\n buttonPlaceholder={buttonPlaceholderText}\n onChange={(projectId: string) => {\n const selectedProjectName = getProjectNameFromProjectId(projectId);\n handleSelectItem(selectedProjectName, projectId);\n }}\n value={selectedSidebarItem?.projectId ?? undefined}\n icon={}\n />\n \n \n \n \n );\n}\n\nexport default SettingsSidebar;\n","import { Button } from '@/components/shadcn-ui/button';\nimport { Input } from '@/components/shadcn-ui/input';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Search, X } from 'lucide-react';\nimport { forwardRef } from 'react';\n\n/** Props for the SearchBar component. */\nexport type SearchBarProps = {\n /** Search query for the search bar */\n value: string;\n /**\n * Callback fired to handle the search query is updated\n *\n * @param searchQuery\n */\n onSearch: (searchQuery: string) => void;\n\n /** Optional string that appears in the search bar without a search string */\n placeholder?: string;\n\n /** Optional boolean to set the input base to full width */\n isFullWidth?: boolean;\n\n /** Additional css classes to help with unique styling of the search bar */\n className?: string;\n\n /** Optional boolean to disable the search bar */\n isDisabled?: boolean;\n\n /** Optional id for the root element */\n id?: string;\n};\n\n/**\n * A search bar component with a search icon and a clear button when the search query is not empty.\n *\n * @param {SearchBarProps} props - The props for the component.\n * @param {string} props.value - The search query for the search bar\n * @param {(searchQuery: string) => void} props.onSearch - Callback fired to handle the search query\n * is updated\n * @param {string} [props.placeholder] - Optional string that appears in the search bar without a\n * search string\n * @param {boolean} [props.isFullWidth] - Optional boolean to set the input base to full width\n * @param {string} [props.className] - Additional css classes to help with unique styling of the\n * search bar\n * @param {boolean} [props.isDisabled] - Optional boolean to disable the search bar\n * @param {string} [props.id] - Optional id for the root element\n */\nexport const SearchBar = forwardRef(\n ({ value, onSearch, placeholder, isFullWidth, className, isDisabled = false, id }, inputRef) => {\n const dir: Direction = readDirection();\n\n return (\n
    \n \n onSearch(e.target.value)}\n disabled={isDisabled}\n />\n {value && (\n {\n onSearch('');\n }}\n >\n \n Clear\n \n )}\n
    \n );\n },\n);\n\nSearchBar.displayName = 'SearchBar';\n\nexport default SearchBar;\n","import { SidebarInset, SidebarProvider } from '@/components/shadcn-ui/sidebar';\nimport { PropsWithChildren } from 'react';\nimport { SearchBar } from '@/components/basics/search-bar.component';\nimport { SettingsSidebar, SettingsSidebarProps } from './settings-sidebar.component';\n\nexport type SettingsSidebarContentSearchProps = SettingsSidebarProps &\n PropsWithChildren & {\n /** The search query in the search bar */\n searchValue: string;\n\n /** Handler to run when the value of the search bar changes */\n onSearch: (searchQuery: string) => void;\n };\n\n/**\n * A component that wraps a search bar and a settings sidebar, providing a way to search and\n * navigate to different settings pages.\n *\n * @param {SettingsSidebarContentSearchProps} props - The props for the component.\n * @param {string} props.id - The id of the sidebar.\n */\nexport function SettingsSidebarContentSearch({\n id,\n extensionLabels,\n projectInfo,\n children,\n handleSelectSidebarItem,\n selectedSidebarItem,\n searchValue,\n onSearch,\n extensionsSidebarGroupLabel,\n projectsSidebarGroupLabel,\n buttonPlaceholderText,\n}: SettingsSidebarContentSearchProps) {\n return (\n
    \n
    \n \n
    \n \n \n {children}\n \n
    \n );\n}\n\nexport default SettingsSidebarContentSearch;\n","import { Button } from '@/components/shadcn-ui/button';\nimport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@/components/shadcn-ui/select';\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@/components/shadcn-ui/table';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Canon } from '@sillsdev/scripture';\nimport {\n Cell,\n ColumnDef,\n flexRender,\n getCoreRowModel,\n getExpandedRowModel,\n getGroupedRowModel,\n getSortedRowModel,\n GroupingState,\n Row,\n RowSelectionState,\n SortingState,\n useReactTable,\n} from '@tanstack/react-table';\nimport '@/components/advanced/scripture-results-viewer/scripture-results-viewer.component.css';\nimport {\n compareScrRefs,\n formatScrRef,\n ScriptureSelection,\n scrRefToBBBCCCVVV,\n} from 'platform-bible-utils';\nimport { MouseEvent, useEffect, useMemo, useState } from 'react';\nimport { ChevronDown, ChevronLeft, ChevronRight } from 'lucide-react';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\n\n/**\n * Information (e.g., a checking error or some other type of \"transient\" annotation) about something\n * noteworthy at a specific place in an instance of the Scriptures.\n */\nexport type ScriptureItemDetail = ScriptureSelection & {\n /**\n * Text of the error, note, etc. In the future, we might want to support something more than just\n * text so that a JSX element could be provided with a link or some other controls related to the\n * issue being reported.\n */\n detail: string;\n};\n\n/**\n * A uniquely identifiable source of results that can be displayed in the ScriptureResultsViewer.\n * Generally, the source will be a particular Scripture check, but there may be other types of\n * sources.\n */\nexport type ResultsSource = {\n /**\n * Uniquely identifies the source.\n *\n * @type {string}\n */\n id: string;\n\n /**\n * Name (potentially localized) of the source, suitable for display in the UI.\n *\n * @type {string}\n */\n displayName: string;\n};\n\nexport type ScriptureSrcItemDetail = ScriptureItemDetail & {\n /** Source/type of detail. Can be used for grouping. */\n source: ResultsSource;\n};\n\n/**\n * Represents a set of results keyed by Scripture reference. Generally, the source will be a\n * particular Scripture check, but this type also allows for other types of uniquely identifiable\n * sources.\n */\nexport type ResultsSet = {\n /**\n * The backing source associated with this set of results.\n *\n * @type {ResultsSource}\n */\n source: ResultsSource;\n\n /**\n * Array of Scripture item details (messages keyed by Scripture reference).\n *\n * @type {ScriptureItemDetail[]}\n */\n data: ScriptureItemDetail[];\n};\n\nconst scrBookColId = 'scrBook';\nconst scrRefColId = 'scrRef';\nconst typeColId = 'source';\nconst detailsColId = 'details';\n\nconst defaultScrRefColumnName = 'Scripture Reference';\nconst defaultScrBookGroupName = 'Scripture Book';\nconst defaultTypeColumnName = 'Type';\nconst defaultDetailsColumnName = 'Details';\n\nexport type ScriptureResultsViewerColumnInfo = {\n /** Optional header to display for the Reference column. Default value: 'Scripture Reference'. */\n scriptureReferenceColumnName?: string;\n\n /** Optional text to display to refer to the Scripture book group. Default value: 'Scripture Book'. */\n scriptureBookGroupName?: string;\n\n /** Optional header to display for the Type column. Default value: 'Type'. */\n typeColumnName?: string;\n\n /** Optional header to display for the Details column. Default value: 'Details' */\n detailsColumnName?: string;\n};\n\nexport type ScriptureResultsViewerProps = ScriptureResultsViewerColumnInfo & {\n /** Groups of ScriptureItemDetail objects from particular sources (e.g., Scripture checks) */\n sources: ResultsSet[];\n\n /** Flag indicating whether to display column headers. Default is false. */\n showColumnHeaders?: boolean;\n\n /** Flag indicating whether to display source column. Default is false. */\n showSourceColumn?: boolean;\n\n /** Callback function to notify when a row is selected */\n onRowSelected?: (selectedRow: ScriptureSrcItemDetail | undefined) => void;\n\n /** Optional id attribute for the outermost element */\n id?: string;\n};\n\nfunction getColumns(\n colInfo?: ScriptureResultsViewerColumnInfo,\n showSourceColumn?: boolean,\n): ColumnDef[] {\n const showSrcCol = showSourceColumn ?? false;\n return [\n {\n accessorFn: (row) => `${row.start.book} ${row.start.chapterNum}:${row.start.verseNum}`,\n id: scrBookColId,\n header: colInfo?.scriptureReferenceColumnName ?? defaultScrRefColumnName,\n cell: (info) => {\n const row = info.row.original;\n if (info.row.getIsGrouped()) {\n return Canon.bookIdToEnglishName(row.start.book);\n }\n return info.row.groupingColumnId === scrBookColId ? formatScrRef(row.start) : undefined;\n },\n getGroupingValue: (row) => Canon.bookIdToNumber(row.start.book),\n sortingFn: (a, b) => {\n return compareScrRefs(a.original.start, b.original.start);\n },\n enableGrouping: true,\n },\n {\n accessorFn: (row) => formatScrRef(row.start),\n id: scrRefColId,\n header: undefined,\n cell: (info) => {\n const row = info.row.original;\n return info.row.getIsGrouped() ? undefined : formatScrRef(row.start);\n },\n sortingFn: (a, b) => {\n return compareScrRefs(a.original.start, b.original.start);\n },\n enableGrouping: false,\n },\n {\n accessorFn: (row) => row.source.displayName,\n id: typeColId,\n header: showSrcCol ? (colInfo?.typeColumnName ?? defaultTypeColumnName) : undefined,\n cell: (info) => (showSrcCol || info.row.getIsGrouped() ? info.getValue() : undefined),\n getGroupingValue: (row) => row.source.id,\n sortingFn: (a, b) =>\n a.original.source.displayName.localeCompare(b.original.source.displayName),\n enableGrouping: true,\n },\n {\n accessorFn: (row) => row.detail,\n id: detailsColId,\n header: colInfo?.detailsColumnName ?? defaultDetailsColumnName,\n cell: (info) => info.getValue(),\n enableGrouping: false,\n },\n ];\n}\n\nconst toRefOrRange = (scriptureSelection: ScriptureSelection) => {\n if (!('offset' in scriptureSelection.start))\n throw new Error('No offset available in range start');\n if (scriptureSelection.end && !('offset' in scriptureSelection.end))\n throw new Error('No offset available in range end');\n const { offset: offsetStart } = scriptureSelection.start;\n let offsetEnd: number = 0;\n if (scriptureSelection.end) ({ offset: offsetEnd } = scriptureSelection.end);\n if (\n !scriptureSelection.end ||\n compareScrRefs(scriptureSelection.start, scriptureSelection.end) === 0\n )\n return `${scrRefToBBBCCCVVV(scriptureSelection.start)}+${offsetStart}`;\n return `${scrRefToBBBCCCVVV(scriptureSelection.start)}+${offsetStart}-${scrRefToBBBCCCVVV(scriptureSelection.end)}+${offsetEnd}`;\n};\n\nconst getRowKey = (row: ScriptureSrcItemDetail) =>\n `${toRefOrRange({ start: row.start, end: row.end })} ${row.source.displayName} ${row.detail}`;\n\n/**\n * Component to display a combined list of detailed items from one or more sources, where the items\n * are keyed primarily by Scripture reference. This is particularly useful for displaying a list of\n * results from Scripture checks, but more generally could be used to display any \"results\" from any\n * source(s). The component allows for grouping by Scripture book, source, or both. By default, it\n * displays somewhat \"tree-like\" which allows it to be more horizontally compact and intuitive. But\n * it also has the option of displaying as a traditional table with column headings (with or without\n * the source column showing).\n */\nexport function ScriptureResultsViewer({\n sources,\n showColumnHeaders = false,\n showSourceColumn = false,\n scriptureReferenceColumnName,\n scriptureBookGroupName,\n typeColumnName,\n detailsColumnName,\n onRowSelected,\n id,\n}: ScriptureResultsViewerProps) {\n const [grouping, setGrouping] = useState([]);\n const [sorting, setSorting] = useState([{ id: scrBookColId, desc: false }]);\n const [rowSelection, setRowSelection] = useState({});\n\n const scriptureResults = useMemo(\n () =>\n sources.flatMap((source) => {\n return source.data.map((item) => ({\n ...item,\n source: source.source,\n }));\n }),\n [sources],\n );\n\n const columns = useMemo(\n () =>\n getColumns(\n {\n scriptureReferenceColumnName,\n typeColumnName,\n detailsColumnName,\n },\n showSourceColumn,\n ),\n [scriptureReferenceColumnName, typeColumnName, detailsColumnName, showSourceColumn],\n );\n\n useEffect(() => {\n // Ensure sorting is applied correctly when grouped by type\n if (grouping.includes(typeColId)) {\n setSorting([\n { id: typeColId, desc: false },\n { id: scrBookColId, desc: false },\n ]);\n } else {\n setSorting([{ id: scrBookColId, desc: false }]);\n }\n }, [grouping]);\n\n const table = useReactTable({\n data: scriptureResults,\n columns,\n state: {\n grouping,\n sorting,\n rowSelection,\n },\n onGroupingChange: setGrouping,\n onSortingChange: setSorting,\n onRowSelectionChange: setRowSelection,\n getExpandedRowModel: getExpandedRowModel(),\n getGroupedRowModel: getGroupedRowModel(),\n getCoreRowModel: getCoreRowModel(),\n getSortedRowModel: getSortedRowModel(),\n getRowId: getRowKey,\n autoResetExpanded: false,\n enableMultiRowSelection: false,\n enableSubRowSelection: false,\n });\n\n useEffect(() => {\n if (onRowSelected) {\n const selectedRows = table.getSelectedRowModel().rowsById;\n const keys = Object.keys(selectedRows);\n if (keys.length === 1) {\n const selectedRow = scriptureResults.find((row) => getRowKey(row) === keys[0]) || undefined;\n if (selectedRow) onRowSelected(selectedRow);\n }\n }\n }, [rowSelection, scriptureResults, onRowSelected, table]);\n\n // Define possible grouping options\n const scrBookGroupName = scriptureBookGroupName ?? defaultScrBookGroupName;\n const typeGroupName = typeColumnName ?? defaultTypeColumnName;\n\n const groupingOptions = [\n { label: 'No Grouping', value: [] },\n { label: `Group by ${scrBookGroupName}`, value: [scrBookColId] },\n { label: `Group by ${typeGroupName}`, value: [typeColId] },\n {\n label: `Group by ${scrBookGroupName} and ${typeGroupName}`,\n value: [scrBookColId, typeColId],\n },\n {\n label: `Group by ${typeGroupName} and ${scrBookGroupName}`,\n value: [typeColId, scrBookColId],\n },\n ];\n\n const handleSelectChange = (selectedGrouping: string) => {\n setGrouping(JSON.parse(selectedGrouping));\n };\n\n const handleRowClick = (row: Row, event: MouseEvent) => {\n if (!row.getIsGrouped() && !row.getIsSelected()) {\n row.getToggleSelectedHandler()(event);\n }\n };\n\n const getEvenOrOddBandingStyle = (row: Row, index: number) => {\n if (row.getIsGrouped()) return '';\n // UX has now said they don't think they want banding. I'm leaving in the code to\n // set even and odd styles, but there's nothing in the CSS to style them differently.\n // The \"even\" style used to also have tw-bg-neutral-300 (along with even) to create\n // a visual banding effect. That could be added back in if UX changes the decision.\n return cn('banded-row', index % 2 === 0 ? 'even' : 'odd');\n };\n\n const getIndent = (\n groupingState: GroupingState,\n row: Row,\n cell: Cell,\n ) => {\n if (groupingState?.length === 0 || row.depth < cell.column.getGroupedIndex()) return undefined;\n if (row.getIsGrouped()) {\n switch (row.depth) {\n case 1:\n return 'tw-ps-4';\n default:\n return undefined;\n }\n }\n switch (row.depth) {\n case 1:\n return 'tw-ps-8';\n case 2:\n return 'tw-ps-12';\n default:\n return undefined;\n }\n };\n\n return (\n
    \n {!showColumnHeaders && (\n {\n handleSelectChange(value);\n }}\n >\n \n \n \n \n \n {groupingOptions.map((option) => (\n \n {option.label}\n \n ))}\n \n \n \n )}\n \n {showColumnHeaders && (\n \n {table.getHeaderGroups().map((headerGroup) => (\n \n {headerGroup.headers\n .filter((h) => h.column.columnDef.header)\n .map((header) => (\n /* For sticky column headers to work, we probably need to change the default definition of the shadcn Table component. See https://github.com/shadcn-ui/ui/issues/1151 */\n \n {header.isPlaceholder ? undefined : (\n
    \n {header.column.getCanGroup() ? (\n \n {header.column.getIsGrouped() ? `๐Ÿ›‘` : `๐Ÿ‘Š `}\n \n ) : undefined}{' '}\n {flexRender(header.column.columnDef.header, header.getContext())}\n
    \n )}\n
    \n ))}\n
    \n ))}\n
    \n )}\n \n {table.getRowModel().rows.map((row, rowIndex) => {\n const dir: Direction = readDirection();\n return (\n handleRowClick(row, event)}\n >\n {row.getVisibleCells().map((cell) => {\n if (\n cell.getIsPlaceholder() ||\n (cell.column.columnDef.enableGrouping &&\n !cell.getIsGrouped() &&\n (cell.column.columnDef.id !== typeColId || !showSourceColumn))\n )\n return undefined;\n return (\n \n {(() => {\n if (cell.getIsGrouped()) {\n return (\n \n {row.getIsExpanded() && }\n {!row.getIsExpanded() &&\n (dir === 'ltr' ? : )}{' '}\n {flexRender(cell.column.columnDef.cell, cell.getContext())} (\n {row.subRows.length})\n \n );\n }\n\n // if (cell.getIsAggregated()) {\n // flexRender(\n // cell.column.columnDef.aggregatedCell ?? cell.column.columnDef.cell,\n // cell.getContext(),\n // );\n // }\n\n return flexRender(cell.column.columnDef.cell, cell.getContext());\n })()}\n \n );\n })}\n \n );\n })}\n \n
    \n
    \n );\n}\n\nexport default ScriptureResultsViewer;\n","import { getSectionForBook, Section } from 'platform-bible-utils';\n\n/**\n * Filters an array of book IDs to only include books from a specific section\n *\n * @param bookIds Array of book IDs to filter\n * @param section The section to filter by\n * @returns Array of book IDs that belong to the specified section\n */\nexport const getBooksForSection = (bookIds: string[], section: Section) => {\n return bookIds.filter((bookId) => {\n try {\n return getSectionForBook(bookId) === section;\n } catch {\n return false;\n }\n });\n};\n\n/**\n * Checks if all books in a given section are included in the selectedBookIds array\n *\n * @param bookIds Array of all available book IDs\n * @param section The section to check\n * @param selectedBookIds Array of currently selected book IDs\n * @returns True if all books from the specified section are selected, false otherwise\n */\nexport const isSectionFullySelected = (\n bookIds: string[],\n section: Section,\n selectedBookIds: string[],\n) => getBooksForSection(bookIds, section).every((bookId) => selectedBookIds.includes(bookId));\n","import { Button } from '@/components/shadcn-ui/button';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { LanguageStrings, Section } from 'platform-bible-utils';\nimport { getSectionShortName } from '@/components/shared/book.utils';\nimport { getBooksForSection, isSectionFullySelected } from './scope-selector.utils';\n\n/**\n * A button component that represents a scripture section (testament) in the book selector. The\n * button shows a different state when all books in its section are selected and becomes disabled\n * when no books are available in its section.\n */\nfunction SectionButton({\n section,\n availableBookIds,\n selectedBookIds,\n onToggle,\n localizedStrings,\n}: {\n section: Section;\n availableBookIds: string[];\n selectedBookIds: string[];\n onToggle: (section: Section) => void;\n localizedStrings: LanguageStrings;\n}) {\n const isDisabled = getBooksForSection(availableBookIds, section).length === 0;\n\n const sectionOtShortText = localizedStrings['%scripture_section_ot_short%'];\n const sectionNtShortText = localizedStrings['%scripture_section_nt_short%'];\n const sectionDcShortText = localizedStrings['%scripture_section_dc_short%'];\n const sectionExtraShortText = localizedStrings['%scripture_section_extra_short%'];\n\n return (\n onToggle(section)}\n className={cn(\n isSectionFullySelected(availableBookIds, section, selectedBookIds) &&\n !isDisabled &&\n 'tw-bg-primary tw-text-primary-foreground hover:tw-bg-primary/70 hover:tw-text-primary-foreground',\n )}\n disabled={isDisabled}\n >\n {getSectionShortName(\n section,\n sectionOtShortText,\n sectionNtShortText,\n sectionDcShortText,\n sectionExtraShortText,\n )}\n \n );\n}\n\nexport default SectionButton;\n","import { BookItem } from '@/components/shared/book-item.component';\nimport { Badge } from '@/components/shadcn-ui/badge';\nimport { Button } from '@/components/shadcn-ui/button';\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandList,\n CommandSeparator,\n} from '@/components/shadcn-ui/command';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/shadcn-ui/popover';\nimport { Canon } from '@sillsdev/scripture';\nimport { ChevronsUpDown } from 'lucide-react';\nimport { getSectionForBook, LanguageStrings, Section } from 'platform-bible-utils';\nimport {\n getSectionLongName,\n getLocalizedBookName,\n doesBookMatchQuery,\n} from '@/components/shared/book.utils';\nimport { Fragment, MouseEvent, useCallback, useMemo, useRef, useState } from 'react';\nimport { generateCommandValue } from '@/components/shared/book-item.utils';\nimport { getBooksForSection, isSectionFullySelected } from './scope-selector.utils';\nimport SectionButton from './section-button.component';\n\n/** Maximum number of badges to show before collapsing into a \"+X more\" badge */\nconst VISIBLE_BADGES_COUNT = 5;\n/** Maximum number of badges that can be shown without triggering the collapse */\nconst MAX_VISIBLE_BADGES = 6;\n\ntype BookSelectorProps = {\n /**\n * Information about available books, formatted as a 123 character long string as defined in a\n * projects BooksPresent setting\n */\n availableBookInfo: string;\n /** Array of currently selected book IDs */\n selectedBookIds: string[];\n /** Callback function that is executed when the book selection changes */\n onChangeSelectedBookIds: (books: string[]) => void;\n /** Object containing the localized strings for the component */\n localizedStrings: LanguageStrings;\n /**\n * Optional map of localized book IDs/short names and full names. Key is the (English) book ID,\n * value contains localized versions of the ID and full book name\n */\n localizedBookNames?: Map;\n};\n\n/**\n * A component for selecting multiple books from the Bible canon. It provides:\n *\n * - Quick selection buttons for major sections (OT, NT, DC, Extra)\n * - A searchable dropdown with all available books\n * - Support for shift-click range selection\n * - Visual feedback with badges showing selected books\n */\nexport function BookSelector({\n availableBookInfo,\n selectedBookIds,\n onChangeSelectedBookIds,\n localizedStrings,\n localizedBookNames,\n}: BookSelectorProps) {\n const booksSelectedText = localizedStrings['%webView_book_selector_books_selected%'];\n const selectBooksText = localizedStrings['%webView_book_selector_select_books%'];\n const searchBooksText = localizedStrings['%webView_book_selector_search_books%'];\n const selectAllText = localizedStrings['%webView_book_selector_select_all%'];\n const clearAllText = localizedStrings['%webView_book_selector_clear_all%'];\n const noBookFoundText = localizedStrings['%webView_book_selector_no_book_found%'];\n const moreText = localizedStrings['%webView_book_selector_more%'];\n\n const { otLong, ntLong, dcLong, extraLong } = {\n otLong: localizedStrings?.['%scripture_section_ot_long%'],\n ntLong: localizedStrings?.['%scripture_section_nt_long%'],\n dcLong: localizedStrings?.['%scripture_section_dc_long%'],\n extraLong: localizedStrings?.['%scripture_section_extra_long%'],\n };\n\n const [isBooksSelectorOpen, setIsBooksSelectorOpen] = useState(false);\n const [inputValue, setInputValue] = useState('');\n const lastSelectedBookRef = useRef(undefined);\n const lastKeyEventShiftKey = useRef(false);\n\n if (availableBookInfo.length !== Canon.allBookIds.length) {\n throw new Error('availableBookInfo length must match Canon.allBookIds length');\n }\n\n const availableBooksIds = useMemo(() => {\n return Canon.allBookIds.filter(\n (bookId, index) =>\n availableBookInfo[index] === '1' && !Canon.isObsolete(Canon.bookIdToNumber(bookId)),\n );\n }, [availableBookInfo]);\n\n const filteredBooksBySection = useMemo(() => {\n if (!inputValue.trim()) {\n const allBooks: Record = {\n [Section.OT]: [],\n [Section.NT]: [],\n [Section.DC]: [],\n [Section.Extra]: [],\n };\n\n availableBooksIds.forEach((bookId) => {\n const section = getSectionForBook(bookId);\n allBooks[section].push(bookId);\n });\n\n return allBooks;\n }\n\n const filteredBooks = availableBooksIds.filter((bookId) =>\n doesBookMatchQuery(bookId, inputValue, localizedBookNames),\n );\n\n const matchingBooks: Record = {\n [Section.OT]: [],\n [Section.NT]: [],\n [Section.DC]: [],\n [Section.Extra]: [],\n };\n\n filteredBooks.forEach((bookId) => {\n const section = getSectionForBook(bookId);\n matchingBooks[section].push(bookId);\n });\n\n return matchingBooks;\n }, [availableBooksIds, inputValue, localizedBookNames]);\n\n const toggleBook = useCallback(\n (bookId: string, shiftKey = false) => {\n if (!shiftKey || !lastSelectedBookRef.current) {\n onChangeSelectedBookIds(\n selectedBookIds.includes(bookId)\n ? selectedBookIds.filter((id) => id !== bookId)\n : [...selectedBookIds, bookId],\n );\n lastSelectedBookRef.current = bookId;\n return;\n }\n\n const lastIndex = availableBooksIds.findIndex((id) => id === lastSelectedBookRef.current);\n const currentIndex = availableBooksIds.findIndex((id) => id === bookId);\n\n if (lastIndex === -1 || currentIndex === -1) return;\n\n const [startIndex, endIndex] = [\n Math.min(lastIndex, currentIndex),\n Math.max(lastIndex, currentIndex),\n ];\n const booksInRange = availableBooksIds.slice(startIndex, endIndex + 1).map((id) => id);\n\n onChangeSelectedBookIds(\n selectedBookIds.includes(bookId)\n ? selectedBookIds.filter((shortname) => !booksInRange.includes(shortname))\n : [...new Set([...selectedBookIds, ...booksInRange])],\n );\n },\n [selectedBookIds, onChangeSelectedBookIds, availableBooksIds],\n );\n\n const handleKeyboardSelect = (bookId: string) => {\n toggleBook(bookId, lastKeyEventShiftKey.current);\n lastKeyEventShiftKey.current = false;\n };\n\n const handleMouseDown = (event: MouseEvent, bookId: string) => {\n event.preventDefault();\n toggleBook(bookId, event.shiftKey);\n };\n\n const toggleSection = useCallback(\n (section: Section) => {\n const sectionBooks = getBooksForSection(availableBooksIds, section).map((bookId) => bookId);\n onChangeSelectedBookIds(\n isSectionFullySelected(availableBooksIds, section, selectedBookIds)\n ? selectedBookIds.filter((shortname) => !sectionBooks.includes(shortname))\n : [...new Set([...selectedBookIds, ...sectionBooks])],\n );\n },\n [selectedBookIds, onChangeSelectedBookIds, availableBooksIds],\n );\n\n const handleSelectAll = () => {\n onChangeSelectedBookIds(availableBooksIds.map((bookId) => bookId));\n };\n\n const handleClearAll = () => {\n onChangeSelectedBookIds([]);\n };\n\n return (\n
    \n
    \n {Object.values(Section).map((section) => {\n return (\n \n );\n })}\n
    \n\n {\n setIsBooksSelectorOpen(open);\n if (!open) {\n setInputValue(''); // Reset search when closing\n }\n }}\n >\n \n \n {selectedBookIds.length > 0\n ? `${booksSelectedText}: ${selectedBookIds.length}`\n : selectBooksText}\n \n \n \n \n {\n if (e.key === 'Enter') {\n // Store shift state in a ref that will be used by onSelect\n lastKeyEventShiftKey.current = e.shiftKey;\n }\n }}\n >\n \n
    \n \n \n
    \n \n {noBookFoundText}\n {Object.values(Section).map((section, index) => {\n const sectionBooks = filteredBooksBySection[section];\n\n if (sectionBooks.length === 0) return undefined;\n\n return (\n \n \n {sectionBooks.map((bookId) => (\n handleKeyboardSelect(bookId)}\n onMouseDown={(event) => handleMouseDown(event, bookId)}\n section={getSectionForBook(bookId)}\n showCheck\n localizedBookNames={localizedBookNames}\n commandValue={generateCommandValue(bookId, localizedBookNames)}\n className=\"tw-flex tw-items-center\"\n />\n ))}\n \n {index < Object.values(Section).length - 1 && }\n \n );\n })}\n \n \n
    \n \n\n {selectedBookIds.length > 0 && (\n
    \n {selectedBookIds\n .slice(\n 0,\n selectedBookIds.length === MAX_VISIBLE_BADGES\n ? MAX_VISIBLE_BADGES\n : VISIBLE_BADGES_COUNT,\n )\n .map((bookId) => (\n \n {getLocalizedBookName(bookId, localizedBookNames)}\n \n ))}\n {selectedBookIds.length > MAX_VISIBLE_BADGES && (\n {`+${selectedBookIds.length - VISIBLE_BADGES_COUNT} ${moreText}`}\n )}\n
    \n )}\n
    \n );\n}\n","import { BookSelector } from '@/components/advanced/scope-selector/book-selector.component';\nimport { Label } from '@/components/shadcn-ui/label';\nimport { RadioGroup, RadioGroupItem } from '@/components/shadcn-ui/radio-group';\nimport { Scope } from '@/components/utils/scripture.util';\nimport { LocalizedStringValue } from 'platform-bible-utils';\n\n/**\n * Object containing all keys used for localization in this component. If you're using this\n * component in an extension, you can pass it into the useLocalizedStrings hook to easily obtain the\n * localized strings and pass them into the localizedStrings prop of this component\n */\nexport const SCOPE_SELECTOR_STRING_KEYS = Object.freeze([\n '%webView_scope_selector_selected_text%',\n '%webView_scope_selector_current_verse%',\n '%webView_scope_selector_current_chapter%',\n '%webView_scope_selector_current_book%',\n '%webView_scope_selector_choose_books%',\n '%webView_scope_selector_scope%',\n '%webView_scope_selector_select_books%',\n '%webView_book_selector_books_selected%',\n '%webView_book_selector_select_books%',\n '%webView_book_selector_search_books%',\n '%webView_book_selector_select_all%',\n '%webView_book_selector_clear_all%',\n '%webView_book_selector_no_book_found%',\n '%webView_book_selector_more%',\n '%scripture_section_ot_long%',\n '%scripture_section_ot_short%',\n '%scripture_section_nt_long%',\n '%scripture_section_nt_short%',\n '%scripture_section_dc_long%',\n '%scripture_section_dc_short%',\n '%scripture_section_extra_long%',\n '%scripture_section_extra_short%',\n] as const);\n\n/** Type definition for the localized strings used in this component */\nexport type ScopeSelectorLocalizedStrings = {\n [localizedInventoryKey in (typeof SCOPE_SELECTOR_STRING_KEYS)[number]]?: LocalizedStringValue;\n};\n\n/**\n * Gets the localized value for the provided key\n *\n * @param strings Object containing localized string\n * @param key Key for a localized string\n * @returns The localized value for the provided key, if available. Returns the key if no localized\n * value is available\n */\nconst localizeString = (\n strings: ScopeSelectorLocalizedStrings,\n key: keyof ScopeSelectorLocalizedStrings,\n) => {\n return strings[key] ?? key;\n};\n\n/** Props for configuring the ScopeSelector component */\ninterface ScopeSelectorProps {\n /** The current scope selection */\n scope: Scope;\n\n /**\n * Optional array of scopes that should be available in the selector. If not provided, all scopes\n * will be shown as defined in the Scope type\n */\n availableScopes?: Scope[];\n\n /** Callback function that is executed when the user changes the scope selection */\n onScopeChange: (scope: Scope) => void;\n\n /**\n * Information about available books, formatted as a 123 character long string as defined in a\n * projects BooksPresent setting\n */\n availableBookInfo: string;\n\n /** Array of currently selected book IDs */\n selectedBookIds: string[];\n\n /** Callback function that is executed when the user changes the book selection */\n onSelectedBookIdsChange: (books: string[]) => void;\n\n /**\n * Object with all localized strings that the component needs to work well across multiple\n * languages. When using this component with Platform.Bible, you can import\n * `SCOPE_SELECTOR_STRING_KEYS` from this library, pass it in to the Platform's localization hook,\n * and pass the localized keys that are returned by the hook into this prop.\n */\n localizedStrings: ScopeSelectorLocalizedStrings;\n /**\n * Optional map of localized book IDs/short names and full names. Key is the (English) book ID,\n * value contains localized versions of the ID and full book name\n */\n localizedBookNames?: Map;\n /** Optional ID that is applied to the root element of this component */\n id?: string;\n}\n\n/**\n * A component that allows users to select the scope of their search or operation. Available scopes\n * are defined in the Scope type. When 'selectedBooks' is chosen as the scope, a BookSelector\n * component is displayed to allow users to choose specific books.\n */\nexport function ScopeSelector({\n scope,\n availableScopes,\n onScopeChange,\n availableBookInfo,\n selectedBookIds,\n onSelectedBookIdsChange,\n localizedStrings,\n localizedBookNames,\n id,\n}: ScopeSelectorProps) {\n const selectedTextText = localizeString(\n localizedStrings,\n '%webView_scope_selector_selected_text%',\n );\n const currentVerseText = localizeString(\n localizedStrings,\n '%webView_scope_selector_current_verse%',\n );\n const currentChapterText = localizeString(\n localizedStrings,\n '%webView_scope_selector_current_chapter%',\n );\n const currentBookText = localizeString(localizedStrings, '%webView_scope_selector_current_book%');\n const chooseBooksText = localizeString(localizedStrings, '%webView_scope_selector_choose_books%');\n const scopeText = localizeString(localizedStrings, '%webView_scope_selector_scope%');\n const selectBooksText = localizeString(localizedStrings, '%webView_scope_selector_select_books%');\n\n const SCOPE_OPTIONS: Array<{ value: Scope; label: string; id: string }> = [\n { value: 'selectedText', label: selectedTextText, id: 'scope-selected-text' },\n { value: 'verse', label: currentVerseText, id: 'scope-verse' },\n { value: 'chapter', label: currentChapterText, id: 'scope-chapter' },\n { value: 'book', label: currentBookText, id: 'scope-book' },\n { value: 'selectedBooks', label: chooseBooksText, id: 'scope-selected' },\n ];\n\n const displayedScopes = availableScopes\n ? SCOPE_OPTIONS.filter((option) => availableScopes.includes(option.value))\n : SCOPE_OPTIONS;\n\n return (\n
    \n
    \n \n \n {displayedScopes.map(({ value, label, id: scopeId }) => (\n
    \n \n \n
    \n ))}\n \n
    \n\n {scope === 'selectedBooks' && (\n
    \n \n \n
    \n )}\n
    \n );\n}\n\nexport default ScopeSelector;\n","import {\n getLocalizeKeyForScrollGroupId,\n LanguageStrings,\n ScrollGroupId,\n} from 'platform-bible-utils';\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@/components/shadcn-ui/select';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\nimport { cn } from '@/utils/shadcn-ui.util';\n\nconst DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS = {\n [getLocalizeKeyForScrollGroupId('undefined')]: 'ร˜',\n [getLocalizeKeyForScrollGroupId(0)]: 'A',\n [getLocalizeKeyForScrollGroupId(1)]: 'B',\n [getLocalizeKeyForScrollGroupId(2)]: 'C',\n [getLocalizeKeyForScrollGroupId(3)]: 'D',\n [getLocalizeKeyForScrollGroupId(4)]: 'E',\n [getLocalizeKeyForScrollGroupId(5)]: 'F',\n [getLocalizeKeyForScrollGroupId(6)]: 'G',\n [getLocalizeKeyForScrollGroupId(7)]: 'H',\n [getLocalizeKeyForScrollGroupId(8)]: 'I',\n [getLocalizeKeyForScrollGroupId(9)]: 'J',\n [getLocalizeKeyForScrollGroupId(10)]: 'K',\n [getLocalizeKeyForScrollGroupId(11)]: 'L',\n [getLocalizeKeyForScrollGroupId(12)]: 'M',\n [getLocalizeKeyForScrollGroupId(13)]: 'N',\n [getLocalizeKeyForScrollGroupId(14)]: 'O',\n [getLocalizeKeyForScrollGroupId(15)]: 'P',\n [getLocalizeKeyForScrollGroupId(16)]: 'Q',\n [getLocalizeKeyForScrollGroupId(17)]: 'R',\n [getLocalizeKeyForScrollGroupId(18)]: 'S',\n [getLocalizeKeyForScrollGroupId(19)]: 'T',\n [getLocalizeKeyForScrollGroupId(20)]: 'U',\n [getLocalizeKeyForScrollGroupId(21)]: 'V',\n [getLocalizeKeyForScrollGroupId(22)]: 'W',\n [getLocalizeKeyForScrollGroupId(23)]: 'X',\n [getLocalizeKeyForScrollGroupId(24)]: 'Y',\n [getLocalizeKeyForScrollGroupId(25)]: 'Z',\n};\n\nexport type ScrollGroupSelectorProps = {\n /**\n * List of scroll group ids to show to the user. Either a `ScrollGroupId` or `undefined` for no\n * scroll group\n */\n availableScrollGroupIds: (ScrollGroupId | undefined)[];\n /** Currently selected scroll group id. `undefined` for no scroll group */\n scrollGroupId: ScrollGroupId | undefined;\n /** Callback function run when the user tries to change the scroll group id */\n onChangeScrollGroupId: (newScrollGroupId: ScrollGroupId | undefined) => void;\n /**\n * Localized strings to use for displaying scroll group ids. Must be an object whose keys are\n * `getLocalizeKeyForScrollGroupId(scrollGroupId)` for all scroll group ids (and `undefined` if\n * included) in {@link ScrollGroupSelectorProps.availableScrollGroupIds} and whose values are the\n * localized strings to use for those scroll group ids.\n *\n * Defaults to English localizations of English alphabet for scroll groups 0-25 (e.g. 0 is A) and\n * ร˜ for `undefined`. Will fill in any that are not provided with these English localizations.\n * Also, if any values match the keys, the English localization will be used. This is useful in\n * case you want to pass in a temporary version of the localized strings while your localized\n * strings load.\n *\n * @example\n *\n * ```typescript\n * const myScrollGroupIdLocalizedStrings = {\n * [getLocalizeKeyForScrollGroupId('undefined')]: 'ร˜',\n * [getLocalizeKeyForScrollGroupId(0)]: 'A',\n * [getLocalizeKeyForScrollGroupId(1)]: 'B',\n * [getLocalizeKeyForScrollGroupId(2)]: 'C',\n * [getLocalizeKeyForScrollGroupId(3)]: 'D',\n * [getLocalizeKeyForScrollGroupId(4)]: 'E',\n * };\n * ```\n *\n * @example\n *\n * ```tsx\n * const availableScrollGroupIds = [undefined, 0, 1, 2, 3, 4];\n *\n * const localizeKeys = getLocalizeKeysForScrollGroupIds();\n *\n * const [localizedStrings] = useLocalizedStrings(localizeKeys);\n *\n * ...\n *\n * \n * ```\n */\n localizedStrings?: LanguageStrings;\n\n /** Size of the scroll group dropdown button. Defaults to 'sm' */\n size?: 'default' | 'sm' | 'lg' | 'icon';\n\n /** Additional css classes to help with unique styling */\n className?: string;\n\n /** Optional id for the select element */\n id?: string;\n};\n\n/** Selector component for choosing a scroll group */\nexport function ScrollGroupSelector({\n availableScrollGroupIds,\n scrollGroupId,\n onChangeScrollGroupId,\n localizedStrings = {},\n size = 'sm',\n className,\n id,\n}: ScrollGroupSelectorProps) {\n const localizedStringsDefaulted = {\n ...DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS,\n ...Object.fromEntries(\n Object.entries(localizedStrings).map(\n ([localizedStringKey, localizedStringValue]: [string, string]) => [\n localizedStringKey,\n localizedStringKey === localizedStringValue &&\n localizedStringKey in DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS\n ? DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS[localizedStringKey]\n : localizedStringValue,\n ],\n ),\n ),\n };\n\n const dir: Direction = readDirection();\n\n return (\n \n onChangeScrollGroupId(\n newScrollGroupString === 'undefined' ? undefined : parseInt(newScrollGroupString, 10),\n )\n }\n >\n \n \n \n \n {availableScrollGroupIds.map((scrollGroupOptionId) => (\n \n {localizedStringsDefaulted[getLocalizeKeyForScrollGroupId(scrollGroupOptionId)]}\n \n ))}\n \n \n );\n}\n\nexport default ScrollGroupSelector;\n","import { PropsWithChildren } from 'react';\nimport { Separator } from '@/components/shadcn-ui/separator';\n\n/** Props for the SettingsList component, currently just children */\ntype SettingsListProps = PropsWithChildren;\n\n/**\n * SettingsList component is a wrapper for list items. Rendered with a formatted div\n *\n * @deprecated Jul 18 2025. This component is no longer supported or tested. Use of this component\n * is discouraged and it may be removed in the future.\n * @param children To populate the list with\n * @returns Formatted div encompassing the children\n */\nexport function SettingsList({ children }: SettingsListProps) {\n return
    {children}
    ;\n}\n\n/** Props for SettingsListItem component */\ntype SettingsListItemProps = PropsWithChildren & {\n /** Primary text of the list item */\n primary: string;\n\n /** Optional text of the list item */\n secondary?: string | undefined;\n\n /** Optional boolean to display a message if the children aren't loaded yet. Defaults to false */\n isLoading?: boolean;\n\n /** Optional message to display if isLoading */\n loadingMessage?: string;\n};\n\n/**\n * SettingsListItem component is a common list item. Rendered with a formatted div\n *\n * @deprecated Jul 18 2025. This component is no longer supported or tested. Use of this component\n * is discouraged and it may be removed in the future.\n * @param SettingsListItemProps\n * @returns Formatted div encompassing the list item content\n */\nexport function SettingsListItem({\n primary,\n secondary,\n children,\n isLoading = false,\n loadingMessage,\n}: SettingsListItemProps) {\n return (\n
    \n
    \n

    {primary}

    \n

    \n {secondary}\n

    \n
    \n\n {isLoading ? (\n

    {loadingMessage}

    \n ) : (\n
    {children}
    \n )}\n
    \n );\n}\n\n/** Props for SettingsListHeader component */\ntype SettingsListHeaderProps = {\n /** The primary text of the list header */\n primary: string;\n\n /** Optional secondary text of the list header */\n secondary?: string | undefined;\n\n /** Optional boolean to include a separator underneath the secondary text. Defaults to false */\n includeSeparator?: boolean;\n};\n\n/**\n * SettingsListHeader component displays text above the list\n *\n * @deprecated Jul 18 2025. This component is no longer supported or tested. Use of this component\n * is discouraged and it may be removed in the future.\n * @param SettingsListHeaderProps\n * @returns Formatted div with list header content\n */\nexport function SettingsListHeader({\n primary,\n secondary,\n includeSeparator = false,\n}: SettingsListHeaderProps) {\n return (\n
    \n
    \n

    {primary}

    \n

    {secondary}

    \n
    \n {includeSeparator ? : ''}\n
    \n );\n}\n","import { GroupsInMultiColumnMenu, Localized } from 'platform-bible-utils';\n\n/**\n * Function that looks up the key of a sub-menu group using the value of it's `menuItem` property.\n *\n * @example\n *\n * ```ts\n * const groups = {\n * 'platform.subMenu': { menuItem: 'platform.subMenuId', order: 1 },\n * 'platform.subSubMenu': { menuItem: 'platform.subSubMenuId', order: 2 },\n * };\n * const id = 'platform.subMenuId';\n * const groupKey = getSubMenuGroupKeyForMenuItemId(groups, id);\n * console.log(groupKey); // Output: 'platform.subMenu'\n * ```\n *\n * @param groups The JSON Object containing the group definitions\n * @param id The value of the `menuItem` property of the group to look up\n * @returns The key of the group that has the `menuItem` property with the value of `id` or\n * `undefined` if no such group exists.\n */\nexport function getSubMenuGroupKeyForMenuItemId(\n groups: Localized,\n id: string,\n): string | undefined {\n return Object.entries(groups).find(\n ([, value]) => 'menuItem' in value && value.menuItem === id,\n )?.[0];\n}\n","import { cn } from '@/utils/shadcn-ui.util';\n\ntype MenuItemIconProps = {\n /** The icon to display */\n icon: string;\n /** The label of the menu item */\n menuLabel: string;\n /** Whether the icon is leading or trailing */\n leading?: boolean;\n};\n\nfunction MenuItemIcon({ icon, menuLabel, leading }: MenuItemIconProps) {\n return icon ? (\n \n ) : undefined;\n}\n\nexport default MenuItemIcon;\n","import {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuPortal,\n DropdownMenuSeparator,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n} from '@/components/shadcn-ui/dropdown-menu';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@/components/shadcn-ui/tooltip';\nimport { MenuIcon } from 'lucide-react';\nimport {\n GroupsInMultiColumnMenu,\n Localized,\n MenuItemContainingCommand,\n MenuItemContainingSubmenu,\n MultiColumnMenu,\n} from 'platform-bible-utils';\nimport { Fragment, ReactNode } from 'react';\nimport { Button } from '@/components/shadcn-ui/button';\nimport { getSubMenuGroupKeyForMenuItemId } from './menu.util';\nimport { SelectMenuItemHandler } from './platform-menubar.component';\nimport MenuItemIcon from './menu-icon.component';\n\nconst getGroupContent = (\n groups: Localized,\n items: Localized<(MenuItemContainingCommand | MenuItemContainingSubmenu)[]>,\n columnOrSubMenuKey: string | undefined,\n onSelectMenuItem: SelectMenuItemHandler,\n) => {\n if (!columnOrSubMenuKey) return undefined;\n\n const sortedGroupsForColumn = Object.entries(groups)\n .filter(\n ([key, group]) =>\n ('column' in group && group.column === columnOrSubMenuKey) || key === columnOrSubMenuKey,\n )\n .sort(([, a], [, b]) => a.order - b.order);\n\n return sortedGroupsForColumn.flatMap(([groupKey]) => {\n const groupItems = items\n .filter((item) => item.group === groupKey)\n .sort((a, b) => a.order - b.order)\n .map((item: Localized) => {\n return (\n \n \n {'command' in item ? (\n {\n // Since the item has a command, we know it is a MenuItemContainingCommand.\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n onSelectMenuItem(item as MenuItemContainingCommand);\n }}\n >\n {item.iconPathBefore && (\n \n )}\n {item.label}\n {item.iconPathAfter && (\n \n )}\n \n ) : (\n \n {item.label}\n\n \n \n {getGroupContent(\n groups,\n items,\n getSubMenuGroupKeyForMenuItemId(groups, item.id),\n onSelectMenuItem,\n )}\n \n \n \n )}\n \n {item.tooltip && {item.tooltip}}\n \n );\n });\n\n return groupItems;\n });\n};\n\nexport type TabDropdownMenuProps = {\n /** The handler to use for menu commands */\n onSelectMenuItem: SelectMenuItemHandler;\n\n /** The menu data to show on the dropdown menu */\n menuData: Localized;\n\n /** Defines a string value that labels the current element */\n tabLabel: string;\n\n /** Optional icon for the dropdown menu trigger. Defaults to hamburger icon. */\n icon?: ReactNode;\n\n /** Additional css class(es) to help with unique styling of the tab dropdown menu */\n className?: string;\n\n /** Style variant for the app menubar component. */\n variant?: 'default' | 'muted';\n\n buttonVariant?: 'default' | 'ghost' | 'outline' | 'secondary';\n\n /** Optional unique identifier */\n id?: string;\n};\n\n/**\n * Dropdown menu designed to be used with Platform.Bible menu data. Column headers are ignored.\n * Column data is separated by a horizontal divider, so groups are not distinguishable. Tooltips are\n * displayed on hovering over menu items, if a tooltip is defined for them.\n *\n * A child component can be passed in to show as an icon on the menu trigger button.\n */\nexport default function TabDropdownMenu({\n onSelectMenuItem,\n menuData,\n tabLabel,\n icon,\n className,\n variant,\n buttonVariant = 'ghost',\n id,\n}: TabDropdownMenuProps) {\n return (\n \n \n \n \n \n {Object.entries(menuData.columns)\n .filter(([, column]) => typeof column === 'object')\n .sort(([, a], [, b]) => {\n if (typeof a === 'boolean' || typeof b === 'boolean') return 0;\n return a.order - b.order;\n })\n .map(([columnKey], index, array) => (\n \n \n \n {getGroupContent(menuData.groups, menuData.items, columnKey, onSelectMenuItem)}\n \n \n\n {index < array.length - 1 && }\n \n ))}\n \n \n );\n}\n","import { Localized, MultiColumnMenu } from 'platform-bible-utils';\nimport React, { PropsWithChildren, ReactNode } from 'react';\nimport { SelectMenuItemHandler } from '../menus/platform-menubar.component';\n\nexport type TabToolbarCommonProps = {\n /**\n * The handler to use for toolbar item commands related to the project menu. Here is a basic\n * example of how to create this:\n *\n * @example\n *\n * ```tsx\n * const projectMenuCommandHandler: SelectMenuItemHandler = async (selectedMenuItem) => {\n * const commandName = selectedMenuItem.command;\n * try {\n * // Assert the more specific type. Assert the more specific type. The menu data should\n * // specify a valid command name here. If not, the error will be caught.\n * // eslint-disable-next-line no-type-assertion/no-type-assertion\n * await papi.commands.sendCommand(commandName as CommandNames);\n * } catch (e) {\n * throw new Error(\n * `handleMenuCommand error: command: ${commandName}. ${JSON.stringify(e)}`,\n * );\n * }\n * };\n * ```\n */\n onSelectProjectMenuItem: SelectMenuItemHandler;\n\n /**\n * Menu data that is used to populate the Menubar component for the project menu. In an extension,\n * the menu data comes from menus.json in the contributions folder. To access that info, use\n * useMemo to get the WebViewMenu.\n */\n projectMenuData?: Localized;\n\n /** Optional unique identifier */\n id?: string;\n\n /** Additional css classes to help with unique styling of the extensible toolbar */\n className?: string;\n\n /** Icon that will be displayed on the Menu Button. Defaults to the hamburger menu icon. */\n menuButtonIcon?: ReactNode;\n};\n\nexport type TabToolbarContainerProps = PropsWithChildren<{\n /** Optional unique identifier */\n id?: string;\n /** Additional css classes to help with unique styling of the extensible toolbar */\n className?: string;\n}>;\n\n/** Wrapper that allows consistent styling for both TabToolbar and TabFloatingMenu. */\nexport const TabToolbarContainer = React.forwardRef(\n ({ id, className, children }, ref) => (\n \n {children}\n \n ),\n);\n\nexport default TabToolbarContainer;\n","import { ReactNode } from 'react';\nimport { Localized, MultiColumnMenu } from 'platform-bible-utils';\nimport { Menu, EllipsisVertical } from 'lucide-react';\nimport TabDropdownMenu from '../menus/tab-dropdown-menu.component';\nimport { SelectMenuItemHandler } from '../menus/platform-menubar.component';\nimport { TabToolbarCommonProps, TabToolbarContainer } from './tab-toolbar-container.component';\n\nexport type TabToolbarProps = TabToolbarCommonProps & {\n /**\n * The handler to use for toolbar item commands related to the tab view menu. Here is a basic\n * example of how to create this from the hello-rock3 extension:\n *\n * @example\n *\n * ```tsx\n * const projectMenuCommandHandler: SelectMenuItemHandler = async (selectedMenuItem) => {\n * const commandName = selectedMenuItem.command;\n * try {\n * // Assert the more specific type. Assert the more specific type. The menu data should\n * // specify a valid command name here. If not, the error will be caught.\n * // eslint-disable-next-line no-type-assertion/no-type-assertion\n * await papi.commands.sendCommand(commandName as CommandNames);\n * } catch (e) {\n * throw new Error(\n * `handleMenuCommand error: command: ${commandName}. ${JSON.stringify(e)}`,\n * );\n * }\n * };\n * ```\n */\n onSelectViewInfoMenuItem: SelectMenuItemHandler;\n\n /** Menu data that is used to populate the Menubar component for the view info menu */\n tabViewMenuData?: Localized;\n\n /**\n * Toolbar children to be put at the start of the the toolbar after the project menu icon (left\n * side in ltr, right side in rtl). Recommended for inner navigation.\n */\n startAreaChildren?: ReactNode;\n\n /** Toolbar children to be put in the center area of the the toolbar. Recommended for tools. */\n centerAreaChildren?: ReactNode;\n\n /**\n * Toolbar children to be put at the end of the the toolbar before the tab view menu icon (right\n * side in ltr, left side in rtl). Recommended for secondary tools and view options.\n */\n endAreaChildren?: ReactNode;\n};\n\n/**\n * Toolbar that holds the project menu icon on one side followed by three different areas/categories\n * for toolbar icons followed by an optional view info menu icon. See the Tab Floating Menu Button\n * component for a menu component that takes up less screen real estate yet is always visible.\n */\nexport function TabToolbar({\n onSelectProjectMenuItem,\n onSelectViewInfoMenuItem,\n projectMenuData,\n tabViewMenuData,\n id,\n className,\n startAreaChildren,\n centerAreaChildren,\n endAreaChildren,\n menuButtonIcon,\n}: TabToolbarProps) {\n return (\n \n {projectMenuData && (\n }\n buttonVariant=\"ghost\"\n />\n )}\n {startAreaChildren && (\n
    \n {startAreaChildren}\n
    \n )}\n {centerAreaChildren && (\n
    \n {centerAreaChildren}\n
    \n )}\n
    \n {tabViewMenuData && (\n }\n className=\"tw-h-full\"\n />\n )}\n {endAreaChildren}\n
    \n
    \n );\n}\n\nexport default TabToolbar;\n","import TabDropdownMenu from '../menus/tab-dropdown-menu.component';\nimport { TabToolbarCommonProps, TabToolbarContainer } from './tab-toolbar-container.component';\n\n/**\n * Renders a TabDropdownMenu with a trigger button that looks like the menuButtonIcon or like the\n * default of three stacked horizontal lines (aka the hamburger). The menu \"floats\" over the content\n * so it is always visible. When clicked, it displays a dropdown menu with the projectMenuData.\n */\nexport function TabFloatingMenu({\n onSelectProjectMenuItem,\n projectMenuData,\n id,\n className,\n menuButtonIcon,\n}: TabToolbarCommonProps) {\n return (\n \n {projectMenuData && (\n \n )}\n \n );\n}\n\nexport default TabFloatingMenu;\n","// adapted from: https://github.com/shadcn-ui/ui/discussions/752\n\n'use client';\n\nimport { TabsContentProps, TabsListProps, TabsTriggerProps } from '@/components/shadcn-ui/tabs';\nimport { Direction, readDirection } from '@/utils/dir-helper.util';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport * as TabsPrimitive from '@radix-ui/react-tabs';\nimport React from 'react';\n\nexport type VerticalTabsProps = React.ComponentPropsWithoutRef & {\n className?: string;\n};\n\nexport type LeftTabsTriggerProps = TabsTriggerProps & {\n value: string;\n ref?: React.Ref;\n};\n\n/**\n * Tabs components provide a set of layered sections of contentโ€”known as tab panelsโ€“that are\n * displayed one at a time. These components are built on Radix UI primitives and styled with Shadcn\n * UI. See Shadcn UI Documentation: https://ui.shadcn.com/docs/components/tabs See Radix UI\n * Documentation: https://www.radix-ui.com/primitives/docs/components/tabs\n */\nexport const VerticalTabs = React.forwardRef<\n React.ElementRef,\n VerticalTabsProps\n>(({ className, ...props }, ref) => {\n const dir: Direction = readDirection();\n return (\n \n );\n});\n\nVerticalTabs.displayName = TabsPrimitive.List.displayName;\n\n/** @inheritdoc VerticalTabs */\nexport const VerticalTabsList = React.forwardRef<\n React.ElementRef,\n TabsListProps\n>(({ className, ...props }, ref) => (\n \n));\nVerticalTabsList.displayName = TabsPrimitive.List.displayName;\n\n/** @inheritdoc VerticalTabs */\nexport const VerticalTabsTrigger = React.forwardRef<\n React.ElementRef,\n LeftTabsTriggerProps\n>(({ className, ...props }, ref) => (\n \n));\n\n/** @inheritdoc VerticalTabs */\nexport const VerticalTabsContent = React.forwardRef<\n React.ElementRef,\n TabsContentProps\n>(({ className, ...props }, ref) => (\n \n));\nVerticalTabsContent.displayName = TabsPrimitive.Content.displayName;\n","import { SearchBar } from '@/components/basics/search-bar.component';\nimport {\n VerticalTabs,\n VerticalTabsContent,\n VerticalTabsList,\n VerticalTabsTrigger,\n} from '@/components/basics/tabs-vertical';\nimport { ReactNode } from 'react';\n\nexport type TabKeyValueContent = {\n key: string;\n value: string;\n content: ReactNode;\n};\n\nexport type TabNavigationContentSearchProps = {\n /** List of values and keys for each tab this component should provide */\n tabList: TabKeyValueContent[];\n\n /** The search query in the search bar */\n searchValue: string;\n\n /** Handler to run when the value of the search bar changes */\n onSearch: (searchQuery: string) => void;\n\n /** Optional placeholder for the search bar */\n searchPlaceholder?: string;\n\n /** Optional title to include in the header */\n headerTitle?: string;\n\n /** Optional className to modify the search input */\n searchClassName?: string;\n\n /** Optional id for the root element */\n id?: string;\n};\n\n/**\n * TabNavigationContentSearch component provides a vertical tab navigation interface with a search\n * bar at the top. This component allows users to filter content within tabs based on a search\n * query.\n *\n * @param {TabNavigationContentSearchProps} props\n * @param {TabKeyValueContent[]} props.tabList - List of objects containing keys, values, and\n * content for each tab to be displayed.\n * @param {string} props.searchValue - The current value of the search input.\n * @param {function} props.onSearch - Callback function called when the search input changes;\n * receives the new search query as an argument.\n * @param {string} [props.searchPlaceholder] - Optional placeholder text for the search input.\n * @param {string} [props.headerTitle] - Optional title to display above the search input.\n * @param {string} [props.searchClassName] - Optional CSS class name to apply custom styles to the\n * search input.\n * @param {string} [props.id] - Optional id for the root element.\n */\nexport function TabNavigationContentSearch({\n tabList,\n searchValue,\n onSearch,\n searchPlaceholder,\n headerTitle,\n searchClassName,\n id,\n}: TabNavigationContentSearchProps) {\n return (\n
    \n
    \n {headerTitle ?

    {headerTitle}

    : ''}\n \n
    \n \n \n {tabList.map((tab) => (\n \n {tab.value}\n \n ))}\n \n {tabList.map((tab) => (\n \n {tab.content}\n \n ))}\n \n
    \n );\n}\n\nexport default TabNavigationContentSearch;\n","import {\n MenuContext,\n MenuContextProps,\n menuVariants,\n useMenuContext,\n} from '@/context/menu.context';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport * as MenubarPrimitive from '@radix-ui/react-menubar';\nimport { Check, ChevronRight, Circle } from 'lucide-react';\nimport React from 'react';\n\nfunction MenubarMenu({ ...props }: React.ComponentProps) {\n return ;\n}\n\nfunction MenubarGroup({ ...props }: React.ComponentProps) {\n return ;\n}\n\nfunction MenubarPortal({ ...props }: React.ComponentProps) {\n return ;\n}\n\nfunction MenubarRadioGroup({ ...props }: React.ComponentProps) {\n return ;\n}\n\nfunction MenubarSub({ ...props }: React.ComponentProps) {\n return ;\n}\n\nconst Menubar = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n variant?: MenuContextProps['variant'];\n }\n>(({ className, variant = 'default', ...props }, ref) => {\n /* #region CUSTOM provide context to add variants */\n const contextValue = React.useMemo(\n () => ({\n variant,\n }),\n [variant],\n );\n return (\n \n {/* #endregion CUSTOM */}\n \n \n );\n});\nMenubar.displayName = MenubarPrimitive.Root.displayName;\n\nconst MenubarTrigger = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n );\n});\nMenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;\n\nconst MenubarSubTrigger = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n inset?: boolean;\n }\n>(({ className, inset, children, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n {children}\n \n \n );\n});\nMenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;\n\nconst MenubarSubContent = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n );\n});\nMenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;\n\nconst MenubarContent = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, align = 'start', alignOffset = -4, sideOffset = 8, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n \n \n );\n});\nMenubarContent.displayName = MenubarPrimitive.Content.displayName;\n\nconst MenubarItem = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n inset?: boolean;\n }\n>(({ className, inset, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n );\n});\nMenubarItem.displayName = MenubarPrimitive.Item.displayName;\n\nconst MenubarCheckboxItem = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, checked, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n \n \n \n \n \n {children}\n \n );\n});\nMenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;\n\nconst MenubarRadioItem = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, ...props }, ref) => {\n const context = useMenuContext(); // CUSTOM use context to add variants\n return (\n \n \n \n \n \n \n {children}\n \n );\n});\nMenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;\n\nconst MenubarLabel = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n inset?: boolean;\n }\n>(({ className, inset, ...props }, ref) => (\n \n));\nMenubarLabel.displayName = MenubarPrimitive.Label.displayName;\n\nconst MenubarSeparator = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nMenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;\n\nfunction MenubarShortcut({ className, ...props }: React.HTMLAttributes) {\n return (\n \n );\n}\nMenubarShortcut.displayname = 'MenubarShortcut';\n\nexport {\n Menubar,\n MenubarCheckboxItem,\n MenubarContent,\n MenubarGroup,\n MenubarItem,\n MenubarLabel,\n MenubarMenu,\n MenubarPortal,\n MenubarRadioGroup,\n MenubarRadioItem,\n MenubarSeparator,\n MenubarShortcut,\n MenubarSub,\n MenubarSubContent,\n MenubarSubTrigger,\n MenubarTrigger,\n};\n","import {\n Menubar,\n MenubarContent,\n MenubarItem,\n MenubarMenu,\n MenubarSeparator,\n MenubarSub,\n MenubarSubContent,\n MenubarSubTrigger,\n MenubarTrigger,\n} from '@/components/shadcn-ui/menubar';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@/components/shadcn-ui/tooltip';\nimport {\n GroupsInMultiColumnMenu,\n Localized,\n MenuItemContainingCommand,\n MenuItemContainingSubmenu,\n MultiColumnMenu,\n} from 'platform-bible-utils';\nimport { RefObject, useEffect, useRef } from 'react';\nimport { useHotkeys } from 'react-hotkeys-hook';\nimport { getSubMenuGroupKeyForMenuItemId } from './menu.util';\nimport MenuItemIcon from './menu-icon.component';\n\n/**\n * Callback function that is invoked when a user selects a menu item. Receives the full\n * `MenuItemContainingCommand` object as an argument.\n */\nexport interface SelectMenuItemHandler {\n (selectedMenuItem: MenuItemContainingCommand): void;\n}\n\nconst simulateKeyPress = (ref: RefObject, keys: KeyboardEventInit[]) => {\n setTimeout(() => {\n keys.forEach((key) => {\n ref.current?.dispatchEvent(new KeyboardEvent('keydown', key));\n });\n }, 0);\n};\n\nconst getMenubarContent = (\n groups: Localized,\n items: Localized<(MenuItemContainingCommand | MenuItemContainingSubmenu)[]>,\n columnOrSubMenuKey: string | undefined,\n onSelectMenuItem: SelectMenuItemHandler,\n) => {\n if (!columnOrSubMenuKey) return undefined;\n\n const sortedGroupsForColumn = Object.entries(groups)\n .filter(\n ([key, group]) =>\n ('column' in group && group.column === columnOrSubMenuKey) || key === columnOrSubMenuKey,\n )\n .sort(([, a], [, b]) => a.order - b.order);\n\n return sortedGroupsForColumn.flatMap(([groupKey], index) => {\n const groupItems = items\n .filter((item) => item.group === groupKey)\n .sort((a, b) => a.order - b.order)\n .map((item: Localized) => {\n return (\n \n \n {'command' in item ? (\n {\n // Since the item has a command, we know it is a MenuItemContainingCommand.\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n onSelectMenuItem(item as MenuItemContainingCommand);\n }}\n >\n {item.iconPathBefore && (\n \n )}\n {item.label}\n {item.iconPathAfter && (\n \n )}\n \n ) : (\n \n {item.label}\n \n {getMenubarContent(\n groups,\n items,\n getSubMenuGroupKeyForMenuItemId(groups, item.id),\n onSelectMenuItem,\n )}\n \n \n )}\n \n {item.tooltip && {item.tooltip}}\n \n );\n });\n\n const itemsWithSeparator = [...groupItems];\n if (groupItems.length > 0 && index < sortedGroupsForColumn.length - 1) {\n itemsWithSeparator.push();\n }\n\n return itemsWithSeparator;\n });\n};\n\ntype PlatformMenubarProps = {\n /** Menu data that is used to populate the Menubar component. */\n menuData: Localized;\n\n /** The handler to use for menu commands. */\n onSelectMenuItem: SelectMenuItemHandler;\n\n /**\n * Optional callback function that is executed whenever a menu on the Menubar is opened or closed.\n * Helpful for handling updates to the menu, as changing menu data when the menu is opened is not\n * desirable.\n */\n onOpenChange?: (isOpen: boolean) => void;\n\n /** Style variant for the app menubar component. */\n variant?: 'default' | 'muted';\n};\n\n/** Menubar component tailored to work with Platform.Bible menu data */\nexport function PlatformMenubar({\n menuData,\n onSelectMenuItem,\n onOpenChange,\n variant,\n}: PlatformMenubarProps) {\n // These refs will always be defined\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const menubarRef = useRef(undefined!);\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const projectMenuRef = useRef(undefined!);\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const windowMenuRef = useRef(undefined!);\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const layoutMenuRef = useRef(undefined!);\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const helpMenuRef = useRef(undefined!);\n\n const getRefForColumn = (columnKey: string) => {\n switch (columnKey) {\n case 'platform.app':\n return projectMenuRef;\n case 'platform.window':\n return windowMenuRef;\n case 'platform.layout':\n return layoutMenuRef;\n case 'platform.help':\n return helpMenuRef;\n default:\n return undefined;\n }\n };\n\n // This is a quick and dirty way to implement some shortcuts by simulating key presses\n useHotkeys(['alt', 'alt+p', 'alt+l', 'alt+n', 'alt+h'], (event, handler) => {\n event.preventDefault();\n\n const escKey: KeyboardEventInit = { key: 'Escape', code: 'Escape', keyCode: 27, bubbles: true };\n const spaceKey: KeyboardEventInit = { key: ' ', code: 'Space', keyCode: 32, bubbles: true };\n\n switch (handler.hotkey) {\n case 'alt':\n simulateKeyPress(projectMenuRef, [escKey]);\n break;\n case 'alt+p':\n projectMenuRef.current?.focus();\n simulateKeyPress(projectMenuRef, [escKey, spaceKey]);\n break;\n case 'alt+l':\n windowMenuRef.current?.focus();\n simulateKeyPress(windowMenuRef, [escKey, spaceKey]);\n break;\n case 'alt+n':\n layoutMenuRef.current?.focus();\n simulateKeyPress(layoutMenuRef, [escKey, spaceKey]);\n break;\n case 'alt+h':\n helpMenuRef.current?.focus();\n simulateKeyPress(helpMenuRef, [escKey, spaceKey]);\n break;\n default:\n break;\n }\n });\n\n useEffect(() => {\n if (!onOpenChange || !menubarRef.current) return;\n\n const observer = new MutationObserver((mutations) => {\n mutations.forEach((mutation) => {\n if (mutation.attributeName === 'data-state' && mutation.target instanceof HTMLElement) {\n const state = mutation.target.getAttribute('data-state');\n\n if (state === 'open') {\n onOpenChange(true);\n } else {\n onOpenChange(false);\n }\n }\n });\n });\n\n const menubarElement = menubarRef.current;\n const dataStateAttributes = menubarElement.querySelectorAll('[data-state]');\n\n dataStateAttributes.forEach((element) => {\n observer.observe(element, { attributes: true });\n });\n\n return () => observer.disconnect();\n }, [onOpenChange]);\n\n if (!menuData) return undefined;\n\n return (\n \n {Object.entries(menuData.columns)\n .filter(([, column]) => typeof column === 'object')\n .sort(([, a], [, b]) => {\n if (typeof a === 'boolean' || typeof b === 'boolean') return 0;\n return a.order - b.order;\n })\n .map(([columnKey, column]) => (\n \n \n {typeof column === 'object' && 'label' in column && column.label}\n \n \n \n {getMenubarContent(menuData.groups, menuData.items, columnKey, onSelectMenuItem)}\n \n \n \n ))}\n \n );\n}\n\nexport default PlatformMenubar;\n","import {\n SelectMenuItemHandler,\n PlatformMenubar,\n} from '@/components/advanced/menus/platform-menubar.component';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Localized, MultiColumnMenu } from 'platform-bible-utils';\nimport { PropsWithChildren, ReactNode, useRef } from 'react';\n\nexport type ToolbarProps = PropsWithChildren<{\n /** The handler to use for menu commands (and eventually toolbar commands). */\n onSelectMenuItem: SelectMenuItemHandler;\n\n /**\n * Menu data that is used to populate the Menubar component. If empty object, no menus will be\n * shown on the App Menubar\n */\n menuData?: Localized;\n\n /**\n * Optional callback function that is executed whenever a menu on the App Menubar is opened or\n * closed. Helpful for handling updates to the menu, as changing menu data when the menu is opened\n * is not desirable.\n */\n onOpenChange?: (isOpen: boolean) => void;\n\n /** Optional unique identifier */\n id?: string;\n\n /** Additional css classes to help with unique styling of the toolbar */\n className?: string;\n\n /**\n * Whether the toolbar should be used as a draggable area for moving the application. This will\n * add an electron specific style `WebkitAppRegion: 'drag'` to the toolbar in order to make it\n * draggable. See:\n * https://www.electronjs.org/docs/latest/tutorial/custom-title-bar#create-a-custom-title-bar\n */\n shouldUseAsAppDragArea?: boolean;\n\n /** Toolbar children to be put at the start of the toolbar (left side in ltr, right side in rtl) */\n appMenuAreaChildren?: ReactNode;\n\n /** Toolbar children to be put at the end of the toolbar (right side in ltr, left side in rtl) */\n configAreaChildren?: ReactNode;\n\n /** Variant of the menubar */\n menubarVariant?: 'default' | 'muted';\n}>;\n\n/**\n * Get tailwind class for reserved space for the window controls / macos \"traffic lights\". Passing\n * 'darwin' will reserve the necessary space for macos traffic lights at the start, otherwise a\n * different amount of space at the end for the window controls.\n *\n * Apply to the toolbar like: `` or ``\n *\n * @param operatingSystem The os platform: 'darwin' (macos) | anything else\n * @returns The class name to apply to the toolbar if os specific space should be reserved\n */\nexport function getToolbarOSReservedSpaceClassName(\n operatingSystem: string | undefined,\n): string | undefined {\n switch (operatingSystem) {\n case undefined:\n return undefined;\n case 'darwin':\n return 'tw-ps-[85px]';\n default:\n return 'tw-pe-[calc(138px+1rem)]';\n }\n}\n\n/**\n * A customizable toolbar component with a menubar, content area, and configure area.\n *\n * This component is designed to be used in the window title bar of an electron application.\n *\n * @param {ToolbarProps} props - The props for the component.\n */\nexport function Toolbar({\n menuData,\n onOpenChange,\n onSelectMenuItem,\n className,\n id,\n children,\n appMenuAreaChildren,\n configAreaChildren,\n shouldUseAsAppDragArea,\n menubarVariant = 'default',\n}: ToolbarProps) {\n // This ref will always be defined\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n const containerRef = useRef(undefined!);\n\n return (\n \n \n {/* App Menu area */}\n
    \n \n {appMenuAreaChildren}\n\n {menuData && (\n \n )}\n
    \n \n\n {/* Content area */}\n \n {children}\n \n\n {/* Configure area */}\n
    \n \n {configAreaChildren}\n
    \n \n \n \n );\n}\n\nexport default Toolbar;\n","import { useState } from 'react';\nimport { LocalizedStringValue, formatReplacementString } from 'platform-bible-utils';\nimport { cn } from '@/utils/shadcn-ui.util';\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../shadcn-ui/select';\nimport { Label } from '../shadcn-ui/label';\n\n/**\n * Immutable array containing all keys used for localization in this component. If you're using this\n * component in an extension, you can pass it into the useLocalizedStrings hook to easily obtain the\n * localized strings and pass them into the localizedStrings prop of this component\n */\nexport const UI_LANGUAGE_SELECTOR_STRING_KEYS = Object.freeze([\n '%settings_uiLanguageSelector_fallbackLanguages%',\n] as const);\n\nexport type UiLanguageSelectorLocalizedStrings = {\n [localizedUiLanguageSelectorKey in (typeof UI_LANGUAGE_SELECTOR_STRING_KEYS)[number]]?: LocalizedStringValue;\n};\n\n/**\n * Gets the localized value for the provided key\n *\n * @param strings Object containing localized string\n * @param key Key for a localized string\n * @returns The localized value for the provided key, if available. Returns the key if no localized\n * value is available\n */\nconst localizeString = (\n strings: UiLanguageSelectorLocalizedStrings,\n key: keyof UiLanguageSelectorLocalizedStrings,\n) => {\n return strings[key] ?? key;\n};\n\nexport type LanguageInfo = {\n /** The name of the language to be displayed (in its native script) */\n autonym: string;\n /**\n * The name of the language in other languages, so that the language can also be displayed in the\n * current UI language, if known.\n */\n uiNames?: Record;\n /**\n * Other known names of the language (for searching). This can include pejorative names and should\n * never be displayed unless typed by the user.\n */\n otherNames?: string[];\n};\n\nexport type UiLanguageSelectorProps = {\n /** Full set of known languages to display. The keys are valid BCP-47 tags. */\n knownUiLanguages: Record;\n /** IETF BCP-47 language tag of the current primary UI language. `undefined` => 'en' */\n primaryLanguage: string;\n /**\n * Ordered list of fallback language tags to use if the localization key can't be found in the\n * current primary UI language. This list never contains English ('en') because it is the ultimate\n * fallback.\n */\n fallbackLanguages: string[] | undefined;\n /**\n * Handler for when either the primary or the fallback languages change (or both). For this\n * handler, the primary UI language is the first one in the array, followed by the fallback\n * languages in order of decreasing preference.\n */\n onLanguagesChange?: (newUiLanguages: string[]) => void;\n /** Handler for the primary language changes. */\n onPrimaryLanguageChange?: (newPrimaryUiLanguage: string) => void;\n /**\n * Handler for when the fallback languages change. The array contains the fallback languages in\n * order of decreasing preference.\n */\n onFallbackLanguagesChange?: (newFallbackLanguages: string[]) => void;\n /**\n * Map whose keys are localized string keys as contained in UI_LANGUAGE_SELECTOR_STRING_KEYS and\n * whose values are the localized strings (in the current UI language).\n */\n localizedStrings: UiLanguageSelectorLocalizedStrings;\n /** Additional css classes to help with unique styling of the control */\n className?: string;\n /** Optional id for the root element */\n id?: string;\n};\n\n/**\n * A component for selecting the user interface language and managing fallback languages. Allows\n * users to choose a primary UI language and optionally select fallback languages.\n *\n * @param {UiLanguageSelectorProps} props - The props for the component.\n */\nexport function UiLanguageSelector({\n knownUiLanguages,\n primaryLanguage = 'en',\n fallbackLanguages = [],\n onLanguagesChange,\n onPrimaryLanguageChange,\n onFallbackLanguagesChange,\n localizedStrings,\n className,\n id,\n}: UiLanguageSelectorProps) {\n const fallbackLanguagesText = localizeString(\n localizedStrings,\n '%settings_uiLanguageSelector_fallbackLanguages%',\n );\n const [isOpen, setIsOpen] = useState(false);\n\n const handleLanguageChange = (code: string) => {\n if (onPrimaryLanguageChange) onPrimaryLanguageChange(code);\n // REVIEW: Should fallback languages be preserved when primary language changes?\n if (onLanguagesChange)\n onLanguagesChange([code, ...fallbackLanguages.filter((lang) => lang !== code)]);\n if (onFallbackLanguagesChange && fallbackLanguages.find((l) => l === code))\n onFallbackLanguagesChange([...fallbackLanguages.filter((lang) => lang !== code)]);\n setIsOpen(false); // Close the dropdown when a selection is made\n };\n\n /**\n * Gets the display name for the given language. This will typically include the autonym (in the\n * native script), along with the name of the language in the current UI locale if known, with a\n * fallback to the English name (if known).\n *\n * @param {string} lang - The BCP-47 code of the language whose display name is being requested.\n * @param {string} uiLang - The BCP-47 code of the current user-interface language used used to\n * try to look up the name of the language in a form that is likely to be helpful to the user if\n * they do not recognize the autonym.\n * @returns {string} The display name of the language.\n */\n const getLanguageDisplayName = (lang: string, uiLang: string) => {\n const altName =\n uiLang !== lang\n ? (knownUiLanguages[lang]?.uiNames?.[uiLang] ?? knownUiLanguages[lang]?.uiNames?.en)\n : undefined;\n\n return altName\n ? `${knownUiLanguages[lang]?.autonym} (${altName})`\n : knownUiLanguages[lang]?.autonym;\n };\n\n return (\n
    \n {/* Language Selector */}\n setIsOpen(open)}\n >\n \n \n \n \n {Object.keys(knownUiLanguages).map((key) => {\n return (\n \n {getLanguageDisplayName(key, primaryLanguage)}\n \n );\n })}\n \n \n\n {/* Fallback Language Button */}\n {primaryLanguage !== 'en' && (\n
    \n \n
    \n )}\n
    \n );\n}\n\nexport default UiLanguageSelector;\n","import { Label } from '@/components/shadcn-ui/label';\nimport { ReactNode } from 'react';\n\ntype SmartLabelProps = {\n item: string;\n createLabel?: (item: string) => string;\n createComplexLabel?: (item: string) => ReactNode;\n};\n\n/** Create labels with text, react elements (e.g. links), or text + react elements */\nfunction SmartLabel({ item, createLabel, createComplexLabel }: SmartLabelProps): ReactNode {\n if (createLabel) {\n return ;\n }\n if (createComplexLabel) {\n return ;\n }\n return ;\n}\n\nexport default SmartLabel;\n","import { Checkbox } from '@/components/shadcn-ui/checkbox';\nimport { ReactNode } from 'react';\nimport SmartLabel from './smart-label.component';\n\nexport type ChecklistProps = {\n /** Optional string representing the id attribute of the Checklist */\n id?: string;\n /** Optional string representing CSS class name(s) for styling */\n className?: string;\n /** Array of strings representing the checkable items */\n listItems: string[];\n /** Array of strings representing the checked items */\n selectedListItems: string[];\n /**\n * Function that is called when a checkbox item is selected or deselected\n *\n * @param item The string description for this item\n * @param selected True if selected, false if not selected\n */\n handleSelectListItem: (item: string, selected: boolean) => void;\n\n /**\n * Optional function creates a label for a provided checkable item\n *\n * @param item The item for which a label is to be created\n * @returns A string representing the label text for the checkbox associated with that item\n */\n createLabel?: (item: string) => string;\n\n /**\n * Optional function creates a label for a provided checkable item\n *\n * @param item The item for which a label is to be created, including text and any additional\n * elements (e.g. links)\n * @returns A react node representing the label text and any additional elements (e.g. links) for\n * the checkbox associated with that item\n */\n createComplexLabel?: (item: string) => ReactNode;\n};\n\n/** Renders a list of checkboxes. Each checkbox corresponds to an item from the `listItems` array. */\nexport function Checklist({\n id,\n className,\n listItems,\n selectedListItems,\n handleSelectListItem,\n createLabel,\n createComplexLabel,\n}: ChecklistProps) {\n return (\n
    \n {listItems.map((item) => (\n
    \n handleSelectListItem(item, value)}\n />\n \n
    \n ))}\n
    \n );\n}\n\nexport default Checklist;\n","import { cn } from '@/utils/shadcn-ui.util';\nimport { MoreHorizontal } from 'lucide-react';\nimport React, { ReactNode } from 'react';\nimport { Button } from '../shadcn-ui/button';\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '../shadcn-ui/dropdown-menu';\n\n/** Props interface for the ResultsCard base component */\nexport interface ResultsCardProps {\n /** Unique key for the card */\n cardKey: string;\n /** Whether this card is currently selected/focused */\n isSelected: boolean;\n /** Callback function called when the card is clicked */\n onSelect: () => void;\n /** Whether the content of this card are in a denied state */\n isDenied?: boolean;\n /** Whether the card should be hidden */\n isHidden?: boolean;\n /** Additional CSS classes to apply to the card */\n className?: string;\n /** Main content to display on the card */\n children: ReactNode;\n /** Content to show in the dropdown menu when selected */\n dropdownContent?: ReactNode;\n /** Additional content to show below the main content when selected */\n additionalSelectedContent?: ReactNode;\n /** Color to use for the card's accent border */\n accentColor?: string;\n}\n\n/**\n * ResultsCard is a base component for displaying scripture-related results in a card format, even\n * though it is not based on the Card component. It provides common functionality like selection\n * state, dropdown menus, and expandable content.\n */\nexport function ResultsCard({\n cardKey,\n isSelected,\n onSelect,\n isDenied,\n isHidden = false,\n className,\n children,\n dropdownContent,\n additionalSelectedContent,\n accentColor,\n}: ResultsCardProps) {\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n onSelect();\n }\n };\n\n return (\n