@toclocoinc/lattice-grid 1.66.0 → 1.68.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.
Files changed (163) hide show
  1. package/README.md +1 -1
  2. package/angular/package.json +1 -1
  3. package/docs/API.html +87 -29
  4. package/docs/api-detail.html +40 -5
  5. package/lattice-grid.d.ts +20 -7
  6. package/lattice-grid.esm.min.js +35 -11
  7. package/lattice-grid.min.cjs +35 -11
  8. package/lattice-grid.min.js +35 -11
  9. package/modules/ai.d.ts +1 -1
  10. package/modules/ai.esm.min.js +3 -3
  11. package/modules/ai.min.cjs +3 -3
  12. package/modules/ai.min.js +3 -3
  13. package/modules/angular.d.ts +1 -1
  14. package/modules/angular.esm.min.js +3 -3
  15. package/modules/angular.min.cjs +3 -3
  16. package/modules/angular.min.js +3 -3
  17. package/modules/chart-alluvial.d.ts +1 -1
  18. package/modules/chart-alluvial.esm.min.js +1 -1
  19. package/modules/chart-alluvial.min.cjs +1 -1
  20. package/modules/chart-alluvial.min.js +1 -1
  21. package/modules/chart-arc.d.ts +1 -1
  22. package/modules/chart-arc.esm.min.js +1 -1
  23. package/modules/chart-arc.min.cjs +1 -1
  24. package/modules/chart-arc.min.js +1 -1
  25. package/modules/chart-bubblemap.d.ts +1 -1
  26. package/modules/chart-bubblemap.esm.min.js +1 -1
  27. package/modules/chart-bubblemap.min.cjs +1 -1
  28. package/modules/chart-bubblemap.min.js +1 -1
  29. package/modules/chart-bump.d.ts +1 -1
  30. package/modules/chart-bump.esm.min.js +1 -1
  31. package/modules/chart-bump.min.cjs +1 -1
  32. package/modules/chart-bump.min.js +1 -1
  33. package/modules/chart-calendar.d.ts +1 -1
  34. package/modules/chart-calendar.esm.min.js +1 -1
  35. package/modules/chart-calendar.min.cjs +1 -1
  36. package/modules/chart-calendar.min.js +1 -1
  37. package/modules/chart-decomposition.d.ts +1 -1
  38. package/modules/chart-decomposition.esm.min.js +1 -1
  39. package/modules/chart-decomposition.min.cjs +1 -1
  40. package/modules/chart-decomposition.min.js +1 -1
  41. package/modules/chart-diverging.d.ts +1 -1
  42. package/modules/chart-diverging.esm.min.js +1 -1
  43. package/modules/chart-diverging.min.cjs +1 -1
  44. package/modules/chart-diverging.min.js +1 -1
  45. package/modules/chart-dumbbell.d.ts +1 -1
  46. package/modules/chart-dumbbell.esm.min.js +1 -1
  47. package/modules/chart-dumbbell.min.cjs +1 -1
  48. package/modules/chart-dumbbell.min.js +1 -1
  49. package/modules/chart-fan.d.ts +1 -1
  50. package/modules/chart-fan.esm.min.js +1 -1
  51. package/modules/chart-fan.min.cjs +1 -1
  52. package/modules/chart-fan.min.js +1 -1
  53. package/modules/chart-hexbin.d.ts +1 -1
  54. package/modules/chart-hexbin.esm.min.js +1 -1
  55. package/modules/chart-hexbin.min.cjs +1 -1
  56. package/modules/chart-hexbin.min.js +1 -1
  57. package/modules/chart-hexmap.d.ts +1 -1
  58. package/modules/chart-hexmap.esm.min.js +1 -1
  59. package/modules/chart-hexmap.min.cjs +1 -1
  60. package/modules/chart-hexmap.min.js +1 -1
  61. package/modules/chart-icicle.d.ts +1 -1
  62. package/modules/chart-icicle.esm.min.js +1 -1
  63. package/modules/chart-icicle.min.cjs +1 -1
  64. package/modules/chart-icicle.min.js +1 -1
  65. package/modules/chart-markermap.d.ts +1 -1
  66. package/modules/chart-markermap.esm.min.js +1 -1
  67. package/modules/chart-markermap.min.cjs +1 -1
  68. package/modules/chart-markermap.min.js +1 -1
  69. package/modules/chart-parallel.d.ts +1 -1
  70. package/modules/chart-parallel.esm.min.js +1 -1
  71. package/modules/chart-parallel.min.cjs +1 -1
  72. package/modules/chart-parallel.min.js +1 -1
  73. package/modules/chart-ridgeline.d.ts +1 -1
  74. package/modules/chart-ridgeline.esm.min.js +1 -1
  75. package/modules/chart-ridgeline.min.cjs +1 -1
  76. package/modules/chart-ridgeline.min.js +1 -1
  77. package/modules/chart-roc.d.ts +1 -1
  78. package/modules/chart-roc.esm.min.js +1 -1
  79. package/modules/chart-roc.min.cjs +1 -1
  80. package/modules/chart-roc.min.js +1 -1
  81. package/modules/chart-slope.d.ts +1 -1
  82. package/modules/chart-slope.esm.min.js +1 -1
  83. package/modules/chart-slope.min.cjs +1 -1
  84. package/modules/chart-slope.min.js +1 -1
  85. package/modules/chart-splom.d.ts +1 -1
  86. package/modules/chart-splom.esm.min.js +1 -1
  87. package/modules/chart-splom.min.cjs +1 -1
  88. package/modules/chart-splom.min.js +1 -1
  89. package/modules/chart-waffle.d.ts +1 -1
  90. package/modules/chart-waffle.esm.min.js +1 -1
  91. package/modules/chart-waffle.min.cjs +1 -1
  92. package/modules/chart-waffle.min.js +1 -1
  93. package/modules/charts.d.ts +1 -1
  94. package/modules/charts.esm.min.js +1376 -1249
  95. package/modules/charts.min.cjs +1376 -1249
  96. package/modules/charts.min.js +1376 -1249
  97. package/modules/data-router.d.ts +8 -3
  98. package/modules/data-router.esm.min.js +229 -11
  99. package/modules/data-router.min.cjs +229 -11
  100. package/modules/data-router.min.js +229 -11
  101. package/modules/devtools.d.ts +1 -1
  102. package/modules/devtools.esm.min.js +1 -1
  103. package/modules/devtools.min.cjs +1 -1
  104. package/modules/devtools.min.js +1 -1
  105. package/modules/dhtmlx-compat.d.ts +1 -1
  106. package/modules/dhtmlx-compat.esm.min.js +3 -3
  107. package/modules/dhtmlx-compat.min.cjs +3 -3
  108. package/modules/dhtmlx-compat.min.js +3 -3
  109. package/modules/gantt.d.ts +8 -3
  110. package/modules/gantt.esm.min.js +56 -8
  111. package/modules/gantt.min.cjs +56 -8
  112. package/modules/gantt.min.js +56 -8
  113. package/modules/geo-europe-nuts.d.ts +1 -1
  114. package/modules/geo-europe-nuts.esm.min.js +1 -1
  115. package/modules/geo-uk.d.ts +1 -1
  116. package/modules/geo-uk.esm.min.js +1 -1
  117. package/modules/geo-us-states.d.ts +1 -1
  118. package/modules/geo-us-states.esm.min.js +1 -1
  119. package/modules/geo-world-110m.d.ts +1 -1
  120. package/modules/geo-world-110m.esm.min.js +1 -1
  121. package/modules/geo-world-50m.d.ts +1 -1
  122. package/modules/geo-world-50m.esm.min.js +1 -1
  123. package/modules/htmx.d.ts +1 -1
  124. package/modules/htmx.esm.min.js +35 -11
  125. package/modules/htmx.min.cjs +35 -11
  126. package/modules/htmx.min.js +35 -11
  127. package/modules/kanban.d.ts +10 -3
  128. package/modules/kanban.esm.min.js +24 -4
  129. package/modules/kanban.min.cjs +24 -4
  130. package/modules/kanban.min.js +24 -4
  131. package/modules/kpi.d.ts +35 -10
  132. package/modules/kpi.esm.min.js +55 -7
  133. package/modules/kpi.min.cjs +55 -7
  134. package/modules/kpi.min.js +55 -7
  135. package/modules/layout.d.ts +1 -1
  136. package/modules/layout.esm.min.js +3 -3
  137. package/modules/layout.min.cjs +3 -3
  138. package/modules/layout.min.js +3 -3
  139. package/modules/mock-socket.d.ts +1 -1
  140. package/modules/mock-socket.esm.min.js +1 -1
  141. package/modules/mock-socket.min.cjs +1 -1
  142. package/modules/mock-socket.min.js +1 -1
  143. package/modules/react.d.ts +1 -1
  144. package/modules/react.esm.min.js +3 -3
  145. package/modules/react.min.cjs +3 -3
  146. package/modules/react.min.js +3 -3
  147. package/modules/svelte.d.ts +1 -1
  148. package/modules/svelte.esm.min.js +3 -3
  149. package/modules/svelte.min.cjs +3 -3
  150. package/modules/svelte.min.js +3 -3
  151. package/modules/tabs.d.ts +1 -1
  152. package/modules/tabs.esm.min.js +13 -14
  153. package/modules/tabs.min.cjs +13 -14
  154. package/modules/tabs.min.js +13 -14
  155. package/modules/vue.d.ts +1 -1
  156. package/modules/vue.esm.min.js +3 -3
  157. package/modules/vue.min.cjs +3 -3
  158. package/modules/vue.min.js +3 -3
  159. package/modules/webcomponent.d.ts +1 -1
  160. package/modules/webcomponent.esm.min.js +35 -11
  161. package/modules/webcomponent.min.cjs +35 -11
  162. package/modules/webcomponent.min.js +35 -11
  163. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.66.0, data-router module
