@toclocoinc/lattice-grid 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.10.0 — core + DOM renderer
2
+ * Lattice Grid 1.11.0 — core + DOM renderer
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -12819,7 +12819,10 @@ this.invalidate('filter');
12819
12819
  }else{
12820
12820
  this.invalidate('total');
12821
12821
  }
12822
- this.#emit('rows:changed',{added:result.added.length,updated:result.updated.length,removed:result.removed.length});
12822
+ this.#emit('rows:changed',{
12823
+ companion:true,
12824
+ added:result.added.length,updated:result.updated.length,removed:result.removed.length,
12825
+ });
12823
12826
  return result;
12824
12827
  }
12825
12828
  groupHeadingsAt(index){
@@ -12856,7 +12859,9 @@ this.#dropTotalState();
12856
12859
  this.#handles.invalidate();
12857
12860
  this.#view=null;
12858
12861
  this.invalidate('filter');
12859
- this.#emit('rows:changed',{added:0,updated:0,removed:0,moved:1});
12862
+ this.#emit('rows:changed',{
12863
+ companion:true,added:0,updated:0,removed:0,moved:1,
12864
+ });
12860
12865
  return true;
12861
12866
  }
12862
12867
  reload(_opts){
@@ -13052,6 +13057,15 @@ return out;
13052
13057
  facetIndices(colId){
13053
13058
  return this.#runFilter(colId||null);
13054
13059
  }
13060
+ facetRows(colId){
13061
+ const indices=this.facetIndices(colId);
13062
+ const out=[];
13063
+ for(let i=0;i<indices.length;i++){
13064
+ const row=this.#leafRow(indices[i]);
13065
+ if(row)out.push(row);
13066
+ }
13067
+ return out;
13068
+ }
13055
13069
  invalidateRows(){
13056
13070
  this.#rowCache.clear();
13057
13071
  this.#quickText.clear();
@@ -15060,7 +15074,14 @@ Object.defineProperty(__exports,"bucketOf",{enumerable:true,get:function(){retur
15060
15074
  Object.defineProperty(__exports,"reduceValues",{enumerable:true,get:function(){return reduceValues;}});
15061
15075
  Object.defineProperty(__exports,"applyLimit",{enumerable:true,get:function(){return applyLimit;}});
15062
15076
  Object.defineProperty(__exports,"applyCumulative",{enumerable:true,get:function(){return applyCumulative;}});
15077
+ Object.defineProperty(__exports,"groupRows",{enumerable:true,get:function(){return groupRows;}});
15078
+ Object.defineProperty(__exports,"finishGroups",{enumerable:true,get:function(){return finishGroups;}});
15063
15079
  Object.defineProperty(__exports,"derive",{enumerable:true,get:function(){return derive;}});
15080
+ Object.defineProperty(__exports,"joinRows",{enumerable:true,get:function(){return joinRows;}});
15081
+ Object.defineProperty(__exports,"preGroup",{enumerable:true,get:function(){return preGroup;}});
15082
+ Object.defineProperty(__exports,"buildJoinIndex",{enumerable:true,get:function(){return buildJoinIndex;}});
15083
+ Object.defineProperty(__exports,"joinOne",{enumerable:true,get:function(){return joinOne;}});
15084
+ Object.defineProperty(__exports,"joinKeyOf",{enumerable:true,get:function(){return joinKeyOf;}});
15064
15085
  const __m0=__req("packages/core/src/internal/util.js");
15065
15086
  const isFunction=__m0["isFunction"];
15066
15087
  const isObject=__m0["isObject"];
@@ -15186,10 +15207,45 @@ return(less?-1:1)*(entry.dir==='desc'?-1:1);
15186
15207
  return 0;
15187
15208
  });
15188
15209
  }
15210
+ function groupRows(rows,spec,keyOf){
15211
+ const by=spec.groupBy
15212
+ ?(Array.isArray(spec.groupBy)?spec.groupBy:[spec.groupBy])
15213
+ :null;
15214
+ const bucket=isObject(spec.bucket)?spec.bucket:null;
15215
+ const groups=new Map();
15216
+ const rowGroup=new Map();
15217
+ if(!by)return{groups,rowGroup};
15218
+ for(const row of rows){
15219
+ const{key,values}=groupKeyOf(row,by,bucket);
15220
+ let group=groups.get(key);
15221
+ if(!group){group={values,rows:new Map()};groups.set(key,group);}
15222
+ const rowKey=keyOf(row);
15223
+ group.rows.set(rowKey,row);
15224
+ rowGroup.set(rowKey,key);
15225
+ }
15226
+ return{groups,rowGroup};
15227
+ }
15228
+ function finishGroups(groups,spec,cache=null,dirty=null){
15229
+ const by=Array.isArray(spec.groupBy)?spec.groupBy:[spec.groupBy];
15230
+ let out=[...groups.entries()].map(([key,group])=>{
15231
+ if(cache&&dirty&&!dirty.has(key)){
15232
+ const kept=cache.get(key);
15233
+ if(kept)return kept;
15234
+ }
15235
+ const row={[DERIVED_ROW_KEY]:key};
15236
+ by.forEach((path,i)=>{
15237
+ row[path.includes('.')?path.split('.').pop():path]=group.values[i];
15238
+ });
15239
+ Object.assign(row,reduceGroup([...group.rows.values()],spec.select||{}));
15240
+ if(cache)cache.set(key,row);
15241
+ return row;
15242
+ });
15243
+ out=sortRows(out,spec.sort);
15244
+ if(isObject(spec.cumulative))out=applyCumulative(out,spec.cumulative);
15245
+ return applyLimit(out,spec.limit,spec.limitPer||null);
15246
+ }
15189
15247
  function derive(rows,spec){
15190
- let working=Array.isArray(rows)?rows:[];
15191
- if(spec.unnest)working=unnest(working,spec.unnest);
15192
- if(isFunction(spec.where))working=working.filter((row)=>!!spec.where(row));
15248
+ const working=preGroup(rows,spec);
15193
15249
  const by=spec.groupBy
15194
15250
  ?(Array.isArray(spec.groupBy)?spec.groupBy:[spec.groupBy])
15195
15251
  :null;
@@ -15219,6 +15275,73 @@ out=sortRows(out,spec.sort);
15219
15275
  if(isObject(spec.cumulative))out=applyCumulative(out,spec.cumulative);
15220
15276
  return applyLimit(out,spec.limit,spec.limitPer||null);
15221
15277
  }
15278
+ function joinRows(rows,spec){
15279
+ const on=spec.on;
15280
+ const leftKey=isObject(on)?String(on.left||on.right||''):String(on||'');
15281
+ const rightKey=isObject(on)?String(on.right||on.left||''):String(on||'');
15282
+ if(!leftKey||!rightKey)return(rows);
15283
+ const index=spec.index instanceof Map
15284
+ ?spec.index
15285
+ :buildJoinIndex(Array.isArray(spec.rows)?spec.rows:[],rightKey);
15286
+ const fields=Array.isArray(spec.select)?spec.select.map(String):null;
15287
+ const prefix=spec.prefix?String(spec.prefix):'';
15288
+ const keep=String(spec.type||'inner')==='left';
15289
+ const out=[];
15290
+ for(const row of rows){
15291
+ const value=readPath(row,leftKey);
15292
+ const match=value==null?undefined:index.get(String(value));
15293
+ if(!match){if(keep)out.push((row));continue;}
15294
+ const merged={...((row))};
15295
+ const names=fields||Object.keys(match);
15296
+ for(const name of names){
15297
+ if(!prefix&&name===leftKey)continue;
15298
+ merged[prefix?prefix+name:name]=readPath(match,name);
15299
+ }
15300
+ out.push(merged);
15301
+ }
15302
+ return out;
15303
+ }
15304
+ function preGroup(rows,spec){
15305
+ let working=Array.isArray(rows)?rows:[];
15306
+ if(spec.unnest)working=unnest(working,spec.unnest);
15307
+ if(isObject(spec.join)&&(Array.isArray(spec.join.rows)||spec.join.index instanceof Map)){
15308
+ working=joinRows(working,spec.join);
15309
+ }
15310
+ if(isFunction(spec.where))working=working.filter((row)=>!!spec.where(row));
15311
+ return working;
15312
+ }
15313
+ function buildJoinIndex(rows,rightKey){
15314
+ const index=new Map();
15315
+ for(const row of rows){
15316
+ const value=readPath(row,rightKey);
15317
+ if(value==null)continue;
15318
+ const key=String(value);
15319
+ if(!index.has(key))index.set(key,(row));
15320
+ }
15321
+ return index;
15322
+ }
15323
+ function joinOne(row,spec){
15324
+ const on=spec.on;
15325
+ const leftKey=isObject(on)?String(on.left||on.right||''):String(on||'');
15326
+ const rightKey=isObject(on)?String(on.right||on.left||''):String(on||'');
15327
+ if(!leftKey||!rightKey)return(row);
15328
+ const index=spec.index instanceof Map?spec.index:new Map();
15329
+ const value=readPath(row,leftKey);
15330
+ const match=value==null?undefined:index.get(String(value));
15331
+ if(!match)return String(spec.type||'inner')==='left'?(row):null;
15332
+ const fields=Array.isArray(spec.select)?spec.select.map(String):null;
15333
+ const prefix=spec.prefix?String(spec.prefix):'';
15334
+ const merged={...((row))};
15335
+ for(const name of fields||Object.keys(match)){
15336
+ if(!prefix&&name===leftKey)continue;
15337
+ merged[prefix?prefix+name:name]=readPath(match,name);
15338
+ }
15339
+ return merged;
15340
+ }
15341
+ function joinKeyOf(row,path){
15342
+ const value=readPath(row,path);
15343
+ return value==null?null:String(value);
15344
+ }
15222
15345
  });
