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