2
+ * Lattice Grid 1.68.0, data-router module
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -54,12 +54,12 @@ Object.defineProperty(__exports,"frameBatched",{enumerable:true,get:function(){r
54
54
  Object.defineProperty(__exports,"settleDebounce",{enumerable:true,get:function(){return settleDebounce;}});
55
55
  Object.defineProperty(__exports,"whenIdle",{enumerable:true,get:function(){return whenIdle;}});
56
56
  Object.defineProperty(__exports,"uid",{enumerable:true,get:function(){return uid;}});
57
- const STAMPED_VERSION="1.66.0";
57
+ const STAMPED_VERSION="1.68.0";
58
58
  async function resolveVersion(){
59
59
  if(STAMPED_VERSION!=='0.0.0-source')return STAMPED_VERSION;
60
60
  return STAMPED_VERSION;
61
61
  }
62
- const VERSION="1.66.0";
62
+ const VERSION="1.68.0";
63
63
  const warned=new Set();
64
64
  const WARNED_LIMIT=2000;
65
65
  function rememberWarned(key){
@@ -1546,6 +1546,180 @@ grandTotals:()=>lastGrand,
1546
1546
  };
1547
1547
  }
1548
1548
  });
1549
+ __def("packages/core/src/model/rowkey.js",function(__exports,__req){
1550
+ 'use strict';
1551
+ Object.defineProperty(__exports,"KEY_DEPENDENT_FEATURES",{enumerable:true,get:function(){return KEY_DEPENDENT_FEATURES;}});
1552
+ Object.defineProperty(__exports,"compositeKey",{enumerable:true,get:function(){return compositeKey;}});
1553
+ Object.defineProperty(__exports,"createRowKey",{enumerable:true,get:function(){return createRowKey;}});
1554
+ Object.defineProperty(__exports,"warnMissingKeys",{enumerable:true,get:function(){return warnMissingKeys;}});
1555
+ Object.defineProperty(__exports,"groupPathKey",{enumerable:true,get:function(){return groupPathKey;}});
1556
+ Object.defineProperty(__exports,"splitPathKey",{enumerable:true,get:function(){return splitPathKey;}});
1557
+ const __m0=__req("packages/core/src/internal/util.js");
1558
+ const warnOnce=__m0["warnOnce"];
1559
+ const pathGetter=__m0["pathGetter"];
1560
+ const isFunction=__m0["isFunction"];
1561
+ const isBlank=__m0["isBlank"];
1562
+ const KEY_DEPENDENT_FEATURES=[
1563
+ 'changes (rows.apply / rows.queue)',
1564
+ 'streaming dedupe',
1565
+ 'selection persistence',
1566
+ 'remote reload',
1567
+ 'cell comments',
1568
+ ];
1569
+ const SEP='\u001f';
1570
+ function part(value){
1571
+ if(value===null)return'\u0000n';
1572
+ if(value===undefined)return'\u0000u';
1573
+ const t=typeof value;
1574
+ if(t==='string')return value;
1575
+ if(t==='number'||t==='bigint'||t==='boolean')return String(value);
1576
+ if(value instanceof Date)return`\u0000d${value.getTime()}`;
1577
+ try{
1578
+ return`\u0000o${JSON.stringify(value)}`;
1579
+ }catch{
1580
+ return'\u0000o?';
1581
+ }
1582
+ }
1583
+ function compositeKey(parts){
1584
+ if(parts.length===1)return part(parts[0]);
1585
+ let out='';
1586
+ for(let i=0;i<parts.length;i++){
1587
+ if(i)out+=SEP;
1588
+ out+=part(parts[i]);
1589
+ }
1590
+ return out;
1591
+ }
1592
+ function createRowKey(spec,opts={}){
1593
+ const warn=opts.warn!==false;
1594
+ if(isFunction(spec)){
1595
+ const fn=(spec);
1596
+ return{
1597
+ stable:true,
1598
+ fields:[],
1599
+ disabled:[],
1600
+ key(data,index){
1601
+ try{
1602
+ const value=fn(data);
1603
+ return Array.isArray(value)?compositeKey(value):part(value);
1604
+ }catch(err){
1605
+ warnOnce('rowKey.throw','rowKey function threw; falling back to index identity for the affected rows.',err);
1606
+ return`#${index??-1}`;
1607
+ }
1608
+ },
1609
+ keyEmpty(data){
1610
+ try{
1611
+ const value=fn(data);
1612
+ return Array.isArray(value)?value.length===0||value.some(isBlank):isBlank(value);
1613
+ }catch{
1614
+ return false;
1615
+ }
1616
+ },
1617
+ missingFields(){return[];},
1618
+ };
1619
+ }
1620
+ if(Array.isArray(spec)&&spec.length>0){
1621
+ const fields=spec.filter((f)=>typeof f==='string');
1622
+ const getters=fields.map((f)=>pathGetter(f));
1623
+ const n=getters.length;
1624
+ return{
1625
+ stable:true,
1626
+ fields,
1627
+ disabled:[],
1628
+ key(data){
1629
+ const parts=new Array(n);
1630
+ for(let i=0;i<n;i++)parts[i]=getters[i](data);
1631
+ return compositeKey(parts);
1632
+ },
1633
+ keyEmpty(data){
1634
+ for(let i=0;i<n;i++)if(isBlank(getters[i](data)))return true;
1635
+ return false;
1636
+ },
1637
+ missingFields(data){
1638
+ const out=[];
1639
+ for(let i=0;i<n;i++)if(isBlank(getters[i](data)))out.push(fields[i]);
1640
+ return out;
1641
+ },
1642
+ };
1643
+ }
1644
+ if(typeof spec==='string'&&spec.length>0){
1645
+ const get=pathGetter(spec);
1646
+ return{
1647
+ stable:true,
1648
+ fields:[spec],
1649
+ disabled:[],
1650
+ key(data){
1651
+ return part(get(data));
1652
+ },
1653
+ keyEmpty(data){
1654
+ return isBlank(get(data));
1655
+ },
1656
+ missingFields(data){
1657
+ return isBlank(get(data))?[spec]:[];
1658
+ },
1659
+ };
1660
+ }
1661
+ if(warn){
1662
+ warnOnce(
1663
+ 'rowKey.missing',
1664
+ `no rowKey configured, so rows are identified by object identity. This disables: ${KEY_DEPENDENT_FEATURES.join(', ')}. Set rowKey to a field name, an array of field names, or a function.`,
1665
+ );
1666
+ }
1667
+ return{
1668
+ stable:false,
1669
+ fields:[],
1670
+ disabled:KEY_DEPENDENT_FEATURES.slice(),
1671
+ key(data,index){
1672
+ if(data===null||typeof data!=='object'){
1673
+ return`#${index??0}:${String(data)}`;
1674
+ }
1675
+ let assigned=autoKeys.get(data);
1676
+ if(assigned===undefined){
1677
+ assigned=`#${autoKeySeq++}`;
1678
+ autoKeys.set(data,assigned);
1679
+ }
1680
+ return assigned;
1681
+ },
1682
+ };
1683
+ }
1684
+ const autoKeys=new WeakMap();
1685
+ let autoKeySeq=0;
1686
+ function warnMissingKeys(resolver,rows){
1687
+ if(!resolver||resolver.stable===false||!isFunction(resolver.keyEmpty))return;
1688
+ if(!Array.isArray(rows)||rows.length===0)return;
1689
+ let missing=0;
1690
+ let sampleFields=null;
1691
+ let sampleData;
1692
+ for(let i=0;i<rows.length;i++){
1693
+ const data=rows[i];
1694
+ if(!resolver.keyEmpty(data))continue;
1695
+ missing++;
1696
+ if(sampleFields===null){
1697
+ sampleFields=isFunction(resolver.missingFields)?resolver.missingFields(data):[];
1698
+ sampleData=data;
1699
+ }
1700
+ }
1701
+ if(missing===0)return;
1702
+ const dedupFields=resolver.fields.length?resolver.fields.join(','):'(function)';
1703
+ const named=sampleFields&&sampleFields.length
1704
+ ?sampleFields.join(', ')
1705
+ :(resolver.fields.length?resolver.fields.join(', '):'the rowKey function');
1706
+ const present=sampleData!==null&&typeof sampleData==='object'
1707
+ ?(Object.keys((sampleData)).join(', ')||'(no fields)')
1708
+ :String(sampleData);
1709
+ warnOnce(
1710
+ `rowKey.emptyValue:${dedupFields}`,
1711
+ `rowKey ${named} is missing on ${missing} of ${rows.length} rows; the first such row has: ${present}. `
1712
+ +'Rows without a key value collapse onto one key, so selection, edits and rows.apply updates land on '
1713
+ +'whichever row now shares it. Set rowKey to a field present on every row, or fix the source data.',
1714
+ );
1715
+ }
1716
+ function groupPathKey(path){
1717
+ return compositeKey(path);
1718
+ }
1719
+ function splitPathKey(key){
1720
+ return key.split(SEP);
1721
+ }
1722
+ });
1549
1723
  __def("packages/modules/data-router/devtools.js",function(__exports,__req){
1550
1724
  'use strict';
1551
1725
  Object.defineProperty(__exports,"mountRouterDevtools",{enumerable:true,get:function(){return mountRouterDevtools;}});
@@ -1631,13 +1805,37 @@ const warnOnce=__m0["warnOnce"];
1631
1805
  const __m1=__req("packages/core/src/source/pushdown.js");
1632
1806
  const planQuery=__m1["planQuery"];
1633
1807
  const capabilitiesOf=__m1["capabilitiesOf"];
1634
- const __m2=__req("packages/modules/data-router/devtools.js");
1635
- const mountRouterDevtools=__m2["mountRouterDevtools"];
1808
+ const __m2=__req("packages/core/src/model/rowkey.js");
1809
+ const compositeKey=__m2["compositeKey"];
1810
+ const __m3=__req("packages/modules/data-router/devtools.js");
1811
+ const mountRouterDevtools=__m3["mountRouterDevtools"];
1636
1812
  function resolver(spec,fallback){
1637
1813
  if(typeof spec==='function')return spec;
1638
1814
  if(typeof spec==='string')return(row)=>row[spec];
1639
1815
  return fallback;
1640
1816
  }
1817
+ function rowKeyTypeName(v){
1818
+ if(Array.isArray(v))return'array';
1819
+ if(v===null)return'null';
1820
+ return typeof v;
1821
+ }
1822
+ function assertRowKeySpec(spec,where){
1823
+ if(spec===undefined)return spec;
1824
+ if(typeof spec==='string'&&spec.length>0)return spec;
1825
+ if(typeof spec==='function'){
1826
+ return(row)=>{
1827
+ const key=spec(row);
1828
+ if(typeof key==='string')return key;
1829
+ if(typeof key==='number'&&Number.isFinite(key))return key;
1830
+ throw new Error(
1831
+ `lattice-data-router: rowKey function must return a string or number; got ${rowKeyTypeName(key)}.`,
1832
+ );
1833
+ };
1834
+ }
1835
+ throw new Error(
1836
+ `lattice-data-router: rowKey must be a property name or a function; composite keys are not supported on ${where}.`,
1837
+ );
1838
+ }
1641
1839
  function comparator(sort){
1642
1840
  if(typeof sort==='function')return sort;
1643
1841
  if(sort&&typeof sort==='object'&&typeof sort.key==='string'){
@@ -1663,6 +1861,8 @@ const maxLag=(Number.isFinite(spec.maxLag)&&spec.maxLag>=0)?Math.floor(Number(sp
1663
1861
  if(minInterval===0&&sample===1&&maxLag===null)return null;
1664
1862
  return{minInterval,sample,maxLag};
1665
1863
  }
1864
+ let routeSeq=0;
1865
+ function nextRouteId(){routeSeq+=1;return`r${routeSeq}`;}
1666
1866
  function grouper(groupBy){
1667
1867
  const specs=Array.isArray(groupBy)?groupBy:[groupBy];
1668
1868
  const parts=specs.map((s)=>({name:typeof s==='string'?s:null,get:resolver(s,()=>undefined)}));
@@ -1674,7 +1874,7 @@ const v=part.get(row);
1674
1874
  vals.push(v);
1675
1875
  if(part.name)fields[part.name]=v;
1676
1876
  }
1677
- return{key:vals.map((v)=>String(v)).join('\u0001'),fields};
1877
+ return{key:compositeKey(vals),fields};
1678
1878
  };
1679
1879
  }
1680
1880
  function aggregateOne(spec,rows){
@@ -1750,7 +1950,7 @@ return evalCondition(node,row);
1750
1950
  }
1751
1951
  function createDataRouter(opts={}){
1752
1952
  const partOf=resolver(opts.key,()=>undefined);
1753
- const defaultKeyOf=resolver(opts.rowKey,(row)=>row.rowKey);
1953
+ const defaultKeyOf=resolver(assertRowKeySpec(opts.rowKey,'routes'),(row)=>row.rowKey);
1754
1954
  const overlap=!!opts.overlap;
1755
1955
  const onUnrouted=typeof opts.onUnrouted==='function'?opts.onUnrouted:null;
1756
1956
  const debounceMs=Number.isFinite(opts.selectionDebounce)?Number(opts.selectionDebounce):16;
@@ -1843,10 +2043,25 @@ return fallbackRoute?[fallbackRoute]:[];
1843
2043
  };
1844
2044
  const applyChange=(route,change)=>{
1845
2045
  if(change.add.length||change.update.length||change.remove.length){
1846
- route.sink(change);
2046
+ const result=route.sink(change);
2047
+ warnRejectedRemovals(route,result);
1847
2048
  }
1848
2049
  return{added:change.add.length,updated:change.update.length,removed:change.remove.length};
1849
2050
  };
2051
+ const warnRejectedRemovals=(route,result)=>{
2052
+ const rejected=(result)?.rejected;
2053
+ if(!Array.isArray(rejected)||!rejected.length)return;
2054
+ const removals=rejected.filter((r)=>r&&r.operation==='remove');
2055
+ if(!removals.length)return;
2056
+ const name=route.label||`route ${route.routeId}`;
2057
+ warnOnce(
2058
+ `data-router:remove-rejected:${route.routeId}`,
2059
+ `route "${name}" asked its target to remove ${removals.length} row(s) the target does not have`
2060
+ +` (for example ${JSON.stringify(String(removals[0].id))}). The rows are still on screen.`
2061
+ +' This is a key mismatch: the key the route derives for a row is not the key the grid derives'
2062
+ +' for it. Check that the route\'s `rowKey` names the same value as the grid\'s.',
2063
+ );
2064
+ };
1850
2065
  const passes=(route,row)=>{
1851
2066
  for(const link of links){
1852
2067
  if(link.to!==route.grid)continue;
@@ -1884,7 +2099,9 @@ shown=new Map(summaries.map((s)=>[s.gkey,s.row]));
1884
2099
  let rows=kept;
1885
2100
  if(route.sort)rows=rows.slice().sort(route.sort);
1886
2101
  shown=new Map();
1887
- for(const row of rows)shown.set(String(route.keyOf(row)),route.transform?route.transform(row):row);
2102
+ for(const row of rows){
2103
+ shown.set(compositeKey([route.keyOf(row)]),route.transform?route.transform(row):row);
2104
+ }
1888
2105
  }
1889
2106
  const add=[];
1890
2107
  const update=[];
@@ -2033,9 +2250,10 @@ return()=>{};
2033
2250
  };
2034
2251
  const makeRoute=(grid,match,o)=>({
2035
2252
  grid,
2253
+ routeId:nextRouteId(),
2036
2254
  sink:targetSink(grid),
2037
2255
  match,
2038
- keyOf:resolver(o.rowKey,defaultKeyOf),
2256
+ keyOf:resolver(assertRowKeySpec(o.rowKey,'routes'),defaultKeyOf),
2039
2257
  partition:new Map(),
2040
2258
  keys:new Map(),
2041
2259
  transform:typeof o.transform==='function'?o.transform:null,
@@ -2728,7 +2946,7 @@ const match=typeof predicate==='function'
2728
2946
  :(row)=>partOf(row)===predicate;
2729
2947
  const al={
2730
2948
  match,
2731
- keyOf:resolver(o.rowKey,defaultKeyOf),
2949
+ keyOf:resolver(assertRowKeySpec(o.rowKey,'alerts'),defaultKeyOf),
2732
2950
  filter:typeof o.filter==='function'?o.filter:null,
2733
2951
  condition:typeof condition==='function'?condition:()=>false,
2734
2952
  handler:typeof handler==='function'?handler:()=>{},
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.66.0, devtools module type declarations
2
+ * Lattice Grid 1.68.0, devtools module type declarations
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.66.0, devtools module
2
+ * Lattice Grid 1.68.0, devtools 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.66.0, devtools module
2
+ * Lattice Grid 1.68.0, devtools 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.66.0, devtools module
2
+ * Lattice Grid 1.68.0, devtools 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.66.0, dhtmlx-compat module type declarations
2
+ * Lattice Grid 1.68.0, dhtmlx-compat module type declarations
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.66.0, dhtmlx-compat module
2
+ * Lattice Grid 1.68.0, dhtmlx-compat module
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -62,12 +62,12 @@ Object.defineProperty(__exports,"frameBatched",{enumerable:true,get:function(){r
62
62
  Object.defineProperty(__exports,"settleDebounce",{enumerable:true,get:function(){return settleDebounce;}});
63
63
  Object.defineProperty(__exports,"whenIdle",{enumerable:true,get:function(){return whenIdle;}});
64
64
  Object.defineProperty(__exports,"uid",{enumerable:true,get:function(){return uid;}});
65
- const STAMPED_VERSION="1.66.0";
65
+ const STAMPED_VERSION="1.68.0";
66
66
  async function resolveVersion(){
67
67
  if(STAMPED_VERSION!=='0.0.0-source')return STAMPED_VERSION;
68
68
  return STAMPED_VERSION;
69
69
  }
70
- const VERSION="1.66.0";
70
+ const VERSION="1.68.0";
71
71
  const warned=new Set();
72
72
  const WARNED_LIMIT=2000;
73
73
  function rememberWarned(key){
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.66.0, dhtmlx-compat module
2
+ * Lattice Grid 1.68.0, dhtmlx-compat module
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -63,12 +63,12 @@ Object.defineProperty(__exports,"frameBatched",{enumerable:true,get:function(){r
63
63
  Object.defineProperty(__exports,"settleDebounce",{enumerable:true,get:function(){return settleDebounce;}});
64
64
  Object.defineProperty(__exports,"whenIdle",{enumerable:true,get:function(){return whenIdle;}});
65
65
  Object.defineProperty(__exports,"uid",{enumerable:true,get:function(){return uid;}});
66
- const STAMPED_VERSION="1.66.0";
66
+ const STAMPED_VERSION="1.68.0";
67
67
  async function resolveVersion(){
68
68
  if(STAMPED_VERSION!=='0.0.0-source')return STAMPED_VERSION;
69
69
  return STAMPED_VERSION;
70
70
  }
71
- const VERSION="1.66.0";
71
+ const VERSION="1.68.0";
72
72
  const warned=new Set();
73
73
  const WARNED_LIMIT=2000;
74
74
  function rememberWarned(key){
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.66.0, dhtmlx-compat module
2
+ * Lattice Grid 1.68.0, dhtmlx-compat module
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -63,12 +63,12 @@ Object.defineProperty(__exports,"frameBatched",{enumerable:true,get:function(){r
63
63
  Object.defineProperty(__exports,"settleDebounce",{enumerable:true,get:function(){return settleDebounce;}});
64
64
  Object.defineProperty(__exports,"whenIdle",{enumerable:true,get:function(){return whenIdle;}});
65
65
  Object.defineProperty(__exports,"uid",{enumerable:true,get:function(){return uid;}});
66
- const STAMPED_VERSION="1.66.0";
66
+ const STAMPED_VERSION="1.68.0";
67
67
  async function resolveVersion(){
68
68
  if(STAMPED_VERSION!=='0.0.0-source')return STAMPED_VERSION;
69
69
  return STAMPED_VERSION;
70
70
  }
71
- const VERSION="1.66.0";
71
+ const VERSION="1.68.0";
72
72
  const warned=new Set();
73
73
  const WARNED_LIMIT=2000;
74
74
  function rememberWarned(key){
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.66.0, gantt module type declarations
2
+ * Lattice Grid 1.68.0, gantt module type declarations
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -579,8 +579,13 @@ export function createGantt(opts?: {
579
579
  grid?: unknown;
580
580
  /** Map task fields to grid column ids to enable drag write-back. */
581
581
  columns?: { start?: string; end?: string; duration?: string };
582
- /** Task identity for the live `rows.apply` surface (a field or fn); default 'id'. */
583
- rowKey?: string | ((row: GanttTask) => unknown);
582
+ /**
583
+ * Task identity for the live `rows.apply` surface (a field or fn, returning
584
+ * a string or number); default 'id'. Composite (`string[]`) keys are
585
+ * core-grid-only: the Gantt keys its own task/board identity from one
586
+ * value, so there is nothing for an array to join into here.
587
+ */
588
+ rowKey?: string | ((row: GanttTask) => string | number);
584
589
  /**
585
590
  * The host's own names for the task properties the scheduler reads, so a
586
591
  * plan can be fed as it already exists rather than renamed for the Gantt:
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.66.0, gantt module
2
+ * Lattice Grid 1.68.0, gantt module
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -52,12 +52,12 @@ Object.defineProperty(__exports,"frameBatched",{enumerable:true,get:function(){r
52
52
  Object.defineProperty(__exports,"settleDebounce",{enumerable:true,get:function(){return settleDebounce;}});
53
53
  Object.defineProperty(__exports,"whenIdle",{enumerable:true,get:function(){return whenIdle;}});
54
54
  Object.defineProperty(__exports,"uid",{enumerable:true,get:function(){return uid;}});
55
- const STAMPED_VERSION="1.66.0";
55
+ const STAMPED_VERSION="1.68.0";
56
56
  async function resolveVersion(){
57
57
  if(STAMPED_VERSION!=='0.0.0-source')return STAMPED_VERSION;
58
58
  return STAMPED_VERSION;
59
59
  }
60
- const VERSION="1.66.0";
60
+ const VERSION="1.68.0";
61
61
  const warned=new Set();
62
62
  const WARNED_LIMIT=2000;
63
63
  function rememberWarned(key){
@@ -2406,6 +2406,8 @@ const STEPS=[1,2,2.5,5,10];
2406
2406
  const MINUTE=60000;
2407
2407
  const HOUR=3600000;
2408
2408
  const DAY=86400000;
2409
+ const YEAR=365.25*DAY;
2410
+ const YEAR_COUNTS=[1,2,5,10,20,25,50,100];
2409
2411
  const TIME_STEPS=[
2410
2412
  1,5,10,25,50,100,250,500,
2411
2413
  1000,5000,15000,30000,
@@ -2413,7 +2415,7 @@ MINUTE,5*MINUTE,15*MINUTE,30*MINUTE,
2413
2415
  HOUR,3*HOUR,6*HOUR,12*HOUR,
2414
2416
  DAY,2*DAY,7*DAY,14*DAY,
2415
2417
  30*DAY,90*DAY,180*DAY,
2416
- 365*DAY,2*365*DAY,5*365*DAY,10*365*DAY,100*365*DAY,
2418
+ ...YEAR_COUNTS.map((years)=>years*YEAR),
2417
2419
  ];
2418
2420
  function isNumber(v){
2419
2421
  return typeof v==='number'&&Number.isFinite(v);
@@ -2499,12 +2501,35 @@ out.push(Math.abs(v)<step/1e6?0:Number(v.toPrecision(12)));
2499
2501
  return out;
2500
2502
  }
2501
2503
  function timeStepFor(span,count=5){
2502
- const target=span/Math.max(1,count);
2503
- for(const candidate of TIME_STEPS)if(candidate>=target)return candidate;
2504
- return TIME_STEPS[TIME_STEPS.length-1];
2504
+ const wanted=Math.max(1,count);
2505
+ const target=span/wanted;
2506
+ let picked=TIME_STEPS[TIME_STEPS.length-1];
2507
+ for(const candidate of TIME_STEPS){
2508
+ if(candidate>=target){picked=candidate;break;}
2509
+ }
2510
+ if(picked<YEAR)return picked;
2511
+ let best=picked;
2512
+ let bestDiff=Math.abs(span/picked-wanted);
2513
+ for(const candidate of TIME_STEPS){
2514
+ if(candidate<YEAR)continue;
2515
+ const diff=Math.abs(span/candidate-wanted);
2516
+ if(diff<bestDiff){bestDiff=diff;best=candidate;}
2517
+ }
2518
+ return best;
2519
+ }
2520
+ function calendarYearTicks(domain,years){
2521
+ const minYear=new Date(domain.min).getUTCFullYear();
2522
+ let year=Math.ceil(minYear/years)*years;
2523
+ if(Date.UTC(year,0,1)<domain.min)year+=years;
2524
+ const out=[];
2525
+ for(;Date.UTC(year,0,1)<=domain.max;year+=years){
2526
+ out.push(Date.UTC(year,0,1));
2527
+ }
2528
+ return out;
2505
2529
  }
2506
2530
  function timeTicks(domain,count=5){
2507
2531
  const step=timeStepFor(domain.max-domain.min,count);
2532
+ if(step>=YEAR)return calendarYearTicks(domain,Math.round(step/YEAR));
2508
2533
  const out=[];
2509
2534
  const first=Math.ceil(domain.min/step)*step;
2510
2535
  for(let t=first;t<=domain.max;t+=step)out.push(t);
@@ -4837,6 +4862,21 @@ Object.defineProperty(__exports,"importMSPDI",{enumerable:true,get:function(){re
4837
4862
  Object.defineProperty(__exports,"exportMSPDI",{enumerable:true,get:function(){return __m4["exportMSPDI"];}});
4838
4863
  Object.defineProperty(__exports,"computeEarnedValue",{enumerable:true,get:function(){return __m3["computeEarnedValue"];}});
4839
4864
  const GANTT_STATE_VERSION=1;
4865
+ function rowKeyTypeName(v){
4866
+ if(Array.isArray(v))return'array';
4867
+ if(v===null)return'null';
4868
+ return typeof v;
4869
+ }
4870
+ function assertGanttRowKeyFn(fn){
4871
+ return(row)=>{
4872
+ const key=fn(row);
4873
+ if(typeof key==='string')return key;
4874
+ if(typeof key==='number'&&Number.isFinite(key))return key;
4875
+ throw new Error(
4876
+ `lattice-gantt: rowKey function must return a string or number; got ${rowKeyTypeName(key)}.`,
4877
+ );
4878
+ };
4879
+ }
4840
4880
  function emitter(){
4841
4881
  const map=new Map();
4842
4882
  return{
@@ -4909,7 +4949,15 @@ const autoSchedule=!!opts.autoSchedule;
4909
4949
  const grid=opts.grid??null;
4910
4950
  const columns=opts.columns&&typeof opts.columns==='object'?{...opts.columns}:null;
4911
4951
  const fields=isObject(opts.fields)?{...opts.fields}:{};
4912
- const rowKeySpec=opts.rowKey??(typeof fields.id==='string'?fields.id:'id');
4952
+ if(opts.rowKey!==undefined&&typeof opts.rowKey!=='function'
4953
+ &&!(typeof opts.rowKey==='string'&&opts.rowKey.length>0)){
4954
+ throw new Error(
4955
+ 'lattice-gantt: rowKey must be a property name or a function; composite keys are not supported on tasks.',
4956
+ );
4957
+ }
4958
+ const rowKeySpec=typeof opts.rowKey==='function'
4959
+ ?assertGanttRowKeyFn(opts.rowKey)
4960
+ :(opts.rowKey??(typeof fields.id==='string'?fields.id:'id'));
4913
4961
  const keyOf=(row)=>String(typeof rowKeySpec==='function'?rowKeySpec(row):row[rowKeySpec]);
4914
4962
  if(fields.id===undefined){
4915
4963
  fields.id=(raw)=>{