@trebco/treb 30.6.2 → 30.8.2

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.
package/dist/treb.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /*! API v30.6. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
1
+ /*! API v30.8. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
2
2
 
3
3
  /**
4
4
  * add our tag to the map
package/esbuild-utils.mjs CHANGED
@@ -74,7 +74,7 @@ export const FormatSize = (size, precision = 1) => {
74
74
 
75
75
  /**
76
76
  * @function
77
- * @param {{verbose?: boolean, minify?: boolean}} [options]
77
+ * @param {{verbose?: boolean, minify?: boolean, define?: Record<string, string>}} [options]
78
78
  * @returns {esbuild.Plugin}
79
79
  *
80
80
  * inlining the worker build. this works out well with one limitation:
@@ -147,6 +147,8 @@ export const WorkerPlugin = (options) => ({
147
147
  bundle: true,
148
148
  format: 'esm',
149
149
 
150
+ define: options?.define,
151
+
150
152
  // don't write to filesystem
151
153
  write: false,
152
154
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebco/treb",
3
- "version": "30.6.2",
3
+ "version": "30.8.2",
4
4
  "license": "LGPL-3.0-or-later",
5
5
  "homepage": "https://treb.app",
6
6
  "repository": {
@@ -2383,6 +2383,9 @@ export class Calculator extends Graph {
2383
2383
  // is it also (3) adding unecessary calculations (building the expression,
2384
2384
  // below)?
2385
2385
 
2386
+ // NOTE: moving all conditional formats into EN-US (i.e. dot-separated).
2387
+ // make sure to evaluate them in this format.
2388
+
2386
2389
  if (!list) {
2387
2390
 
2388
2391
  // we could in theory remove all of the leaves (the ones we know to
@@ -2416,8 +2419,15 @@ export class Calculator extends Graph {
2416
2419
  let expression = '';
2417
2420
 
2418
2421
  switch (entry.type) {
2422
+
2419
2423
  case 'cell-match':
2420
- expression = this.Unresolve(entry.area, context, true, false) + ' ' + entry.expression;
2424
+ if (entry.between) {
2425
+ const addr = this.Unresolve(entry.area, context, true, false);
2426
+ expression = `BETWEEN(${[addr, ...entry.between].join(', ')})`;
2427
+ }
2428
+ else {
2429
+ expression = this.Unresolve(entry.area, context, true, false) + ' ' + entry.expression;
2430
+ }
2421
2431
  break;
2422
2432
 
2423
2433
  case 'expression':
@@ -2440,7 +2450,7 @@ export class Calculator extends Graph {
2440
2450
  entry.min ?? '',
2441
2451
  entry.max ?? '',
2442
2452
 
2443
- ].join(this.parser.argument_separator)
2453
+ ].join(',')
2444
2454
  })`;
2445
2455
  break;
2446
2456
 
@@ -2464,13 +2474,17 @@ export class Calculator extends Graph {
2464
2474
 
2465
2475
  entry.internal.vertex = vertex;
2466
2476
 
2467
- let options: EvaluateOptions|undefined;
2477
+ let options: EvaluateOptions = {
2478
+ argument_separator: ',',
2479
+ };
2480
+
2468
2481
  if (entry.type !== 'gradient' && entry.type !== 'duplicate-values') {
2469
- options = entry.options;
2482
+ options = {...entry.options, ...options};
2470
2483
  }
2471
2484
 
2472
2485
  // first pass, run the calculation
2473
2486
  const check = this.Evaluate(expression, context, options, true);
2487
+
2474
2488
  entry.internal.vertex.result = check;
2475
2489
  entry.internal.vertex.updated = true;
2476
2490
 
@@ -2478,7 +2492,7 @@ export class Calculator extends Graph {
2478
2492
 
2479
2493
  const vertex = entry.internal.vertex as LeafVertex;
2480
2494
  this.AddLeafVertex(vertex);
2481
- this.UpdateLeafVertex(vertex, expression, context);
2495
+ this.UpdateLeafVertex(vertex, expression, context, DecimalMarkType.Period); // force en-us
2482
2496
 
2483
2497
  }
2484
2498
 
@@ -2846,10 +2860,15 @@ export class Calculator extends Graph {
2846
2860
  return dependencies;
2847
2861
  }
2848
2862
 
2849
- protected UpdateLeafVertex(vertex: LeafVertex, formula: string, context: Sheet): void {
2863
+ protected UpdateLeafVertex(vertex: LeafVertex, formula: string, context: Sheet, decimal_mark?: DecimalMarkType): void {
2850
2864
 
2851
2865
  vertex.Reset();
2852
2866
 
2867
+ if (decimal_mark) {
2868
+ this.parser.Save();
2869
+ this.parser.SetLocaleSettings(decimal_mark);
2870
+ }
2871
+
2853
2872
  const parse_result = this.parser.Parse(formula);
2854
2873
  if (parse_result.expression) {
2855
2874
  const dependencies =
@@ -2895,6 +2914,10 @@ export class Calculator extends Graph {
2895
2914
 
2896
2915
  // vertex.UpdateState();
2897
2916
 
2917
+ if (decimal_mark) {
2918
+ this.parser.Restore();
2919
+ }
2920
+
2898
2921
  }
2899
2922
 
2900
2923
  /**
@@ -31,7 +31,7 @@ import { ValueType, GetValueType, Area } from 'treb-base-types';
31
31
  import type { Parser, ExpressionUnit, UnitBinary, UnitIdentifier,
32
32
  UnitGroup, UnitUnary, UnitAddress, UnitRange, UnitCall, UnitDimensionedQuantity, UnitStructuredReference } from 'treb-parser';
33
33
  import type { DataModel, MacroFunction, Sheet } from 'treb-data-model';
34
- import { NameError, ReferenceError, ExpressionError, UnknownError, SpillError } from './function-error';
34
+ import { NameError, ReferenceError, ExpressionError, UnknownError, SpillError, ValueError } from './function-error';
35
35
 
36
36
  import * as Primitives from './primitives';
37
37
 
@@ -633,7 +633,7 @@ export class ExpressionCalculator {
633
633
 
634
634
  }
635
635
 
636
- protected UnaryExpression(x: UnitUnary): (expr: UnitUnary) => UnionValue /*UnionOrArray*/ { // operator: string, operand: any){
636
+ protected UnaryExpression(x: UnitUnary, return_reference = false): (expr: UnitUnary) => UnionValue /*UnionOrArray*/ { // operator: string, operand: any){
637
637
 
638
638
  // there are basically three code paths here: negate, identity, and error.
639
639
  // they have very different semantics so we're going to do them completely
@@ -663,6 +663,110 @@ export class ExpressionCalculator {
663
663
 
664
664
  }
665
665
 
666
+ case '@':
667
+ return (expr: UnitUnary) => {
668
+
669
+ // if the operand is a range, then we need to do implicit intersection.
670
+ // otherwise, calculate the expression and return the first value
671
+ // if it's an array.
672
+
673
+ let address: UnitAddress|undefined;
674
+
675
+ switch (expr.operand.type) {
676
+ case 'address':
677
+ if (expr.operand.spill) {
678
+
679
+ // we need to calculate the result so we know how large the
680
+ // range is... perhaps there's a way to look this up without
681
+ // calculating? (TODO/FIXME)
682
+
683
+ const calculated = this.CellFunction2(expr.operand)();
684
+ if (calculated.type === ValueType.array) {
685
+
686
+ const row = this.context.address.row ?? -1;
687
+ const column = this.context.address.column ?? -1;
688
+
689
+ // for this verison we already have the result, so unless
690
+ // we're trying to preserve the address, we could just
691
+ // return it
692
+
693
+ if (row >= expr.operand.row && row < expr.operand.row + calculated.value[0]?.length && calculated.value.length === 1) {
694
+
695
+ if (!return_reference) {
696
+ return calculated.value[0][row - expr.operand.row];
697
+ }
698
+
699
+ address = {
700
+ ...expr.operand,
701
+ row,
702
+ spill: false,
703
+ };
704
+ }
705
+ else if (column >= expr.operand.column && column < expr.operand.column + calculated.value.length && calculated.value[0]?.length === 1) {
706
+
707
+ if (!return_reference) {
708
+ return calculated.value[column - expr.operand.column][0];
709
+ }
710
+
711
+ address = {
712
+ ...expr.operand,
713
+ column,
714
+ spill: false,
715
+ };
716
+ }
717
+ else {
718
+ return ValueError(); // out of range
719
+ }
720
+
721
+ }
722
+
723
+ // return { type: ValueType.string, value: 'implicit (spill)' };
724
+ }
725
+ break;
726
+
727
+ case 'range':
728
+ {
729
+ // how do we intersect, if at all?
730
+
731
+ const row = this.context.address.row ?? -1;
732
+ const column = this.context.address.column ?? -1;
733
+ if (row >= expr.operand.start.row && row <= expr.operand.end.row && expr.operand.start.column === expr.operand.end.column) {
734
+ address = {
735
+ ...expr.operand.start,
736
+ row,
737
+ spill: false,
738
+ };
739
+ }
740
+ else if (column >= expr.operand.start.column && column <= expr.operand.end.column && expr.operand.start.row === expr.operand.end.row) {
741
+ address = {
742
+ ...expr.operand.start,
743
+ column,
744
+ spill: false,
745
+ };
746
+ }
747
+ else {
748
+ return ValueError(); // out of range
749
+ }
750
+ }
751
+ }
752
+
753
+ if (address) {
754
+ if (return_reference) {
755
+ return { type: ValueType.object, value: address,
756
+ }
757
+ }
758
+ return this.CellFunction2(address)();
759
+ }
760
+
761
+ const operand = this.CalculateExpression(expr.operand as ExtendedExpressionUnit);
762
+
763
+ if (operand.type === ValueType.array) {
764
+ return operand.value[0][0];
765
+ }
766
+
767
+ return operand;
768
+ };
769
+
666
770
  default:
667
771
  return () => {
668
772
  console.warn('unexpected unary operator:', x.operator);
@@ -977,7 +1081,7 @@ export class ExpressionCalculator {
977
1081
  return (expr.fn = this.BinaryExpression(expr))(expr); // check
978
1082
 
979
1083
  case 'unary':
980
- return (expr.fn = this.UnaryExpression(expr))(expr); // check
1084
+ return (expr.fn = this.UnaryExpression(expr, return_reference))(expr); // check
981
1085
 
982
1086
  case 'identifier':
983
1087
  return (expr.fn = this.Identifier(expr))(); // check
@@ -244,6 +244,23 @@ const ZLookup = (value: number|string|boolean|undefined, table: (number|string|b
244
244
 
245
245
  };
246
246
 
247
+ const NumberArgument = (argument?: UnionValue, default_value: number|false = false) => {
248
+
249
+ if (!argument) {
250
+ return default_value;
251
+ }
252
+
253
+ switch (argument.type) {
254
+ case ValueType.number:
255
+ return argument.value;
256
+ case ValueType.undefined:
257
+ return default_value;
258
+ }
259
+
260
+ return false;
261
+
262
+ };
263
+
247
264
  /**
248
265
  * alternate functions. these are used (atm) only for changing complex
249
266
  * behavior.
@@ -534,7 +551,6 @@ export const BaseFunctionLibrary: FunctionMap = {
534
551
  value: delta / divisor,
535
552
  };
536
553
 
537
- return NAError();
538
554
  },
539
555
  },
540
556
 
@@ -1924,6 +1940,17 @@ export const BaseFunctionLibrary: FunctionMap = {
1924
1940
  },
1925
1941
  },
1926
1942
 
1943
+ Ceiling: {
1944
+ arguments: [ { unroll: true }, { unroll: true } ], // FIXME: lazy
1945
+
1946
+ fn: (a: number) => {
1947
+ return {
1948
+ type: ValueType.number,
1949
+ value: Math.ceil(a),
1950
+ };
1951
+ },
1952
+ },
1953
+
1927
1954
  Round: {
1928
1955
  arguments: [ { unroll: true }, { unroll: true } ], // FIXME: lazy
1929
1956
 
@@ -2359,6 +2386,21 @@ export const BaseFunctionLibrary: FunctionMap = {
2359
2386
  },
2360
2387
  },
2361
2388
 
2389
+ Between: {
2390
+ arguments: [
2391
+ { name: 'target', boxed: true, unroll: true },
2392
+ { name: 'min' },
2393
+ { name: 'max' },
2394
+ ],
2395
+ visibility: 'internal',
2396
+ fn: (target: UnionValue, min = 0, max = 1) => {
2397
+ return {
2398
+ type: ValueType.boolean,
2399
+ value: (target.type === ValueType.number) && (target.value >= min && target.value <= max),
2400
+ };
2401
+ }
2402
+ },
2403
+
2362
2404
  Gradient: {
2363
2405
  arguments: [
2364
2406
  { name: 'range', boxed: true },
@@ -2510,7 +2552,41 @@ export const BaseFunctionLibrary: FunctionMap = {
2510
2552
  return ArgumentError();
2511
2553
 
2512
2554
  },
2513
- }
2555
+ },
2556
+
2557
+ Sequence: {
2558
+ arguments:[
2559
+ { name: 'rows', boxed: true },
2560
+ { name: 'columns', default: 1, boxed: true },
2561
+ { name: 'start', default: 1, boxed: true },
2562
+ { name: 'step', default: 1, boxed: true }
2563
+ ],
2564
+ fn: (rows: UnionValue, columns: UnionValue, start: UnionValue, step: UnionValue) => {
2565
+
2566
+ const rx = NumberArgument(rows, 1);
2567
+ const cx = NumberArgument(columns, 1);
2568
+ const step_ = NumberArgument(step, 1);
2569
+ const start_ = NumberArgument(start, 1);
2570
+
2571
+ if (rx === false || cx === false || step_ === false || start_ === false) {
2572
+ return ArgumentError();
2573
+ }
2574
+
2575
+ const value: UnionValue[][] = [];
2576
+ for (let c = 0; c < cx; c++) {
2577
+ const col: UnionValue[] = [];
2578
+ for (let r = 0; r < rx; r++) {
2579
+ col.push({ type: ValueType.number, value: start_ + r * step_ * cx + c * step_ });
2580
+ }
2581
+ value.push(col);
2582
+ }
2583
+
2584
+ return { type: ValueType.array, value };
2585
+
2586
+ },
2587
+
2588
+ },
2589
+
2514
2590
 
2515
2591
  };
2516
2592
 
@@ -173,6 +173,16 @@ export class Sparkline {
173
173
  }
174
174
  }
175
175
 
176
+ if (min === max) {
177
+ if (min) {
178
+ min -= 1;
179
+ max += 1;
180
+ }
181
+ else {
182
+ max += 1;
183
+ }
184
+ }
185
+
176
186
  if (min !== max) {
177
187
 
178
188
  const step = (width * (1 - 2 * x_margin)) / (values.length - 1);
@@ -251,6 +261,16 @@ export class Sparkline {
251
261
  }
252
262
  }
253
263
 
264
+ if (min === max) {
265
+ if (min) {
266
+ min -= 1;
267
+ max += 1;
268
+ }
269
+ else {
270
+ max += 1;
271
+ }
272
+ }
273
+
254
274
  if (values.length) {
255
275
 
256
276
  // const step = (width - 2 * x_margin - 2) / (values.length-1);
@@ -685,6 +685,8 @@ export const StatisticsFunctionLibrary: FunctionMap = {
685
685
  export const StatisticsFunctionAliases: {[index: string]: string} = {
686
686
  Mean: 'Average',
687
687
  'StDev': 'StDev.S',
688
+ 'StDevA': 'StDev.S',
689
+ 'StDevPA': 'StDev.P',
688
690
  'Var': 'Var.S',
689
691
  'Quartile': 'Quartile.Inc',
690
692
  };
@@ -196,6 +196,7 @@ export const ChartFunctions: Record<ChartFunction|SupportFunction, CompositeFunc
196
196
  arguments: [
197
197
  { name: 'Data', metadata: true, },
198
198
  { name: 'Chart Title' },
199
+ { name: 'Min/Max Style' }
199
200
  ],
200
201
  fn: Identity,
201
202
  category: ['chart functions'],
@@ -1,3 +1,23 @@
1
+ /*
2
+ * This file is part of TREB.
3
+ *
4
+ * TREB is free software: you can redistribute it and/or modify it under the
5
+ * terms of the GNU General Public License as published by the Free Software
6
+ * Foundation, either version 3 of the License, or (at your option) any
7
+ * later version.
8
+ *
9
+ * TREB is distributed in the hope that it will be useful, but WITHOUT ANY
10
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12
+ * details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License along
15
+ * with TREB. If not, see <https://www.gnu.org/licenses/>.
16
+ *
17
+ * Copyright 2022-2024 trebco, llc.
18
+ * info@treb.app
19
+ *
20
+ */
1
21
 
2
22
  import { type UnionValue, ValueType, type ArrayUnion, IsComplex, type CellValue } from 'treb-base-types';
3
23
  import { IsArrayUnion, IsMetadata, IsSeries, LegendStyle } from './chart-types';
@@ -7,6 +27,8 @@ import { Util } from './util';
7
27
  import type { ReferenceSeries } from './chart-types';
8
28
  import type { RangeScale } from 'treb-utils';
9
29
 
30
+ import { QuickSort } from './quicksort';
31
+
10
32
  /**
11
33
  * this file is the concrete translation from function arguments
12
34
  * to chart data. chart data is a (somewhat complicated) type with
@@ -148,6 +170,48 @@ export const ArrayToSeries = (array_data: ArrayUnion): SeriesType => {
148
170
 
149
171
  // series.y.data = flat.map(item => typeof item.value === 'number' ? item.value : undefined);
150
172
 
173
+ // console.trace();
174
+
175
+ const values: number[] = []; // filter any undefineds
176
+
177
+ for (const [index, item] of flat.entries()) {
178
+
179
+ let value = 0;
180
+
181
+ // why is this testing type instead of using the union type?
182
+
183
+ if (typeof item.value === 'number') {
184
+ value = item.value;
185
+ // series.y.data[index] = item.value;
186
+ values.push(item.value);
187
+ }
188
+ else if (IsMetadata(item)) {
189
+ if (IsComplex(item.value.value)) {
190
+ series.x.data[index] = item.value.value.real;
191
+ // series.y.data[index] = item.value.value.imaginary;
192
+ // values.push(item.value.value.imaginary);
193
+ value = item.value.value.imaginary;
194
+ }
195
+ else if (typeof item.value.value === 'number') {
196
+ // series.y.data[index] = item.value.value;
197
+ // values.push(item.value.value);
198
+ value = item.value.value;
199
+ }
200
+ else {
201
+ continue;
202
+ }
203
+ }
204
+ else {
205
+ // series.y.data[index] = undefined;
206
+ continue;
207
+ }
208
+
209
+ series.y.data[index] = value;
210
+ values.push(value);
211
+
212
+ }
213
+
214
+ /*
151
215
  series.y.data = flat.map((item, index) => {
152
216
 
153
217
  // if the data is passed in from the output of a function, it will not
@@ -178,6 +242,7 @@ export const ArrayToSeries = (array_data: ArrayUnion): SeriesType => {
178
242
  return undefined;
179
243
 
180
244
  });
245
+ */
181
246
 
182
247
  let first_format = '';
183
248
  if (IsMetadata(flat[0])) {
@@ -190,7 +255,9 @@ export const ArrayToSeries = (array_data: ArrayUnion): SeriesType => {
190
255
  series.y.labels = series.y.data.map(value => (value === undefined) ? undefined : format.Format(value));
191
256
  }
192
257
 
193
- const values = series.y.data.filter(value => value || value === 0) as number[];
258
+ // moved up, integrated loops
259
+ // const values = series.y.data.filter(value => value || value === 0) as number[];
260
+
194
261
  series.y.range = ArrayMinMax(values);
195
262
 
196
263
  // experimenting with complex... this should only be set if we populated
@@ -589,19 +656,31 @@ export const CreateBoxPlot = (args: UnionValue[]): ChartData => {
589
656
 
590
657
  let max_n = 0;
591
658
 
659
+ // change to min-max style
660
+ const minmax = !!args[2];
661
+
592
662
  const stats: BoxPlotData['data'] = series.map(series => {
593
663
  // const data = series.y.data.slice(0).filter((test): test is number => test !== undefined).sort((a, b) => a - b);
594
664
  const data: number[] = [];
595
665
  for (const entry of series.y.data) {
596
666
  if (entry !== undefined) { data.push(entry); }
597
667
  }
598
- data.sort((a, b) => a - b);
668
+
669
+ // data.sort((a, b) => a - b);
670
+ QuickSort(data);
599
671
 
600
672
  const result = BoxStats(data);
601
673
  max_n = Math.max(max_n, result.n);
674
+
675
+ if (minmax) {
676
+ result.whiskers[0] = result.min;
677
+ result.whiskers[1] = result.max;
678
+ }
679
+
602
680
  return result;
603
681
  });
604
682
 
683
+
605
684
  const title = args[1]?.toString() || undefined;
606
685
  const x_labels: string[] = [];
607
686
  const series_names: string[] = [];
@@ -0,0 +1,54 @@
1
+ /*
2
+ * This file is part of TREB.
3
+ *
4
+ * TREB is free software: you can redistribute it and/or modify it under the
5
+ * terms of the GNU General Public License as published by the Free Software
6
+ * Foundation, either version 3 of the License, or (at your option) any
7
+ * later version.
8
+ *
9
+ * TREB is distributed in the hope that it will be useful, but WITHOUT ANY
10
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12
+ * details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License along
15
+ * with TREB. If not, see <https://www.gnu.org/licenses/>.
16
+ *
17
+ * Copyright 2022-2024 trebco, llc.
18
+ * info@treb.app
19
+ *
20
+ */
21
+
22
+ // FIXME: move
23
+
24
+ export const QuickSort = (data: number[]) => {
25
+ Sort(data, 0, data.length);
26
+ };
27
+
28
+ const Partition = (data: number[], left: number, right: number) => {
29
+ const compare = data[right - 1];
30
+ let min_end = left;
31
+ for (let max_end = left; max_end < right - 1; max_end += 1) {
32
+ if (data[max_end] <= compare) {
33
+ Swap(data, max_end, min_end);
34
+ min_end += 1;
35
+ }
36
+ }
37
+ Swap(data, min_end, right - 1);
38
+ return min_end;
39
+ };
40
+
41
+ const Swap = (data: number[], i: number, j: number) => {
42
+ const temp = data[i];
43
+ data[i] = data[j];
44
+ data[j] = temp;
45
+ };
46
+
47
+ const Sort = (data: number[], left: number, right: number) => {
48
+ if (left < right) {
49
+ const p = Partition(data, left, right);
50
+ Sort(data, left, p);
51
+ Sort(data, p + 1, right);
52
+ }
53
+ }
54
+
@@ -139,6 +139,7 @@ export interface ConditionalFormatCellMatch extends ConditionalFormatCellMatchOp
139
139
  export interface ConditionalFormatCellMatchOptions {
140
140
  style: CellStyle;
141
141
  expression: string;
142
+ between?: [number, number];
142
143
  options?: EvaluateOptions;
143
144
  }
144
145
 
@@ -178,8 +179,10 @@ export interface ConditionalFormatDuplicateValues extends ConditionalFormatDupli
178
179
  *
179
180
  * ...everybody has a vertex now, we could standardize it
180
181
  *
182
+ * update: adding a priority field, optional
183
+ *
181
184
  */
182
- export type ConditionalFormat = { internal?: unknown } & (
185
+ export type ConditionalFormat = { internal?: unknown, priority?: number } & (
183
186
  ConditionalFormatDuplicateValues |
184
187
  ConditionalFormatExpression |
185
188
  ConditionalFormatCellMatch |
@@ -3338,9 +3338,6 @@ export class Sheet {
3338
3338
  for (const format of this.conditional_formats) {
3339
3339
 
3340
3340
  if (format.internal?.vertex?.updated) {
3341
-
3342
- // console.info('updated');
3343
-
3344
3341
  format.internal.vertex.updated = false;
3345
3342
  }
3346
3343
 
@@ -3354,6 +3351,10 @@ export class Sheet {
3354
3351
  // stop rule. if you go forwards, you need some sort of indicator
3355
3352
  // or flag).
3356
3353
 
3354
+ // there's more to this, because there are rules that apply to areas,
3355
+ // which might stop, and there's priority. so we probably need those
3356
+ // flags eventually.
3357
+
3357
3358
  const area = JSON.parse(JSON.stringify(format.area));
3358
3359
 
3359
3360
  if (area.start.row === null || area.end.row === null) {