15223
15346
  __def("packages/core/src/source/derived.js",function(__exports,__req){
15224
15347
  'use strict';
@@ -15234,6 +15357,13 @@ const __m1=__req("packages/core/src/source/memory.js");
15234
15357
  const MemorySource=__m1["MemorySource"];
15235
15358
  const __m2=__req("packages/core/src/source/derive.js");
15236
15359
  const derive=__m2["derive"];
15360
+ const preGroup=__m2["preGroup"];
15361
+ const groupRows=__m2["groupRows"];
15362
+ const finishGroups=__m2["finishGroups"];
15363
+ const readPath=__m2["readPath"];
15364
+ const buildJoinIndex=__m2["buildJoinIndex"];
15365
+ const joinOne=__m2["joinOne"];
15366
+ const joinKeyOf=__m2["joinKeyOf"];
15237
15367
  const FOLLOW_MODES=__m2["FOLLOW_MODES"];
15238
15368
  const DERIVED_ROW_KEY=__m2["DERIVED_ROW_KEY"];
15239
15369
  const IDLE_REFRESH_MS=16;
@@ -15249,6 +15379,11 @@ class DerivedSource extends MemorySource{
15249
15379
  #gone=false;
15250
15380
  #ctx={};
15251
15381
  #deriving=false;
15382
+ #index=null;
15383
+ #filterCols=null;
15384
+ #coalesced=false;
15385
+ #cross=[];
15386
+ #rightIndex=null;
15252
15387
  constructor(config,ctx){
15253
15388
  super({mode:'memory',rows:[]},ctx);
15254
15389
  this.#ctx=ctx||{};
@@ -15276,20 +15411,108 @@ if(follow==='filtered'||follow==='grouped')events.push('filter:changed');
15276
15411
  if(follow==='selected')events.push('selection:changed');
15277
15412
  if(follow==='grouped')events.push('column:grouped');
15278
15413
  for(const name of events){
15279
- const handler=()=>this.schedule();
15414
+ const handler=(payload)=>{
15415
+ if(name==='rows:changed'&&skipCompanion(payload))return;
15416
+ this.schedule(name==='rows:changed'?payload:null);
15417
+ };
15280
15418
  grid.on(name,handler);
15281
15419
  this.#off.push(()=>isFunction(grid.off)&&grid.off(name,handler));
15282
15420
  }
15421
+ this.#watchJoin();
15283
15422
  }
15284
- schedule(){
15423
+ #watchJoin(){
15424
+ const join=this.#spec.join;
15425
+ const partner=join&&join.with;
15426
+ if(!partner||!isFunction(partner.on))return;
15427
+ const follow=String(join.follow||'all');
15428
+ const events=follow==='all'?['rows:changed']:['rows:changed','filter:changed'];
15429
+ for(const name of events){
15430
+ const handler=(payload)=>{
15431
+ if(name==='rows:changed'&&skipCompanion(payload))return;
15432
+ this.schedule({partner:true});
15433
+ };
15434
+ partner.on(name,handler);
15435
+ this.#off.push(()=>isFunction(partner.off)&&partner.off(name,handler));
15436
+ }
15437
+ }
15438
+ #joinPaths(){
15439
+ const join=this.#spec.join;
15440
+ if(!join||!join.with)return null;
15441
+ const on=join.on;
15442
+ const left=isObject(on)?String(on.left||on.right||''):String(on||'');
15443
+ const right=isObject(on)?String(on.right||on.left||''):String(on||'');
15444
+ return left&&right?{left,right}:null;
15445
+ }
15446
+ #refreshRight(){
15447
+ const paths=this.#joinPaths();
15448
+ if(!paths){this.#rightIndex=null;return null;}
15449
+ const previous=this.#rightIndex;
15450
+ this.#rightIndex=buildJoinIndex(this.#joinRows(),paths.right);
15451
+ if(!previous)return null;
15452
+ const join=this.#spec.join||{};
15453
+ const fields=Array.isArray(join.select)?join.select.map(String):null;
15454
+ const signature=(row)=>{
15455
+ if(!row)return null;
15456
+ const names=fields||Object.keys(row);
15457
+ return names.map((name)=>String(readPath(row,name))).join('\u0001');
15458
+ };
15459
+ const affected=new Set();
15460
+ const gained=new Set();
15461
+ for(const[key,row]of this.#rightIndex){
15462
+ if(signature(row)===signature(previous.get(key)))continue;
15463
+ affected.add(key);
15464
+ if(!previous.has(key))gained.add(key);
15465
+ }
15466
+ for(const key of previous.keys())if(!this.#rightIndex.has(key))affected.add(key);
15467
+ return{affected,gained};
15468
+ }
15469
+ #patchPartner(keys,spec){
15470
+ const index=this.#index;
15471
+ if(!index||!index.leftByJoinKey)return null;
15472
+ const touched=[];
15473
+ for(const key of keys){
15474
+ const rows=index.leftByJoinKey.get(key);
15475
+ if(!rows)continue;
15476
+ for(const rowKey of rows){
15477
+ const data=index.rowData.get(rowKey);
15478
+ if(data!==undefined)touched.push({key:rowKey,data});
15479
+ }
15480
+ }
15481
+ return this.#applyPatch({updated:touched,added:[],removed:[]},spec);
15482
+ }
15483
+ #partnerPatchable(spec,gained){
15484
+ if(!this.#index||!this.#index.leftByJoinKey)return false;
15485
+ if(spec.unnest||spec.where||String(spec.follow||'filtered')==='grouped')return false;
15486
+ const join=spec.join||{};
15487
+ if(String(join.type||'inner')==='left')return true;
15488
+ return gained.size===0;
15489
+ }
15490
+ #joinRows(){
15491
+ const join=this.#spec.join;
15492
+ const partner=join&&join.with;
15493
+ if(!partner||!partner.rows)return[];
15494
+ const out=[];
15495
+ const take=(row)=>{if(!row.group)out.push(unwrap(row));};
15496
+ if(String(join.follow||'all')==='filtered'&&isFunction(partner.rows.forEach)){
15497
+ partner.rows.forEach(take);
15498
+ }else if(isFunction(partner.rows.forEachAll)){
15499
+ partner.rows.forEachAll(take);
15500
+ }
15501
+ return out;
15502
+ }
15503
+ schedule(change){
15285
15504
  const mode=this.#spec.refresh===undefined?'idle':this.#spec.refresh;
15286
15505
  if(mode==='manual')return;
15287
- if(mode==='live'){this.refresh();return;}
15506
+ if(mode==='live'){this.refresh(change);return;}
15288
15507
  const wait=typeof mode==='number'&&Number.isFinite(mode)?mode:IDLE_REFRESH_MS;
15289
- if(this.#pending)return;
15290
- this.#pending=setTimeout(()=>{this.#pending=null;this.refresh();},wait);
15508
+ if(this.#pending){this.#coalesced=true;return;}
15509
+ this.#coalesced=false;
15510
+ this.#pending=setTimeout(()=>{
15511
+ this.#pending=null;
15512
+ this.refresh(this.#coalesced?null:change);
15513
+ },wait);
15291
15514
  }
15292
- refresh(){
15515
+ refresh(change){
15293
15516
  if(this.#gone||!this.#from)return;
15294
15517
  if(this.#deriving){
15295
15518
  warnOnce(
@@ -15303,7 +15526,7 @@ this.#deriving=true;
15303
15526
  try{
15304
15527
  const rows=this.#spec.profile
15305
15528
  ?this.#deriveProfile()
15306
- :derive(this.#sourceRows(),this.#resolvedSpec());
15529
+ :this.#deriveRows(change);
15307
15530
  this.setRows(rows);
15308
15531
  this.invalidate('filter');
15309
15532
  if(isFunction(this.#ctx.emit)){
@@ -15313,13 +15536,157 @@ if(isFunction(this.#ctx.emit)){
15313
15536
  this.#deriving=false;
15314
15537
  }
15315
15538
  }
15539
+ #deriveRows(change){
15540
+ const partnerMoved=isObject(change)&&change.partner===true;
15541
+ const delta=partnerMoved?this.#refreshRight():null;
15542
+ const spec=this.#resolvedSpec();
15543
+ if(delta&&this.#index){
15544
+ if(!delta.affected.size){
15545
+ return finishGroups(this.#index.groups,spec,this.#index.reduced,new Set());
15546
+ }
15547
+ if(this.#partnerPatchable(spec,delta.gained)){
15548
+ const dirty=this.#patchPartner(delta.affected,spec);
15549
+ if(dirty)return finishGroups(this.#index.groups,spec,this.#index.reduced,dirty);
15550
+ }
15551
+ }
15552
+ const patch=this.#patchable(spec,change);
15553
+ if(patch&&this.#index){
15554
+ const dirty=this.#applyPatch(patch,spec);
15555
+ return finishGroups(this.#index.groups,spec,this.#index.reduced,dirty);
15556
+ }
15557
+ const rows=preGroup(this.#sourceRows(),spec);
15558
+ this.#filterCols=this.#sourceFilterColumns();
15559
+ if(!spec.groupBy){this.#index=null;return derive(rows,spec);}
15560
+ const keyOf=this.#keyOf();
15561
+ const{groups,rowGroup}=groupRows(rows,spec,keyOf);
15562
+ const rowData=new Map();
15563
+ for(const[key,groupKey]of rowGroup){
15564
+ const group=groups.get(groupKey);
15565
+ if(group)rowData.set(key,group.rows.get(key));
15566
+ }
15567
+ const reduced=new Map();
15568
+ const paths=this.#joinPaths();
15569
+ const leftJoinKey=new Map();
15570
+ const leftByJoinKey=new Map();
15571
+ if(paths){
15572
+ for(const[key,data]of rowData){
15573
+ const jk=joinKeyOf(data,paths.left);
15574
+ if(jk===null)continue;
15575
+ leftJoinKey.set(key,jk);
15576
+ let bucket=leftByJoinKey.get(jk);
15577
+ if(!bucket){bucket=new Set();leftByJoinKey.set(jk,bucket);}
15578
+ bucket.add(key);
15579
+ }
15580
+ }
15581
+ this.#index={groups,rowGroup,rowData,reduced,leftJoinKey,leftByJoinKey};
15582
+ return finishGroups(groups,spec,reduced,null);
15583
+ }
15584
+ #patchable(spec,change){
15585
+ if(!this.#index||!spec.groupBy)return null;
15586
+ if(spec.unnest||spec.where||String(spec.follow||'filtered')==='grouped')return null;
15587
+ if(isObject(spec.join)&&spec.join.with&&!(spec.join.index instanceof Map))return null;
15588
+ if(!isObject(change))return null;
15589
+ const updated=Array.isArray(change.updated)?change.updated:null;
15590
+ const added=Array.isArray(change.added)?change.added:null;
15591
+ const removed=Array.isArray(change.removed)?change.removed:null;
15592
+ if(!updated&&!added&&!removed)return null;
15593
+ const cols=this.#filterCols;
15594
+ if(cols&&cols.size&&String(spec.follow||'filtered')!=='all'){
15595
+ for(const row of updated||[]){
15596
+ const key=row&&row.key;
15597
+ const before=key===undefined?undefined:this.#index.rowData.get(String(key));
15598
+ const after=unwrap(row);
15599
+ for(const col of cols){
15600
+ if(readPath(before,col)!==readPath(after,col))return null;
15601
+ }
15602
+ }
15603
+ }
15604
+ return{updated:updated||[],added:added||[],removed:removed||[]};
15605
+ }
15606
+ #applyPatch(patch,spec){
15607
+ const index=
15608
+ (this.#index);
15609
+ const keyOf=this.#keyOf();
15610
+ const dirty=new Set();
15611
+ const join=isObject(spec.join)&&spec.join.index instanceof Map?spec.join:null;
15612
+ const paths=this.#joinPaths();
15613
+ const drop=(key)=>{
15614
+ const was=index.rowGroup.get(key);
15615
+ if(was===undefined)return;
15616
+ dirty.add(was);
15617
+ const group=index.groups.get(was);
15618
+ if(group){
15619
+ group.rows.delete(key);
15620
+ if(!group.rows.size)index.groups.delete(was);
15621
+ }
15622
+ index.rowGroup.delete(key);
15623
+ index.rowData.delete(key);
15624
+ if(index.leftJoinKey){
15625
+ const jk=index.leftJoinKey.get(key);
15626
+ if(jk!==undefined){
15627
+ const bucket=index.leftByJoinKey.get(jk);
15628
+ if(bucket){bucket.delete(key);if(!bucket.size)index.leftByJoinKey.delete(jk);}
15629
+ index.leftJoinKey.delete(key);
15630
+ }
15631
+ }
15632
+ };
15633
+ const place=(raw)=>{
15634
+ const row=join?joinOne(raw,join):raw;
15635
+ if(!row)return;
15636
+ const key=keyOf(row);
15637
+ const single=groupRows([row],spec,keyOf);
15638
+ for(const[groupKey,built]of single.groups){
15639
+ let group=index.groups.get(groupKey);
15640
+ if(!group){group={values:built.values,rows:new Map()};index.groups.set(groupKey,group);}
15641
+ group.rows.set(key,row);
15642
+ index.rowGroup.set(key,groupKey);
15643
+ dirty.add(groupKey);
15644
+ }
15645
+ index.rowData.set(key,row);
15646
+ if(paths&&index.leftByJoinKey){
15647
+ const jk=joinKeyOf(row,paths.left);
15648
+ if(jk!==null){
15649
+ index.leftJoinKey.set(key,jk);
15650
+ let bucket=index.leftByJoinKey.get(jk);
15651
+ if(!bucket){bucket=new Set();index.leftByJoinKey.set(jk,bucket);}
15652
+ bucket.add(key);
15653
+ }
15654
+ }
15655
+ };
15656
+ for(const key of patch.removed)drop(String(key));
15657
+ for(const row of patch.updated){drop(String(row.key));place(unwrap(row));}
15658
+ for(const row of patch.added)place(unwrap(row));
15659
+ return dirty;
15660
+ }
15661
+ #keyOf(){
15662
+ const grid=this.#from;
15663
+ const field=(grid&&grid.config&&grid.config.rowKey)||'id';
15664
+ return(row)=>String(readPath(row,typeof field==='string'?field:'id')??'');
15665
+ }
15666
+ #sourceFilterColumns(){
15667
+ const grid=this.#from;
15668
+ const state=grid&&grid.state&&isFunction(grid.state.get)?grid.state.get():null;
15669
+ const ids=new Set();
15670
+ const walk=(node)=>{
15671
+ if(!node)return;
15672
+ if(Array.isArray(node.conditions)){node.conditions.forEach(walk);return;}
15673
+ if(node.col)ids.add(String(node.col));
15674
+ };
15675
+ if(state)walk(state.filters);
15676
+ return ids;
15677
+ }
15316
15678
  #resolvedSpec(){
15317
- if(String(this.#spec.follow||'filtered')!=='grouped')return this.#spec;
15679
+ let spec=this.#spec;
15680
+ if(spec.join&&spec.join.with){
15681
+ if(!this.#rightIndex)this.#refreshRight();
15682
+ spec={...spec,join:{...spec.join,index:this.#rightIndex}};
15683
+ }
15684
+ if(String(spec.follow||'filtered')!=='grouped')return spec;
15318
15685
  const grid=this.#from;
15319
15686
  const state=grid&&grid.state&&isFunction(grid.state.get)?grid.state.get():null;
15320
15687
  const grouped=state&&Array.isArray(state.group)?state.group:[];
15321
- if(!grouped.length)return this.#spec;
15322
- return{...this.#spec,groupBy:[...grouped]};
15688
+ if(!grouped.length)return spec;
15689
+ return{...spec,groupBy:[...grouped]};
15323
15690
  }
15324
15691
  #sourceRows(){
15325
15692
  const grid=this.#from;
@@ -15336,12 +15703,61 @@ if(follow==='selected'){
15336
15703
  const rows=grid.selection&&isFunction(grid.selection.rows)?grid.selection.rows():[];
15337
15704
  return rows.filter(Boolean).map(unwrap);
15338
15705
  }
15706
+ const cross=this.#crossColumn();
15339
15707
  const out=[];
15708
+ if(cross&&this.#cross.length&&isFunction(grid.rows.forEachExcept)){
15709
+ grid.rows.forEachExcept(cross,(row)=>{if(!row.group)out.push(unwrap(row));});
15710
+ return out;
15711
+ }
15340
15712
  if(isFunction(grid.rows.forEach)){
15341
15713
  grid.rows.forEach((row)=>{if(!row.group)out.push(unwrap(row));});
15342
15714
  }
15343
15715
  return out;
15344
15716
  }
15717
+ #crossColumn(){
15718
+ const cfg=this.#spec.crossFilter;
15719
+ if(!cfg)return null;
15720
+ if(typeof cfg==='string')return cfg;
15721
+ if(cfg.col)return String(cfg.col);
15722
+ const by=this.#spec.groupBy;
15723
+ const first=Array.isArray(by)?by[0]:by;
15724
+ return first?String(first):null;
15725
+ }
15726
+ #pushCross(){
15727
+ const col=this.#crossColumn();
15728
+ const grid=this.#from;
15729
+ if(!col||!grid||!grid.filters||!isFunction(grid.filters.set))return;
15730
+ const compute=this.#ctx.compute||{};
15731
+ const current=isFunction(grid.filters.get)?grid.filters.get():null;
15732
+ const others=isFunction(compute.pruneColumn)?compute.pruneColumn(current,col):null;
15733
+ const keys=this.#cross;
15734
+ if(!keys.length){grid.filters.set(others||null);return;}
15735
+ const mine=keys.length===1
15736
+ ?{col,op:'eq',value:keys[0]}
15737
+ :{col,op:'in',value:keys.slice()};
15738
+ const rest=others?(others.conditions&&others.op?others.conditions:[others]):[];
15739
+ grid.filters.set(rest.length?{op:'and',conditions:[...rest,mine]}:mine);
15740
+ }
15741
+ crossFilter(){
15742
+ const self=this;
15743
+ return{
15744
+ enabled(){return!!self.#crossColumn();},
15745
+ column(){return self.#crossColumn();},
15746
+ get(){return self.#cross.slice();},
15747
+ set(keys){
15748
+ const next=keys==null?[]:(Array.isArray(keys)?keys:[keys]);
15749
+ self.#cross=next.map((k)=>String(k));
15750
+ self.#pushCross();
15751
+ },
15752
+ toggle(key){
15753
+ const k=String(key);
15754
+ const at=self.#cross.indexOf(k);
15755
+ if(at>=0)self.#cross.splice(at,1);else self.#cross.push(k);
15756
+ self.#pushCross();
15757
+ },
15758
+ clear(){self.#cross=[];self.#pushCross();},
15759
+ };
15760
+ }
15345
15761
  #deriveProfile(){
15346
15762
  const grid=this.#from;
15347
15763
  if(!grid||!grid.statistics||!isFunction(grid.statistics.profile))return[];
@@ -15388,6 +15804,9 @@ this.#from=null;
15388
15804
  super.destroy();
15389
15805
  }
15390
15806
  }
15807
+ function skipCompanion(payload){
15808
+ return isObject(payload)&&payload.companion===true;
15809
+ }
15391
15810
  function unwrap(row){
15392
15811
  return isObject(row)&&'data'in(row)
15393
15812
  ?(row).data
@@ -22934,6 +23353,7 @@ result.rejected=rejected;
22934
23353
  if(this.#run)this.#run(plan,change);
22935
23354
  if(!opts.silent){
22936
23355
  this.#emit('rows:changed',{
23356
+ identified:true,
22937
23357
  added:result.added,updated:result.updated,removed:result.removed,
22938
23358
  rejected:result.rejected,plan,
22939
23359
  },opts.origin||'api');
@@ -27413,7 +27833,7 @@ const before=flash?grid.#captureCells(change):null;
27413
27833
  const result=grid.#changes.apply(change);
27414
27834
  if(before)grid.#flashChanged(before,result.updated,flash);
27415
27835
  grid.#refresh('rows');
27416
- grid.#bus.emit('rows:changed',{change},'api');
27836
+ grid.#bus.emit('rows:changed',{companion:true,change},'api');
27417
27837
  return result;
27418
27838
  },
27419
27839
  queue(change){return grid.#changes.queue(change);},
@@ -27472,6 +27892,16 @@ const row=source.at(i);
27472
27892
  if(row&&!row.group)fn(row,i);
27473
27893
  }
27474
27894
  },
27895
+ forEachExcept(colId,fn){
27896
+ if(!isFunction(fn))return;
27897
+ const source=grid.#source;
27898
+ if(colId&&isFunction(source.facetRows)){
27899
+ const rows=source.facetRows(colId);
27900
+ for(let i=0;i<rows.length;i++)fn(rows[i],i);
27901
+ return;
27902
+ }
27903
+ this.forEach(fn);
27904
+ },
27475
27905
  value(key,colId){
27476
27906
  const row=grid.#rowByKey(key);
27477
27907
  return grid.#cellValue(row,grid.#columnsView().get(colId)||colId);
@@ -27526,6 +27956,7 @@ if(target===from)return{moved:false,from,to:target,reason:'unchanged'};
27526
27956
  if(!grid.#source.moveRow(from,target))return refuse('unchanged');
27527
27957
  grid.#rebuildStore();
27528
27958
  grid.#refresh('rows');
27959
+ grid.#bus.emit('rows:changed',{moved:1,key,from,to:target},'api');
27529
27960
  grid.#bus.emit('row:moved',{key,from,to:target,data:row.data});
27530
27961
  return{moved:true,from,to:target};
27531
27962
  },
@@ -28466,6 +28897,18 @@ return grid.#detail.inline?'inline':'target';
28466
28897
  config(){return grid.#detail?grid.#detail.config:null;},
28467
28898
  };
28468
28899
  }
28900
+ get crossFilter(){
28901
+ const source=(this.#source);
28902
+ if(source&&isFunction(source.crossFilter))return source.crossFilter();
28903
+ return{
28904
+ enabled(){return false;},
28905
+ column(){return null;},
28906
+ get(){return[];},
28907
+ set(){},
28908
+ toggle(){},
28909
+ clear(){},
28910
+ };
28911
+ }
28469
28912
  get facets(){
28470
28913
  const grid=this;
28471
28914
  return{
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.10.0 — charts module
2
+ * Lattice Grid 1.11.0 — charts module
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.10.0 — devtools module
2
+ * Lattice Grid 1.11.0 — devtools module
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */