PageSourceSearch

https://papajoes-portal.web.app/insight-cascade.js

js papajoes-portal.web.app collected 2026-10-03 08:20:14 UTC 3,274 bytes, 22 lines download raw bytes

1import {widget,recipeFor} from './insight-model.js';
2export function cascadeScope(scope,axis,value){
3 const next=structuredClone(scope);next[axis]=value;next.products=[];next.productLabels=[];next.focus=[];next.focusLabels=[];
4 if(axis==='locations')next.departments=[];
5 return next;
6}
7export function facetRecipe(scope,group,search=''){
8 const parent={...scope,products:[],productLabels:[],focus:[],focusLabels:[],search};
9 if(group==='department')parent.departments=[];
10 return recipeFor(widget({group}),parent,0,100);
11}
12export function productPicker(host,{h,scope,query,onChange}){
13 let serial=0,timer;const selected=new Map((scope.products||[]).map((key,i)=>[key,scope.productLabels?.[i]||key]));
14 host.innerHTML='<details class="ix-multi" name="insights-filter-menu"><summary></summary><div class="ix-multi-menu"><input type="search" aria-label="Search available products" placeholder="Name or item code"><div data-picked></div><div data-results></div><small data-status></small><button class="btn" data-clear-products>Clear selection</button><button class="btn primary" data-apply-products>Apply products</button></div></details>';
15 const summary=host.querySelector('summary'),status=host.querySelector('[data-status]'),results=host.querySelector('[data-results]');
16 const label=()=>{summary.textContent=selected.size?`Products · ${selected.size} selected`:'Products · All';const picked=host.querySelector('[data-picked]');picked.innerHTML=[...selected].map(([key,name],i)=>`<button class="ix-chip ix-filter-chip" data-unpick="${i}" aria-label="Remove ${h(name)}">${h(name)} ×</button>`).join('');picked.querySelectorAll('[data-unpick]').forEach(b=>b.onclick=()=>{selected.delete([...selected.keys()][Number(b.dataset.unpick)]);label();load();});};label();
17 async function load(){const turn=++serial;status.textContent='Finding matching products…';results.innerHTML='';try{const data=await query(facetRecipe(scope,'item',host.querySelector('input').value.trim()));if(turn!==serial||!host.isConnected)return;results.innerHTML=data.rows.map(r=>{const key=JSON.parse(r.bucket)[0];return `<label><input type="checkbox" value="${h(key)}" data-name="${h(r.item)}" ${selected.has(key)?'checked':''}>${h(r.item)}</label>`;}).join('');if(!data.rows.length)results.innerHTML='<p>No products match. Try another name or broaden the department selection.</p>';status.textContent=`${data.rows.length} of ${data.groups.toLocaleString()} matching products. Search to narrow.`;results.querySelectorAll('input').forEach(input=>input.onchange=()=>{if(input.checked&&selected.size>=100){input.checked=false;status.textContent='Select up to 100 products, or use a department filter.';return;}input.checked?selected.set(input.value,input.dataset.name):selected.delete(input.value);label();});}catch(e){if(turn===serial)status.textContent=e.message;}}
18 host.querySelector('details').ontoggle=e=>{if(e.target.open)load();};
19 host.querySelector('input').oninput=()=>{serial++;clearTimeout(timer);timer=setTimeout(load,300);};
20 host.querySelector('[data-clear-products]').onclick=()=>{selected.clear();label();load();};
21 host.querySelector('[data-apply-products]').onclick=()=>{serial++;clearTimeout(timer);onChange([...selected.keys()],[...selected.values()]);};
22}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.