Searchable fuzzy/pinyin matching for long native select and ARIA listbox dropdowns.
// ==UserScript==
// @name Chrome List Locator
// @namespace https://github.com/gdyan2022/chrome-list-locator
// @version 3.0.2
// @description Searchable fuzzy/pinyin matching for long native select and ARIA listbox dropdowns.
// @license MIT
// @homepageURL https://github.com/gdyan2022/chrome-list-locator
// @supportURL https://github.com/gdyan2022/chrome-list-locator/issues
// @match *://*/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addValueChangeListener
// @grant GM_removeValueChangeListener
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @run-at document-idle
// ==/UserScript==
/* ---- content/pinyin-data.js ---- */
/**
* Auto-generated pinyin lookup for Chrome List Locator.
*
* DO NOT EDIT BY HAND (except regenerating via scripts/generate-pinyin-data.mjs).
*
* Data source: mozillazg/pinyin-data
* Source URL: https://github.com/mozillazg/pinyin-data
* Source commit/hash: 923b108dc5d45dee061324c011b478fb649f8b73
* Coverage file: kMandarin_8105.txt (通用规范汉字表 8105)
* Generated: 2026-07-09
* Content fingerprint: e6d27de11f6287b2
* Stats: chars=8105, syllables=415, rawBytes≈67477
*
* License (summary): mozillazg/pinyin-data is MIT-licensed.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, subject to the MIT copyright notice and permission
* notice being included in all copies or substantial portions of the Software.
* Full license: https://github.com/mozillazg/pinyin-data/blob/master/LICENSE
*
* Readings are derived from Unicode Unihan-related sources merged upstream
* (see pinyin-data README). This file redistributes a compressed subset for
* offline fuzzy matching only.
*/
(function (global) {
"use strict";
const VERSION = "pinyin-data:mozillazg:923b108dc5d45dee061324c011b478fb649f8b73:coverage-8105";
const SYLLABLES = "zhou,yi,fu,han,duo,yao,xie,jie,tong,yong,pian,si,jiu,meng,ling,song,hui,kuai,lang,huan,xian,che,gang,qu,li,chong,xu,xi,se,jue,kao,huo,yu,tu,gan,huang,lou,lve,di,zha,can,jiong,ran,zeng,zhuan,shi,die,ti,jun,zhun,qiong,ying,qi,zhuo,xiu,zhe,ting,xin,chu,gong,tang,po,yin,chun,teng,jiao,lie,jing,ju,pi,yan,ding,zheng,wan,mo,zhang,san,shang,xia,bu,fou,mian,gai,chou,qie,cu,qiu,bing,ye,cong,dong,cheng,sheng,diu,liang,sang,ge,ya,zhong,feng,chuan,guan,quan,lin,dan,wei,zhu,ai,nai,me,ma,zhi,wu,zuo,hu,fa,le,yue,ping,pang,qiao,guai,mie,nie,xiang,shu,ji,mai,luan,ru,na,qian,liao,zi,er,kui,yun,gen,xuan,geng,suo,wang,kang,hai,chan,heng,peng,mu,qin,qing,bo,zhan,men,ren,shen,ze,jin,nu,pu,reng,zong,lun,cang,zai,ta,tuo,dai,lian,sa,yang,ang,bi,jian,mou,jia,fen,bin,fang,fan,you,chang,chen,bai,ba,gu,ni,ban,pan,ci,ga,chi,dian,tian,tan,ben,cui,he,she,fo,gou,kou,ning,wa,pei,lao,tiao,diao,dao,xiao,kan,kua,hua,e,mao,cha,lv,zhen,ce,zhai,chai,nong,hou,bian,chuo,shun,dun,zu,su,bao,lia,lai,pai,biao,beng,an,bei,chui,zhui,kong,juan,wo,luo,ruo,re,cai,ou,tou,gui,bang,que,nuo,ao,sha,lu,zun,cuan,seng,ceng,zen,zhuang,jiang,shan,lei,rang,yuan,xiong,kuang,zhao,guang,ke,wen,dui,rui,dang,dou,liu,lan,hong,xing,shou,nei,nan,rong,ming,leng,cou,kai,deng,zao,guo,huai,chuang,pao,bie,gua,ku,shua,kei,xue,shao,la,fei,pou,piao,chao,xun,shuo,pin,za,te,tie,ka,qia,kun,gun,mang,cuo,du,cen,shuang,sou,tao,pa,tai,hao,ha,a,tun,pen,keng,hang,miao,gao,ne,m,qiang,mei,nao,zui,long,mi,lo,zan,hen,n,da,yo,o,sao,hng,ng,ken,len,chuai,weng,hei,dia,cao,de,dei,sai,ca,nang,tuan,qun,man,nian,ruan,duan,wai,zou,zang,nv,niu,rao,niang,hun,nen,nun,nou,cun,sun,kuan,niao,sui,min,shuai,shui,kuo,tui,tei,nin,en,den,zhua,shuan,zhuai,rou,pie,zuan,suan,ri,shai,nuan,sen,ei,chua,run,nve,gei,miu,neng,shei,zei,zhei";
const CHARS = "㑇㑊㕮㘎㙍㙘㙦㛃㛚㛹㟃㠇㠓㤘㥄㧐㧑㧟㫰㬊㬎㬚㭎㭕㮾㰀㳇㳘㳚㴔㵐㶲㸆㸌㺄㻬㽏㿠䁖䂮䃅䃎䅟䌹䎃䎖䏝䏡䏲䐃䓖䓛䓨䓫䓬䗖䗛䗪䗴䜣䝙䢺䢼䣘䥽䦃䲟䲠䲢䴓䴔䴕䴖䴗䴘䴙䶮一丁七万丈三上下不与丏丐丑专且丕世丘丙业丛东丝丞丢两严丧个丫中丰串临丸丹为主丽举乂乃久么义之乌乍乎乏乐乒乓乔乖乘乙乜九乞也习乡书乩买乱乳乸乾了予争事二亍于亏云互亓五井亘亚些亟亡亢交亥亦产亨亩享京亭亮亲亳亵亶亸亹人亿什仁仂仃仄仅仆仇仉今介仍从仑仓仔仕他仗付仙仝仞仟仡代令以仨仪仫们仰仲仳仵件价任份仿企伈伉伊伋伍伎伏伐休众优伙会伛伞伟传伢伣伤伥伦伧伪伫伭伯估伲伴伶伸伺似伽伾佁佃但位低住佐佑体何佖佗佘余佚佛作佝佞佟你佣佤佥佩佬佯佰佳佴佶佸佺佻佼佽佾使侁侂侃侄侈侉例侍侏侑侔侗侘供依侠侣侥侦侧侨侩侪侬侮侯侴侵侹便促俄俅俊俍俎俏俐俑俗俘俙俚俜保俞俟信俣俦俨俩俪俫俭修俯俱俳俵俶俸俺俾倌倍倏倒倓倔倕倘候倚倜倞借倡倥倦倧倨倩倪倬倭倮倴债倻值倾偁偃假偈偌偎偏偓偕做停偡健偬偭偰偲偶偷偻偾偿傀傃傅傈傉傍傒傕傣傥傧储傩催傲傺傻僇僎像僔僖僚僦僧僬僭僮僰僳僵僻儆儇儋儒儡儦儳儴儿兀允元兄充兆先光克免兑兔兕兖党兜兢入全八公六兮兰共关兴兵其具典兹养兼兽冀冁内冈冉册再冏冒冔冕冗写军农冠冢冤冥冬冮冯冰冱冲决况冶冷冻冼冽净凄准凇凉凋凌减凑凓凘凛凝几凡凤凫凭凯凰凳凶凸凹出击凼函凿刀刁刃分切刈刊刍刎刑划刖列刘则刚创初删判刨利别刬刭刮到刳制刷券刹刺刻刽刿剀剁剂剃剅削剋剌前剐剑剔剕剖剜剞剟剡剥剧剩剪副割剽剿劁劂劄劈劐劓力劝办功加务劢劣动助努劫劬劭励劲劳劼劾势勃勇勉勋勍勐勒勔勖勘勚募勠勤勰勺勾勿匀包匆匈匍匏匐匕化北匙匜匝匠匡匣匦匪匮匹区医匼匾匿十千卅升午卉半华协卑卒卓单卖南博卜卞卟占卡卢卣卤卦卧卫卬卮卯印危即却卵卷卸卺卿厂厄厅历厉压厌厍厕厖厘厚厝原厢厣厥厦厨厩厮去厾县叁参叆叇又叉及友双反发叔叕取受变叙叚叛叟叠口古句另叨叩只叫召叭叮可台叱史右叵叶号司叹叻叼叽吁吃各吆合吉吊同名后吏吐向吒吓吕吖吗君吝吞吟吠吡吣否吧吨吩含听吭吮启吱吲吴吵吸吹吻吼吽吾呀呃呆呇呈告呋呐呒呓呔呕呖呗员呙呛呜呢呣呤呦周呱呲味呵呶呷呸呻呼命咀咂咄咆咇咉咋和咍咎咏咐咒咔咕咖咙咚咛咝咡咣咤咥咦咧咨咩咪咫咬咯咱咳咴咸咺咻咽咿哀品哂哃哄哆哇哈哉哌响哎哏哐哑哒哓哔哕哗哙哚哝哞哟哢哥哦哧哨哩哪哭哮哱哲哳哺哼哽哿唁唆唇唉唏唐唑唔唛唝唠唢唣唤唧唪唬售唯唰唱唳唵唷唼唾唿啁啃啄商啉啊啐啕啖啜啡啤啥啦啧啪啫啬啭啮啰啴啵啶啷啸啻啼啾喀喁喂喃善喆喇喈喉喊喋喏喑喔喘喙喜喝喟喤喧喱喳喵喷喹喻喽喾嗄嗅嗉嗌嗍嗐嗑嗒嗓嗔嗖嗜嗝嗞嗟嗡嗣嗤嗥嗦嗨嗪嗫嗬嗯嗲嗳嗵嗷嗽嗾嘀嘁嘈嘉嘌嘎嘏嘘嘚嘛嘞嘟嘡嘣嘤嘧嘬嘭嘱嘲嘴嘶嘹嘻嘿噀噂噇噌噍噎噔噗噘噙噜噢噤器噩噪噫噬噱噶噻噼嚄嚅嚆嚎嚏嚓嚚嚣嚭嚯嚷嚼囊囔囚四回囟因囡团囤囫园困囱围囵囷囹固国图囿圃圄圆圈圉圊圌圐圙圜土圢圣在圩圪圫圬圭圮圯地圲圳圹场圻圾址坂均坉坊坋坌坍坎坏坐坑坒块坚坛坜坝坞坟坠坡坤坥坦坨坩坪坫坬坭坯坰坳坷坻坼坽垂垃垄垆垈型垌垍垎垏垒垓垕垙垚垛垞垟垠垡垢垣垤垦垧垩垫垭垮垯垱垲垴垵垸垺垾垿埂埃埆埇埋埌城埏埒埔埕埗埘埙埚埝域埠埤埪埫埭埯埴埵埸培基埼埽堂堃堆堇堉堋堌堍堎堐堑堕堙堞堠堡堤堧堨堪堰堲堵堼堽堾塄塅塆塌塍塑塔塘塝塞塥填塬塱塾墀墁境墅墈墉墐墒墓墕墘墙墚增墟墡墣墦墨墩墼壁壅壑壕壤士壬壮声壳壶壸壹处备复夏夐夔夕外夙多夜够夤夥大天太夫夬夭央夯失头夷夸夹夺夼奁奂奄奇奈奉奋奎奏契奓奔奕奖套奘奚奠奡奢奥奭女奴奶奸她好妁如妃妄妆妇妈妊妍妒妓妖妗妘妙妞妣妤妥妧妨妩妪妫妭妮妯妲妹妻妾姆姈姊始姐姑姒姓委姗姘姚姜姝姞姣姤姥姨姬姮姱姶姹姻姽姿娀威娃娄娅娆娇娈娉娌娑娓娘娜娟娠娣娥娩娱娲娴娵娶娼婀婆婉婊婌婍婕婘婚婞婠婢婤婧婪婫婳婴婵婶婷婺婻婼婿媂媄媆媒媓媖媚媛媞媪媭媱媲媳媵媸媾嫁嫂嫄嫉嫌嫒嫔嫕嫖嫘嫚嫜嫠嫡嫣嫦嫩嫪嫫嫭嫱嫽嬉嬖嬗嬛嬥嬬嬴嬷嬿孀孅子孑孓孔孕孖字存孙孚孛孜孝孟孢季孤孥学孩孪孬孰孱孳孵孺孽宁它宄宅宇守安宋完宏宓宕宗官宙定宛宜宝实宠审客宣室宥宦宧宪宫宬宰害宴宵家宸容宽宾宿寁寂寄寅密寇富寐寒寓寝寞察寡寤寥寨寮寰寸对寺寻导寿封射将尉尊小少尔尕尖尘尚尜尝尢尤尥尧尨尪尬就尴尸尹尺尻尼尽尾尿局屁层屃居屈屉届屋屎屏屐屑展屙属屠屡屣履屦屯山屹屺屼屾屿岁岂岈岊岌岍岐岑岔岖岗岘岙岚岛岜岞岠岢岣岨岩岫岬岭岱岳岵岷岸岽岿峁峂峃峄峋峒峗峘峙峛峡峣峤峥峦峧峨峪峭峰峱峻峿崀崁崂崃崄崆崇崌崎崒崔崖崚崛崞崟崡崤崦崧崩崭崮崴崶崽崾崿嵁嵅嵇嵊嵋嵌嵎嵖嵘嵚嵛嵝嵩嵫嵬嵯嵲嵴嶂嶅嶍嶒嶓嶙嶝嶟嶦嶲嶷巅巇巉巍川州巡巢工左巧巨巩巫差巯己已巳巴巷巽巾币市布帅帆师希帏帐帑帔帕帖帘帙帚帛帜帝帡带帧帨席帮帱帷常帻帼帽幂幄幅幌幔幕幖幛幞幡幢幪干平年并幸幺幻幼幽广庄庆庇床庋序庐庑库应底庖店庙庚府庞废庠庤庥度座庭庱庳庵庶康庸庹庼庾廆廉廊廋廑廒廓廖廙廛廨廪延廷建廿开弁异弃弄弆弇弈弊弋式弑弓引弗弘弛弟张弢弥弦弧弨弩弭弯弱弶弸弹强弼彀归当录彖彗彘彝彟形彤彦彧彩彪彬彭彰影彳彷役彻彼往征徂径待徇很徉徊律徐徒徕得徘徙徛徜御徨循徭微徵德徼徽心必忆忉忌忍忏忐忑忒忖志忘忙忝忞忠忡忤忧忪快忭忮忱忳念忸忺忻忽忾忿怀态怂怃怄怅怆怊怍怎怏怒怔怕怖怙怛怜思怠怡急怦性怨怩怪怫怯怵总怼怿恁恂恃恋恍恐恒恓恔恕恙恚恝恢恣恤恧恨恩恪恫恬恭息恰恳恶恸恹恺恻恼恽恿悃悄悆悈悉悌悍悒悔悖悚悛悝悟悠悢患悦您悫悬悭悯悰悱悲悴悸悻悼情惆惇惊惋惎惑惔惕惘惙惚惛惜惝惟惠惦惧惨惩惫惬惭惮惯惰想惴惶惹惺愀愁愃愆愈愉愍愎意愐愔愕愚感愠愣愤愦愧愫愭愿慆慈慊慌慎慑慕慝慢慥慧慨慬慭慰慵慷憋憎憔憕憙憧憨憩憬憭憷憺憾懂懈懊懋懑懒懔懦懵懿戆戈戊戋戌戍戎戏成我戒戕或戗战戚戛戟戡戢戣戤戥截戬戭戮戳戴户戽戾房所扁扂扃扅扆扇扈扉扊手才扎扑扒打扔托扛扞扣扦执扩扪扫扬扭扮扯扰扳扶批扺扼扽找承技抃抄抉把抑抒抓抔投抖抗折抚抛抟抠抡抢护报抨披抬抱抵抹抻押抽抿拂拃拄担拆拇拈拉拊拌拍拎拐拒拓拔拖拗拘拙招拜拟拢拣拤拥拦拧拨择括拭拮拯拱拳拴拶拷拼拽拾拿持挂指挈按挎挑挓挖挚挛挝挞挟挠挡挣挤挥挦挨挪挫振挲挹挺挽捂捃捅捆捉捋捌捍捎捏捐捕捞损捡换捣捧捩捭据捯捶捷捺捻捽掀掂掇授掉掊掌掎掏掐排掖掘掞掠探掣接控推掩措掬掭掮掰掳掴掷掸掺掼掾揄揆揉揍描提插揕揖揠握揣揩揪揭揳援揶揸揽揿搀搁搂搅搋搌搏搐搒搓搔搛搜搞搠搡搦搪搬搭搴携搽摁摄摅摆摇摈摊摏摒摔摘摛摞摧摩摭摴摸摹摽撂撄撅撇撑撒撕撖撙撞撤撩撬播撮撰撵撷撸撺撼擀擂擅操擎擐擒擘擞擢擤擦擿攀攉攒攘攥攫攮支收攸改攻攽放政故效敉敌敏救敔敕敖教敛敝敞敢散敦敩敫敬数敲整敷文斋斌斐斑斓斗料斛斜斝斟斠斡斤斥斧斩斫断斯新斶方於施旁旃旄旅旆旋旌旎族旐旒旖旗旞无既日旦旧旨早旬旭旮旯旰旱旴旵时旷旸旺旻旿昀昂昃昄昆昇昈昉昊昌明昏昒易昔昕昙昝星映昡昣昤春昧昨昪昫昭是昱昳昴昵昶昺昼昽显晁晃晅晊晋晌晏晐晒晓晔晕晖晗晙晚晞晟晡晢晤晦晨晪晫普景晰晱晴晶晷智晾暂暄暅暇暌暑暕暖暗暝暧暨暮暲暴暵暶暹暾暿曈曌曙曛曜曝曦曩曰曲曳更曷曹曼曾替最月有朋服朏朐朓朔朕朗望朝期朦木未末本札术朱朳朴朵朸机朽杀杂权杄杆杈杉杌李杏材村杓杕杖杙杜杞束杠条来杧杨杩杪杭杯杰杲杳杵杷杻杼松板极构枅枇枉枋枍析枕林枘枚果枝枞枢枣枥枧枨枪枫枭枯枰枲枳枵架枷枸枹柁柃柄柈柊柏某柑柒染柔柖柘柙柚柜柝柞柠柢查柩柬柯柰柱柳柴柷柽柿栀栅标栈栉栊栋栌栎栏栐树栒栓栖栗栝栟校栩株栲栳栴样核根栻格栽栾桀桁桂桃桄桅框案桉桊桌桎桐桑桓桔桕桠桡桢档桤桥桦桧桨桩桫桯桲桴桶桷桹梁梃梅梆梌梏梓梗梠梢梣梦梧梨梭梯械梳梴梵梼梽梾梿检棁棂棉棋棍棐棒棓棕棘棚棠棣棤棨棪棫棬森棰棱棵棹棺棻棼棽椀椁椅椆椋植椎椐椑椒椓椟椠椤椪椭椰椴椸椹椽椿楂楒楔楗楙楚楝楞楠楣楦楩楪楫楮楯楷楸楹楼概榃榄榅榆榇榈榉榍榑榔榕榖榛榜榧榨榫榭榰榱榴榷榻槁槃槊槌槎槐槔槚槛槜槟槠槭槱槲槽槿樊樗樘樟模樨横樯樱樵樽樾橄橇橐橑橘橙橛橞橡橥橦橱橹橼檀檄檎檐檑檗檞檠檩檫檬櫆欂欠次欢欣欤欧欲欸欹欺欻款歃歅歆歇歉歌歙止正此步武歧歪歹死歼殁殂殃殄殆殇殉殊残殍殒殓殖殚殛殡殣殪殳殴段殷殿毁毂毅毋毌母每毐毒毓比毕毖毗毙毛毡毪毫毯毳毵毹毽氅氆氇氍氏氐民氓气氕氖氘氙氚氛氟氡氢氤氦氧氨氩氪氮氯氰氲水永氾氿汀汁求汆汇汈汉汊汋汐汔汕汗汛汜汝汞江池污汤汧汨汩汪汫汭汰汲汴汶汹汽汾沁沂沃沄沅沆沇沈沉沌沏沐沓沔沘沙沚沛沟没沣沤沥沦沧沨沩沪沫沭沮沱河沸油沺治沼沽沾沿泂泃泄泅泇泉泊泌泐泓泔法泖泗泙泚泛泜泞泠泡波泣泥注泪泫泮泯泰泱泳泵泷泸泺泻泼泽泾洁洄洇洈洋洌洎洑洒洓洗洘洙洚洛洞洢洣津洧洨洪洫洭洮洱洲洳洴洵洸洹洺活洼洽派洿流浃浅浆浇浈浉浊测浍济浏浐浑浒浓浔浕浙浚浛浜浞浟浠浡浣浥浦浩浪浬浭浮浯浰浲浴海浸浼涂涄涅消涉涌涍涎涐涑涓涔涕涘涛涝涞涟涠涡涢涣涤润涧涨涩涪涫涮涯液涴涵涸涿淀淄淅淆淇淋淌淏淑淖淘淙淜淝淞淟淠淡淤淦淫淬淮淯深淳淴混淹添淼清渊渌渍渎渐渑渔渗渚渝渟渠渡渣渤渥温渫渭港渰渲渴游渺渼湃湄湉湍湎湑湓湔湖湘湛湜湝湟湣湫湮湲湴湾湿溁溃溅溆溇溉溍溏源溘溚溜溞溟溠溢溥溦溧溪溯溱溲溴溵溶溷溹溺溻溽滁滂滃滆滇滉滋滍滏滑滓滔滕滗滘滚滞滟滠满滢滤滥滦滧滨滩滪滫滴滹漂漆漈漉漋漏漓演漕漖漠漤漦漩漪漫漭漯漱漳漴漶漷漹漻漼漾潆潇潋潍潏潖潘潜潞潟潢潦潩潭潮潲潴潵潸潺潼潽潾澂澄澈澉澌澍澎澛澜澡澥澧澪澭澳澴澶澹澼澽激濂濉濋濑濒濞濠濡濩濮濯瀌瀍瀑瀔瀚瀛瀣瀱瀵瀹瀼灈灌灏灞火灭灯灰灵灶灸灼灾灿炀炅炆炉炊炌炎炒炔炕炖炘炙炜炝炟炣炫炬炭炮炯炱炳炷炸点炻炼炽烀烁烂烃烈烊烔烘烙烛烜烝烟烠烤烦烧烨烩烫烬热烯烶烷烹烺烻烽焆焉焊焌焐焓焕焖焗焘焙焚焜焞焦焯焰焱然煁煃煅煊煋煌煎煓煜煞煟煤煦照煨煮煲煳煴煸煺煽熄熇熊熏熔熘熙熛熜熟熠熥熨熬熵熹熻燃燊燋燎燏燔燕燚燠燥燧燮燹爆爇爔爚爝爟爨爪爬爰爱爵父爷爸爹爻爽爿牁牂片版牌牍牒牖牙牚牛牝牟牡牢牤牥牦牧物牮牯牲牵特牺牻牾牿犀犁犄犇犊犋犍犏犒犟犨犬犯犰犴状犷犸犹狁狂狃狄狈狉狍狎狐狒狗狙狝狞狠狡狨狩独狭狮狯狰狱狲狳狴狷狸狺狻狼猁猃猄猇猊猎猕猖猗猛猜猝猞猡猢猥猩猪猫猬献猯猰猱猴猷猹猺猾猿獍獐獒獗獠獬獭獯獴獾玃玄率玉王玎玑玒玓玕玖玘玙玚玛玞玟玠玡玢玤玥玦玩玫玭玮环现玱玲玳玶玷玹玺玻玼玿珀珂珅珇珈珉珊珋珌珍珏珐珑珒珕珖珙珛珝珞珠珢珣珥珦珧珩珪珫班珰珲珵珷珸珹珺珽琀球琄琅理琇琈琉琊琎琏琐琔琚琛琟琡琢琤琥琦琨琪琫琬琭琮琯琰琲琳琴琵琶琼瑀瑁瑂瑃瑄瑅瑆瑑瑓瑔瑕瑖瑗瑙瑚瑛瑜瑝瑞瑟瑢瑧瑨瑬瑭瑰瑱瑳瑶瑷瑾璀璁璃璆璇璈璋璎璐璒璘璜璞璟璠璥璧璨璩璪璬璮璱璲璺瓀瓒瓖瓘瓜瓞瓠瓢瓣瓤瓦瓮瓯瓴瓶瓷瓻瓿甄甍甏甑甓甗甘甚甜生甡甥甦用甩甪甫甬甭甯田由甲申电男甸町画甾畀畅畈畋界畎畏畔畖留畚畛畜畤略畦番畬畯畲畴畸畹畿疁疃疆疍疏疐疑疔疖疗疙疚疝疟疠疡疢疣疤疥疫疬疭疮疯疰疱疲疳疴疵疸疹疼疽疾痂痃痄病症痈痉痊痍痒痓痔痕痘痛痞痢痣痤痦痧痨痪痫痰痱痴痹痼痿瘀瘁瘃瘅瘆瘊瘌瘐瘕瘗瘘瘙瘛瘟瘠瘢瘤瘥瘦瘩瘪瘫瘭瘰瘳瘴瘵瘸瘼瘾瘿癀癃癌癍癔癖癗癜癞癣癫癯癸登白百癿皂的皆皇皈皋皎皑皓皕皖皙皛皞皤皦皭皮皱皲皴皿盂盅盆盈盉益盍盎盏盐监盒盔盖盗盘盛盟盥盦目盯盱盲直盷相盹盼盾省眄眇眈眉眊看眍眙眚真眠眢眦眨眩眬眭眯眵眶眷眸眺眼着睁睃睄睇睎睐睑睚睛睡睢督睥睦睨睫睬睹睽睾睿瞀瞄瞅瞋瞌瞍瞎瞑瞒瞟瞠瞢瞥瞧瞩瞪瞫瞬瞭瞰瞳瞵瞻瞽瞿矍矗矛矜矞矢矣知矧矩矫矬短矮矰石矶矸矻矼矾矿砀码砂砄砆砉砌砍砑砒研砖砗砘砚砜砝砟砠砣砥砧砫砬砭砮砰破砵砷砸砹砺砻砼砾础硁硅硇硊硌硍硎硐硒硔硕硖硗硙硚硝硪硫硬硭确硼硿碃碇碈碉碌碍碎碏碑碓碗碘碚碛碜碟碡碣碥碧碨碰碱碲碳碴碶碹碾磁磅磉磊磋磏磐磔磕磙磜磡磨磬磲磴磷磹磻礁礅礌礓礞礴礵示礼社祀祁祃祆祇祈祉祊祋祎祏祐祓祕祖祗祚祛祜祝神祟祠祢祥祧票祭祯祲祷祸祺祼祾禀禁禄禅禊禋福禒禔禘禚禛禤禧禳禹禺离禽禾秀私秃秆秉秋种科秒秕秘租秣秤秦秧秩秫秬秭积称秸移秽秾稀稂稃稆程稌稍税稑稔稗稙稚稞稠稣稳稷稹稻稼稽稿穄穆穑穗穙穜穟穰穴究穷穸穹空穿窀突窃窄窅窈窊窍窎窑窒窕窖窗窘窜窝窟窠窣窥窦窨窬窭窳窸窿立竑竖竘站竞竟章竣童竦竫竭端竹竺竽竿笃笄笆笈笊笋笏笑笔笕笙笛笞笠笤笥符笨笪笫第笮笯笱笳笸笺笼笾筀筅筇等筋筌筏筐筑筒答策筘筚筛筜筝筠筢筤筥筦筮筱筲筵筶筷筹筻筼签简箅箍箐箓箔箕箖算箜管箢箦箧箨箩箪箫箬箭箱箴箸篁篆篇篌篑篓篙篚篝篡篥篦篪篮篯篱篷篼篾簃簇簉簋簌簏簕簖簝簟簠簧簪簰簸簿籀籁籍籥米籴类籼籽粉粑粒粕粗粘粜粝粞粟粢粤粥粪粮粱粲粳粹粼粽精粿糁糅糇糈糊糌糍糒糕糖糗糙糜糟糠糨糯糵系紊素索紧紫累絜絮絷綦綮縠縢縻繁繄繇纂纛纠纡红纣纤纥约级纨纩纪纫纬纭纮纯纰纱纲纳纴纵纶纷纸纹纺纻纼纽纾线绀绁绂练组绅细织终绉绊绋绌绍绎经绐绑绒结绔绕绖绗绘给绚绛络绝绞统绠绡绢绣绤绥绦继绨绩绪绫续绮绯绰绱绲绳维绵绶绷绸绹绺绻综绽绾绿缀缁缂缃缄缅缆缇缈缉缊缌缎缐缑缒缓缔缕编缗缘缙缚缛缜缝缞缟缠缡缢缣缤缥缦缧缨缩缪缫缬缭缮缯缰缱缲缳缴缵缶缸缺罂罄罅罍罐网罔罕罗罘罚罟罡罢罨罩罪置罱署罴罶罹罽罾羁羊羌美羑羓羔羕羖羚羝羞羟羡群羧羯羰羱羲羸羹羼羽羿翀翁翂翃翅翈翊翌翎翔翕翘翙翚翛翟翠翡翥翦翩翮翯翰翱翳翷翻翼翾耀老考耄者耆耇耋而耍耏耐耑耒耔耕耖耗耘耙耜耠耢耤耥耦耧耨耩耪耰耱耳耵耶耷耸耻耽耿聂聃聆聊聋职聍聒联聘聚聩聪聱聿肃肄肆肇肉肋肌肓肖肘肚肛肝肟肠股肢肤肥肩肪肫肭肮肯肱育肴肷肸肺肼肽肾肿胀胁胂胃胄胆胈背胍胎胖胗胙胚胛胜胝胞胠胡胣胤胥胧胨胩胪胫胬胭胯胰胱胲胳胴胶胸胺胼能脂脆脉脊脍脎脏脐脑脒脓脔脖脘脚脞脟脩脬脯脱脲脶脸脾脿腆腈腊腋腌腐腑腒腓腔腕腘腙腚腠腥腧腨腩腭腮腯腰腱腴腹腺腻腼腽腾腿膀膂膈膊膏膑膘膙膛膜膝膦膨膳膺膻臀臂臃臆臊臌臑臜臣臧自臬臭至致臻臼臾舀舁舂舄舅舆舌舍舐舒舔舛舜舞舟舠舢舣舥航舫般舭舯舰舱舲舳舴舵舶舷舸船舻舾艄艅艇艉艋艎艏艘艚艟艨艮良艰色艳艴艺艽艾艿节芃芄芈芊芋芍芎芏芑芒芗芘芙芜芝芟芠芡芣芤芥芦芨芩芪芫芬芭芮芯芰花芳芴芷芸芹芼芽芾苁苄苇苈苉苊苋苌苍苎苏苑苒苓苔苕苗苘苛苜苞苟苠苡苣苤若苦苧苫苯英苴苷苹苻苾茀茁茂范茄茅茆茈茉茋茌茎茏茑茓茔茕茗茚茛茜茝茧茨茫茬茭茯茱茳茴茵茶茸茹茺茼茽荀荁荃荄荆荇草荏荐荑荒荓荔荖荙荚荛荜荞荟荠荡荣荤荥荦荧荨荩荪荫荬荭荮药荷荸荻荼荽莅莆莉莎莒莓莘莙莛莜莝莞莠莨莩莪莫莰莱莲莳莴莶获莸莹莺莼莽莿菀菁菂菅菇菉菊菌菍菏菔菖菘菜菝菟菠菡菥菩菪菰菱菲菹菼菽萁萃萄萆萋萌萍萎萏萑萘萚萜萝萣萤营萦萧萨萩萱萳萸萹萼落葆葎葑葖著葙葚葛葜葡董葩葫葬葭葰葱葳葴葵葶葸葺蒂蒄蒇蒈蒉蒋蒌蒎蒐蒗蒙蒜蒟蒡蒨蒯蒱蒲蒴蒸蒹蒺蒻蒽蒿蓁蓂蓄蓇蓉蓊蓍蓏蓐蓑蓓蓖蓝蓟蓠蓢蓣蓥蓦蓬蓰蓼蓿蔀蔃蔈蔊蔌蔑蔓蔗蔚蔟蔡蔫蔬蔷蔸蔹蔺蔻蔼蔽蕃蕈蕉蕊蕖蕗蕙蕞蕤蕨蕰蕲蕴蕹蕺蕻蕾薁薄薅薇薏薛薜薢薤薨薪薮薯薰薳薷薸薹薿藁藉藏藐藓藕藜藟藠藤藦藨藩藻藿蘅蘑蘖蘘蘧蘩蘸蘼虎虏虐虑虒虓虔虚虞虢虤虫虬虮虱虷虸虹虺虻虼虽虾虿蚀蚁蚂蚄蚆蚊蚋蚌蚍蚓蚕蚜蚝蚣蚤蚧蚨蚩蚪蚬蚯蚰蚱蚲蚴蚶蚺蛀蛃蛄蛆蛇蛉蛊蛋蛎蛏蛐蛑蛔蛘蛙蛛蛞蛟蛤蛩蛭蛮蛰蛱蛲蛳蛴蛸蛹蛾蜀蜂蜃蜇蜈蜉蜊蜍蜎蜐蜒蜓蜕蜗蜘蜚蜜蜞蜡蜢蜣蜥蜩蜮蜱蜴蜷蜻蜾蜿蝇蝈蝉蝌蝎蝓蝗蝘蝙蝠蝣蝤蝥蝮蝰蝲蝴蝶蝻蝼蝽蝾螂螃螅螈螋融螗螟螠螣螨螫螬螭螯螱螳螵螺螽蟀蟆蟊蟋蟏蟑蟒蟛蟠蟥蟪蟫蟮蟹蟾蠃蠊蠋蠓蠕蠖蠡蠢蠲蠹蠼血衃衄衅行衍衎衒衔街衙衠衡衢衣补表衩衫衬衮衰衲衷衽衾衿袁袂袄袅袆袈袋袍袒袖袗袜袢袤袪被袭袯袱袷袼裁裂装裆裈裉裎裒裔裕裘裙裛裟裢裣裤裥裨裰裱裳裴裸裹裼裾褂褊褐褒褓褕褙褚褛褟褡褥褪褫褯褰褴褶襁襄襕襚襜襞襟襦襫襻西要覃覆见观觃规觅视觇览觉觊觋觌觎觏觐觑角觖觚觜觞觟解觥触觫觭觯觱觳觿言訄訇訚訾詈詟詹誉誊誓謇警譬计订讣认讥讦讧讨让讪讫训议讯记讱讲讳讴讵讶讷许讹论讻讼讽设访诀证诂诃评诅识诇诈诉诊诋诌词诎诏诐译诒诓诔试诖诗诘诙诚诛诜话诞诟诠诡询诣诤该详诧诨诩诫诬语诮误诰诱诲诳说诵请诸诹诺读诼诽课诿谀谁谂调谄谅谆谇谈谊谋谌谍谎谏谐谑谒谓谔谕谖谗谙谚谛谜谝谞谟谠谡谢谣谤谥谦谧谨谩谪谫谬谭谮谯谰谱谲谳谴谵谶谷谼谿豁豆豇豉豌豕豚象豢豨豪豫豮豳豸豹豺貂貅貆貉貊貌貔貘贝贞负贡财责贤败账货质贩贪贫贬购贮贯贰贱贲贳贴贵贶贷贸费贺贻贼贽贾贿赀赁赂赃资赅赆赇赈赉赊赋赌赍赎赏赐赑赒赓赔赕赖赗赘赙赚赛赜赝赞赟赠赡赢赣赤赦赧赪赫赭走赳赴赵赶起趁趄超越趋趑趔趟趣趯趱足趴趵趸趺趼趾趿跂跃跄跆跋跌跎跏跐跑跖跗跚跛距跞跟跣跤跨跪跬路跱跳践跶跷跸跹跺跻跽踅踉踊踌踏踒踔踝踞踟踢踣踦踩踪踬踮踯踱踵踶踹踺踽蹀蹁蹂蹄蹅蹇蹈蹉蹊蹋蹐蹑蹒蹙蹚蹜蹢蹦蹩蹬蹭蹯蹰蹲蹴蹶蹼蹽蹾蹿躁躅躇躏躐躔躜躞身躬躯躲躺车轧轨轩轪轫转轭轮软轰轱轲轳轴轵轶轷轸轹轺轻轼载轾轿辀辁辂较辄辅辆辇辈辉辊辋辌辍辎辏辐辑辒输辔辕辖辗辘辙辚辛辜辞辟辣辨辩辫辰辱边辽达辿迁迂迄迅过迈迎运近迓返迕还这进远违连迟迢迤迥迦迨迩迪迫迭迮述迳迷迸迹迺追退送适逃逄逅逆选逊逋逍透逐逑递途逖逗通逛逝逞速造逡逢逦逭逮逯逴逵逶逸逻逼逾遁遂遄遆遇遍遏遐遑遒道遗遘遛遢遣遥遨遭遮遴遵遹遽避邀邂邃邈邋邑邓邕邗邘邙邛邝邠邡邢那邦邨邪邬邮邯邰邱邲邳邴邵邶邸邹邺邻邽邾邿郁郃郄郅郇郈郊郎郏郐郑郓郗郚郛郜郝郡郢郤郦郧部郪郫郭郯郴郸都郾郿鄀鄂鄃鄄鄅鄌鄑鄗鄘鄙鄚鄜鄞鄠鄢鄣鄫鄯鄱鄹酂酃酅酆酉酊酋酌配酎酏酐酒酗酚酝酞酡酢酣酤酥酦酩酪酬酮酯酰酱酲酴酵酶酷酸酹酺酽酾酿醅醇醉醋醌醍醐醑醒醚醛醢醨醪醭醮醯醴醵醺醾采釉释里重野量釐金釜鉴銎銮鋆鋈錾鍪鎏鏊鏖鐾鑫钆钇针钉钊钋钌钍钎钏钐钒钓钔钕钖钗钘钙钚钛钜钝钞钟钠钡钢钣钤钥钦钧钨钩钪钫钬钭钮钯钰钱钲钳钴钵钷钹钺钻钼钽钾钿铀铁铂铃铄铅铆铈铉铊铋铌铍铎铏铐铑铒铕铖铗铘铙铚铛铜铝铞铟铠铡铢铣铤铥铧铨铩铪铫铬铭铮铯铰铱铲铳铴铵银铷铸铹铺铻铼铽链铿销锁锂锃锄锅锆锇锈锉锊锋锌锍锎锏锐锑锒锓锔锕锖锗锘错锚锛锜锝锞锟锡锢锣锤锥锦锧锨锩锪锫锬锭键锯锰锱锲锳锴锵锶锷锸锹锺锻锼锽锾锿镀镁镂镃镄镅镆镇镈镉镊镋镌镍镎镏镐镑镒镓镔镕镖镗镘镚镛镜镝镞镠镡镢镣镤镥镦镧镨镩镪镫镬镭镮镯镰镱镲镳镴镵镶长门闩闪闫闭问闯闰闱闲闳间闵闶闷闸闹闺闻闼闽闾闿阀阁阂阃阄阅阆阇阈阉阊阋阌阍阎阏阐阑阒阔阕阖阗阘阙阚阜队阡阪阮阱防阳阴阵阶阻阼阽阿陀陂附际陆陇陈陉陋陌降陎限陑陔陕陛陞陟陡院除陧陨险陪陬陲陴陵陶陷隃隅隆隈隋隍随隐隔隗隘隙障隧隩隰隳隶隹隺隼隽难雀雁雄雅集雇雉雊雌雍雎雏雒雕雠雨雩雪雯雱雳零雷雹雾需霁霄霅霆震霈霉霍霎霏霓霖霜霞霨霪霭霰露霸霹霾青靓靖静靛非靠靡面靥革靬靰靳靴靶靸靺靼靽靿鞁鞅鞋鞍鞑鞒鞔鞘鞠鞡鞣鞧鞨鞫鞬鞭鞮鞯鞲鞳鞴韂韦韧韨韩韪韫韬韭音韵韶页顶顷顸项顺须顼顽顾顿颀颁颂颃预颅领颇颈颉颊颋颌颍颎颏颐频颓颔颖颗题颙颚颛颜额颞颟颠颡颢颤颥颦颧风飏飐飑飒飓飔飕飗飘飙飞食飧飨餍餐餮饔饕饥饧饨饩饪饫饬饭饮饯饰饱饲饳饴饵饶饷饸饹饺饻饼饽饿馁馃馄馅馆馇馈馉馊馋馌馍馏馐馑馒馓馔馕首馗馘香馝馞馥馧馨马驭驮驯驰驱驲驳驴驵驶驷驸驹驺驻驼驽驾驿骀骁骂骃骄骅骆骇骈骉骊骋验骍骎骏骐骑骒骓骕骖骗骘骙骚骛骜骝骞骟骠骡骢骣骤骥骦骧骨骰骱骶骷骸骺骼髀髁髂髃髅髋髌髎髑髓高髡髢髦髫髭髯髹髻髽鬃鬈鬏鬒鬓鬘鬟鬣鬯鬲鬶鬷鬻鬼魁魂魃魄魅魆魇魈魉魋魍魏魑魔鱼鱽鱾鱿鲀鲁鲂鲃鲅鲆鲇鲈鲉鲊鲋鲌鲍鲎鲏鲐鲑鲒鲔鲕鲖鲗鲘鲙鲚鲛鲜鲝鲞鲟鲠鲡鲢鲣鲤鲥鲦鲧鲨鲩鲪鲫鲬鲭鲮鲯鲰鲱鲲鲳鲴鲵鲷鲸鲹鲺鲻鲼鲽鲾鲿鳀鳁鳂鳃鳄鳅鳇鳈鳉鳊鳌鳍鳎鳏鳐鳑鳒鳓鳔鳕鳖鳗鳘鳙鳚鳛鳜鳝鳞鳟鳠鳡鳢鳣鳤鸟鸠鸡鸢鸣鸤鸥鸦鸧鸨鸩鸪鸫鸬鸭鸮鸯鸰鸱鸲鸳鸵鸶鸷鸸鸹鸺鸻鸼鸽鸾鸿鹀鹁鹂鹃鹄鹅鹆鹇鹈鹉鹊鹋鹌鹍鹎鹏鹐鹑鹒鹔鹕鹖鹗鹘鹙鹚鹛鹜鹝鹞鹟鹠鹡鹢鹣鹤鹦鹧鹨鹩鹪鹫鹬鹭鹮鹯鹰鹱鹲鹳鹴鹾鹿麀麂麇麈麋麑麒麓麖麝麟麦麸麹麻麽麾黄黇黉黍黎黏黑黔默黛黜黝黟黠黡黢黥黧黩黪黯黹黻黼黾鼋鼍鼎鼐鼒鼓鼗鼙鼠鼢鼩鼫鼬鼯鼱鼷鼹鼻鼽鼾齁齇齉齐齑齿龀龁龂龃龄龅龆龇龈龉龊龋龌龙龚龛龟龠龢鿍鿎鿏𠅤𠙶𠳐𡎚𡐓𣗋𣲗𣲘𣸣𤧛𤩽𤫉𥔲𥕢𥖨𥻗𦈡𦒍𦙶𦝼𦭜𦰡𧿹𨐈𨙸𨚕𨟠𨭉𨱇𨱏𨱑𨱔𨺙𩽾𩾃𩾌𪟝𪣻𪤗𪨰𪨶𪩘𪾢𫄧𫄨𫄷𫄸𫇭𫌀𫍣𫍯𫍲𫍽𫐄𫐐𫐓𫑡𫓧𫓯𫓶𫓹𫔍𫔎𫔶𫖮𫖯𫖳𫗧𫗴𫘜𫘝𫘦𫘧𫘨𫘪𫘬𫚕𫚖𫚭𫛭𫞩𫟅𫟦𫟹𫟼𫠆𫠊𫠜𫢸𫫇𫭟𫭢𫭼𫮃𫰛𫵷𫶇𫷷𫸩𬀩𬀪𬂩𬃊𬇕𬇙𬇹𬉼𬊈𬊤𬌗𬍛𬍡𬍤𬒈𬒔𬒗𬕂𬘓𬘘𬘡𬘩𬘫𬘬𬘭𬘯𬙂𬙊𬙋𬜬𬜯𬞟𬟁𬟽𬣙𬣞𬣡𬣳𬤇𬤊𬤝𬨂𬨎𬩽𬪩𬬩𬬭𬬮𬬱𬬸𬬹𬬻𬬿𬭁𬭊𬭎𬭚𬭛𬭤𬭩𬭬𬭯𬭳𬭶𬭸𬭼𬮱𬮿𬯀𬯎𬱖𬱟𬳵𬳶𬳽𬳿𬴂𬴃𬴊𬶋𬶍𬶏𬶐𬶟𬶠𬶨𬶭𬶮𬷕𬸘𬸚𬸣𬸦𬸪𬹼𬺈𬺓";
const MAP = "0;1;2;3;4;5;6;7;8,9;10;11;12;13;0;14;15;16;17;18;19;20;21;22;23;18;24;2;25;26;1,27,28;29;9;30;31;32;33;34;35;36;37;38;39;40;41;42;43;44;45;46,47;48,49;50;23;51;52;53;38;54;55;56;57;58;58;59;60;61;53;62;63;64;45;65;66;67;68;47;69;70;1;71,72;52;73,74;75;76;77;78;79,80,2;32;81;82;83;44;84,68,85;69;45;86;87;88;89;90;11;91,92,72;93;94;70;95;96,34;97;98;99;100,101,102;103;73;104;105;106;24;68;1,107;108,107;12;109,5,74,110;1;111,106;112;39,113;114;115;116,117;118;119;120;121;91,92;1,29;122,123;12;52;88,1;27;124;125;126;127;128;129;130;131,34;116,132;32,106;72;45,133;134;58;32,105,26;135,32;136;114;52;112;67;137,138,139;97;6,140;126,52;141,112;142,22,139;65;143,7;1;144;145,124,146;147;124;67;56;94;148,149;150;6;104,144,151;4;105,152;153;1;154,45;153;116,24;71;155;156,2,157;158;83,86,68;75;156;7,96;159;89,160;161;162;163,133;45;164,165;75;2;20;8;153;131;96,1,112;166;14,167;1,11;168;1;147;152;169,170;98;69,171;112;172,173;174,7;153,103;175,176;177,119;52;57,103;142,22;1;126,178;112;126,111,52;2;115;54,26;98,62;179;31;16,17;32;76;105;100,44;97;131;77;180;161;162,181;105;106;20;150,182,74,183;184;185;186,187;14;154;188,11;45,11;189,174,84;69;1,107,11,190;191,192;104,193,70;105,24;38;106;113;179;47,194,195;196;171;165,1;197;32,33,26;1,46;2,198,150,171;113;199,200,68;201;8;185;9;202;131;203;204,132;169;182,74;174;134,108;126;31;102;205,206,5,207;65,208;188;1;45;154;165;209;111;190;210,211,212,112;24,66;45;106,0;179;173,213;90,8;214;59;1;78;215;65,5;216;217,155,218;120;17;219;220;112;221;83;148;56;222,10;85,223;212;86;48,224,225;94,18;226;120,208;24;9;227;2;27;24;118;228;32,125;52,11;57,154;32;83;70;229,94;24;230;172;54;2;68;231;232;58,125,47;99,233;234,70;171,235,69;101;235,203;125;207;193,104;29;236,237;60,180;221;1,126;47,206,0;67,94;7;180;238;239;160;68;131,149;185,123;53;240,105;241;194;218;88;111;149;91;70;174,7,78,96;126,7,52;242,243;105;10;240;6,7;113;56;151;172;160,89;81;6;244,11;245;246;36,215;175;180;247,135,17;227;2;24;157;248,119,233,146;27;29,249;166;60;176;58;250;195;251;190;252;253,132;44,254;124;254,255;27;132,204;12;256,257;65;172,258;8,259,25;150;227;260;69;67;138;104,261;129;262;232;144;263,124;134,153;112;136,264;264;265,266;25;267;20;268;269;81,270,73;271,272,4;33,144;11;70;273;274;67;129;102;183;59;275,253;27;276;59,277;101;278;87;52,126;68;191,192;133,188;169;172;279;126;144;280,130,272;22;42,281,104;217,39;163;41;213,74;26;81;282;6;48;220;101;98;264;283,81;90;22;99,118;87,201;114;25;29;266;88;284,14;90;20,92;66;67,91;52;49;15;94;206;14;172;285;24;11;103;201;126;178;99;2;118;286;35;287;265;33;251,202;58;126;273;3;288,113;207,206;206;153;175;84,52;1;209;58;270;278;211,289,290;117;66,24;275;155;22;291;58;261;187;292,228;24;293;144;67;294;207;295,200;111;296;102,138;252,214;188,52;269,297;247;247;286;4;126;47;36,274;298,208,120,299;269,297;300;131,172;294;172;47;301;302,61;73;126;4,190;261,70;150,228,158;68;92;172;2,69;96;303,232;65,304;120;29;39;69;31,211;1;24;102;186;59;174;112;127;66;90;106,58;157;7;23;299;24;156,67;204;7;196,286;45;150;9;81;305;149;13;262,116;81;26,213;209;1;147,150;253;148,52;6;299,306,53,38;199;112,74;136,48;228,292,2;89;265;158;292;2;171,307;211,31;235;45,190;1;308;260;266,141;78;247;301,175;135,247;69;23,245;1;269,212,234;222;185,309;45;131;168;92;112;16;186,187;211;6;235,171,69,186;226,85,195;53;104,144,261;127;281,130;150;150,79,158;222,187;79,126;151,310;311,312;253;179;253,27;294;240;105;170,169;111;213;62,1;105;126;249;128,313;239,102,314,48;6;156;149;180,3,70,234;212;56;24;24;97;70;197;217,11;119,315;24,144;221;316,126;264;124;70;29;78,252;58;12;11;23;317;20;76;40,318,154;107;166;179;214;126;179;319;178;115;125;53,1,24,29;23;279,207;222;26;174,78;187;320,208;46;200;184,295;68,199,23;14;207,321;200;111;65;267,299;183,322;71;269,96;323,1,11;190,211,212;45;179;61;88,6;324,208;11,188;193,1,179;116,24;206;126,65;26,32;190,52;96;5;196,96;126;206;8;283;221;24;33;124;39;78,196,325;215;97,326;110;48;103;327,192;62,156;301;171,69;148;80,69;183,322;225,327;175,328;3;56,62,1;329,330;224;52;111,133,52;62,154;112,327;304,331;27;236;270;221;277,245,221;112,32,97;97,78;212,107;166,228,107;52,152;91,266;332,68,184;2;130,333,250;112,334;1;166,323;245;24;235,182;264,136;289;335;112;333,185;334,173;14;179;0;184,294;188,133;105,336;196,325,326,269;337,130,157;189,78,174;203;154;114,208,26,196;283;68,338;308;4;292;293,171;169;308,155,39;196,114,31;143,323;12,332;9;2;0;311,220;184;311,189,174;339;90;201;11;134;268,59;39;27,46,111;1,27;66;133;122;340,122,127;111;5,65;96,311,341,241;342,308;269,143,82;16,143;20,172;138;54,26,208;70,88,264;1;107;307;154;8;277;4,190,39,46;202,247,211;325,196,164,252;163;231,184;124;107;137,343,344;266,335;97;345;208;171;16,117;211;17;4;220;173;346;339;96;347,212;190,78,196;299,348,208;24,127,51;130,333,250,108;295;208,298;150,61,235,183;55;39;79,2;145,349;139,51,350,344;96;70;140,296;63,216;107;27,6;60;113,45;112,350,334,344;110,127;59,277;204;140;288;19;126,7;99,233;114,208,289,78;279,125;105;296;180;24;234,350,344;346,32;252,84;165;114;267,0,207,205;351;53,0;77;103,276,352;326,212;195,226,308,212;321;104;353,223,53;301,203,231,182;69;252;300;155;322;55;28;44;123;241;144,193;150;71;18;208;190,38;47;12;311,269;9,32;105;281;261;55;300;7,6;221;3,209,172;46,39,84;250,243;62;347,240,112;100;16,0;27,190;196,88,286;135,290;35;138;24;39,214;331;328;135;32;36;295;326,252,78;54;227;107,1,240;140,306;143;269,196,78;345,164;95;181,192;320,227;45;96;133;7,29;354;11;190;324;140;143,355;148;123;196;344,350;46,356;107;8;251;320,306,125;320;38,55;52,226,308;357;174;303;189;184,174;26,45;358,359,166;110;262,116;317;60;233;51;340;353,113;146;106;304,267;338;11;132;27;355,74,147;305;254;291;257,91;65,12;88,1,252;287;158;29;148;253;347,32,251;156;52;212;288;1,107;45;29,298;189,96;360;69;31,240,347;129;324;324;47;361,214;62;208,251;69;31,298;263;29,65;362;362;86;11;16;57;62;281,123;363,86;225,327;114;264,73;313;89,291;105;161;364;14;184;289;33;179;158;32;264;102,239;32;149;100,236;295;37;19,264;33,317,214;56;92,295;163;105,26,32;96,1;32,165,49;112;247;69;1;38,358;131;216,102,83,290;266;180;52,62;126,7;111;186;48,136;327,225;177;194,175;194;193;209;290,69,203;113;329,142;171;17,117;172;193;24;183;112;175;237;61;313;23,68;193;165,1;34;118;191,216;294,202;185;69,290;41;251,179;269,41;190,38;21;14;236,237;300;339;253;166;278;90,8;126;196;215;262;82;221;268;5;4;214;169;62,351;115;199;264;46;351,62;77,41;212,92;191;97;210;345;273;286;337;234;264,19;2,80,203,302;3,234;26;139;107,111;249,29;9;127,365;18;91;261,70;66;158,79;91;79;45;305;289;366,191,123;32;79;69,171,235;238;25,60,77;166;234,70;111;4;1;203,302,69;126;52;348;60;313;271,338;156,148;32;146,233,118;184;33;284;97;131;4,16;62;46;221;228,79,158;38,47,45,105;367,250;88,212,107;209,181;70;188,126;317,55;145;22;63;284;368;73;164,345;91;227;164,345;60;248;360,28;96;192,181,216;264;18;125;190;365;67;125,88;209;9;156,148;77;147;70;131;335;94;43,257;26;261;158;178;74,336;225;126;171;9,354;196,31;324;263;45;153;259;92,149;269,120;114;313;1,62;58;235;2;78,174;265,138;135;27,1;369;227;4;88;199;62;31;345,166,323;192;323,164;2;121,29;5,240,369;169,51;330,194;45,1;246;1;210;174,189;4;266;167;19;70;52,126,107,1;108;99;175,142;135;370,285;52,6,84,7;39,197,190;194,175;1;260;321;371,259;27;191,56,71,72;251,208;197;251,32,179;45;372,129;157;108;172,34;164,7,190;324;306,117;129;301,203;141;259;2;110;153;70;317;126;5,65;156,20;136;331;373,324;171;32;165;73,264;177;112;32;247;183,150;185;0,83;345;336;52;84;147;14;133;45;7,68,26,226;184;11;278,92;105;261;307;5,205,321;260;125;126;65,208;199;204,147;1;126,1;145;210,114;212,97;214;62;247,202;133;15;105;202,247;36;97;374;65;128;118,307;24;140;105;375;130,250;239;154;38;212;81,73,270;32;202;20;68;23,68,125;180;212;61;73;232;125;52;7,84;102,239;376;278;73,101;171;83,0;67;276;313,376;211;51;144;154;56;112,173,147;281;223,242;26;38,47;336;367,377,378;336;35;51;336;264;45,47,111,166;251,136,240;26;5;69,171;27;51,92;190;199;174;348;264;126;20;107;307;1;303,232;262;365,264;75;24;38;70;180;377;204;74;114;335;132,204;27;171;261,144;19,138,50;205,206;129,379;51;110,74;70;319;131,20;133;7;29;238;136;110,133;133;380;381;2;235,150;133;208;13;228;126;184;157;298;143;128;337;125;40,144,172,151;133;2;129;123;201,106;164,165,1;247;218,21,317;32;279;234;15;73,382;277;340,2;273;160;101;0;71;73,264,136,32;1;228;45;25;154;269,312;138;45;179;19;1;20,265;59;91;163;143,196;70;208;174,7,184;181;282,9;382;176;227,54,52;342;126;126;62;340;200;2;336;3;32;148;74;214,195;294;112;132;218,28,131;132;19,20;380;271;11,45;305,57;207;279;99,222;197,88,1;260,335;105,32,136;254;208;299;134;189;172;181;77,180;189;180;179,141;179;132,383;5;315,13,119;141;189;12;34;45;62,136;190,21;30;185;156;105,1;383,384;68;69;257;27;68,126;23,29,249,68;47;7;112;45,27;118,87;126;6;151;212;125,106;33;215;27;215;68;327,49;261;1,96;52;112;154;32;384;52,286;97,78;7;126;131;52;318;214;23;22;20;251;276;207;183;113;68;269;199;23,68,226;70;54;174;14;166;117;114;385;234;90;135;213;8;298;1;305;90,8;105;19;111,45;24,66;78;5;65,120;72;128;65;212;32;120;99;337;48;32,112;18;209;204;230;20;238;25;68;52,1;226,195;195;97;284,14;29,32;289;62;3;208,5;70;15;233;151;184;369,105;99;163;5;212;209,151;3;126,27;92,91;336;131,3,209;32;214;282;148;32;36;15;133;105;316,188;123;126;75;251;27;257,72;150;103;287;254;151,261;27;1,185;191;27;144;105;100;0;305,70,224;304;59;113;120;68,23;59;112;214,219,188,316;86;126,52;1,11;11,1;183;124,330;305,44;156;171,62;45,2;79;386;178;45;27;105;75;60,157;203,69;322,74;310;167,181;111;0;150;111;38;118;166;216,72;387;27;248;83,207;105;180;155;289;213;340;240;2,171;35;365;147,365;232;75;2;178;291,259;13;34,234;118,10,87,233;366,201;87;278,123;5,340;19;179,5;179;268,70,234;259,146;149;171,69;291;247;26;253;112;295;51;38,358;292;191;331;139;2;119;301;124;111;54;317,4,218;113;56;91;171,69;234,70,212;125,106,55;142;9;165;149;32;247,105,16;167;18;320;156,148;251;388;132;1;144;6;103;70;56;172;366;286;222,187;1;52;220,339;68,23;70,281;1;171;1;45,309;45;59;62;2;277;190;38,47,389;75;321;340;20;114;304;157;340;73;242;260;146;104,193;335,260;171;199,200;247;273;253;363,45;16,384;111;1;117;278;8;70,187;32;244;232;176,186;146,119,248;75;51;190,2;119,177;1;21;171;141;72;85;67;166;305;343;169;290,16;215;26;33;230;358,359;231;27,11;126;180;32,97;35;305;5;105;72,111,91;358;65,5;16;57;171;1;207;126;153;144,131;193,329;309,207;309,389,390;380;111;141;315;192;385,270;98;25;112;179;15,98;17;222;111,52;181,104;327,49,225;366;373;20;57;114;286,52;175;290,2;323;15;112;245;180;291;304;113,39;258;169;157;72;322,150;79;114,310;345,104;167,14;11,360;166,1;1;126;146;278;264,136;185;121;2,301,235;84;58,26;160;271;1;377,153,391;305,224;45,111;167;35,268;238;145;27,52,26;65,208;125;169;16;174,52;16;133;26;372;343;392;269;90,8;192;59;27;312;351;212,112;8;70;286;217;337;136;9,8;313;120;32,125;7,269;27;47;3;1;16;235;15;102,305;135,24;112;179;94,18;19;117;391;249;138;131;385;89;301;235;195;126;278;207;149;83,86,207;225;67,94;73;126;31;193,104;47;141;223,236;114;376,152;27;180,60;105;16;191;68;40;91;235;84;40;104;101;4,165;124;237,100,294;35;243,242;278;120,86;83,120,12;138;131;32;32,246;385,175;171;1;81;62;212;32;34,3;136,270;284;175;135;135;227;52;264;321;188;131,84,20;35;154,216;197;147;309,185;365;288,357;16;286;148,156;62;105;9;142;293;43;120;91,72,287;27;25,259;3;52;67;132;58;104;3,104;90;6;251,32;213;152;276;103,276;250;13;1;22,259;96;112;172;26,23;125;282,159;27,114;91;240;7;335,371;31,32;335;151;52,85;174,189;126;209,216;126;135;82;287;7;172;70,179;253;223;166;114;114;24;177,119;140;222,10;191;41;1;1;261;114;301;70;279;244,163;39,308;158,69;183,322,182,293;345;159;165;142,22;34,3;200;131;111;388;152;348;169;373,83,0;186,175,31;21;374,179;186,187;2,158;69;111,52;212;393;267,211;91,72;126,52;222;304,140;29;183,322;1;125;394;302;246,274;274;142,22;55,197,47;2;292;363;200;161;335;114;228;146,233;69;323,190;228,292,302;38,111,52;74,110;181,154;97,78,174;83;385;2,171,69,301;39,151;106;104,7;219,21,190,361;147;366,191;300;2,79;186,187;231,150;103,14;121;68;165,164,111;183,150,293,115;165,190;251,373,32;68,199;53;267,120,299;182;185;339;172;312;9;276;201;150;155,218;388,294;45;7,174;72;59,68;102;395,102;308,342;30;307,87;396,88;45,197,7;130;190;294;111;84,52,174,312;234;210,295,200;205,321,206;39;202;111;128;240,394;164;6,174;337;273;72;126;16;20;107;250;316,113;216;168,140,252;1;56;73;112;48;8;313,376;53;215,241;183,293;3,20,34;299,208,120;123;239,264;79;204;381;172;19;207;146,99;66,24;182,183,171;68;207;236,4;7,84,214;130;366,123;113,85,227,254;20,343;191;4,53;279;206,250;302,2,203;75;126,1;321;312;231,182;88;29,295;261,70;37;193,20;21;7,6,252,214;238,335;389;70;316,155,188;68;192;131;182;253;121,289;111;104,261;40,144,261;101;264,100;32,83,179,125;135;397;370,285;331,213;47,38,190,45;214,39;216;1,126;97;240,245;353,4,237,363;286,174;12;7,52,196;6,174;264,19;88;39;276;148;144;96;36;65;353,190,1;151;150;58;248,146,233;316,219;348;172,167;320,208,299;332,120,30;306;95;250;60;186,227;345,164;131;6;214;392;197;125;182;5;176;193;25;87;386;218;190;241;195,338,316;74,110,340;111;58,190;74;74;232,303,292;132;51;29,247;398,293;91;168;11,27;3,131;254;259;21;132,204;120;150;316,113,338,399;44,138,400;366;6;253;255;3;34;262;261;357;149;19,239,138;148;182,150;320;53;278;361;47,111,218;187;31,249;342,255;263,201,124;399;29;362;111,52;279;179;82;59;186,176;177;72;184;208;340;38,211;385;12;32;190,320;251;65;167;171;180,91,72;34;76;225,271,363,206;208,298;65,120;67;125,306;120;72;2;270;218;176;301;186;276;274,106;132;114;6,78,214,88;174;216;65;240,101;156;190,21,55;2;151;53,223;368;11,45;57;58;177,119,141,99;32,112;45,1;119,146,233,248;151;213,112;215;203;138;67;185;226,320,285,370;267;275;1;52;384;112,74;126,27;401;104;12;111;288;305,48;26;189,26;300;34,3;3;26;144;45;266;169;141;385;112;136;170,169;155;186;313,376;92;114;177;324;180;283,13;376;114;1;27,316;57,138;193,32;342;278;51,169;138;216;14;63;336,270,74;113;222;26,265;267;45,47;32;46,1;213;185,111;180;87;0;339;20;304,267;35;138;111;156;77;70;82;402;208;88;136;16;3;48;73;27;91,92,67;79;55,111;112;16;181;192;53;158;67,51;27;261;149;67;247;111;94;342;138;139,138;78,174;135;125;172,276;403,138;234;283;107,403;126,7;147;75;228,158,150;3;138;20;327;27;8;267;125;305;5;158,228;27;362;117;23;88;139;196,212;357;365;257,43;47;338,316;117,129;179,105;146;2,171,150;301,295;23,26,63;205,179;306;216;18;141;304,267,106;52,126;13,315;147;105;74,109;194;39,97;125,106;106,125;183;158,303,61;4;24;126,105;54;252;308,4;102;131;34;214;261,252;112,240;24;278;244;380;232,299,306,38;38,4;75;1;317,33;52;125;22,59;205;230;315;169;110;331;330,142;235;7;332;5;58;322,183;83,373;106,125;15;186;126;199;126;69,171;141,266;177,87;1;27,11;216,181;103;272,377;336;289,241,101;111,52;89,160;125;288;24;172;91;335;99;208;295,184;118;27;111;208;174;174;199,68,23;228,2;4,165;14;87;186,187;98;182,150;173,336;34,131;52;42;397;299;55;78,174;179,0;247,68;165;39,113,155;201,58,106;38,190;214,39,219;12;172;269;108;106;275;219,188,218,133;58,106;91,29;45;111;39,261,217;232;151;111;339;90;253;24,117;276;9;125;305,381;395,102;52,27;24,66;294,192,388;194,87;208,65,120;26,32;106;30;204;151;169;196,114,82,286;137;45;96,241,196;163;128;7;145,330;247;321,205,267;268;105,247;266;234;234;239,102;53;111;8,90;95;19;68,7,6;12;97;374;216;273;52;120;211;247,16;260;259;140;56,51;61,150;2;8;29;18;94;56;336;248;33,214;184,29;133;139;215;299,208,348;318,181,148;13;112,32;24;140,305;47;6;125;144;178;321;111;230;167;172;53,165,272;14;81;52,126;314,376,251;301;248;248,235,302,203;160;126;146;60;38,47,166;316;52;70;32;102,239;404;236,4;284,14,91;269,382;267,53;101;175;175;154,181;73;289;1;83,0,206;94;111;236,237;68;235,69,171,231;65;53;317;131;241;146;165;88;368;1;154,216;100;63;39,214;11;6;172;213;58;167;284;281;336;138;10;88,46;126;58,106;225,224,63;286,7;86;51;36;82,247,7;193;276;270,136;32;181;215;68;6;2,150;18;282;184;216;248,233,119,146;301;39;381;6;111;195;275;249;164;332,30;187;306;236,237,271;214;290;332;174;209,172;338;176,87;106;52,85,226,28;179,304;114;357,288;156,148;178;58;60,91;75;74,147;27;145,268,35;335;51;120;254;117;34;120;165,317,241;204,132;68;91,287,181;29;16;124;106;8,291,98,25;58;253;264;193,261;27;148;70,104;262;150,171;7,6;149,67;103;214,168;13;135;150;131;188,133;19;57;32;245;32;107,6,405;1,52;52;406,26,114;382,57;252,78;62;57;6,97;131;96;197,27,6;111;72;188;79;112;52;369;166,212;11;172;74,270;85;169;192;166;77;305;125;40;303,171;136;167;111,45;104;126;176;156;1;125;245;368;62,70;191;16;184;1;112,173;101;147,112;336;107;317,166;32;171,69;171;171;69;171;213;151;147;324;193;195,120,78;76;125,32;172;180;158;253;23;45,111,67;38,111;385;315,13;52;398;108;207;20;100;175;2;90;149;62,70;143;169;234;97;269;104;215;149;136;387;9;178;247,12,86;56,71;111,6,45;86;255;16;206;3;214;53,117,249,306;27;52;261,395;3,34;305;11;129;59;260;190,165,21;112;60,77;131,70;340;184,32,114;141,277;67;272,327;323;126;222;270,385,152;265;52,82,1;175,328;148;1,62;240;136;264;330,142;70,105;154,181,193;181;225,44,327,63;52,84;147;345,164;81;171;252,140;111;203;199;336,74,109;99;245;24;161;162;99;105;114;74;125;68,172,226;165,4,190;196;301,2;179;192;111,190;267;184;151,192,191,144;70;41,51;68,199;6,1;86,179;174;102;61,150;340,171;116;277;34,3;115;213,275;11;118,146;188,133;178,99,115;111,190;201,106;14;292;150,235,171;52,24,28;185,123,201;106,0;262;138,239;187;385,81;323;169;9;233,307,275;339,319;253;241,61;6;61;155;67;7,126;16;62,70,88;105;169,124;66;126;2;168,27,20,404;28,52,140,133;27,20;30;106;260,277;241;90,8;1;340;156;105;208;277;26,1;266;321,5,207;134;0;129;118,146;305,138;268,35;19;283;31,289;202,247;312,196;231,127,182,322;112,114;275;174;131,172;260;65;216;45;53;217;16,17;126;275;144;376;114,26;220;305;156;55;48,305,380;3,34;248,176;53;179,38;27;150;19;1,97;158;324,332,96;18;24,143;139;2;112;167,24;99,277;32;143;156,148;336;33,58,88;118;123;208;197,46;9,25;208;20,70,191;212;227,320,125;239,264,138;318,131,342;47;11;321;204;230;167;105;240,289;136;19,16;38;407;172;75;28;2,302;101;395,296;97;88,45;240,264,73;3;196;53;191;133;27;208;52;103;60,180;324;125,58;337,267,53,223;321;89,319;118,146;301;15;192;69,203;104,70,193;32;34,3;62,70,5;195,226;290;32;154;63,49;114;376,314,313;70;192;331;149;264;253;133;317;172;81,92;32;154;106;32;56;23,68;317;39;150;240,245,112;270,136;6,46,39,1;105;22,277;70;138;269,7,286,196;179,275;331;336;231,183;336;192;363,44;81;26;328;172,342,151,131;114;124;151,181,104,193;45;7,6;35,266;385,376,81;65,86,12;70,62;264;186,187;73;45;51;135,16;172;26;36;82,6;156;60;264;269,286;164,345;275;348;283,340;39;1;158,2,79,150;105,336;24;27,52;227,306;148,216;320,299;54,83;62;282;376;140,28;185,242,383;164;129;58;119,146;354;96;191,192,216;35;133,188,138;111;2;211,184;133;321;64;171;65;314;111;70;197;365;51;215;276;128;5,208;176;193;32;54;38;114;303,232;52,84;126;253;339;36;24;70;357;65;74;276;190,323;138;1;365;315;241,164,262;125;75;259,25,319,291;19;31,388;70;132,208,275;195;169;51;208;167;105;32,29,125;322;187,150,178;131;253;27;35,268;204,132;1;193,305,62,104;304;299;106;168,76;261;144;8,25,98;158;103;91;91,287;21;34,3;11;125,106;146;253;276;288,357;6;24;14;9;251,32;19,138;144,104,151;104,193,261;69;68;126,65;167,20;384;58;230;176;171,69;324;129,367,134,403;31,114;158;53,306,267;232;144;158,228,150;184;3;51;6;126;175;117,5;263,362;23;101,19;324;183;31;122;287,71;16;14;288;12;53;163;40;169;41,247;270;253;236;286;70,193;304;247,249,298;142,330;225,327;57;111;105;335;345;269;138;68;193;292,228;41;323;87;106;39;191;45;167;190;114;306;276;56;66;169;8,90;277;204,241;106,25;138,16;72;70,62;16,107;30;178;299;88;16;60;156;243;27;56;73;146;18;70,261;99;239,88,117,264;70,1;3;48,23;112;3;19;152;68;207,321;235;175;313;327,389,48;65,120;304,53,223;70;70,1;42;181;135;368;138;278;35;172;363;32;252;105;336;26,54;267;105,32;106;228;114;136,270;222;389;261;27;196,208,30;265;305;282;275;27,1;232;89,160;125,279;1;64,8;136,32,105;251;77;27;27;42;154;65,120,29,53;132;32;178,175;70;1;32,251;288,348;384;6;20,87;228,150;242;27;117;29,65;101;255;267,394;322;264;107;29;2;88;183;46;5,208;319;187,335;269;371;10,187;186;231;317;46;179;97;91;373;307;173,147,213;147;204,36;315;177;213;147;112;172;184;92;131;309;27;315;112;184;27;24;126,1;194;317;68;172,131;10;30;260;83;102;178;86;234,3,172;259;268;110;179;136;266,29;373,372;38,47;235;69;292;78;114;301;199;68;20;201;343,70,351,330;65,208;282;279;317;78;45;17;72;32;381;32;171;239;24;62;400,305,48;18,330;24;20;67;208;185;66,27,249;340;180;1,126,212,105;13;244;85;197;241;114;105;278;106;213,331;105;20;363;97,174,84;337;221;179;214;5;211;264;67;75;251;29;132,204;6,325,7;164;305;13;19,102;29;138;215,386,37;32;141,32;71;126;277;38;34;12;52;32;180,169;110;2;270,385;7;97;176,175;248;117;29;73;336;307;105;19;20;335;14;166;118;191;138,20;27;150;188,316;299;61;269;154;226,68;174;385;261;275;171;216;29;115;339;156;24;268;59;54;26;241,24;106;62,351;305;134;124;5;330,145;247;25;186;273;16,376;91,56;112;112;91;48;56;3;86;138;18;24;54;2;275;97;156;167;140;191;68;181;105,32;58;113,53;91;114;52;313;52;233;73;253;89;101,314;70;235;103;148;69;322;50;32;213;336;63;138;47;278;44;167;102;78;368;264,19;337;114;51;32;35;272;28;282;216;156;275;60;247;216,192;316;5;107;156;195;89;24;86;138;251;75;51;253;287;103;35;158;67;178;67;171;40;23;288;65;193;28;384;270;367;342;124;101;294;46;114,31,184;303;186;263;202;354;245;14;118;188;190;79,302;216,239;13;233;43;69;70;34,3;154;192;92;154;92;227;9;386;253;2,158;9,90;233,52;201;192;179,5;174;154;191;281;191,192,92,51;56,71,72,192;211;163,133;171;180;178;192;7;102;105;187;202;275;194;216;58,26;111,83,45;37;52;178,187,150,61;197,32;48;197;83;126,52;73,264;126;275;363;260;104;125;111,38;1,201;71,333;7;132;96,1;12;261;408,5;24;169;181;179;183;7;1;24;160;291;99;106;292;69;34;269,212,312;188,133,218,126;104,345;216,181;64;68;126;174;138;39;87;72;9;67;102;1;169;190;111;343,137;274;8;69;24;111;316;112,69;252;204;19,363;20;193;301;190;171;184;105;32;195;106;104;154;221;300;32;174,78;1;36;348;190;270,240,136;126;186;275;219,316;279;345;293;193;232;241;83,253;75;218,126;249;74;62;51;35;339;107,70;186;1;69;262;191;230;138;191;23;247;287,358;182,150;182,150,74;84,293;288;358,38;7;35,141;247;332,324,184;65;107;324,16;171;73,19;27;208,65,61;324;61,187;65;65;69;0;48;380;385,283;32;98,25;328;51;196;1;196,269;170;151;70;172;196,234;135;82,96;207;187;92,91;13,283;101;234;147;71,91;26;315;111;192,20,385;124;225,49;187,175;225,224,136;92,278,20;81;331;104,181;336;213,336;209;200;1,190;92;216;81,385;264;133;39;138,19,239;339;384,16,6,105;340;190;266;239;173;205;70,270;55,267,53;72;140,48,239;299,120,208;38,47;27;230;172;97;67;387;384,16,105;317;69;147;185;7,197;244;317;135,126;332,324;272;213,112;331;83;181,192,154;269;320;78;283,13,81;365;303;91,72;13,315;398,171;120;106;287;154;224;132;209;8;103,167;151;184;23,68,126;29;58;213;156,148,101;32,29,26;45;1,20;111;154;68;65;316;368;107;43;45,104;126;34,3;295,312;22,238,335;178;266;273;110;252;29;2;31,211,26;52,84;209;97;69;70,278;44;21;225;70;99;115,7,96;39,113;68,226;165;38,111;216;106;300,24;222;157;146,118;61;150,212;154;308;107;24;339;8;24;58;329;247,196;337;105,16,247;96,241,24;20,62,351,329;278,329;90,8,275;27;277,59;306;78;120;105;120;208,120;240,212,1;275,58;51,139;315;249;146;238;149;71;385,376;206;253,275,241;107;384;249,27;235;271;73;191;235;52;181;46,197;317,0;7,269,97;222;171;105;146;172,20;38;193;214;52;138;366;188;248,119;95;262;316;167,131;187;55;269;314;52;209;74;149;23;287;103,14;193,191;187,150;65;225;262;260;13;150;319;45,52,111;24;197;11;52,111;110;20;52,190,111;52,247;111;233,177;271;1;45;179;2,301;340,171;226,7;111;113;23;114;106,0,58;154;384;188,11;340,185;124;205;303;126,218;216;156;207;31;52;101;14;87;156;253;144,261;27;62;2;20;111;38;53;216;138;27;263;32;32;24,190;148;196;54;11;33;34;87;86;98,25;269;331;171;340,171,293;226,68;74;91,118;148;169;111;125;68;133;126,111;91,181;7,126;1,190;16;220;27;18;2;215;91;33,125;299;387,165,389,363;253;153;182;111;111;269,211;83,205,206;227;270;126,155;216,222;207;174;126,52;332;126;147;28;384;158;98,8;384;263,159;298,29;12;50;27;50,238;238;100,264;49,327;33;84;218;5;5;202;120;206;5;111,46;205;65,288;291,89;41;255;240;295;269;227;135;274;305,62;32,274;68;32;27;339;24,105;277;125;23,200;151;67;67;75;48;8,98;15;67;7;368;106;106,317;32;34;317;126;183;126;267;381;114,270,112;208;171;172;92;38;190;24;205,299;11;2;194;345;133;38;155,113,39;157;199;174;61;172;339;222;247;20;50;287;156,131;102;115;266;106;8,90;345;217;200;171;402;273;72;136,48;322;18;68;101;45;208;299;70;332;17;83;22;136;131;172;171;184;149,67,335;253;150;126;103;400;238;101;264,73;155;84;165;241;104;208;242,130;172;124;216,172;106,53;35;44;10;221;135;36;332;301;199;255;24;171,69;190;276;172;24;146;274;122;1;85,223,285;288,83;247;227;253;116;368;132;191;2;35;342;231;150;79,150;0;230;126,7;117;340;38,308;262;20;133;175;183;24;61;85;151,366;205;24;27;227;133,188,126;117;0,32;175;94;94;40;67;195,384;103;160;67,149;289,211;76,154;397;221;26;114;342;188;235;332;60;86;357;340,336;288;142;260;250;123;27,126;270;227;140;156;133;262,215,66;7,6,312,174;26,58,372,130;111;52;52,149;114;64;340;178,61,187;1;5,179,0;399;207,317;12;32;277,59;0;20,131;96,196;117,5;126;73;266;126;153;105;136;277;63;69;252;22;130;153;160;161,101;175;111;270;177;106;216;373;125;20;34;6;2;167;226;154;27;111;98;0;186;2;58;299;1;67;166;248;282;7;295;374;46;330;16;409,126;138;260;241,204;29;65;8;139;208;239;54;27;384;321;126;47;126;26;14;26;52;301;223,304;77;314;92;105;81;279;233;83;321;275;102;160,43;151;73;215,253;237;133;269;124;172;81;276;47;331;126,52;136;11;368;20;199;237;19;38;215;222;385;264;156;2;129;216;99;195;332;144;24;1;172;176;303;365;262;51;140,227;173,331,410;348;6;132;261;43;260;131;120,348;19;65,53;399;80;22;249,135;51;149;78;262;101;141;141;3;241;2;115;184;22;183;70;267;338;111;276,281;125;69;275;24;126;43;126;169;335;336;179;183;332;169;184;14;38;54;335;20,70,1;364;140,338;7;60;264;27;262,167;139,18;144;32,114;1;25;354;175;277;190;78;1;1;14;124;27;120;16;16;208,125;38,218;195;301;106;172;10;196,24;196,324;3;251;1;103;178;1;138;5;204;30;213;55;52,111,45;199;46;134,411;296;108,134;108,411;368,44;262;133;139;304;324,213;136;183,322;11;31;204;126,7;60;245;36;379;260;119;179;74;134,159;71;88,6;345,55;15;190;104;139;123;104;14;132,275;339;111;201;294,289;167;307,118;68;135;89;251,179;32;227;1,11;11,47;267;397,129;116,262,156;126;35;208;0;317;22;34;240;180;184;111,45;2;301,171;172,20;177;49,63,327,53;130,157;170,330,22;351;59;32,0,346;5;131,26;27,171;301,203;67;323;154;98;75;6;154,181;105;0;104,193,345;183;235;294,184,114;323;119,187;216,49;113;203;174;92,278,149;111,190,38;228,292;23;114;190;62;26;339;90;311;253;67;157,372;70;210;1;268;143,82;96,189;90;65,208;265;234,212;10;411,323,108,265;111;195;127,74;126;17;168;371;52;337;340;220;128,126;150;73,19;65,29;316,84;66,128,292;54,179,205,208;292;158,2;165,389;383;241;167;69,231,171;232;192;67;300,27;88;70,326,170;2;2;68;301;335,238;73;289;160;71;285;278;125,32;395;281;212;360;33,225;5;172,131;32;2;20;185;81;202;64;389;248,119;215;96;150,61,66;332;176;232,303;260;60;74;27;103,167;146;261;51;261,104;327;171,235;9;1;348;184;337,129,134,377;308;181;371,162;133;123;83,54;111,46;111,237;216;12;32,9,135;5;32;25,291,98;27,249,165;12;32;197,294;197,45;45;125,32;192,193;100;224;112;0;207;261;1;322;330;177;186,187,150;171;98;172;162;14;106,0;155;4;150;20;96;100;253;27;299;32;56;105;13;35;279;320;357;25,259,8;13;137,343;94;172;28,402;70;2,150,203;1;65,86;107,1;108,159;7;146;73;340;131;32,26;299,208,249,38;50,265;317;52;315,35,141;124;69,171;2;112;111;261,105;270;131;2,80;200;7,82;253,114;126;148,62;52,190;70,264;175;183,322;272,242;57;126;211;177;112,114;111;136;148;213;97;301,2;89;222;105;24;69;212;20;180;162;106;227;264,32,136;42;14,167;323;299,205;331;149;269,196;147;228,292,232;199;385;1;68,23;398,69;242,243;295,184,114;201,106;261,192,144;194;51,169;68,214,39,226;34;118,146;2,158;171,293,340;2,150,301,235;53,106;213;178;174,84;213;213;188,133,219;74;111,38;190;67;339;383;298;51;50;283;62;137,172;131,27;219,111;172,25;188;315,35;214,190;65,208,120;2;106;260;16;62;214;282;129;25;8;98;305;19;102,223;82;67;278;357,288;153;172;47,1;35,142;118,146;24;204,214;345;174;374;171;120;16;126,52;273;282;376,305;278,51;241;51;305,131;156;381;62;127;277;0;5;196;171;38;33,214,88,125;384,105;24;158,2;24,190;252,140,384;68;336;154,57;48;56;179,206,38;316;101,73;179,54;18,94;2,303;212;74,147;209;230;167;45;240;20;31;179;51;51;63;315;188;73,32,136;67;38;172,101;184;253,215;68;48;123,153;196,96;2;180;15;244;183;33;150;3;27,11;158,235,150;273;184;14;301;68,226;193;125,65;52,126;195;321;171,69,235,183;52;13,283;118;105;104;19,237;108;165;310;241;71;51;51;51;208;168;86,65;138;281;32;222,10;212;241,300,204;228;215;99;33;106,55,53,58;124;153,154;96;312;158,235;90,98;322;114;371;174,78;384,48,140;89,291;105;216,131;135;56,71;27;52;38;101;144;286;135;260;36;231;320,16;18;13;400;68;248,119;131;17;158;158,150;306;72;172;126;242;392;324,332;216,148;283,340;26;184;282;354;45;241;129;140,384;235;171;276,300;126;24;18;32;51;74;146;27;132,253,204,275;26,227;79;335,260;232,303;3;227;122;365,73;55;105,32;85,285,223;244,168,361;366,70;125;335;274;167;103;200;107;171,293,398;178,150,69;305,193;65,120;272,239;23;253;16;338,29,53;272;29;270,136;52;136;354,9;126,84;277;262;32,251;228,150,79;324;105;1;298;171,150,182,69;6;6;277;57;320;125;305;105,264;129;303;323;185;332;126,7;162,371;331,74;20;245;24;262;65;64;74;232,292;178;288;31,196;145;74;123,150;263,124,362;23,68;178;151;340;114;253;408;215,171;11,27,47,111;208;131;26;32;289;70;25,16;86;126;45;3,34;133;277,260,59;16;13;96;384;78,325;219;45;1;110;177,248;183;270;272;248,233,69,99;69;62;40,192;97;324,188;59,98;288,267;7;2;190;274;20;86;179,106;39;118;179,373;3;42,192;106;87;184;23,68;197,1,165,190;14;184;104;24;91;23;173,213;16;169;202,29;106;388,197;65;325,96,212;50,59;111;365;55;174;337;11;52;299,208;9;212,1;125;99;154;55;112;2;24;58,32;264,138;7;70,104;56,191;389,117;240;111;301,203,235;340;52;300,23,39,126;13;335;27;205,206;32,289;69,331;1,27;102,239;149,67;289,241;73;51;289;144;269;6,196;32;35;70;222,10;2;179;86,179,12;213,112;2;135;300;114;46,310;281;36;63;282;18;119,248;27,188;264;320;282;60;283;1;309,64;365;45,55;357;190;251;105;60;303;241;98;386;110,74;213,13;27;208;75;315,13;146;187,178;35;16;62,305;261;6;144;241,289;167;106;13;129;31,117;24,241;63;239;317;23,29;298,6;203,80;372;57;278,330,145;70;209;138;20;7;97,32;49;145;23;1;79;232;214;261;181;314;386,140,195;130;98;153;148;156,148;264;336,1;251;383;16;174;166;292,228;193,151;54;216;202,74;187,178;213,173;23;235,171,69;27;150;2;174,312,7;96,241;244;66;259;273;313;351;91;302,228;1;32;86;364;1;252;167,299;167;295;172;171,69;4;232;77,180;203,301;241;289;47,27;68;294;222,10;196;228;228;32,246;235;58,55,106;215;164;345;129,157;389,327;190;7;131;276;55,46,27;335;124;276;384;144,104;171;156;129;45;187;27;5;193,148,70;2;172,20;101;70;247;340;45;144;276;29,65;126;27;38;32;199;156;23;65,29,253,184;29,135,247;184;133,338;77;211,6;7,6;59;58;227;126,52;111;171;114,249,29;27,105;70,62;86,30;277,48,145;62;133;24;55;151,104;32;64;45;172;67;69;126;71;2;153;126;7;277;321;263;261;52;305;1;305;126;153;260;16;245;68;97;333;26,114;212;161;265;15;99;197;177;29;72;184;196;118;226;45,111;265;39;227;216;38;0;188;23;267;171;1;1;266;262;45;294;45;126,7;16;91;106;154;211;104;199;102;247;305;1;72;82;124;214;376;26;7;112;32;120;112;332;179;16;266;306,387,117;15;149;106;370;250;317,274;53;301;269;105;32;387,412;154;206,205;144;94;49;384;193;1;173;181;46;35;172;6;298;88;105;212;32;138;144;234;70;38;340,336;10;26;74;273;227;6;5;248;45;131;340;156;365;55;172;410;193;258;120;276;158;29;70;131;151;181;184,253,32;277;27,126;31,211;274;260;45,190;73;45;327,225;124;19;27;324;32,6,125;175;176,186;111,218;228;219;206;54;19;324,196,74,110;74,110;213,74;69;74;235;216;2;59;244;155;20;182;75;31;111;178;193;307;222;199;106;101;134;172;194,171;45;310;247;266;166;213;301;196;1;413;111;174,184;16;133;103;253;371;133;82;156;86;216;230;197;2;317;126;125;77;188;171;0;139;203;104;230;99;237;2;44,399;360;155;70;342;136;43;261;51;34;190;197,217;281;91;196,45;55;370;12;2;267;34,131;52;181,216,366;68,84;304,205;117,31;23;133,188;66;60,72,91;23,85,285,370;47,117,5;342;226,68;322;228,150,53,223;225;2;172,70;111;164,168,52;52,126,111;117;335;323;183,235;46,33;165;174;188,133;292,150;111;2;261;150,171,61;68;24,241;137;20,381;65,120;210,295;247;135,6;253,241;111;205,206,321;172;345;120;171;20;4;126;126;298,190;94,18;9;83;164;240,105,272;223,206,53,205;290;68;190;47,46;150,302;1,52,126;244,135;160;111;191;111;4,223;98;38,111,47,190;353,395,368,100;172;68;46;10;397;47,38;214,39;172;207;316;52,27;164;126;123;187;85;60,91;227;38,111;233;293;287;257;178;58;225,254,380,255;85,226,12;29,247;158;132;225;255;288;106,53;58,223;103;66;144,151;399;6;154,239;59;23;4;60;21,68;97,39,189;247;138;166;153;44,396;212;161;367;277;184;269;253;0;111;1;114;216;24;5;149;45;163;111;65;0;102;253;65;55;2;94;366;235;16;314;141;94;223;133;285;2;126;270;125;203;264;78;366,151;253;55;103;57;184;188;69,171,340;300;222,186,10;222;222;181;129;222;132;345,47,164;144;131;32;52;305;289;127;51;136;156;97;178;112;143,19,2;55,414;156;264;105;167;190;205;1,165;41;174,6;166;134;38;61,231;46,1,345;155,113;125;67;340;233;126;108;237,271,389;389;15;45,388;321;119,99;221;185;138;305;79;208;246,125;106,38,0,327;86;38;33;47;274,106,246,52;8;268,266;45;91,51;227;288,357;364,305,140;99,146,119;24;19;166,38;253,166;223;135;105;1;241;171;32,274;225,364,305;384;100;47;32,9,245;222;212;78;35;86;207;1,105;199;275;164;131;5;251;288;55;103;254;32;68,23;171;5;6;384;331;300,66;1,212;287,261;9;3;32;315;50;266;176;177;278,139;130,250,280,333;248;380;6,97,88,26;112;179;3;323;86;171,222;69;87;299;235;38;370;88,86;103;247;106;45;32;196,78;84,27;111,126;19,305;221;65;18;174;17;72;136;27,190;112,32;2;332;324,45;48;51,91;27;24;136;79,302;52;69;289;193;181,276;104;274,317;70;336;242;212;125;239;32;60;133;324,120,65;9;171;213,74;2,253;62;114;70;75;43,257;261;61,69,187;370,68;316,342;14;27;99;179;71;86;53;203;0;1;34,330;12;26;175;136;323;165,4;85,113;3;184;227;61,115;283;204,241,253;83;8,90,25;111;20;260;91;33;65;336;295;400;262;158;70;402,45;375;203;63;338;85,113;313;47;114;26;278,91,67;340;102,223;143;24;204;79;65,120,151;27;24;68;305;340;244;179;45;24;25,98,8;88,125;94;24,27,230,323;156;2;172;50;128;136,48;112;342;173;275;251;251,232;235;57,305;189;1;216;71;267;61;132;33;131;100;261;178;206;152;372;169;219;278;82;79;323;68;225;304;98;130;235;22;186;131;5,117;148;48;112;199;142;177;31;246,274;373;183,322;32;131;72;131;184;150;61;150;117;399;147;193;174;191,192;179;310;150;14;306;131,70;213;45;138;164,165;171;185;69;4;278;30;204;134;179;91;174;88;337;111;273,91;8;215;206;62;286;39;106;27,20;71,56;93;211;102;252;325;206,5;96;283;72;28;65;1;144;25;60;234;62;129;106;204;158;112,32;230;309;167;329;208;140;24;43;58;289;332;212;54;316;37;99;57;275;286;172;272;47;18;148;68;326;335;55;250;316;213;194;52;358;269;313;27;184;241;236;237;156;111;20;239;31;203;193,20;71;172;68;13;133;84;51;286;335;11;212;214;120;98;368;320;35;19;107;317;336;36;133;301;336;74;216;150;96;123;60;239;123;130;275;332,324;248;1;174;176;282;232;60;365;233;9;67;38;226;275;144,193,57;29;132;158;253;271,225;276;158;255;335;287;31;262;19;53;167;1;214;232;300;144;124;180,75;152;395;261;70;171;270;291;407;105;20;277;172;385;142;152;39;337;247;270;164;385;215;286;115;96;196;313;12;117;18;317,197;32;70;180;27;270;376;70;212,70;144;276;23;388;249;196;192;345,164;249;3,209;2;271;131;186;367,264;67;177;169;62;216;7;226,106;113;191,70;326,212;165,4;235,69,171,61;2,79;126;253,275;339;181;278;36;74;260,124;125;20,270;134;82;261;171;92;111,358;274;264;58,106,125;123;136;20;203;370,55;236;69,171;14;321,5,207;20;125,32,5;32;339;105;384,4,165;35;384;62;96,282,126;135,105,247;107,212;27;75;384,237;251,32;27,6;16;24,166,1,38;237,195,105;114,196,249;381;239,48;281;249,120;70;265;97;126;184,114;111,286,1,11;199;188;9;68;58;241;206;83;32;32,26;298;270;119,177;24;14,167;262;228;112;26,250,129,367;126;208;39,252,168,1;56;216,154;203;336;31,196,140;252;301;185;103;319;78;105;62;107;20,76;253,36;183,61;69;127,24;149,67;67,94;67;67;191;301;30;340,110;81;88;96,126;131,172,209,3;112;156;298;183;168,164;74,202;345;186;5;235,182,171;169;6,202;234;345;120;365,152;120,299;68,23,50;300;397;86;196,197,74;68,23;172;222;38;172;199;164;235,2,79,182;144;105;153;2;3;105;136;321;12;62;136;299;88;71;149;3;124;224;26;26;73;184;225,317;52;186;15;330;32;253;14;61;67,139;7,6;174;56;196,96;51;41;269;1;307;389;3;51;269;47;9;212;44;70;212;123;365;191;95;324;144,151;129;307;102;99;169;151;232;168;68;11;320;275;303;232;301;45,11,1;381;124;70;40,381;310;9;321;126;60,278;327;27;153;32;190;178;62;172;45;228;11;4;1;134;374;124;196;116,96;65;27;87;150;212;280;289;376;20;101;214,39;135;184;320;144;88;74;275;54;156;365;76;44;362;279;135,86;289,26;124;171;150,61;2,171;136,240;57;110;32;165,4;305;190;23;401;150;215;371;45;11;2;68;370;106;165;157;174;1;166,323;208;110;62;65;211;241;143;10;232;24;91;70;278;148;48;52;52;269;237;227;40;10;111;135;348;112;251;275;131;261;232,303;241;89;144;0;126;319;124;184;246,184;7,174,6;38;295;143,82;221;96;171;269,210;312,96;32;36;382;176;132;317;384;332;313;38;213;205;133;42;54;126,7;394;160;102;12;216;176;365;19;66;180;96,24,212;247;160,43;32,0,68;247;135,17;376;183;61,150,165;336;26;70;208;94;389,236;141;105;190;74;32;207;126;179;327;253;177;183;183,150;118;366;253;179;39;2;183,150;228;221;69;323;247,6;7;105;134;8;413;221;17;126;65;20;39;124;305;139;24;167;172;24;45;205;314;252;19;48;126;9;149,72;14;52;370;301;313;180;184;185;206;67;154;45;133;175;46;171;180;47;270;105;360;212;86;35;102;260;222;251;52;164;101;5;119;172;116;232;298;293;365;385;9;105;27;247;261;103;254;114;34;24;151;101;383,206;12;126;264;283;45;245;97;162;228;216;184;90;253;97;208;169;14;190;23;264;165;11;111;134;294;54;145;0;96;128;277;112;150;24;239;184,114;212;32;20;47;112;249;331;234;313;235;146;131;63;139;227;114;196;212;184,114;86;188;336;112;1;5;354;275;126;1;172;196;51;55;275;132;65;12;32;253;19;151;51;114;13;101;319;316;253,215;179;126;48,364;106;340;185;52;253;67;197;103;127;2;23;110;74,110,109;16;35;192;277;125;24;366;355;131;74;166;58;179,1;1;78;70;23;149;24,230;317;40;234;111,20;2;2;385,81,13;264;165;71,216;108;133;184;321;69;125;175;23;45;179;112;67;27;70;171;86;3;221,295;39;362;52,126;126;190;181;196;62;68;14;228;205;133;351,62;32;223;23;240;339;59;209;247,48,86;117;196;22;164,345;127;27;245;248;10;142;273;105;112;175;38;19;6;212;357;288;214;26;8;184;215,36;111;130,250;147;268;52,111;222;102,249,29;186;86;345;35;254;185;234;81;142;126;36;132;23;197;70;20;70;190;1;305;105;126;8;20;208;138;117;185;79,397;13;2;126;138;126;178;29;123;1;2;136;227;151;270;29;321;253;47;264;27;45;188;66;266;152;94;384;277;345;135;138;185;104;212;245,23;161;204;261;278;24;46;57;200;105;20;174;111;73;203,235;289;245;305;144;196;24;273;305;249;139;276;59,339;305;104;62;56;19;131;103,181;49;70;74;124;365;94;307;1;90;26;106;172;343;62;45;16;52;179;305;220;1;161;180;156;125;154;253;267;147;317;277;63,271;150;221;354;105,16;398;27;355;103;384;62;52,82;126;389;38;105;69;41;154;33;301;31;103;68;165;105;267;300;167;126;126;27;79;70;117;20;53;178;6;1;58";
let decoded = null;
function decode() {
if (decoded) return decoded;
const syllables = SYLLABLES.split(",");
const map = new Map();
const readingGroups = MAP.split(";");
let i = 0;
for (const ch of CHARS) {
const group = readingGroups[i++] || "";
const readings = group
.split(",")
.filter(Boolean)
.map((n) => syllables[Number(n)])
.filter(Boolean);
if (readings.length) map.set(ch, readings);
}
decoded = { syllables, map };
return decoded;
}
function getReadings(ch) {
if (!ch) return null;
const c = typeof ch === "string" ? ch : String.fromCodePoint(ch);
return decode().map.get(c) || null;
}
global.ListLocatorPinyinData = {
version: VERSION,
getReadings,
// test/helper
_decode: decode,
};
})(typeof globalThis !== "undefined" ? globalThis : window);
/* ---- content/fuzzy.js ---- */
/**
* Lightweight subsequence fuzzy matcher with optional CJK pinyin path.
* Public API: fuzzyScore, fuzzyFilter, preparePinyin.
* Latin-only text scoring is byte-equivalent to pre-v2.1 fuzzyScoreTextOnly path.
*/
(function (global) {
"use strict";
const MAX_READINGS_PER_CHAR = 4;
const MAX_PINYIN_KEYS = 32;
const MAX_FULL_KEY_CJK_LEN = 32;
const MAX_TEXT_META = 2000;
/** @type {Map<string, { fullKeys: {key:string,variantIndex:number,aliasPenalty:number}[], initialKeys: {key:string,variantIndex:number,aliasPenalty:number}[] }>} */
const textMetaCache = new Map();
/** @type {string[]} */
const textMetaOrder = [];
// ---------------------------------------------------------------------------
// Text-only fuzzy (unchanged scoring semantics)
// ---------------------------------------------------------------------------
/**
* @param {string} query
* @param {string} text
* @returns {number|null}
*/
function fuzzyScoreTextOnly(query, text) {
if (query == null || text == null) return null;
const q = String(query).trim();
if (q.length === 0) return 0;
const qLower = q.toLocaleLowerCase();
const tLower = String(text).toLocaleLowerCase();
// Exact match
if (tLower === qLower) return 10000 + qLower.length * 10;
// Case-insensitive substring (continuous) bonus path
const subIdx = tLower.indexOf(qLower);
if (subIdx !== -1) {
let score = 5000 + qLower.length * 20 - subIdx;
if (subIdx === 0) score += 800;
else if (isWordBoundary(tLower, subIdx)) score += 400;
return score;
}
// Subsequence match with consecutive-run weighting
let qi = 0;
let score = 0;
let consecutive = 0;
let firstMatch = -1;
let lastMatch = -1;
for (let ti = 0; ti < tLower.length && qi < qLower.length; ti++) {
if (tLower[ti] === qLower[qi]) {
if (firstMatch === -1) firstMatch = ti;
if (lastMatch === ti - 1) {
consecutive += 1;
} else {
consecutive = 1;
}
score += 10 + consecutive * 15;
if (ti === 0 || isWordBoundary(tLower, ti)) score += 25;
lastMatch = ti;
qi += 1;
}
}
if (qi < qLower.length) return null;
const span = lastMatch - firstMatch + 1;
score += Math.max(0, 200 - span * 2);
score += Math.max(0, 100 - firstMatch * 3);
score += Math.max(0, 50 - tLower.length);
return score;
}
/**
* @param {string} text lowercased
* @param {number} index
*/
function isWordBoundary(text, index) {
if (index === 0) return true;
const prev = text[index - 1];
return /[\s\-_/.,;:|()[\]{}]/.test(prev);
}
// ---------------------------------------------------------------------------
// Pinyin helpers
// ---------------------------------------------------------------------------
function normalizeLatinQuery(query) {
return String(query || "")
.toLocaleLowerCase()
.replace(/[\s'_-]+/g, "");
}
function isCjk(ch) {
if (!ch) return false;
const cp = ch.codePointAt(0);
// CJK Unified Ideographs + common extensions used in UI
return (
(cp >= 0x4e00 && cp <= 0x9fff) ||
(cp >= 0x3400 && cp <= 0x4dbf) ||
(cp >= 0x20000 && cp <= 0x2a6df) ||
(cp >= 0xf900 && cp <= 0xfaff)
);
}
function hasCjk(text) {
const s = String(text || "");
for (const ch of s) {
if (isCjk(ch)) return true;
}
return false;
}
/**
* For syllables containing v (ü), also offer u alias with aliasPenalty 40.
* @param {string[]} readings
* @returns {{ syl: string, aliasPenalty: number }[]}
*/
function withVowelAliases(readings) {
/** @type {{ syl: string, aliasPenalty: number }[]} */
const out = [];
const seen = new Set();
for (const r of readings.slice(0, MAX_READINGS_PER_CHAR)) {
if (!r) continue;
if (!seen.has(r)) {
seen.add(r);
out.push({ syl: r, aliasPenalty: 0 });
}
if (r.includes("v")) {
const alias = r.replace(/v/g, "u");
if (alias !== r && !seen.has(alias)) {
seen.add(alias);
out.push({ syl: alias, aliasPenalty: 40 });
}
}
}
return out;
}
function dedupeKeyEntries(entries) {
const seen = new Set();
const out = [];
for (const e of entries) {
const sig = e.key + "\0" + e.variantIndex + "\0" + e.aliasPenalty;
// Prefer first occurrence of same key with lower penalty
if (seen.has(e.key)) continue;
seen.add(e.key);
out.push(e);
if (out.length >= MAX_PINYIN_KEYS) break;
}
return out;
}
/**
* Beam-expand reading combinations into keys with variant/alias metadata.
* @param {{ type: string, readings?: {syl:string,aliasPenalty:number}[], text?: string }[]} units
* @param {"full"|"initial"} mode
*/
function expandReadings(units, mode) {
/** @type {{ key: string, variantIndex: number, aliasPenalty: number }[]} */
let keys = [{ key: "", variantIndex: 0, aliasPenalty: 0 }];
let cjkCount = 0;
for (const u of units) {
if (u.type === "cjk") cjkCount += 1;
}
const fullPathOnlyFirst =
mode === "full" && cjkCount > MAX_FULL_KEY_CJK_LEN;
for (const unit of units) {
/** @type {{ add: string, variantBoost: number, aliasPenalty: number }[]} */
let additions;
if (unit.type === "cjk") {
let parts = unit.readings || [];
if (fullPathOnlyFirst) parts = parts.slice(0, 1);
additions = parts.map((p, i) => ({
add: mode === "initial" ? (p.syl[0] || "") : p.syl,
variantBoost: i,
aliasPenalty: p.aliasPenalty || 0,
}));
} else {
const t = unit.text || "";
additions = [
{
add: mode === "initial" ? (t[0] || "") : t,
variantBoost: 0,
aliasPenalty: 0,
},
];
}
if (!additions.length) continue;
/** @type {{ key: string, variantIndex: number, aliasPenalty: number }[]} */
const next = [];
for (const prefix of keys) {
for (const add of additions) {
next.push({
key: prefix.key + add.add,
variantIndex: prefix.variantIndex + add.variantBoost,
aliasPenalty: prefix.aliasPenalty + add.aliasPenalty,
});
}
}
// Cap beam: keep first MAX_PINYIN_KEYS unique keys
keys = dedupeKeyEntries(next);
}
return keys.filter((k) => k.key.length > 0);
}
function buildPinyinMeta(text) {
const data = global.ListLocatorPinyinData;
if (!data || typeof data.getReadings !== "function") {
return { fullKeys: [], initialKeys: [] };
}
/** @type {{ type: string, readings?: {syl:string,aliasPenalty:number}[], text?: string }[]} */
const units = [];
for (const ch of String(text || "")) {
if (isCjk(ch)) {
const readings = data.getReadings(ch);
if (readings && readings.length) {
units.push({ type: "cjk", readings: withVowelAliases(readings) });
}
} else if (/[a-z0-9]/i.test(ch)) {
units.push({ type: "latin", text: ch.toLocaleLowerCase() });
}
// skip spaces/punct for pinyin keys
}
return {
fullKeys: expandReadings(units, "full"),
initialKeys: expandReadings(units, "initial"),
};
}
function rememberTextMeta(key, meta) {
if (textMetaCache.has(key)) {
textMetaCache.set(key, meta);
return;
}
textMetaCache.set(key, meta);
textMetaOrder.push(key);
while (textMetaOrder.length > MAX_TEXT_META) {
const old = textMetaOrder.shift();
if (old != null) textMetaCache.delete(old);
}
}
function getPinyinMeta(text) {
const key = String(text || "");
const cached = textMetaCache.get(key);
if (cached) return cached;
const meta = buildPinyinMeta(key);
rememberTextMeta(key, meta);
return meta;
}
function isPinyinBoundary(key, index) {
// Approximate syllable boundary: start, or after previous syllable end.
// Without syllable breaks stored, treat every character start of a new letter run
// after vowels→consonants poorly — use simple: index 0 only + after known finals
// Design: "if (i === 0 || isPinyinBoundary(key, i))"
// We mark boundaries at positions that could start a syllable by scanning
// common initials; for scoring bonus only. Conservative: index 0 or after 'g'/'n'/'r' etc.
if (index === 0) return true;
// Heuristic: previous char is a vowel-like ending letter
const prev = key[index - 1];
return /[aeiouvng]/.test(prev) && /[bcdfghjklmnpqrstwxyz]/.test(key[index]);
}
function subsequenceRunScore(q, key) {
let qi = 0;
let score = 0;
let consecutive = 0;
let last = -1;
for (let i = 0; i < key.length && qi < q.length; i++) {
if (key[i] === q[qi]) {
consecutive = last === i - 1 ? consecutive + 1 : 1;
score += 20 + consecutive * 20;
if (i === 0 || isPinyinBoundary(key, i)) score += 30;
last = i;
qi += 1;
}
}
if (qi < q.length) return null;
return Math.min(score, 900);
}
/**
* Score one key set (full or initial).
* @param {string} q
* @param {{ key: string, variantIndex: number, aliasPenalty: number }[]} keys
* @param {"full"|"initial"} kind
*/
function scoreKeySet(q, keys, kind) {
let best = null;
const qLen = q.length;
for (let vi = 0; vi < keys.length; vi++) {
const entry = keys[vi];
const key = entry.key;
const variantPenalty =
(entry.variantIndex || 0) * 30 + (entry.aliasPenalty || 0);
let score = null;
if (kind === "full") {
if (key === q) {
score = 4600 + Math.min(qLen, 20) * 8 - variantPenalty;
} else if (key.startsWith(q)) {
score = 4300 + Math.min(qLen, 20) * 8 - variantPenalty;
} else {
const idx = key.indexOf(q);
if (idx !== -1) {
if (idx === 0 || isPinyinBoundary(key, idx)) {
score = 3950 + Math.min(qLen, 20) * 7 - idx - variantPenalty;
} else {
score = 3700 + Math.min(qLen, 20) * 6 - idx - variantPenalty;
}
} else {
const sub = subsequenceRunScore(q, key);
if (sub != null) score = 1600 + sub - variantPenalty;
}
}
} else {
// initial
if (key === q) {
score = 3400 + qLen * 80 - variantPenalty;
} else if (key.startsWith(q)) {
score = 3150 + qLen * 70 - variantPenalty;
} else {
const idx = key.indexOf(q);
if (idx !== -1) {
score = 2850 + qLen * 60 - idx - variantPenalty;
} else {
const sub = subsequenceRunScore(q, key);
if (sub != null) score = 1200 + sub - variantPenalty;
}
}
}
if (score != null && (best == null || score > best)) best = score;
}
return best;
}
function maxScore(a, b) {
if (a == null) return b;
if (b == null) return a;
return a > b ? a : b;
}
function pinyinScore(q, text) {
const meta = getPinyinMeta(text);
let best = null;
best = maxScore(best, scoreKeySet(q, meta.fullKeys, "full"));
best = maxScore(best, scoreKeySet(q, meta.initialKeys, "initial"));
return best;
}
/**
* @param {string} query
* @param {string} text
* @returns {number|null}
*/
function fuzzyScore(query, text) {
const original = fuzzyScoreTextOnly(query, text);
if (original != null) return original;
const q = normalizeLatinQuery(query);
if (!q || !/^[a-z]+$/.test(q)) return null;
if (!hasCjk(text)) return null;
if (!global.ListLocatorPinyinData) return null;
return pinyinScore(q, text);
}
/**
* Warm pinyin meta cache for option labels.
* @template T
* @param {T[]} items
* @param {(item: T) => string} getText
*/
function preparePinyin(items, getText) {
if (!global.ListLocatorPinyinData || !items) return;
for (let i = 0; i < items.length; i++) {
const text = getText(items[i]);
if (hasCjk(text)) getPinyinMeta(text);
}
}
/**
* Filter and rank items by fuzzy score.
* @template T
* @param {string} query
* @param {T[]} items
* @param {(item: T) => string} getText
* @returns {T[]}
*/
function fuzzyFilter(query, items, getText) {
const q = String(query || "").trim();
if (!q) return items.slice();
const scored = [];
for (let i = 0; i < items.length; i++) {
const item = items[i];
const score = fuzzyScore(q, getText(item));
if (score != null) scored.push({ item, score, index: i });
}
scored.sort((a, b) => b.score - a.score || a.index - b.index);
return scored.map((s) => s.item);
}
global.ListLocatorFuzzy = { fuzzyScore, fuzzyFilter, preparePinyin };
})(typeof globalThis !== "undefined" ? globalThis : window);
/* ---- content/aria-listbox.js ---- */
/**
* Chrome List Locator v2 — ARIA combobox/listbox enhancer.
* Filters original option DOM; independent search HUD; never steals focus.
* See docs/design/v2-aria-listbox.md
*/
(function (global) {
"use strict";
const ENHANCED_ATTR = "data-cll-aria-enhanced";
const ACTIVE_ATTR = "data-cll-aria-active";
const HOST_ID = "chrome-list-locator-aria-hud";
const TRIGGER_WINDOW_MS = 1500;
const SELECT_STUCK_MS = 500;
const STYLE_KEY = "__cllAriaDisplay";
// ---------------------------------------------------------------------------
// DOM / ARIA helpers
// ---------------------------------------------------------------------------
function getRole(el) {
if (!el || el.nodeType !== 1) return "";
const explicit = el.getAttribute && el.getAttribute("role");
if (explicit) return explicit.trim().toLowerCase().split(/\s+/)[0];
return "";
}
function isVisible(el) {
if (!el || !el.isConnected) return false;
if (el.hidden) return false;
if (el.getAttribute && el.getAttribute("aria-hidden") === "true") return false;
try {
const style = window.getComputedStyle(el);
if (style.display === "none" || style.visibility === "hidden") return false;
if (parseFloat(style.opacity) === 0) return false;
} catch (_) {
/* ignore */
}
try {
if (el.getClientRects && el.getClientRects().length === 0) return false;
} catch (_) {
return false;
}
return true;
}
function composedPathOf(node) {
const path = [];
let cur = node;
while (cur) {
path.push(cur);
if (cur.assignedSlot) {
cur = cur.assignedSlot;
continue;
}
const root = cur.getRootNode && cur.getRootNode();
if (root && root instanceof ShadowRoot && root.host) {
cur = root.host;
continue;
}
cur = cur.parentNode || cur.parentElement;
}
return path;
}
/**
* @param {EventTarget|null} start
* @param {string} selector
* @returns {Element|null}
*/
function composedClosest(start, selector) {
if (!start) return null;
let el =
start.nodeType === 1
? /** @type {Element} */ (start)
: /** @type {Node} */ (start).parentElement;
while (el) {
try {
if (el.matches && el.matches(selector)) return el;
} catch (_) {
/* ignore */
}
if (el.assignedSlot) {
el = el.assignedSlot;
continue;
}
const root = el.getRootNode && el.getRootNode();
if (root && root instanceof ShadowRoot && root.host) {
el = root.host;
continue;
}
el = el.parentElement;
}
return null;
}
/**
* @param {Node} root
* @param {string} role
* @returns {Element[]}
*/
function queryRole(root, role) {
/** @type {Element[]} */
const out = [];
const walk = (node) => {
if (!node) return;
if (node.nodeType === 1) {
const el = /** @type {Element} */ (node);
if (getRole(el) === role) out.push(el);
if (el.shadowRoot) walk(el.shadowRoot);
}
const children =
node.nodeType === 1 || node.nodeType === 9 || node.nodeType === 11
? node.childNodes
: null;
if (!children) return;
for (let i = 0; i < children.length; i++) walk(children[i]);
};
// Fast path for light DOM
try {
if (root && typeof /** @type {any} */ (root).querySelectorAll === "function") {
const list = /** @type {ParentNode} */ (root).querySelectorAll('[role="' + role + '"]');
for (let i = 0; i < list.length; i++) {
if (getRole(list[i]) === role) out.push(list[i]);
}
// Open shadow hosts
const all = /** @type {ParentNode} */ (root).querySelectorAll("*");
for (let i = 0; i < all.length; i++) {
if (all[i].shadowRoot) walk(all[i].shadowRoot);
}
return dedupeElements(out);
}
} catch (_) {
/* fall through */
}
walk(root);
return dedupeElements(out);
}
/**
* @param {Element[]} list
*/
function dedupeElements(list) {
const seen = new Set();
const out = [];
for (const el of list) {
if (seen.has(el)) continue;
seen.add(el);
out.push(el);
}
return out;
}
/**
* Nearest ancestor (inclusive) with a given role, staying within open shadow.
* @param {Element} el
* @param {string} role
*/
function nearestRole(el, role) {
let cur = el;
while (cur) {
if (getRole(cur) === role) return cur;
if (cur.assignedSlot) {
cur = cur.assignedSlot;
continue;
}
const root = cur.getRootNode && cur.getRootNode();
if (root && root instanceof ShadowRoot && root.host) {
// Stop at shadow host boundary for option ownership? Design says
// nearestRole(el, "listbox") === listbox for nesting — walk parents only
// within the same root first.
}
cur = cur.parentElement;
}
return null;
}
function isInsideV1Overlay(el) {
if (!el) return false;
try {
if (el.closest && el.closest("#chrome-list-locator-host, [data-chrome-list-locator='host']")) {
return true;
}
} catch (_) {
/* ignore */
}
return false;
}
function isComboboxListboxTrigger(el) {
if (!el || getRole(el) !== "combobox") return false;
const hasPopup = (el.getAttribute("aria-haspopup") || "").toLowerCase();
if (hasPopup && hasPopup !== "listbox" && hasPopup !== "true") return false;
// Prefer explicit listbox haspopup; allow missing if controls/owns used later
return true;
}
function isEditableTrigger(trigger) {
if (!trigger) return true;
const tag = trigger.tagName;
if (tag === "INPUT" || tag === "TEXTAREA") return true;
if (trigger.isContentEditable) return true;
const autocomplete = (trigger.getAttribute("aria-autocomplete") || "").toLowerCase();
if (autocomplete && autocomplete !== "none") return true;
try {
const editables = trigger.querySelectorAll(
'input:not([type="hidden"]), textarea, [contenteditable=""], [contenteditable="true"]'
);
for (let i = 0; i < editables.length; i++) {
if (isVisible(editables[i])) return true;
}
} catch (_) {
/* ignore */
}
return false;
}
function isMultiSelectable(listbox, trigger) {
if (!listbox) return false;
if ((listbox.getAttribute("aria-multiselectable") || "").toLowerCase() === "true") {
return true;
}
if (trigger) {
const multi = (trigger.getAttribute("aria-multiselectable") || "").toLowerCase();
if (multi === "true") return true;
}
return false;
}
function getOptionLabel(el) {
if (!el) return "";
const aria = el.getAttribute("aria-label");
if (aria && aria.trim()) return aria.trim();
return (el.textContent || "").trim();
}
function isDisabledOption(el) {
if (!el) return true;
if (el.getAttribute("aria-disabled") === "true") return true;
if (el.hasAttribute("disabled")) return true;
try {
if (typeof el.matches === "function" && el.matches(":disabled")) return true;
} catch (_) {
/* ignore */
}
return false;
}
/**
* @param {Element} listbox
* @returns {{ el: Element, index: number, label: string }[]}
*/
function collectOptions(listbox) {
/** @type {{ el: Element, index: number, label: string }[]} */
const out = [];
let opts;
try {
opts = listbox.querySelectorAll('[role="option"]');
} catch (_) {
return out;
}
let index = 0;
for (let i = 0; i < opts.length; i++) {
const el = opts[i];
if (nearestRole(el, "listbox") !== listbox) continue;
if (isDisabledOption(el)) continue;
// Visibility: when we hide via display:none, re-collect would skip —
// during filter we use snapshot; collect is for initial + re-render.
// Treat currently hidden-by-us as still present if STYLE_KEY set.
const hiddenByUs = !!(/** @type {any} */ (el)[STYLE_KEY]);
if (!hiddenByUs && !isVisible(el)) continue;
const label = getOptionLabel(el);
if (!label) continue;
out.push({ el, index: index++, label });
}
return out;
}
function maxNumber(nums) {
let max = NaN;
for (const n of nums) {
if (typeof n === "number" && !isNaN(n)) {
if (isNaN(max) || n > max) max = n;
}
}
return max;
}
/**
* @param {Element} listbox
* @param {{ el: Element }[]} options
*/
function looksVirtualized(listbox, options) {
const setSizes = [
parseInt(listbox.getAttribute("aria-setsize") || "", 10),
...options.map((o) => parseInt(o.el.getAttribute("aria-setsize") || "", 10)),
];
const declared = maxNumber(setSizes);
if (declared && declared > options.length) return true;
const maxPos = maxNumber(
options.map((o) => parseInt(o.el.getAttribute("aria-posinset") || "", 10))
);
if (declared && maxPos && declared > options.length) return true;
return false;
}
function majorityLabelsEmpty(listbox) {
let opts;
try {
opts = listbox.querySelectorAll('[role="option"]');
} catch (_) {
return true;
}
let total = 0;
let empty = 0;
for (let i = 0; i < opts.length; i++) {
const el = opts[i];
if (nearestRole(el, "listbox") !== listbox) continue;
total += 1;
if (!getOptionLabel(el)) empty += 1;
}
if (total === 0) return true;
return empty / total > 0.5;
}
function isBusy(listbox) {
if (!listbox) return false;
let cur = listbox;
while (cur) {
if ((cur.getAttribute("aria-busy") || "").toLowerCase() === "true") return true;
cur = cur.parentElement;
}
return false;
}
function tokenListHas(attrValue, id) {
if (!attrValue || !id) return false;
return attrValue.trim().split(/\s+/).indexOf(id) !== -1;
}
function rectsRelated(a, b) {
if (!a || !b) return true;
// Reasonable proximity: horizontally overlapping or near, and vertically adjacent-ish
const hOverlap = !(a.right < b.left - 80 || a.left > b.right + 80);
const vNear = Math.abs(a.bottom - b.top) < 400 || Math.abs(b.bottom - a.top) < 400;
return hOverlap && vNear;
}
function pointsToOrLooksLikeTrigger(trigger, listbox) {
if (!trigger || !listbox) return false;
if (!trigger.isConnected) return false;
if (!isComboboxListboxTrigger(trigger)) return false;
const id = listbox.id;
if (id) {
if (tokenListHas(trigger.getAttribute("aria-controls") || "", id)) return true;
if (tokenListHas(trigger.getAttribute("aria-owns") || "", id)) return true;
}
const hasPopup = (trigger.getAttribute("aria-haspopup") || "").toLowerCase();
if (hasPopup === "listbox" || hasPopup === "true") {
// Position heuristic when no id link
try {
return rectsRelated(trigger.getBoundingClientRect(), listbox.getBoundingClientRect());
} catch (_) {
return true;
}
}
return false;
}
function findExpandedComboboxByIdRef(id) {
if (!id) return null;
/** @type {Element[]} */
const candidates = [];
try {
const all = document.querySelectorAll('[role="combobox"]');
for (let i = 0; i < all.length; i++) {
const el = all[i];
if (
tokenListHas(el.getAttribute("aria-controls") || "", id) ||
tokenListHas(el.getAttribute("aria-owns") || "", id)
) {
candidates.push(el);
}
}
} catch (_) {
return null;
}
if (!candidates.length) return null;
const expanded = candidates.filter(
(c) => (c.getAttribute("aria-expanded") || "").toLowerCase() === "true"
);
if (expanded.length === 1) return expanded[0];
if (expanded.length > 1) {
// Prefer focused
const focused = composedClosest(document.activeElement, '[role="combobox"]');
if (focused && expanded.indexOf(focused) !== -1) return focused;
return null; // not unique
}
// Not expanded attribute — allow if focused
const focused = composedClosest(document.activeElement, '[role="combobox"]');
if (focused && candidates.indexOf(focused) !== -1) return focused;
if (candidates.length === 1) return candidates[0];
return null;
}
function isPrintableKey(e) {
if (e.ctrlKey || e.metaKey || e.altKey) return false;
if (e.key === "Unidentified" || e.key == null) return false;
// Single character printable (including Unicode)
if (e.key.length === 1) return true;
return false;
}
function hideOption(el) {
if (!el) return;
const any = /** @type {any} */ (el);
if (!any[STYLE_KEY]) {
any[STYLE_KEY] = {
value: el.style.getPropertyValue("display"),
priority: el.style.getPropertyPriority("display"),
};
}
el.style.setProperty("display", "none", "important");
}
function restoreDisplay(el) {
if (!el) return;
const any = /** @type {any} */ (el);
const saved = any[STYLE_KEY];
if (!saved) {
// May still have our display:none without key after re-render clone — clear only if we set important none
return;
}
if (saved.value) {
el.style.setProperty("display", saved.value, saved.priority || "");
} else {
el.style.removeProperty("display");
}
delete any[STYLE_KEY];
}
function clearActiveMarker(el) {
if (el && el.removeAttribute) el.removeAttribute(ACTIVE_ATTR);
}
function dispatchMouseSequence(el) {
// Omit view: sandboxed userscript window is a proxy and fails UIEventInit conversion.
const init = { bubbles: true, cancelable: true, composed: true, button: 0 };
try {
el.dispatchEvent(
new PointerEvent("pointerdown", {
...init,
pointerId: 1,
pointerType: "mouse",
isPrimary: true,
buttons: 1,
})
);
} catch (_) {
/* older browsers */
}
el.dispatchEvent(new MouseEvent("mousedown", { ...init, buttons: 1 }));
try {
el.dispatchEvent(
new PointerEvent("pointerup", {
...init,
pointerId: 1,
pointerType: "mouse",
isPrimary: true,
buttons: 0,
})
);
} catch (_) {
/* ignore */
}
el.dispatchEvent(new MouseEvent("mouseup", { ...init, buttons: 0 }));
el.dispatchEvent(new MouseEvent("click", init));
}
function dispatchMousemove(el) {
try {
el.dispatchEvent(
new MouseEvent("mousemove", {
bubbles: true,
cancelable: true,
composed: true,
})
);
} catch (_) {
/* ignore */
}
}
// ---------------------------------------------------------------------------
// Search HUD
// ---------------------------------------------------------------------------
const HUD_CSS = `
:host {
all: initial;
position: fixed;
z-index: 2147483645;
pointer-events: none;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 12px;
color: #1a1d21;
line-height: 1.3;
--cll-surface: rgba(255, 255, 255, 0.72);
--cll-text: #1a1d21;
--cll-text-secondary: rgba(26, 29, 33, 0.55);
--cll-accent: #3D63DD;
--cll-border: rgba(0, 0, 0, 0.08);
--cll-shadow: 0 8px 24px rgba(15, 18, 26, 0.14), 0 2px 6px rgba(15, 18, 26, 0.08);
}
:host([hidden]) {
display: none !important;
}
@media (prefers-color-scheme: dark) {
:host {
color: #f2f3f5;
--cll-surface: rgba(32, 34, 40, 0.68);
--cll-text: #f2f3f5;
--cll-text-secondary: rgba(242, 243, 245, 0.55);
--cll-accent: #7C9BFF;
--cll-border: rgba(255, 255, 255, 0.12);
--cll-shadow: 0 10px 28px rgba(0, 0, 0, 0.42), 0 3px 8px rgba(0, 0, 0, 0.28);
}
}
.box {
pointer-events: none;
position: fixed;
min-width: 200px;
max-width: min(420px, calc(100vw - 16px));
padding: 8px 12px;
background: var(--cll-surface);
-webkit-backdrop-filter: blur(16px) saturate(1.4);
backdrop-filter: blur(16px) saturate(1.4);
color: var(--cll-text);
border-radius: 10px;
box-shadow: var(--cll-shadow), inset 0 1px 0 rgba(255, 255, 255, 0.6);
display: flex;
flex-direction: column;
gap: 4px;
border: 1px solid var(--cll-border);
opacity: 0;
transform: translateY(4px);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
.box {
background: rgba(250, 250, 251, 0.97);
}
}
@media (prefers-color-scheme: dark) {
.box {
box-shadow: var(--cll-shadow), inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
.box {
background: rgba(30, 32, 38, 0.97);
}
}
}
.box.is-shown {
animation: cll-hud-in 140ms cubic-bezier(0.2, 0, 0, 1) both;
}
@keyframes cll-hud-in {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
.box {
opacity: 1;
transform: none;
}
.box.is-shown {
animation: none;
}
.caret {
animation: none !important;
opacity: 1 !important;
}
}
.row {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.icon {
flex: 0 0 auto;
width: 14px;
height: 14px;
color: var(--cll-text);
opacity: 0.55;
display: block;
}
.query-wrap {
flex: 1 1 auto;
min-width: 0;
display: flex;
align-items: center;
min-height: 1.25em;
overflow: hidden;
}
.query {
font-weight: 600;
font-size: 13.5px;
color: var(--cll-text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
.query.is-empty {
/* query text empty; placeholder lives in meta */
display: none;
}
.caret {
flex: 0 0 auto;
width: 1px;
height: 1.05em;
margin-left: 1px;
background: var(--cll-accent);
animation: cll-caret-blink 1s steps(1, end) infinite;
}
@keyframes cll-caret-blink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: 0; }
}
.count {
flex: 0 0 auto;
font-size: 11px;
font-variant-numeric: tabular-nums;
color: var(--cll-text-secondary);
letter-spacing: 0.01em;
}
.meta {
font-size: 11px;
color: var(--cll-text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
`;
const PAGE_ACTIVE_CSS = `
[data-cll-aria-active="1"] {
box-shadow: inset 3px 0 0 0 #3D63DD !important;
}
@media (prefers-color-scheme: dark) {
[data-cll-aria-active="1"] {
box-shadow: inset 3px 0 0 0 #7C9BFF !important;
}
}
`;
function ensurePageActiveStyles() {
try {
if (document.getElementById("cll-aria-active-style")) return;
const style = document.createElement("style");
style.id = "cll-aria-active-style";
style.textContent = PAGE_ACTIVE_CSS;
(document.documentElement || document.head || document.body).appendChild(style);
} catch (_) {
/* ignore */
}
}
/** Magnifier SVG via createElementNS (Trusted Types-safe; no HTML sinks). */
function createMagnifierSvg() {
const NS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(NS, "svg");
svg.setAttribute("width", "14");
svg.setAttribute("height", "14");
svg.setAttribute("viewBox", "0 0 16 16");
svg.setAttribute("fill", "none");
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
const circle = document.createElementNS(NS, "circle");
circle.setAttribute("cx", "7");
circle.setAttribute("cy", "7");
circle.setAttribute("r", "4.5");
circle.setAttribute("stroke", "currentColor");
circle.setAttribute("stroke-width", "1.75");
const path = document.createElementNS(NS, "path");
path.setAttribute("d", "M10.5 10.5L13.5 13.5");
path.setAttribute("stroke", "currentColor");
path.setAttribute("stroke-width", "1.75");
path.setAttribute("stroke-linecap", "round");
svg.appendChild(circle);
svg.appendChild(path);
return svg;
}
class SearchHud {
constructor() {
/** @type {HTMLElement|null} */
this._host = null;
/** @type {HTMLElement|null} */
this._box = null;
/** @type {HTMLElement|null} */
this._queryEl = null;
/** @type {HTMLElement|null} */
this._countEl = null;
/** @type {HTMLElement|null} */
this._metaEl = null;
/** @type {boolean} */
this._enterAnimated = false;
/** @type {boolean} */
this._failed = false;
}
mount() {
if (this._failed) return;
if (this._host && this._host.isConnected) return;
if (this._host && !this._host.isConnected) {
this._host = null;
this._box = null;
this._queryEl = null;
this._countEl = null;
this._metaEl = null;
this._enterAnimated = false;
}
try {
const host = document.createElement("div");
host.id = HOST_ID;
host.setAttribute("data-chrome-list-locator", "aria-hud");
host.setAttribute("aria-hidden", "true");
(document.documentElement || document.body).appendChild(host);
const shadow = host.attachShadow({ mode: "closed" });
const style = document.createElement("style");
style.textContent = HUD_CSS;
const box = document.createElement("div");
box.className = "box";
const row = document.createElement("div");
row.className = "row";
const icon = document.createElement("div");
icon.className = "icon";
icon.setAttribute("aria-hidden", "true");
icon.appendChild(createMagnifierSvg());
const queryWrap = document.createElement("div");
queryWrap.className = "query-wrap";
const queryEl = document.createElement("span");
queryEl.className = "query is-empty";
queryEl.textContent = "";
const caret = document.createElement("span");
caret.className = "caret";
caret.setAttribute("aria-hidden", "true");
queryWrap.appendChild(queryEl);
queryWrap.appendChild(caret);
const countEl = document.createElement("div");
countEl.className = "count";
countEl.textContent = "";
row.appendChild(icon);
row.appendChild(queryWrap);
row.appendChild(countEl);
const meta = document.createElement("div");
meta.className = "meta";
meta.textContent = "Type to filter…";
box.appendChild(row);
box.appendChild(meta);
shadow.appendChild(style);
shadow.appendChild(box);
this._host = host;
this._box = box;
this._queryEl = queryEl;
this._countEl = countEl;
this._metaEl = meta;
this._enterAnimated = false;
} catch (_) {
// Trusted Types / CSP / DOM failures: leave session functional without HUD
this._failed = true;
try {
if (this._host && this._host.parentNode) {
this._host.parentNode.removeChild(this._host);
}
} catch (__) {
/* ignore */
}
this._host = null;
this._box = null;
this._queryEl = null;
this._countEl = null;
this._metaEl = null;
}
}
/**
* @param {Element} listbox
*/
updatePosition(listbox) {
this.mount();
if (!this._box || !listbox) return;
let rect;
try {
rect = listbox.getBoundingClientRect();
} catch (_) {
return;
}
const box = this._box;
const width = Math.max(200, Math.min(rect.width, 420));
box.style.visibility = "hidden";
box.style.left = "0px";
box.style.top = "0px";
box.style.width = Math.round(width) + "px";
box.style.minWidth = Math.round(width) + "px";
const bh = box.offsetHeight || 44;
const margin = 8;
const gap = 6;
let top = rect.top - gap - bh;
let left = rect.left;
let below = false;
// Prefer above; if not enough room, dock below listbox top edge (mirror gap)
if (top < margin) {
top = rect.top + gap;
below = true;
// If still tight, clamp into viewport
if (top + bh + margin > window.innerHeight) {
top = Math.max(margin, window.innerHeight - bh - margin);
}
}
top = Math.max(margin, Math.min(top, window.innerHeight - bh - margin));
left = Math.max(margin, Math.min(left, window.innerWidth - width - margin));
// Prefer exact listbox left edge when it fits
if (rect.left >= margin && rect.left + width <= window.innerWidth - margin) {
left = rect.left;
}
box.style.left = Math.round(left) + "px";
box.style.top = Math.round(top) + "px";
box.dataset.placement = below ? "below" : "above";
box.style.visibility = "visible";
if (this._host) this._host.hidden = false;
if (!this._enterAnimated) {
box.classList.remove("is-shown");
// Restart enter animation
void box.offsetWidth;
box.classList.add("is-shown");
this._enterAnimated = true;
}
}
/**
* @param {string} query
* @param {number} visibleCount
* @param {number} totalCount
*/
setQuery(query, visibleCount, totalCount) {
this.mount();
if (!this._queryEl || !this._metaEl || !this._countEl) return;
const q = String(query || "");
if (!q) {
this._queryEl.textContent = "";
this._queryEl.classList.add("is-empty");
this._metaEl.textContent = "Type to filter…";
} else {
this._queryEl.textContent = q;
this._queryEl.classList.remove("is-empty");
if (visibleCount === 0) {
this._metaEl.textContent = "No matches — Backspace to edit";
} else {
this._metaEl.textContent = "↵ select · esc close";
}
}
this._countEl.textContent = String(visibleCount) + "/" + String(totalCount);
}
destroy() {
if (this._host && this._host.parentNode) {
this._host.parentNode.removeChild(this._host);
}
this._host = null;
this._box = null;
this._queryEl = null;
this._countEl = null;
this._metaEl = null;
this._enterAnimated = false;
}
}
// ---------------------------------------------------------------------------
// Session
// ---------------------------------------------------------------------------
class AriaListboxSession {
/**
* @param {AriaListboxController} controller
* @param {{ listbox: Element, trigger: Element, options: { el: Element, index: number, label: string }[] }} candidate
*/
constructor(controller, candidate) {
this.controller = controller;
this.listbox = candidate.listbox;
this.trigger = candidate.trigger;
this.options = candidate.options.slice();
this.visibleOptions = this.options.slice();
this.query = "";
this.activeIndex = 0;
this.hud = new SearchHud();
this._destroyed = false;
this._observer = null;
this._onKeyDown = this._onKeyDown.bind(this);
this._onPointerDown = this._onPointerDown.bind(this);
this._onScrollResize = this._onScrollResize.bind(this);
this._positionRaf = 0;
this._refreshRaf = 0;
this._selectTimer = 0;
this._closeCheckRaf = 0;
this._churnCount = 0;
this._churnWindowStart = 0;
}
mount() {
if (this._destroyed) return this;
this.listbox.setAttribute(ENHANCED_ATTR, "1");
if (global.ListLocatorFuzzy && global.ListLocatorFuzzy.preparePinyin) {
global.ListLocatorFuzzy.preparePinyin(this.options, (o) => o.label);
}
// HUD is best-effort: filter/keyboard must still register if HUD fails (e.g. CSP)
try {
this.hud.mount();
this.hud.updatePosition(this.listbox);
this.hud.setQuery(this.query, this.visibleOptions.length, this.options.length);
} catch (_) {
try {
this.hud.destroy();
} catch (__) {
/* ignore */
}
}
document.addEventListener("keydown", this._onKeyDown, true);
document.addEventListener("pointerdown", this._onPointerDown, true);
window.addEventListener("scroll", this._onScrollResize, true);
window.addEventListener("resize", this._onScrollResize, true);
this._observer = new MutationObserver((mutations) => this._onMutations(mutations));
try {
this._observer.observe(this.listbox, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: [
"aria-disabled",
"aria-selected",
"aria-hidden",
"aria-busy",
"class",
"style",
"hidden",
],
});
} catch (_) {
/* ignore */
}
return this;
}
destroy() {
if (this._destroyed) return;
this._destroyed = true;
if (this._selectTimer) {
clearTimeout(this._selectTimer);
this._selectTimer = 0;
}
if (this._positionRaf) cancelAnimationFrame(this._positionRaf);
if (this._refreshRaf) cancelAnimationFrame(this._refreshRaf);
if (this._closeCheckRaf) cancelAnimationFrame(this._closeCheckRaf);
document.removeEventListener("keydown", this._onKeyDown, true);
document.removeEventListener("pointerdown", this._onPointerDown, true);
window.removeEventListener("scroll", this._onScrollResize, true);
window.removeEventListener("resize", this._onScrollResize, true);
if (this._observer) {
try {
this._observer.disconnect();
} catch (_) {
/* ignore */
}
this._observer = null;
}
// Restore all option styles we touched (including detached nodes we tracked)
for (const opt of this.options) {
restoreDisplay(opt.el);
clearActiveMarker(opt.el);
try {
if (opt.el.style) opt.el.style.outline = "";
} catch (_) {
/* ignore */
}
}
// Also clear any remaining markers/styles under current listbox
try {
if (this.listbox && this.listbox.isConnected) {
const marked = this.listbox.querySelectorAll("[" + ACTIVE_ATTR + "]");
for (let i = 0; i < marked.length; i++) {
clearActiveMarker(marked[i]);
try {
marked[i].style.outline = "";
} catch (_) {
/* ignore */
}
}
const allOpts = this.listbox.querySelectorAll('[role="option"]');
for (let i = 0; i < allOpts.length; i++) restoreDisplay(allOpts[i]);
}
} catch (_) {
/* ignore */
}
if (this.listbox && this.listbox.removeAttribute) {
this.listbox.removeAttribute(ENHANCED_ATTR);
}
this.hud.destroy();
this.controller._onSessionDestroyed(this);
}
/**
* @param {string} nextQuery
*/
setQuery(nextQuery) {
if (this._destroyed) return;
this.query = nextQuery;
this.refreshOptions();
this.applyFilter();
this.hud.setQuery(this.query, this.visibleOptions.length, this.options.length);
this._schedulePosition();
if (this.visibleOptions.length && this.query) {
try {
this.visibleOptions[Math.max(0, this.activeIndex)].el.scrollIntoView({
block: "nearest",
});
} catch (_) {
/* ignore */
}
}
}
refreshOptions() {
if (this._destroyed || !this.listbox.isConnected) return;
// Preserve STYLE_KEY entries: re-collect from DOM
this.options = collectOptions(this.listbox);
if (global.ListLocatorFuzzy && global.ListLocatorFuzzy.preparePinyin) {
global.ListLocatorFuzzy.preparePinyin(this.options, (o) => o.label);
}
}
applyFilter() {
if (this._destroyed) return;
const fuzzy = global.ListLocatorFuzzy;
let visible;
if (!this.query) {
visible = this.options.slice();
} else if (fuzzy && fuzzy.fuzzyFilter) {
visible = fuzzy.fuzzyFilter(this.query, this.options, (o) => o.label);
} else {
const q = this.query.toLocaleLowerCase();
visible = this.options.filter((o) => o.label.toLocaleLowerCase().includes(q));
}
const visibleSet = new Set(visible.map((o) => o.el));
for (const opt of this.options) {
if (visibleSet.has(opt.el)) restoreDisplay(opt.el);
else hideOption(opt.el);
}
this.visibleOptions = visible;
if (!visible.length) {
this.activeIndex = -1;
} else {
this.activeIndex = Math.max(0, Math.min(this.activeIndex, visible.length - 1));
}
this.updateActiveVisual();
}
updateActiveVisual() {
ensurePageActiveStyles();
for (const opt of this.options) {
clearActiveMarker(opt.el);
// Clear legacy outline marker if any residual remains
try {
if (opt.el.style && opt.el.style.outline) opt.el.style.outline = "";
} catch (_) {
/* ignore */
}
}
if (this.activeIndex < 0 || !this.visibleOptions[this.activeIndex]) return;
const el = this.visibleOptions[this.activeIndex].el;
el.setAttribute(ACTIVE_ATTR, "1");
if (this.query) {
dispatchMousemove(el);
}
}
/**
* @param {number} delta
*/
moveActive(delta) {
if (!this.visibleOptions.length) return;
let next = this.activeIndex + delta;
if (next < 0) next = this.visibleOptions.length - 1;
if (next >= this.visibleOptions.length) next = 0;
this.activeIndex = next;
this.updateActiveVisual();
try {
this.visibleOptions[this.activeIndex].el.scrollIntoView({ block: "nearest" });
} catch (_) {
/* ignore */
}
}
chooseActive() {
if (this.activeIndex < 0 || !this.visibleOptions[this.activeIndex]) return;
const el = this.visibleOptions[this.activeIndex].el;
this.selectOption(el);
}
/**
* @param {Element} optionEl
*/
selectOption(optionEl) {
try {
optionEl.scrollIntoView({ block: "nearest" });
} catch (_) {
/* ignore */
}
dispatchMouseSequence(optionEl);
// If still open after timeout, clear filter to avoid stuck filtered UI
if (this._selectTimer) clearTimeout(this._selectTimer);
this._selectTimer = setTimeout(() => {
this._selectTimer = 0;
if (this._destroyed) return;
if (this.listbox.isConnected && isVisible(this.listbox)) {
this.setQuery("");
}
}, SELECT_STUCK_MS);
}
/**
* @param {KeyboardEvent} e
*/
_onKeyDown(e) {
if (this._destroyed) return;
if (!this.controller.ctx.isGloballyActive()) {
this.destroy();
return;
}
// Escape: never intercept
if (e.key === "Escape") return;
// Printable characters always append to query while session active
if (isPrintableKey(e)) {
e.preventDefault();
e.stopPropagation();
this.setQuery(this.query + e.key);
return;
}
// When query empty: do not intercept arrows/enter/backspace
if (!this.query) return;
if (e.key === "Backspace") {
e.preventDefault();
e.stopPropagation();
this.setQuery(this.query.slice(0, -1));
return;
}
if (e.key === "Delete") {
e.preventDefault();
e.stopPropagation();
this.setQuery("");
return;
}
if (e.key === "ArrowDown") {
e.preventDefault();
e.stopPropagation();
this.moveActive(1);
return;
}
if (e.key === "ArrowUp") {
e.preventDefault();
e.stopPropagation();
this.moveActive(-1);
return;
}
if (e.key === "Home") {
e.preventDefault();
e.stopPropagation();
this.activeIndex = 0;
this.updateActiveVisual();
return;
}
if (e.key === "End") {
e.preventDefault();
e.stopPropagation();
this.activeIndex = this.visibleOptions.length - 1;
this.updateActiveVisual();
return;
}
if (e.key === "Enter") {
e.preventDefault();
e.stopPropagation();
this.chooseActive();
}
}
/**
* @param {PointerEvent} e
*/
_onPointerDown(e) {
if (this._destroyed) return;
const path = typeof e.composedPath === "function" ? e.composedPath() : composedPathOf(/** @type {Node} */ (e.target));
const inListbox = path.indexOf(this.listbox) !== -1;
const inTrigger = path.indexOf(this.trigger) !== -1;
const inHud = this.hud._host && path.indexOf(this.hud._host) !== -1;
if (inListbox || inTrigger || inHud) return;
// Outside click: next frame, destroy if listbox gone/hidden
this._closeCheckRaf = requestAnimationFrame(() => {
if (this._destroyed) return;
if (!this.listbox.isConnected || !isVisible(this.listbox)) {
this.destroy();
return;
}
if (
this.trigger &&
((this.trigger.getAttribute("aria-expanded") || "").toLowerCase() === "false" ||
!this.trigger.isConnected)
) {
this.destroy();
}
});
}
_onScrollResize() {
this._schedulePosition();
this._checkStillOpen();
}
_schedulePosition() {
if (this._positionRaf) return;
this._positionRaf = requestAnimationFrame(() => {
this._positionRaf = 0;
if (this._destroyed) return;
if (!this.listbox.isConnected) {
this.controller._handleListboxDisconnected(this);
return;
}
// Remount HUD if page removed it
if (!this.hud._host || !this.hud._host.isConnected) {
this.hud.mount();
this.hud.setQuery(this.query, this.visibleOptions.length, this.options.length);
}
this.hud.updatePosition(this.listbox);
});
}
_checkStillOpen() {
if (this._destroyed) return;
if (!this.listbox.isConnected) {
this.controller._handleListboxDisconnected(this);
return;
}
if (!isVisible(this.listbox)) {
this.destroy();
return;
}
if (!this.trigger || !this.trigger.isConnected) {
this.destroy();
return;
}
if ((this.trigger.getAttribute("aria-expanded") || "").toLowerCase() === "false") {
this.destroy();
}
}
/**
* @param {MutationRecord[]} mutations
*/
_onMutations(mutations) {
if (this._destroyed) return;
let childList = false;
for (const m of mutations) {
if (m.type === "childList") {
childList = true;
// Virtualization churn detection: rapid option replacement
const now = performance.now();
if (!this._churnWindowStart || now - this._churnWindowStart > 1000) {
this._churnWindowStart = now;
this._churnCount = 0;
}
this._churnCount += (m.addedNodes ? m.addedNodes.length : 0) + (m.removedNodes ? m.removedNodes.length : 0);
}
}
if (this._churnCount > 40) {
// Continuous option replacement → likely virtualized
const opts = collectOptions(this.listbox);
if (looksVirtualized(this.listbox, opts) || this._churnCount > 80) {
this.destroy();
return;
}
}
if (childList || mutations.length) {
if (this._refreshRaf) return;
this._refreshRaf = requestAnimationFrame(() => {
this._refreshRaf = 0;
if (this._destroyed) return;
if (!this.listbox.isConnected) {
this.controller._handleListboxDisconnected(this);
return;
}
// Restore styles on detached previous option nodes, then re-apply
for (const opt of this.options) {
if (!opt.el.isConnected) {
restoreDisplay(opt.el);
clearActiveMarker(opt.el);
try {
if (opt.el.style) opt.el.style.outline = "";
} catch (_) {
/* ignore */
}
}
}
this.refreshOptions();
if (looksVirtualized(this.listbox, this.options)) {
this.destroy();
return;
}
this.applyFilter();
this.hud.setQuery(this.query, this.visibleOptions.length, this.options.length);
this._schedulePosition();
});
}
}
}
// ---------------------------------------------------------------------------
// Controller
// ---------------------------------------------------------------------------
class AriaListboxController {
/**
* @param {{ getSettings: Function, isGloballyActive: Function, getThreshold: Function }} ctx
*/
constructor(ctx) {
this.ctx = ctx;
/** @type {Map<Element, AriaListboxSession>} */
this.sessions = new Map();
/** @type {{ el: Element, time: number, type: string }|null} */
this.lastTrigger = null;
this._rootObserver = null;
this._scanRaf = 0;
this._onPointerDown = this._onPointerDown.bind(this);
this._onKeyDown = this._onKeyDown.bind(this);
this._onFocusIn = this._onFocusIn.bind(this);
this._onMutations = this._onMutations.bind(this);
}
start() {
document.addEventListener("pointerdown", this._onPointerDown, true);
document.addEventListener("keydown", this._onKeyDown, true);
document.addEventListener("focusin", this._onFocusIn, true);
if (typeof MutationObserver !== "undefined") {
this._rootObserver = new MutationObserver(this._onMutations);
try {
this._rootObserver.observe(document.documentElement || document, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: [
"role",
"aria-expanded",
"aria-controls",
"aria-owns",
"aria-hidden",
"aria-busy",
"hidden",
"class",
"style",
],
});
} catch (_) {
/* ignore */
}
}
this.scanRoot(document);
return this;
}
refreshSettings() {
if (!this.ctx.isGloballyActive()) {
this.destroyActiveSessions();
} else {
this.scanRoot(document);
}
}
destroyActiveSessions() {
const list = Array.from(this.sessions.values());
for (const s of list) s.destroy();
}
/**
* @param {AriaListboxSession} session
*/
_onSessionDestroyed(session) {
if (this.sessions.get(session.listbox) === session) {
this.sessions.delete(session.listbox);
}
}
/**
* Listbox node was replaced (re-render). Migrate query when the same id reappears.
* @param {AriaListboxSession} session
*/
_handleListboxDisconnected(session) {
if (!session || session._destroyed) return;
const id = session.listbox && session.listbox.id;
const query = session.query;
session.destroy();
if (!id || !this.ctx.isGloballyActive()) return;
// New node may already be in the tree (replace) or arrive next frame
const tryMigrate = () => {
let neu = null;
try {
neu = document.getElementById(id);
} catch (_) {
neu = null;
}
if (!neu || getRole(neu) !== "listbox" || !neu.isConnected) return false;
if (!isVisible(neu)) return false;
this.maybeAttachListbox(neu);
const next = this.sessions.get(neu);
if (next && query) next.setQuery(query);
return !!next;
};
if (!tryMigrate()) {
requestAnimationFrame(() => {
tryMigrate();
});
}
}
/**
* @param {Node} root
*/
scanRoot(root) {
if (!this.ctx.isGloballyActive()) return;
const listboxes = queryRole(root || document, "listbox");
for (const lb of listboxes) {
this.maybeAttachListbox(lb);
}
// Prune dead sessions
for (const [lb, session] of Array.from(this.sessions.entries())) {
if (!lb.isConnected || !isVisible(lb)) {
session.destroy();
}
}
}
/**
* @param {Element} listbox
*/
maybeAttachListbox(listbox) {
if (!this.ctx.isGloballyActive()) return;
if (!listbox || getRole(listbox) !== "listbox") return;
if (listbox.tagName === "SELECT") return;
if (isInsideV1Overlay(listbox)) return;
if (listbox.getAttribute(ENHANCED_ATTR) === "1" && this.sessions.has(listbox)) return;
if (!isVisible(listbox)) return;
if (isBusy(listbox)) {
requestAnimationFrame(() => {
if (listbox.isConnected) this.maybeAttachListbox(listbox);
});
return;
}
const trigger = this.findTriggerForListbox(listbox);
const result = this.evaluateCandidate(listbox, trigger);
if (!result.ok) return;
// Replace existing session on same listbox
const existing = this.sessions.get(listbox);
if (existing) existing.destroy();
const session = new AriaListboxSession(this, result).mount();
this.sessions.set(listbox, session);
}
/**
* Attempt to migrate query when listbox node is replaced but controls id stays.
* @param {Element} oldListbox
* @param {Element} newListbox
* @param {string} query
*/
migrateSession(oldListbox, newListbox, query) {
const old = this.sessions.get(oldListbox);
if (old) old.destroy();
this.maybeAttachListbox(newListbox);
const next = this.sessions.get(newListbox);
if (next && query) next.setQuery(query);
}
/**
* @param {Element} listbox
*/
findTriggerForListbox(listbox) {
const id = listbox.id;
if (id) {
const linked = findExpandedComboboxByIdRef(id);
if (linked) return linked;
}
const focused = composedClosest(document.activeElement, '[role="combobox"]');
if (focused && pointsToOrLooksLikeTrigger(focused, listbox)) return focused;
if (this.lastTrigger && performance.now() - this.lastTrigger.time < TRIGGER_WINDOW_MS) {
const el = this.lastTrigger.el;
if (el && el.isConnected && pointsToOrLooksLikeTrigger(el, listbox)) {
const expanded = (el.getAttribute("aria-expanded") || "").toLowerCase();
if (expanded !== "false") return el;
}
}
return null;
}
/**
* @param {Element} listbox
* @param {Element|null} trigger
*/
evaluateCandidate(listbox, trigger) {
const reject = (reason) => ({ ok: false, reason });
if (!trigger) return reject("no-trigger");
if (isEditableTrigger(trigger)) return reject("editable-trigger");
if (isMultiSelectable(listbox, trigger)) return reject("multiselect");
if (majorityLabelsEmpty(listbox)) return reject("empty-labels");
const options = collectOptions(listbox);
const threshold = this.ctx.getThreshold();
if (options.length < threshold) return reject("below-threshold");
if (looksVirtualized(listbox, options)) return reject("virtualized");
return { ok: true, listbox, trigger, options };
}
/**
* @param {PointerEvent} e
*/
_onPointerDown(e) {
const trigger = composedClosest(/** @type {Node} */ (e.target), '[role="combobox"]');
if (trigger && isComboboxListboxTrigger(trigger) && !isEditableTrigger(trigger)) {
this.lastTrigger = { el: trigger, time: performance.now(), type: "pointer" };
}
}
/**
* @param {KeyboardEvent} e
*/
_onKeyDown(e) {
const opens =
e.key === "Enter" || e.key === " " || e.key === "ArrowDown" || e.key === "F4";
if (!opens) return;
const trigger = composedClosest(/** @type {Node} */ (e.target), '[role="combobox"]');
if (trigger && isComboboxListboxTrigger(trigger) && !isEditableTrigger(trigger)) {
this.lastTrigger = { el: trigger, time: performance.now(), type: "key" };
}
}
/**
* @param {FocusEvent} e
*/
_onFocusIn(e) {
const trigger = composedClosest(/** @type {Node} */ (e.target), '[role="combobox"]');
if (trigger && isComboboxListboxTrigger(trigger) && !isEditableTrigger(trigger)) {
this.lastTrigger = { el: trigger, time: performance.now(), type: "focus" };
}
}
/**
* @param {MutationRecord[]} mutations
*/
_onMutations(mutations) {
let needScan = false;
/** @type {Map<string, { query: string, trigger: Element|null }>} */
const disconnectedById = new Map();
for (const [lb, session] of this.sessions.entries()) {
if (!lb.isConnected) {
if (lb.id) {
disconnectedById.set(lb.id, { query: session.query, trigger: session.trigger });
}
}
}
for (const m of mutations) {
if (m.type === "childList" && m.addedNodes && m.addedNodes.length) needScan = true;
if (m.type === "childList" && m.removedNodes && m.removedNodes.length) needScan = true;
if (m.type === "attributes") needScan = true;
}
// Try migrate sessions for replaced listboxes with same id
if (disconnectedById.size) {
for (const [id, state] of disconnectedById) {
let neu = null;
try {
neu = document.getElementById(id);
} catch (_) {
neu = null;
}
if (neu && getRole(neu) === "listbox" && neu.isConnected) {
// Destroy old sessions pointing at disconnected nodes
for (const [lb, session] of Array.from(this.sessions.entries())) {
if (!lb.isConnected) session.destroy();
}
this.maybeAttachListbox(neu);
const next = this.sessions.get(neu);
if (next && state.query) next.setQuery(state.query);
needScan = false;
}
}
}
// Destroy sessions whose trigger collapsed
for (const session of Array.from(this.sessions.values())) {
if (!session.listbox.isConnected || !isVisible(session.listbox)) {
session.destroy();
continue;
}
if (
!session.trigger.isConnected ||
(session.trigger.getAttribute("aria-expanded") || "").toLowerCase() === "false"
) {
session.destroy();
}
}
if (needScan) this._scheduleScan();
}
_scheduleScan() {
if (this._scanRaf) return;
this._scanRaf = requestAnimationFrame(() => {
this._scanRaf = 0;
this.scanRoot(document);
});
}
}
global.ListLocatorAriaListbox = {
/**
* @param {{ getSettings: Function, isGloballyActive: Function, getThreshold: Function }} ctx
*/
init(ctx) {
return new AriaListboxController(ctx).start();
},
};
})(typeof globalThis !== "undefined" ? globalThis : window);
/* ---- content/overlay.js ---- */
/**
* Shadow-DOM isolated searchable select overlay.
*/
(function (global) {
"use strict";
const HOST_ID = "chrome-list-locator-host";
const PANEL_CSS = `
:host {
all: initial;
position: fixed;
z-index: 2147483646;
pointer-events: none;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 13px;
color: #1a1d21;
line-height: 1.4;
--cll-surface: rgba(255, 255, 255, 0.72);
--cll-text: #1a1d21;
--cll-text-secondary: rgba(26, 29, 33, 0.55);
--cll-accent: #3D63DD;
--cll-border: rgba(0, 0, 0, 0.08);
--cll-shadow: 0 8px 24px rgba(15, 18, 26, 0.14), 0 2px 6px rgba(15, 18, 26, 0.08);
--cll-divider: rgba(0, 0, 0, 0.06);
--cll-input-bg: rgba(255, 255, 255, 0.55);
--cll-thumb: rgba(26, 29, 33, 0.22);
}
/* all:initial resets display to inline, which overrides UA [hidden]{display:none} */
:host([hidden]) {
display: none !important;
}
@media (prefers-color-scheme: dark) {
:host {
color: #f2f3f5;
--cll-surface: rgba(32, 34, 40, 0.68);
--cll-text: #f2f3f5;
--cll-text-secondary: rgba(242, 243, 245, 0.55);
--cll-accent: #7C9BFF;
--cll-border: rgba(255, 255, 255, 0.12);
--cll-shadow: 0 10px 28px rgba(0, 0, 0, 0.42), 0 3px 8px rgba(0, 0, 0, 0.28);
--cll-divider: rgba(255, 255, 255, 0.08);
--cll-input-bg: rgba(255, 255, 255, 0.06);
--cll-thumb: rgba(242, 243, 245, 0.28);
}
}
*, *::before, *::after { box-sizing: border-box; }
.panel {
pointer-events: auto;
position: fixed;
min-width: 220px;
max-width: min(420px, calc(100vw - 16px));
max-height: min(360px, calc(100vh - 16px));
display: flex;
flex-direction: column;
background: var(--cll-surface);
-webkit-backdrop-filter: blur(16px) saturate(1.4);
backdrop-filter: blur(16px) saturate(1.4);
border: 1px solid var(--cll-border);
border-radius: 10px;
box-shadow: var(--cll-shadow), inset 0 1px 0 rgba(255, 255, 255, 0.6);
overflow: hidden;
color: var(--cll-text);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
.panel {
background: rgba(250, 250, 251, 0.97);
}
}
@media (prefers-color-scheme: dark) {
.panel {
box-shadow: var(--cll-shadow), inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
.panel {
background: rgba(30, 32, 38, 0.97);
}
}
}
.search-wrap {
flex: 0 0 auto;
padding: 8px 10px;
border-bottom: 1px solid var(--cll-divider);
}
.search-field {
display: flex;
align-items: center;
gap: 8px;
padding: 0 10px;
border: 1px solid var(--cll-border);
border-radius: 8px;
background: var(--cll-input-bg);
transition: box-shadow 120ms ease, border-color 120ms ease;
}
.search-field:focus-within {
border-color: var(--cll-accent);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--cll-accent) 40%, transparent);
}
.search-icon {
flex: 0 0 auto;
width: 14px;
height: 14px;
color: var(--cll-text-secondary);
opacity: 0.55;
display: block;
}
.search {
flex: 1 1 auto;
width: 100%;
min-width: 0;
padding: 8px 0;
border: 0;
border-radius: 0;
outline: none;
font: inherit;
font-size: 13.5px;
background: transparent;
color: var(--cll-text);
}
.search::placeholder {
color: var(--cll-text-secondary);
}
.list {
flex: 1 1 auto;
overflow-y: auto;
overscroll-behavior: contain;
padding: 4px 0;
margin: 0;
list-style: none;
}
.list::-webkit-scrollbar {
width: 6px;
}
.list::-webkit-scrollbar-track {
background: transparent;
}
.list::-webkit-scrollbar-thumb {
background: var(--cll-thumb);
border-radius: 999px;
}
.group-label {
padding: 6px 12px 4px;
font-size: 11px;
font-weight: 600;
color: var(--cll-text-secondary);
letter-spacing: 0.02em;
user-select: none;
}
.option {
display: block;
width: 100%;
text-align: left;
padding: 7px 12px;
border: 0;
background: transparent;
font: inherit;
color: inherit;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.option:hover {
background: color-mix(in srgb, var(--cll-accent) 6%, transparent);
}
.option.is-active {
background: color-mix(in srgb, var(--cll-accent) 10%, transparent);
box-shadow: inset 3px 0 0 0 var(--cll-accent);
color: var(--cll-text);
}
.option.is-selected {
font-weight: 600;
}
.empty {
padding: 16px 12px;
color: var(--cll-text-secondary);
text-align: center;
}
.footer {
flex: 0 0 auto;
padding: 6px 12px 8px;
font-size: 11px;
color: var(--cll-text-secondary);
border-top: 1px solid var(--cll-divider);
}
`;
/**
* @typedef {{ value: string, label: string, disabled?: boolean, group?: string|null, selected?: boolean, optionIndex: number }} FlatOption
*/
/** Magnifier SVG via createElementNS (Trusted Types-safe; no HTML sinks). */
function createMagnifierSvg() {
const NS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(NS, "svg");
svg.setAttribute("width", "14");
svg.setAttribute("height", "14");
svg.setAttribute("viewBox", "0 0 16 16");
svg.setAttribute("fill", "none");
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
const circle = document.createElementNS(NS, "circle");
circle.setAttribute("cx", "7");
circle.setAttribute("cy", "7");
circle.setAttribute("r", "4.5");
circle.setAttribute("stroke", "currentColor");
circle.setAttribute("stroke-width", "1.75");
const path = document.createElementNS(NS, "path");
path.setAttribute("d", "M10.5 10.5L13.5 13.5");
path.setAttribute("stroke", "currentColor");
path.setAttribute("stroke-width", "1.75");
path.setAttribute("stroke-linecap", "round");
svg.appendChild(circle);
svg.appendChild(path);
return svg;
}
class SelectOverlay {
constructor() {
/** @type {HTMLElement|null} */
this._host = null;
/** @type {ShadowRoot|null} */
this._shadow = null;
/** @type {HTMLElement|null} */
this._panel = null;
/** @type {HTMLInputElement|null} */
this._search = null;
/** @type {HTMLElement|null} */
this._list = null;
/** @type {HTMLElement|null} */
this._footer = null;
/** @type {HTMLSelectElement|null} */
this._select = null;
/** @type {FlatOption[]} */
this._allOptions = [];
/** @type {FlatOption[]} */
this._visible = [];
/** @type {number} */
this._activeIndex = 0;
/** @type {((opt: FlatOption) => void)|null} */
this._onSelect = null;
/** @type {(() => void)|null} */
this._onClose = null;
/** @type {boolean} */
this._open = false;
this._onDocPointerDown = this._onDocPointerDown.bind(this);
this._onKeyDown = this._onKeyDown.bind(this);
this._onScrollOrResize = this._onScrollOrResize.bind(this);
this._onSearchInput = this._onSearchInput.bind(this);
}
get isOpen() {
return this._open;
}
get activeSelect() {
return this._select;
}
/**
* @param {HTMLSelectElement} select
* @param {FlatOption[]} options
* @param {{ onSelect: (opt: FlatOption) => void, onClose?: () => void }} handlers
*/
open(select, options, handlers) {
this.close({ silent: true });
this._select = select;
this._allOptions = options.filter((o) => !o.disabled);
if (global.ListLocatorFuzzy && global.ListLocatorFuzzy.preparePinyin) {
global.ListLocatorFuzzy.preparePinyin(this._allOptions, (o) => o.label);
}
this._onSelect = handlers.onSelect;
this._onClose = handlers.onClose || null;
this._ensureDom();
this._open = true;
this._host.hidden = false;
this._search.value = "";
this._applyFilter("");
this._position();
document.addEventListener("pointerdown", this._onDocPointerDown, true);
document.addEventListener("keydown", this._onKeyDown, true);
window.addEventListener("scroll", this._onScrollOrResize, true);
window.addEventListener("resize", this._onScrollOrResize, true);
// Focus search after paint so native select does not steal focus
requestAnimationFrame(() => {
try {
this._search.focus({ preventScroll: true });
this._search.select();
} catch (_) {
/* ignore */
}
});
}
/**
* @param {{ silent?: boolean }} [opts]
*/
close(opts) {
if (!this._open) return;
this._open = false;
if (this._host) this._host.hidden = true;
document.removeEventListener("pointerdown", this._onDocPointerDown, true);
document.removeEventListener("keydown", this._onKeyDown, true);
window.removeEventListener("scroll", this._onScrollOrResize, true);
window.removeEventListener("resize", this._onScrollOrResize, true);
const onClose = this._onClose;
this._select = null;
this._allOptions = [];
this._visible = [];
this._onSelect = null;
this._onClose = null;
if (!opts || !opts.silent) {
if (onClose) onClose();
}
}
destroy() {
this.close({ silent: true });
if (this._host && this._host.parentNode) {
this._host.parentNode.removeChild(this._host);
}
this._host = null;
this._shadow = null;
this._panel = null;
this._search = null;
this._list = null;
this._footer = null;
}
_ensureDom() {
if (this._host) return;
const host = document.createElement("div");
host.id = HOST_ID;
host.setAttribute("data-chrome-list-locator", "host");
// Attach at documentElement so it works even if body is missing briefly
(document.documentElement || document.body).appendChild(host);
const shadow = host.attachShadow({ mode: "closed" });
const style = document.createElement("style");
style.textContent = PANEL_CSS;
const panel = document.createElement("div");
panel.className = "panel";
panel.setAttribute("role", "listbox");
panel.setAttribute("aria-label", "Searchable select options");
const searchWrap = document.createElement("div");
searchWrap.className = "search-wrap";
const searchField = document.createElement("div");
searchField.className = "search-field";
const iconWrap = document.createElement("div");
iconWrap.className = "search-icon";
iconWrap.setAttribute("aria-hidden", "true");
iconWrap.appendChild(createMagnifierSvg());
const search = document.createElement("input");
search.className = "search";
search.type = "search";
search.placeholder = "Type to filter…";
search.setAttribute("autocomplete", "off");
search.setAttribute("spellcheck", "false");
search.setAttribute("aria-label", "Filter options");
searchField.appendChild(iconWrap);
searchField.appendChild(search);
searchWrap.appendChild(searchField);
const list = document.createElement("div");
list.className = "list";
list.setAttribute("role", "presentation");
const footer = document.createElement("div");
footer.className = "footer";
footer.textContent = "↑↓ navigate · Enter select · Esc close";
panel.appendChild(searchWrap);
panel.appendChild(list);
panel.appendChild(footer);
shadow.appendChild(style);
shadow.appendChild(panel);
search.addEventListener("input", this._onSearchInput);
// Prevent page shortcuts while typing
search.addEventListener("keydown", (e) => e.stopPropagation());
this._host = host;
this._shadow = shadow;
this._panel = panel;
this._search = search;
this._list = list;
this._footer = footer;
}
_onSearchInput() {
this._applyFilter(this._search.value);
}
/**
* @param {string} query
*/
_applyFilter(query) {
const fuzzy = global.ListLocatorFuzzy;
this._visible = fuzzy
? fuzzy.fuzzyFilter(query, this._allOptions, (o) => o.label)
: this._allOptions.filter((o) =>
o.label.toLocaleLowerCase().includes(String(query).toLocaleLowerCase())
);
// Prefer currently selected option as default highlight when unfiltered
let active = 0;
if (!String(query || "").trim()) {
const selIdx = this._visible.findIndex((o) => o.selected);
if (selIdx >= 0) active = selIdx;
}
this._activeIndex = this._visible.length ? Math.min(active, this._visible.length - 1) : 0;
this._renderList();
this._updateFooter();
}
_renderList() {
const list = this._list;
list.textContent = "";
if (!this._visible.length) {
const empty = document.createElement("div");
empty.className = "empty";
empty.textContent = "No matches";
list.appendChild(empty);
return;
}
let lastGroup = undefined;
this._visible.forEach((opt, i) => {
const group = opt.group == null ? null : opt.group;
if (group !== lastGroup && group != null && group !== "") {
const label = document.createElement("div");
label.className = "group-label";
label.textContent = group;
list.appendChild(label);
}
lastGroup = group;
const btn = document.createElement("button");
btn.type = "button";
btn.className = "option";
if (i === this._activeIndex) btn.classList.add("is-active");
if (opt.selected) btn.classList.add("is-selected");
btn.setAttribute("role", "option");
btn.setAttribute("aria-selected", i === this._activeIndex ? "true" : "false");
btn.dataset.index = String(i);
btn.textContent = opt.label;
btn.title = opt.label;
btn.addEventListener("mouseenter", () => {
this._setActive(i, false);
});
btn.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
this._choose(i);
});
list.appendChild(btn);
});
this._scrollActiveIntoView();
}
_updateFooter() {
const n = this._visible.length;
const total = this._allOptions.length;
this._footer.textContent =
(n === total ? `${n} options` : `${n} of ${total}`) +
" · ↑↓ navigate · Enter select · Esc close";
}
/**
* @param {number} index
* @param {boolean} scroll
*/
_setActive(index, scroll) {
if (!this._visible.length) return;
const next = Math.max(0, Math.min(index, this._visible.length - 1));
if (next === this._activeIndex) {
if (scroll) this._scrollActiveIntoView();
return;
}
this._activeIndex = next;
const buttons = this._list.querySelectorAll(".option");
buttons.forEach((el, i) => {
const active = i === next;
el.classList.toggle("is-active", active);
el.setAttribute("aria-selected", active ? "true" : "false");
});
if (scroll) this._scrollActiveIntoView();
}
_scrollActiveIntoView() {
const el = this._list.querySelector(`.option[data-index="${this._activeIndex}"]`);
if (el && typeof el.scrollIntoView === "function") {
el.scrollIntoView({ block: "nearest" });
}
}
/**
* @param {number} index
*/
_choose(index) {
const opt = this._visible[index];
if (!opt || !this._onSelect) return;
const handler = this._onSelect;
this.close({ silent: true });
handler(opt);
}
_position() {
if (!this._select || !this._panel) return;
const rect = this._select.getBoundingClientRect();
const panel = this._panel;
const margin = 8;
const gap = 4;
// Measure with temporary visibility
panel.style.visibility = "hidden";
panel.style.left = "0px";
panel.style.top = "0px";
panel.style.minWidth = Math.max(rect.width, 220) + "px";
const pw = panel.offsetWidth || 280;
const ph = panel.offsetHeight || 280;
let left = rect.left;
let top = rect.bottom + gap;
// Flip above if not enough room below
if (top + ph + margin > window.innerHeight && rect.top - gap - ph > margin) {
top = rect.top - gap - ph;
}
// Clamp vertical
top = Math.max(margin, Math.min(top, window.innerHeight - ph - margin));
// Clamp horizontal
left = Math.max(margin, Math.min(left, window.innerWidth - pw - margin));
panel.style.left = Math.round(left) + "px";
panel.style.top = Math.round(top) + "px";
panel.style.visibility = "visible";
}
_onScrollOrResize() {
if (!this._open) return;
// Close if select is no longer in the document / not visible
if (!this._select || !this._select.isConnected) {
this.close();
return;
}
this._position();
}
/**
* @param {PointerEvent} e
*/
_onDocPointerDown(e) {
if (!this._open) return;
const path = typeof e.composedPath === "function" ? e.composedPath() : [];
if (path.includes(this._host) || path.includes(this._panel)) return;
if (this._select && (e.target === this._select || this._select.contains(/** @type {Node} */ (e.target)))) {
// Click on the same select: keep open / re-focus search
e.preventDefault();
try {
this._search.focus({ preventScroll: true });
} catch (_) {
/* ignore */
}
return;
}
this.close();
}
/**
* @param {KeyboardEvent} e
*/
_onKeyDown(e) {
if (!this._open) return;
const key = e.key;
if (key === "Escape") {
e.preventDefault();
e.stopPropagation();
this.close();
return;
}
if (key === "ArrowDown") {
e.preventDefault();
e.stopPropagation();
this._setActive(this._activeIndex + 1, true);
return;
}
if (key === "ArrowUp") {
e.preventDefault();
e.stopPropagation();
this._setActive(this._activeIndex - 1, true);
return;
}
if (key === "Enter") {
e.preventDefault();
e.stopPropagation();
if (this._visible.length) this._choose(this._activeIndex);
return;
}
if (key === "Tab") {
// Close and allow natural tab navigation
this.close();
}
}
}
global.ListLocatorOverlay = SelectOverlay;
})(typeof globalThis !== "undefined" ? globalThis : window);
/* ---- content/settings-schema.js ---- */
/**
* Shared settings defaults and normalization (channel-agnostic).
* Safe for extension popup, content scripts, and userscript builds.
*/
(function (global) {
"use strict";
const DEFAULT_SETTINGS = {
enabled: true,
threshold: 10,
disabledHosts: [],
};
function normalizeHosts(list) {
return Array.isArray(list)
? list
.filter((h) => typeof h === "string")
.map((h) => h.trim().toLowerCase())
.filter(Boolean)
: [];
}
function normalizeSettings(input) {
const src = input || {};
const threshold =
typeof src.threshold === "number" && src.threshold >= 1
? Math.min(9999, Math.floor(src.threshold))
: DEFAULT_SETTINGS.threshold;
return {
enabled: src.enabled !== false,
threshold,
disabledHosts: normalizeHosts(src.disabledHosts),
};
}
function mergeSettings(current, partial) {
return normalizeSettings(Object.assign({}, current || {}, partial || {}));
}
global.ListLocatorSettingsSchema = {
DEFAULT_SETTINGS,
normalizeHosts,
normalizeSettings,
mergeSettings,
};
})(typeof globalThis !== "undefined" ? globalThis : window);
/* ---- content/settings-store-gm.js ---- */
/**
* Tampermonkey / Greasemonkey storage settings backend (userscript only).
*/
(function (global) {
"use strict";
const SETTINGS_KEY = "cll.settings.v1";
const LAST_HOST_KEY = "cll.lastHost.v1";
/**
* @param {{ getValue?: Function, setValue?: Function, addValueChangeListener?: Function }} [gm]
*/
function createGmStore(gm) {
const schema = global.ListLocatorSettingsSchema;
if (!schema) throw new Error("ListLocatorSettingsSchema is required");
const api = gm || {
getValue: global.GM_getValue,
setValue: global.GM_setValue,
addValueChangeListener: global.GM_addValueChangeListener,
};
if (typeof api.getValue !== "function" || typeof api.setValue !== "function") {
throw new Error("GM_getValue/GM_setValue are unavailable");
}
/** @type {Set<Function>} */
const listeners = new Set();
async function get() {
let raw;
try {
raw = await Promise.resolve(api.getValue(SETTINGS_KEY, schema.DEFAULT_SETTINGS));
} catch (_) {
raw = schema.DEFAULT_SETTINGS;
}
return schema.normalizeSettings(raw);
}
async function set(partial) {
const current = await get();
const next = schema.mergeSettings(current, partial);
await Promise.resolve(api.setValue(SETTINGS_KEY, next));
// Local emit: some managers only notify remote tabs
emit(next, Object.keys(partial || {}), "local");
return next;
}
function emit(settings, changedKeys, source) {
for (const fn of Array.from(listeners)) {
try {
fn({ settings, changedKeys, source });
} catch (_) {
/* ignore */
}
}
}
function subscribe(fn) {
listeners.add(fn);
let id = null;
if (typeof api.addValueChangeListener === "function") {
try {
id = api.addValueChangeListener(
SETTINGS_KEY,
(_name, _oldValue, newValue, remote) => {
emit(
schema.normalizeSettings(newValue),
["enabled", "threshold", "disabledHosts"],
remote ? "userscript" : "local"
);
}
);
} catch (_) {
id = null;
}
}
return () => {
listeners.delete(fn);
if (
id != null &&
typeof global.GM_removeValueChangeListener === "function"
) {
try {
global.GM_removeValueChangeListener(id);
} catch (_) {
/* ignore */
}
}
};
}
async function getLastHost() {
let raw = "";
try {
raw = await Promise.resolve(api.getValue(LAST_HOST_KEY, ""));
} catch (_) {
raw = "";
}
return typeof raw === "string" ? raw.trim().toLowerCase() : "";
}
async function setLastHost(host) {
try {
await Promise.resolve(api.setValue(LAST_HOST_KEY, host || ""));
} catch (_) {
/* ignore */
}
}
return { get, set, subscribe, getLastHost, setLastHost };
}
global.ListLocatorSettingsStoreGm = { createGmStore };
})(typeof globalThis !== "undefined" ? globalThis : window);
/* ---- content/content-core.js ---- */
/**
* Chrome List Locator — shared content core (channel-agnostic).
* Channel-agnostic: no browser storage APIs; settings come from an injected store.
*/
(function (global) {
"use strict";
const ENHANCED_ATTR = "data-cll-enhanced";
const BOUND_FLAG = "__cllBound";
function hostname() {
try {
return location.hostname || "";
} catch (_) {
return "";
}
}
function claimInstallGuard(channel) {
try {
const root = document.documentElement;
if (!root) return true;
const existing = root.getAttribute("data-cll-runtime");
if (existing) return false;
root.setAttribute("data-cll-runtime", channel || "unknown");
return true;
} catch (_) {
return true;
}
}
class ContentCoreApp {
/**
* @param {{ get: Function, set?: Function, subscribe: Function, getLastHost?: Function, setLastHost: Function }} store
* @param {string} channel
*/
constructor(store, channel) {
this.store = store;
this.channel = channel || "unknown";
const schema = global.ListLocatorSettingsSchema;
this.settings = schema
? schema.normalizeSettings(schema.DEFAULT_SETTINGS)
: { enabled: true, threshold: 10, disabledHosts: [] };
/** @type {InstanceType<typeof global.ListLocatorOverlay>|null} */
this.overlay = null;
/** @type {{ refreshSettings: () => void, destroyActiveSessions?: () => void }|null} */
this.ariaListboxController = null;
/** @type {WeakSet<HTMLSelectElement>} */
this.tracked = new WeakSet();
/** @type {WeakMap<HTMLSelectElement, () => void>} */
this.cleanups = new WeakMap();
this.unsubscribe = null;
this.selectObserver = null;
this.periodicTimer = null;
this.started = false;
this.moScheduled = false;
/** @type {Set<Node>} */
this.pendingRoots = new Set();
this.isGloballyActive = this.isGloballyActive.bind(this);
this.refreshAll = this.refreshAll.bind(this);
}
isHostDisabled() {
const host = hostname();
if (!host) return false;
return this.settings.disabledHosts.some(
(h) => h === host || (h && host.endsWith("." + h))
);
}
isGloballyActive() {
return this.settings.enabled !== false && !this.isHostDisabled();
}
async start() {
if (this.started) return this;
if (!claimInstallGuard(this.channel)) return this;
// JS-world guard (same isolated world double-inject)
if (global.__chromeListLocatorInstalled) return this;
global.__chromeListLocatorInstalled = true;
try {
this.settings = await this.store.get();
} catch (_) {
/* keep defaults */
}
const host = hostname();
if (host) {
try {
await this.store.setLastHost(host);
} catch (_) {
/* ignore */
}
}
this.scanRoot(document);
this.selectObserver = this.observe(document.documentElement || document);
this.startAriaController();
this.unsubscribe = this.store.subscribe(({ settings }) => {
if (settings) this.settings = settings;
this.refreshAll();
});
this.installPeriodicScan();
this.installPagehideCleanup();
this.started = true;
return this;
}
startAriaController() {
if (global.ListLocatorAriaListbox) {
this.ariaListboxController = global.ListLocatorAriaListbox.init({
getSettings: () => this.settings,
isGloballyActive: this.isGloballyActive,
getThreshold: () => this.settings.threshold,
});
}
}
refreshAll() {
if (this.overlay && this.overlay.isOpen && !this.isGloballyActive()) {
this.overlay.close({ silent: true });
}
this.scanRoot(document);
if (this.ariaListboxController) this.ariaListboxController.refreshSettings();
}
destroy() {
if (this.unsubscribe) {
try {
this.unsubscribe();
} catch (_) {
/* ignore */
}
this.unsubscribe = null;
}
if (this.periodicTimer) {
clearInterval(this.periodicTimer);
this.periodicTimer = null;
}
if (this.selectObserver && this.selectObserver.disconnect) {
try {
this.selectObserver.disconnect();
} catch (_) {
/* ignore */
}
this.selectObserver = null;
}
if (this.overlay) {
try {
this.overlay.destroy();
} catch (_) {
/* ignore */
}
this.overlay = null;
}
if (
this.ariaListboxController &&
this.ariaListboxController.destroyActiveSessions
) {
try {
this.ariaListboxController.destroyActiveSessions();
} catch (_) {
/* ignore */
}
}
this.started = false;
}
// -------------------------------------------------------------------------
// Select helpers
// -------------------------------------------------------------------------
isEligibleSelect(el) {
if (!el || el.tagName !== "SELECT") return false;
if (el.multiple) return false;
if (el.disabled) return false;
if (el.size && el.size > 1) return false;
return true;
}
countOptions(select) {
return select.options ? select.options.length : 0;
}
shouldEnhance(select) {
if (!this.isGloballyActive()) return false;
if (!this.isEligibleSelect(select)) return false;
return this.countOptions(select) >= this.settings.threshold;
}
flattenOptions(select) {
/** @type {Array<{ value: string, label: string, disabled: boolean, group: string|null, selected: boolean, optionIndex: number }>} */
const out = [];
const children = select.children;
let optionIndex = 0;
function isOptionDisabled(opt) {
try {
if (typeof opt.matches === "function") return opt.matches(":disabled");
} catch (_) {
/* ignore */
}
return !!(
opt.disabled ||
(opt.parentElement && opt.parentElement.disabled)
);
}
function pushOption(opt, group) {
out.push({
value: opt.value,
label: (opt.label || opt.textContent || opt.value || "").trim(),
disabled: isOptionDisabled(opt),
group: group,
selected: !!opt.selected,
optionIndex: optionIndex++,
});
}
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child.tagName === "OPTGROUP") {
const groupLabel = child.label || "";
const opts = child.children;
for (let j = 0; j < opts.length; j++) {
if (opts[j].tagName === "OPTION") {
pushOption(/** @type {HTMLOptionElement} */ (opts[j]), groupLabel);
}
}
} else if (child.tagName === "OPTION") {
pushOption(/** @type {HTMLOptionElement} */ (child), null);
}
}
if (!out.length && select.options) {
for (let i = 0; i < select.options.length; i++) {
const opt = select.options[i];
out.push({
value: opt.value,
label: (opt.label || opt.text || opt.value || "").trim(),
disabled: isOptionDisabled(opt),
group:
opt.parentElement && opt.parentElement.tagName === "OPTGROUP"
? opt.parentElement.label || null
: null,
selected: !!opt.selected,
optionIndex: i,
});
}
}
return out;
}
setSelectValue(select, value) {
const proto =
window.HTMLSelectElement && window.HTMLSelectElement.prototype;
const descriptor = proto
? Object.getOwnPropertyDescriptor(proto, "value")
: null;
const setter = descriptor && descriptor.set;
const prev = select.value;
if (setter) {
setter.call(select, value);
} else {
select.value = value;
}
if (select.value !== value) {
const opts = select.options;
for (let i = 0; i < opts.length; i++) {
if (opts[i].value === value) {
select.selectedIndex = i;
break;
}
}
}
try {
select.dispatchEvent(new Event("input", { bubbles: true }));
} catch (_) {
/* ignore */
}
try {
select.dispatchEvent(new Event("change", { bubbles: true }));
} catch (_) {
/* ignore */
}
return prev !== select.value;
}
getOverlay() {
if (!this.overlay) {
const OverlayCtor = global.ListLocatorOverlay;
if (!OverlayCtor) return null;
this.overlay = new OverlayCtor();
}
return this.overlay;
}
openForSelect(select) {
if (!this.shouldEnhance(select)) return;
const ov = this.getOverlay();
if (!ov) return;
if (ov.isOpen && ov.activeSelect === select) return;
const options = this.flattenOptions(select);
const self = this;
ov.open(select, options, {
onSelect: (opt) => {
self.setSelectValue(select, opt.value);
try {
select.focus({ preventScroll: true });
} catch (_) {
/* ignore */
}
},
onClose: () => {
try {
if (select.isConnected) select.focus({ preventScroll: true });
} catch (_) {
/* ignore */
}
},
});
}
bindSelect(select) {
if (select[BOUND_FLAG]) return;
select[BOUND_FLAG] = true;
this.tracked.add(select);
const self = this;
const onPointerDown = (e) => {
if (!self.shouldEnhance(select)) return;
if (e.button != null && e.button !== 0) return;
e.preventDefault();
e.stopPropagation();
self.openForSelect(select);
};
const onFocus = () => {
/* no auto-open on pure focus */
};
const onKeyDown = (e) => {
if (!self.shouldEnhance(select)) return;
const opens =
e.key === " " ||
e.key === "Spacebar" ||
e.key === "Enter" ||
e.key === "ArrowDown" ||
e.key === "ArrowUp" ||
e.key === "F4";
if (!opens) return;
e.preventDefault();
e.stopPropagation();
self.openForSelect(select);
};
select.addEventListener("mousedown", onPointerDown, true);
select.addEventListener("keydown", onKeyDown, true);
select.addEventListener("focus", onFocus, true);
select.setAttribute(ENHANCED_ATTR, "1");
this.cleanups.set(select, () => {
select.removeEventListener("mousedown", onPointerDown, true);
select.removeEventListener("keydown", onKeyDown, true);
select.removeEventListener("focus", onFocus, true);
select.removeAttribute(ENHANCED_ATTR);
try {
delete select[BOUND_FLAG];
} catch (_) {
select[BOUND_FLAG] = false;
}
});
}
unbindSelect(select) {
const cleanup = this.cleanups.get(select);
if (cleanup) {
cleanup();
this.cleanups.delete(select);
}
if (
this.overlay &&
this.overlay.isOpen &&
this.overlay.activeSelect === select
) {
this.overlay.close({ silent: true });
}
}
syncSelect(select) {
if (!this.isEligibleSelect(select)) {
this.unbindSelect(select);
return;
}
if (this.shouldEnhance(select)) {
this.bindSelect(select);
select.setAttribute(ENHANCED_ATTR, "1");
} else {
if (select[BOUND_FLAG]) {
select.removeAttribute(ENHANCED_ATTR);
} else {
this.bindSelect(select);
if (!this.shouldEnhance(select)) select.removeAttribute(ENHANCED_ATTR);
}
}
}
walkSelects(root, visit) {
if (!root) return;
if (root.nodeType === 1) {
const el = /** @type {Element} */ (root);
if (el.tagName === "SELECT") {
visit(/** @type {HTMLSelectElement} */ (el));
}
if (el.shadowRoot) {
this.walkSelects(el.shadowRoot, visit);
}
}
const docOrEl =
root.nodeType === 9 || root.nodeType === 11 || root.nodeType === 1
? root
: null;
if (!docOrEl) return;
/** @type {NodeListOf<HTMLSelectElement>|HTMLSelectElement[]} */
let selects = [];
try {
if (typeof docOrEl.querySelectorAll === "function") {
selects = docOrEl.querySelectorAll("select");
}
} catch (_) {
selects = [];
}
for (let i = 0; i < selects.length; i++) {
visit(selects[i]);
}
try {
const all =
typeof docOrEl.querySelectorAll === "function"
? docOrEl.querySelectorAll("*")
: [];
for (let i = 0; i < all.length; i++) {
const node = all[i];
if (node.shadowRoot) {
this.walkSelects(node.shadowRoot, visit);
}
}
} catch (_) {
/* ignore */
}
}
scanRoot(root) {
this.walkSelects(root || document, (select) => {
this.syncSelect(select);
});
}
scheduleScan(root) {
this.pendingRoots.add(root || document);
if (this.moScheduled) return;
this.moScheduled = true;
requestAnimationFrame(() => {
this.moScheduled = false;
const roots = Array.from(this.pendingRoots);
this.pendingRoots.clear();
for (const r of roots) {
try {
this.scanRoot(r);
} catch (_) {
/* ignore */
}
}
});
}
observe(root) {
if (!root || typeof MutationObserver === "undefined") return null;
const self = this;
const observer = new MutationObserver((mutations) => {
let needFull = false;
for (const m of mutations) {
if (m.type === "childList") {
if (m.addedNodes && m.addedNodes.length) needFull = true;
if (m.target && m.target.tagName === "SELECT") {
self.syncSelect(/** @type {HTMLSelectElement} */ (m.target));
}
if (m.target && m.target.tagName === "OPTGROUP") {
const sel = m.target.closest && m.target.closest("select");
if (sel) self.syncSelect(/** @type {HTMLSelectElement} */ (sel));
}
} else if (
m.type === "attributes" &&
m.target &&
m.target.tagName === "SELECT"
) {
self.syncSelect(/** @type {HTMLSelectElement} */ (m.target));
}
}
if (needFull) self.scheduleScan(root);
});
try {
observer.observe(root, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ["disabled", "multiple", "size"],
});
} catch (_) {
/* ignore */
}
return observer;
}
installPeriodicScan() {
let ticks = 0;
this.periodicTimer = setInterval(() => {
ticks += 1;
if (ticks > 20) {
if (ticks % 5 !== 0) return;
}
if (document.hidden) return;
this.scanRoot(document);
}, 2000);
}
installPagehideCleanup() {
const self = this;
if (typeof window.addEventListener !== "function") return;
window.addEventListener(
"pagehide",
() => {
self.destroy();
},
{ once: true }
);
}
}
global.ListLocatorCore = {
/**
* @param {{ store: object, channel: string }} opts
*/
async start(opts) {
const store = opts && opts.store;
const channel = (opts && opts.channel) || "unknown";
if (!store) throw new Error("ListLocatorCore.start requires store");
const app = new ContentCoreApp(store, channel);
await app.start();
return app;
},
};
})(typeof globalThis !== "undefined" ? globalThis : window);
/* ---- content/userscript-menu.js ---- */
/**
* Tampermonkey menu commands for settings (userscript only).
*/
(function (global) {
"use strict";
function hostname() {
try {
return (location.hostname || "").toLowerCase();
} catch (_) {
return "";
}
}
function formatSettings(settings, host) {
const disabled = host
? settings.disabledHosts.indexOf(host) !== -1
: false;
return [
"Chrome List Locator (userscript)",
"enabled: " + (settings.enabled ? "yes" : "no"),
"threshold: " + settings.threshold,
"host: " + (host || "(none)"),
"host disabled: " + (disabled ? "yes" : "no"),
"disabledHosts: " +
(settings.disabledHosts.length
? settings.disabledHosts.join(", ")
: "(none)"),
].join("\n");
}
async function toggleHost(store, host, disable) {
if (!host) return;
const settings = await store.get();
const schema = global.ListLocatorSettingsSchema;
let hosts = schema
? schema.normalizeHosts(settings.disabledHosts)
: settings.disabledHosts.slice();
if (disable) {
if (hosts.indexOf(host) === -1) hosts.push(host);
} else {
hosts = hosts.filter((h) => h !== host);
}
await store.set({ disabledHosts: hosts });
}
/**
* @param {{ store: object, app?: object }} opts
*/
function install(opts) {
const store = opts && opts.store;
if (!store) return;
const registerMenu =
typeof global.GM_registerMenuCommand === "function"
? global.GM_registerMenuCommand.bind(global)
: null;
const unregisterMenu =
typeof global.GM_unregisterMenuCommand === "function"
? global.GM_unregisterMenuCommand.bind(global)
: null;
if (!registerMenu) return;
/** @type {Array<string|number>} */
let commandIds = [];
function unregisterOldCommands() {
if (!unregisterMenu) {
commandIds = [];
return;
}
for (const id of commandIds) {
try {
unregisterMenu(id);
} catch (_) {
/* ignore */
}
}
commandIds = [];
}
async function register() {
unregisterOldCommands();
const settings = await store.get();
const host = hostname();
const disabled = host
? settings.disabledHosts.indexOf(host) !== -1
: false;
commandIds.push(
registerMenu(
settings.enabled
? "Chrome List Locator: Disable globally"
: "Chrome List Locator: Enable globally",
async () => {
await store.set({ enabled: !settings.enabled });
}
)
);
if (host) {
commandIds.push(
registerMenu(
disabled
? "Enable on this site: " + host
: "Disable on this site: " + host,
async () => {
await toggleHost(store, host, !disabled);
}
)
);
}
commandIds.push(
registerMenu("Set option threshold...", async () => {
const input = prompt(
"Minimum options before enhancement",
String(settings.threshold)
);
if (input == null) return;
let n = parseInt(input, 10);
if (!Number.isFinite(n) || n < 1) n = 10;
n = Math.max(1, Math.min(9999, n));
await store.set({ threshold: n });
})
);
commandIds.push(
registerMenu("Show current settings", async () => {
const latest = await store.get();
alert(formatSettings(latest, host));
})
);
}
store.subscribe(() => {
register();
});
register();
}
global.ListLocatorUserscriptMenu = { install };
})(typeof globalThis !== "undefined" ? globalThis : window);
/* ---- content/userscript-bootstrap.js ---- */
/**
* Userscript-only bootstrap: GM settings store + menu + shared core.
*/
(async function (global) {
"use strict";
if (global.__chromeListLocatorBootstrapStarted) return;
global.__chromeListLocatorBootstrapStarted = true;
try {
if (!global.ListLocatorSettingsStoreGm || !global.ListLocatorCore) {
return;
}
const store = global.ListLocatorSettingsStoreGm.createGmStore();
const app = await global.ListLocatorCore.start({
store,
channel: "userscript",
});
if (global.ListLocatorUserscriptMenu) {
global.ListLocatorUserscriptMenu.install({ store, app });
}
} catch (err) {
try {
console.error("[Chrome List Locator] userscript bootstrap failed", err);
} catch (_) {
/* ignore */
}
}
})(typeof globalThis !== "undefined" ? globalThis : window);