@rowsncolumns/spreadsheet 10.6.0 → 10.6.4

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 (182) hide show
  1. package/dist/cjs/api/cf-data-bar.d.ts +73 -0
  2. package/dist/cjs/api/cf-data-bar.d.ts.map +1 -0
  3. package/dist/cjs/api/cf-data-bar.js +182 -0
  4. package/dist/cjs/api/cf-icons.d.ts +73 -0
  5. package/dist/cjs/api/cf-icons.d.ts.map +1 -0
  6. package/dist/cjs/api/cf-icons.js +322 -0
  7. package/dist/cjs/api/cf-overlays.d.ts +89 -3
  8. package/dist/cjs/api/cf-overlays.d.ts.map +1 -1
  9. package/dist/cjs/api/cf-overlays.js +256 -80
  10. package/dist/cjs/api/cf-priority.d.ts +34 -0
  11. package/dist/cjs/api/cf-priority.d.ts.map +1 -0
  12. package/dist/cjs/api/cf-priority.js +94 -0
  13. package/dist/cjs/api/heatmap.d.ts +7 -1
  14. package/dist/cjs/api/heatmap.d.ts.map +1 -1
  15. package/dist/cjs/api/heatmap.js +27 -11
  16. package/dist/cjs/api/selections.d.ts +2 -2
  17. package/dist/cjs/api/validation.d.ts +6 -0
  18. package/dist/cjs/api/validation.d.ts.map +1 -1
  19. package/dist/cjs/api/validation.js +10 -1
  20. package/dist/cjs/components/canvas-grid/index.d.ts.map +1 -1
  21. package/dist/cjs/components/canvas-grid/index.js +194 -51
  22. package/dist/cjs/components/canvas-grid/overflow.d.ts +37 -0
  23. package/dist/cjs/components/canvas-grid/overflow.d.ts.map +1 -0
  24. package/dist/cjs/components/canvas-grid/overflow.js +49 -0
  25. package/dist/cjs/components/canvas-grid/page-breaks.d.ts +15 -0
  26. package/dist/cjs/components/canvas-grid/page-breaks.d.ts.map +1 -0
  27. package/dist/cjs/components/canvas-grid/page-breaks.js +77 -0
  28. package/dist/cjs/components/canvas-grid/typings.d.ts +25 -0
  29. package/dist/cjs/components/canvas-grid/typings.d.ts.map +1 -1
  30. package/dist/cjs/components/cell/alignment.d.ts +58 -1
  31. package/dist/cjs/components/cell/alignment.d.ts.map +1 -1
  32. package/dist/cjs/components/cell/alignment.js +144 -1
  33. package/dist/cjs/components/cell/cell-background.d.ts +2 -0
  34. package/dist/cjs/components/cell/cell-background.d.ts.map +1 -1
  35. package/dist/cjs/components/cell/cell-background.js +5 -6
  36. package/dist/cjs/components/cell/cell-backgrounds.d.ts.map +1 -1
  37. package/dist/cjs/components/cell/cell-backgrounds.js +5 -1
  38. package/dist/cjs/components/cell/cell-text-runs.js +1 -1
  39. package/dist/cjs/components/cell/index.d.ts.map +1 -1
  40. package/dist/cjs/components/cell/index.js +378 -37
  41. package/dist/cjs/components/cell/number-format-layout.d.ts +35 -0
  42. package/dist/cjs/components/cell/number-format-layout.d.ts.map +1 -0
  43. package/dist/cjs/components/cell/number-format-layout.js +106 -0
  44. package/dist/cjs/components/cell/typings.d.ts +22 -0
  45. package/dist/cjs/components/cell/typings.d.ts.map +1 -1
  46. package/dist/cjs/components/cell-editor/index.js +2 -2
  47. package/dist/cjs/components/cell-filter/index.d.ts.map +1 -1
  48. package/dist/cjs/components/cell-filter/index.js +3 -3
  49. package/dist/cjs/components/cell-overlay/index.d.ts +20 -0
  50. package/dist/cjs/components/cell-overlay/index.d.ts.map +1 -1
  51. package/dist/cjs/components/cell-overlay/index.js +155 -28
  52. package/dist/cjs/components/cf-icon-preview/index.d.ts +16 -0
  53. package/dist/cjs/components/cf-icon-preview/index.d.ts.map +1 -0
  54. package/dist/cjs/components/cf-icon-preview/index.js +48 -0
  55. package/dist/cjs/components/embeds/embed-component.d.ts.map +1 -1
  56. package/dist/cjs/components/embeds/embed-component.js +3 -2
  57. package/dist/cjs/components/hidden-markers/index.d.ts.map +1 -1
  58. package/dist/cjs/components/hidden-markers/index.js +8 -7
  59. package/dist/cjs/components/hidden-markers/style.d.ts +7 -0
  60. package/dist/cjs/components/hidden-markers/style.d.ts.map +1 -1
  61. package/dist/cjs/components/hidden-markers/style.js +13 -2
  62. package/dist/cjs/components/protect-sheet-dialog/index.d.ts +67 -0
  63. package/dist/cjs/components/protect-sheet-dialog/index.d.ts.map +1 -0
  64. package/dist/cjs/components/protect-sheet-dialog/index.js +196 -0
  65. package/dist/cjs/components/toolbar/types.d.ts +1 -1
  66. package/dist/cjs/components/toolbar/types.d.ts.map +1 -1
  67. package/dist/cjs/config.d.ts +13 -1
  68. package/dist/cjs/config.d.ts.map +1 -1
  69. package/dist/cjs/config.js +15 -3
  70. package/dist/cjs/helpers.d.ts +10 -5
  71. package/dist/cjs/helpers.d.ts.map +1 -1
  72. package/dist/cjs/helpers.js +43 -31
  73. package/dist/cjs/hooks/use-border-styles.d.ts.map +1 -1
  74. package/dist/cjs/hooks/use-border-styles.js +5 -28
  75. package/dist/cjs/hooks/use-cell-renderer.d.ts +35 -3
  76. package/dist/cjs/hooks/use-cell-renderer.d.ts.map +1 -1
  77. package/dist/cjs/hooks/use-cell-renderer.js +85 -47
  78. package/dist/cjs/hooks/use-conditional-formatting.d.ts +13 -1
  79. package/dist/cjs/hooks/use-conditional-formatting.d.ts.map +1 -1
  80. package/dist/cjs/hooks/use-conditional-formatting.js +281 -230
  81. package/dist/cjs/index.d.ts +5 -0
  82. package/dist/cjs/index.d.ts.map +1 -1
  83. package/dist/cjs/index.js +5 -0
  84. package/dist/cjs/release-date.js +1 -1
  85. package/dist/cjs/themes.d.ts +8 -0
  86. package/dist/cjs/themes.d.ts.map +1 -1
  87. package/dist/cjs/themes.js +21 -3
  88. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  89. package/dist/cjs/types.d.ts +81 -4
  90. package/dist/cjs/types.d.ts.map +1 -1
  91. package/dist/esm/api/cf-data-bar.d.ts +73 -0
  92. package/dist/esm/api/cf-data-bar.d.ts.map +1 -0
  93. package/dist/esm/api/cf-data-bar.js +178 -0
  94. package/dist/esm/api/cf-icons.d.ts +73 -0
  95. package/dist/esm/api/cf-icons.d.ts.map +1 -0
  96. package/dist/esm/api/cf-icons.js +306 -0
  97. package/dist/esm/api/cf-overlays.d.ts +89 -3
  98. package/dist/esm/api/cf-overlays.d.ts.map +1 -1
  99. package/dist/esm/api/cf-overlays.js +223 -67
  100. package/dist/esm/api/cf-priority.d.ts +34 -0
  101. package/dist/esm/api/cf-priority.d.ts.map +1 -0
  102. package/dist/esm/api/cf-priority.js +61 -0
  103. package/dist/esm/api/heatmap.d.ts +7 -1
  104. package/dist/esm/api/heatmap.d.ts.map +1 -1
  105. package/dist/esm/api/heatmap.js +24 -9
  106. package/dist/esm/api/selections.d.ts +2 -2
  107. package/dist/esm/api/validation.d.ts +6 -0
  108. package/dist/esm/api/validation.d.ts.map +1 -1
  109. package/dist/esm/api/validation.js +6 -0
  110. package/dist/esm/components/canvas-grid/index.d.ts.map +1 -1
  111. package/dist/esm/components/canvas-grid/index.js +163 -47
  112. package/dist/esm/components/canvas-grid/overflow.d.ts +37 -0
  113. package/dist/esm/components/canvas-grid/overflow.d.ts.map +1 -0
  114. package/dist/esm/components/canvas-grid/overflow.js +39 -0
  115. package/dist/esm/components/canvas-grid/page-breaks.d.ts +15 -0
  116. package/dist/esm/components/canvas-grid/page-breaks.d.ts.map +1 -0
  117. package/dist/esm/components/canvas-grid/page-breaks.js +50 -0
  118. package/dist/esm/components/canvas-grid/typings.d.ts +25 -0
  119. package/dist/esm/components/canvas-grid/typings.d.ts.map +1 -1
  120. package/dist/esm/components/cell/alignment.d.ts +58 -1
  121. package/dist/esm/components/cell/alignment.d.ts.map +1 -1
  122. package/dist/esm/components/cell/alignment.js +110 -1
  123. package/dist/esm/components/cell/cell-background.d.ts +2 -0
  124. package/dist/esm/components/cell/cell-background.d.ts.map +1 -1
  125. package/dist/esm/components/cell/cell-background.js +4 -5
  126. package/dist/esm/components/cell/cell-backgrounds.d.ts.map +1 -1
  127. package/dist/esm/components/cell/cell-backgrounds.js +5 -1
  128. package/dist/esm/components/cell/cell-text-runs.js +2 -2
  129. package/dist/esm/components/cell/index.d.ts.map +1 -1
  130. package/dist/esm/components/cell/index.js +339 -39
  131. package/dist/esm/components/cell/number-format-layout.d.ts +35 -0
  132. package/dist/esm/components/cell/number-format-layout.d.ts.map +1 -0
  133. package/dist/esm/components/cell/number-format-layout.js +99 -0
  134. package/dist/esm/components/cell/typings.d.ts +22 -0
  135. package/dist/esm/components/cell/typings.d.ts.map +1 -1
  136. package/dist/esm/components/cell-editor/index.js +2 -2
  137. package/dist/esm/components/cell-filter/index.d.ts.map +1 -1
  138. package/dist/esm/components/cell-filter/index.js +4 -4
  139. package/dist/esm/components/cell-overlay/index.d.ts +20 -0
  140. package/dist/esm/components/cell-overlay/index.d.ts.map +1 -1
  141. package/dist/esm/components/cell-overlay/index.js +111 -25
  142. package/dist/esm/components/cf-icon-preview/index.d.ts +16 -0
  143. package/dist/esm/components/cf-icon-preview/index.d.ts.map +1 -0
  144. package/dist/esm/components/cf-icon-preview/index.js +41 -0
  145. package/dist/esm/components/embeds/embed-component.d.ts.map +1 -1
  146. package/dist/esm/components/embeds/embed-component.js +4 -3
  147. package/dist/esm/components/hidden-markers/index.d.ts.map +1 -1
  148. package/dist/esm/components/hidden-markers/index.js +9 -8
  149. package/dist/esm/components/hidden-markers/style.d.ts +7 -0
  150. package/dist/esm/components/hidden-markers/style.d.ts.map +1 -1
  151. package/dist/esm/components/hidden-markers/style.js +8 -1
  152. package/dist/esm/components/protect-sheet-dialog/index.d.ts +67 -0
  153. package/dist/esm/components/protect-sheet-dialog/index.d.ts.map +1 -0
  154. package/dist/esm/components/protect-sheet-dialog/index.js +154 -0
  155. package/dist/esm/components/toolbar/types.d.ts +1 -1
  156. package/dist/esm/components/toolbar/types.d.ts.map +1 -1
  157. package/dist/esm/config.d.ts +13 -1
  158. package/dist/esm/config.d.ts.map +1 -1
  159. package/dist/esm/config.js +13 -1
  160. package/dist/esm/helpers.d.ts +10 -5
  161. package/dist/esm/helpers.d.ts.map +1 -1
  162. package/dist/esm/helpers.js +40 -28
  163. package/dist/esm/hooks/use-border-styles.d.ts.map +1 -1
  164. package/dist/esm/hooks/use-border-styles.js +5 -26
  165. package/dist/esm/hooks/use-cell-renderer.d.ts +35 -3
  166. package/dist/esm/hooks/use-cell-renderer.d.ts.map +1 -1
  167. package/dist/esm/hooks/use-cell-renderer.js +83 -42
  168. package/dist/esm/hooks/use-conditional-formatting.d.ts +13 -1
  169. package/dist/esm/hooks/use-conditional-formatting.d.ts.map +1 -1
  170. package/dist/esm/hooks/use-conditional-formatting.js +215 -120
  171. package/dist/esm/index.d.ts +5 -0
  172. package/dist/esm/index.d.ts.map +1 -1
  173. package/dist/esm/index.js +5 -0
  174. package/dist/esm/release-date.js +1 -1
  175. package/dist/esm/themes.d.ts +8 -0
  176. package/dist/esm/themes.d.ts.map +1 -1
  177. package/dist/esm/themes.js +20 -3
  178. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  179. package/dist/esm/types.d.ts +81 -4
  180. package/dist/esm/types.d.ts.map +1 -1
  181. package/dist/spreadsheet.min.css +1 -1
  182. package/package.json +4 -4
@@ -1,7 +1,93 @@
1
- import type { CfThreshold, IconSetFamily } from "../types";
1
+ import type { CellFormat, CfThreshold, ConditionalFormatRule, IconSetFamily } from "../types";
2
2
  export declare const ICON_SET_GLYPHS: Record<IconSetFamily, string[]>;
3
+ /** The colour each icon is painted in — what text stand-ins use. */
3
4
  export declare const ICON_SET_COLORS: Record<IconSetFamily, string[]>;
4
- export declare const resolveThreshold: (threshold: CfThreshold, rangeMin: number, rangeMax: number) => number;
5
- export declare const pickIconBucket: (value: number, sortedThresholds: number[]) => number;
5
+ /**
6
+ * Numeric statistics of a rule's covered cells — what cfvo thresholds
7
+ * resolve against. `sorted` (ascending) backs PERCENTILE thresholds; it may
8
+ * be omitted when the caller already knows the rule needs no percentile.
9
+ */
10
+ export type CfRangeStats = {
11
+ count: number;
12
+ min: number;
13
+ max: number;
14
+ sorted?: number[];
15
+ /**
16
+ * Engine-backed stand-ins for `sorted` on windowed hosts: PERCENTILE.INC
17
+ * at `p` (0..100), the k-th largest / smallest value (`k` a percent of the
18
+ * count when `percent`), the mean and the sample standard deviation.
19
+ */
20
+ percentile?: (p: number) => number;
21
+ rankValue?: (k: number, top: boolean, percent?: boolean) => number;
22
+ mean?: number;
23
+ stdDev?: number;
24
+ };
25
+ /** What a rule needs from its full-range statistics (the engine request). */
26
+ export type CfRuleStatsRequest = {
27
+ percentiles: number[];
28
+ ranks: Array<{
29
+ k: number;
30
+ top: boolean;
31
+ percent?: boolean;
32
+ }>;
33
+ };
34
+ /** The engine's reply to a {@link CfRuleStatsRequest} (`conditionalFormatRuleStats`). */
35
+ export type CfRuleStatsResponse = {
36
+ count: number;
37
+ min: number | null;
38
+ max: number | null;
39
+ mean: number | null;
40
+ stdDev: number | null;
41
+ percentiles: Array<number | null>;
42
+ ranks: Array<number | null>;
43
+ };
44
+ /** The percentiles / ranks a rule's thresholds resolve with. */
45
+ export declare const buildCfRuleStatsRequest: (rule: ConditionalFormatRule) => CfRuleStatsRequest;
46
+ /**
47
+ * Turn the engine's reply into {@link CfRangeStats} for the CF hook — what a
48
+ * windowed host returns from `getConditionalFormatRuleStats`.
49
+ */
50
+ export declare const toCfRangeStats: (request: CfRuleStatsRequest, response: CfRuleStatsResponse | null | undefined) => CfRangeStats | undefined;
51
+ export declare const buildCfRangeStats: (values: number[]) => CfRangeStats;
52
+ /** Excel's PERCENTILE.INC over ascending values (`p` in 0..100). */
53
+ export declare const percentileInc: (sorted: number[], p: number) => number;
54
+ /**
55
+ * Resolves a FORMULA threshold's text (e.g. `=$B$1`, `=10`) to a number,
56
+ * or `undefined` when it can't be evaluated.
57
+ */
58
+ export type CfFormulaResolver = (formula: string) => number | undefined;
59
+ /**
60
+ * Resolve a cfvo threshold against the rule's range statistics (OOXML cfvo
61
+ * semantics). `role` picks the AUTOMATIC bound (data bars only): the lower
62
+ * bound is `min(0, lowest)`, the upper `max(0, highest)`. Returns NaN when
63
+ * the threshold can't be resolved.
64
+ */
65
+ export declare const resolveCfThreshold: (threshold: CfThreshold, stats: CfRangeStats, options?: {
66
+ role?: "min" | "max";
67
+ resolveFormula?: CfFormulaResolver;
68
+ }) => number;
69
+ export declare const resolveThreshold: (threshold: CfThreshold, rangeMin: number, rangeMax: number, sorted?: number[]) => number;
70
+ export declare const pickIconBucket: (value: number, thresholds: number[], strict?: boolean[]) => number;
6
71
  export declare const computeDataBarFill: (value: number, min: number, max: number) => number;
72
+ /**
73
+ * Whether a cell counts as a number for data bars, colour scales, icon sets
74
+ * and the other numeric rules: its computed value is a number (Excel's rule —
75
+ * a typed `1` or a spilled `=SEQUENCE(3)` value qualifies without any number
76
+ * format), or it carries a number format (the legacy store's signal).
77
+ */
78
+ export declare const isConditionalFormatNumberCell: (effectiveValue: Record<string, any> | null | undefined, cellFormat: CellFormat | null | undefined) => boolean;
79
+ /**
80
+ * The comparison bound of an above / below average rule: the mean, moved
81
+ * `stdDev` sample standard deviations (Excel's STDEV) above or below it for
82
+ * the "1/2/3 std dev above / below" options. Mirrors the engine's
83
+ * `average_bound` (rnc-store `derive.rs`).
84
+ */
85
+ export declare const averageBound: (values: number[], above: boolean, stdDev?: number | null) => number;
86
+ /**
87
+ * Whether a CSS colour (`#rrggbb` / `rgb()`) is dark — relative luminance
88
+ * under 0.5. Unparseable colours count as light.
89
+ */
90
+ export declare const isDarkCssColor: (color: string | null | undefined) => boolean;
91
+ /** {@link averageBound} from full-range statistics (sorted values or the engine's mean / std dev). */
92
+ export declare const averageBoundFromStats: (stats: CfRangeStats, above: boolean, stdDev?: number | null) => number | undefined;
7
93
  //# sourceMappingURL=cf-overlays.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cf-overlays.d.ts","sourceRoot":"","sources":["../../../api/cf-overlays.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE3D,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,EAAE,CAqB3D,CAAC;AAIF,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,EAAE,CAkB3D,CAAC;AAKF,eAAO,MAAM,gBAAgB,GAC3B,WAAW,WAAW,EACtB,UAAU,MAAM,EAChB,UAAU,MAAM,KACf,MA6BF,CAAC;AAIF,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,EAAE,kBAAkB,MAAM,EAAE,WAQvE,CAAC;AAKF,eAAO,MAAM,kBAAkB,GAC7B,OAAO,MAAM,EACb,KAAK,MAAM,EACX,KAAK,MAAM,KACV,MAWF,CAAC"}
1
+ {"version":3,"file":"cf-overlays.d.ts","sourceRoot":"","sources":["../../../api/cf-overlays.ts"],"names":[],"mappings":"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"}
@@ -1,106 +1,203 @@
1
1
  // Helpers for IconSetRule and DataBarRule evaluation.
2
2
  //
3
- // Excel ships ~17 named icon-set families. We render them with Unicode glyphs
4
- // so no font / SVG asset is required. Each family is an ordered list of
5
- // glyphs from "worst" → "best"; `reverse` on the rule flips this. The number
6
- // of glyphs encodes the family's bucket count (3, 4, or 5).
3
+ // Icon sets are painted as vector icons (`cf-icons.ts`); the glyphs below
4
+ // are the plain-text stand-ins menus (filter by icon, rule lists) show. Every
5
+ // family lists its icons in OOXML `iconId` order — index 0 is the icon of the
6
+ // LOWEST bucket; `reverse` on the rule flips the order. The number of icons
7
+ // encodes the family's bucket count (3, 4, or 5).
7
8
  //
8
- // Bucket selection: given thresholds[0..n-2] (ascending) and the cell's
9
- // numeric value, the bucket index is the count of thresholds the value
10
- // equals-or-exceeds. (i.e. value < thresholds[0] → 0; >= thresholds[n-2] →
11
- // n-1.) Threshold types follow OOXML cfvo semantics: NUMBER is absolute,
12
- // PERCENT/PERCENTILE are computed relative to the range.
9
+ // Bucket selection (Excel): thresholds are checked from the top bucket down;
10
+ // a value lands in the first bucket whose threshold it meets (`>=`, or `>`
11
+ // when the threshold's `gte` is false), else in bucket 0. Threshold types
12
+ // follow OOXML cfvo semantics: NUMBER is absolute, PERCENT is relative to
13
+ // the range's min..max, PERCENTILE is PERCENTILE.INC over the range values.
14
+ import { getExtendedValueNumber } from "@rowsncolumns/utils";
15
+ import { CF_ICON_SETS } from "./cf-icons";
13
16
  export const ICON_SET_GLYPHS = {
14
- // 3-icon families (worst → best)
17
+ // 3-icon families (lowest → highest)
15
18
  "3Arrows": ["▼", "▶", "▲"],
16
19
  "3ArrowsGray": ["▽", "▷", "△"],
17
- "3Flags": ["⚑", "⚐", "⚑"],
20
+ "3Flags": ["⚑", "⚑", "⚑"],
18
21
  "3TrafficLights1": ["●", "●", "●"],
19
- "3TrafficLights2": ["■", "■", "■"],
22
+ "3TrafficLights2": ["●", "●", "●"],
20
23
  "3Signs": ["◆", "▲", "●"],
21
- "3Symbols": ["✖", "❗", "✔"],
24
+ "3Symbols": ["✖", "!", "✔"],
22
25
  "3Symbols2": ["✗", "!", "✓"],
26
+ "3Stars": ["☆", "⯪", "★"],
27
+ "3Triangles": ["▼", "▬", "▲"],
23
28
  // 4-icon families
24
29
  "4Arrows": ["▼", "↘", "↗", "▲"],
25
30
  "4ArrowsGray": ["▽", "↘", "↗", "△"],
26
31
  "4RedToBlack": ["●", "●", "●", "●"],
27
- "4Rating": ["▁", "▃", "▅", "▇"],
28
- "4TrafficLights": ["■", "●", "●", "●"],
32
+ "4Rating": ["▂", "▃", "▅", "▇"],
33
+ "4TrafficLights": ["●", "●", "●", "●"],
29
34
  // 5-icon families
30
35
  "5Arrows": ["▼", "↘", "→", "↗", "▲"],
31
36
  "5ArrowsGray": ["▽", "↘", "→", "↗", "△"],
32
37
  "5Rating": ["▁", "▂", "▄", "▆", "▇"],
33
38
  "5Quarters": ["○", "◔", "◑", "◕", "●"],
39
+ "5Boxes": ["□", "◰", "◧", "◩", "■"],
34
40
  };
35
- // Per-family default colors for traffic lights / signs / arrows. When the
36
- // imported rule doesn't carry explicit per-icon colors we use these.
37
- export const ICON_SET_COLORS = {
38
- "3Arrows": ["#dc2626", "#f59e0b", "#16a34a"],
39
- "3ArrowsGray": ["#6b7280", "#6b7280", "#6b7280"],
40
- "3Flags": ["#dc2626", "#f59e0b", "#16a34a"],
41
- "3TrafficLights1": ["#dc2626", "#f59e0b", "#16a34a"],
42
- "3TrafficLights2": ["#dc2626", "#f59e0b", "#16a34a"],
43
- "3Signs": ["#dc2626", "#f59e0b", "#16a34a"],
44
- "3Symbols": ["#dc2626", "#f59e0b", "#16a34a"],
45
- "3Symbols2": ["#dc2626", "#f59e0b", "#16a34a"],
46
- "4Arrows": ["#dc2626", "#f59e0b", "#84cc16", "#16a34a"],
47
- "4ArrowsGray": ["#6b7280", "#6b7280", "#6b7280", "#6b7280"],
48
- "4RedToBlack": ["#dc2626", "#f97316", "#525252", "#000000"],
49
- "4Rating": ["#6b7280", "#6b7280", "#6b7280", "#6b7280"],
50
- "4TrafficLights": ["#dc2626", "#f59e0b", "#84cc16", "#16a34a"],
51
- "5Arrows": ["#dc2626", "#f59e0b", "#eab308", "#84cc16", "#16a34a"],
52
- "5ArrowsGray": ["#6b7280", "#6b7280", "#6b7280", "#6b7280", "#6b7280"],
53
- "5Rating": ["#6b7280", "#6b7280", "#6b7280", "#6b7280", "#6b7280"],
54
- "5Quarters": ["#6b7280", "#6b7280", "#6b7280", "#6b7280", "#6b7280"],
41
+ const shapeColor = (shape) => {
42
+ switch (shape.kind) {
43
+ case "star":
44
+ return "#F5B800";
45
+ case "rating":
46
+ case "boxes":
47
+ return "#2F6EBA";
48
+ case "quarters":
49
+ return "#262626";
50
+ default:
51
+ return shape.color;
52
+ }
55
53
  };
56
- // Resolve a cfvo threshold value against a range's min/max numeric values.
57
- // Returns NaN when the threshold can't be resolved (e.g. PERCENT against
58
- // an empty range), which downstream callers should skip.
59
- export const resolveThreshold = (threshold, rangeMin, rangeMax) => {
60
- const raw = threshold.value;
61
- const num = typeof raw === "number" ? raw : Number(raw);
54
+ /** The colour each icon is painted in — what text stand-ins use. */
55
+ export const ICON_SET_COLORS = Object.fromEntries(Object.entries(CF_ICON_SETS).map(([family, shapes]) => [
56
+ family,
57
+ shapes.map(shapeColor),
58
+ ]));
59
+ /** The percentiles / ranks a rule's thresholds resolve with. */
60
+ export const buildCfRuleStatsRequest = (rule) => {
61
+ const percentiles = new Set();
62
+ const addPoint = (point) => {
63
+ if (point?.type !== "PERCENTILE")
64
+ return;
65
+ const p = Number(point.value);
66
+ if (!Number.isFinite(p))
67
+ return;
68
+ percentiles.add(p);
69
+ // Colour scales read a 0..1 percentile as a fraction.
70
+ if (p <= 1)
71
+ percentiles.add(p * 100);
72
+ };
73
+ addPoint(rule.dataBarRule?.minpoint);
74
+ addPoint(rule.dataBarRule?.maxpoint);
75
+ rule.iconSetRule?.thresholds.forEach(addPoint);
76
+ addPoint(rule.gradientRule?.minpoint);
77
+ addPoint(rule.gradientRule?.midpoint);
78
+ addPoint(rule.gradientRule?.maxpoint);
79
+ const ranks = [];
80
+ if (rule.topBottomRule) {
81
+ ranks.push({
82
+ k: rule.topBottomRule.rank,
83
+ top: rule.topBottomRule.type === "TOP",
84
+ percent: !!rule.topBottomRule.isPercent,
85
+ });
86
+ }
87
+ return { percentiles: [...percentiles], ranks };
88
+ };
89
+ /**
90
+ * Turn the engine's reply into {@link CfRangeStats} for the CF hook — what a
91
+ * windowed host returns from `getConditionalFormatRuleStats`.
92
+ */
93
+ export const toCfRangeStats = (request, response) => {
94
+ if (!response)
95
+ return undefined;
96
+ const at = (list, i) => i >= 0 && list[i] != null ? list[i] : NaN;
97
+ return {
98
+ count: response.count,
99
+ min: response.min ?? NaN,
100
+ max: response.max ?? NaN,
101
+ mean: response.mean ?? undefined,
102
+ stdDev: response.stdDev ?? undefined,
103
+ percentile: (p) => at(response.percentiles, request.percentiles.indexOf(p)),
104
+ rankValue: (k, top, percent) => at(response.ranks, request.ranks.findIndex((r) => r.k === k && r.top === top && !!r.percent === !!percent)),
105
+ };
106
+ };
107
+ export const buildCfRangeStats = (values) => {
108
+ const sorted = values.slice().sort((a, b) => a - b);
109
+ return {
110
+ count: sorted.length,
111
+ min: sorted.length ? sorted[0] : NaN,
112
+ max: sorted.length ? sorted[sorted.length - 1] : NaN,
113
+ sorted,
114
+ };
115
+ };
116
+ /** Excel's PERCENTILE.INC over ascending values (`p` in 0..100). */
117
+ export const percentileInc = (sorted, p) => {
118
+ if (!sorted.length || !Number.isFinite(p))
119
+ return NaN;
120
+ const k = (Math.max(0, Math.min(100, p)) / 100) * (sorted.length - 1);
121
+ const lower = Math.floor(k);
122
+ const upper = Math.ceil(k);
123
+ if (lower === upper)
124
+ return sorted[lower];
125
+ return sorted[lower] + (sorted[upper] - sorted[lower]) * (k - lower);
126
+ };
127
+ const numericValue = (raw) => typeof raw === "number" ? raw : raw == null || raw === "" ? NaN : Number(raw);
128
+ /**
129
+ * Resolve a cfvo threshold against the rule's range statistics (OOXML cfvo
130
+ * semantics). `role` picks the AUTOMATIC bound (data bars only): the lower
131
+ * bound is `min(0, lowest)`, the upper `max(0, highest)`. Returns NaN when
132
+ * the threshold can't be resolved.
133
+ */
134
+ export const resolveCfThreshold = (threshold, stats, options = {}) => {
135
+ const num = numericValue(threshold.value);
62
136
  switch (threshold.type) {
63
137
  case "MIN":
64
- return rangeMin;
138
+ return stats.min;
65
139
  case "MAX":
66
- return rangeMax;
140
+ return stats.max;
141
+ case "AUTOMATIC":
142
+ return options.role === "max"
143
+ ? Math.max(0, stats.max)
144
+ : Math.min(0, stats.min);
67
145
  case "NUMBER":
68
146
  return Number.isFinite(num) ? num : NaN;
69
- case "PERCENT": {
147
+ case "PERCENT":
70
148
  if (!Number.isFinite(num))
71
149
  return NaN;
72
- const span = rangeMax - rangeMin;
73
- return rangeMin + (span * num) / 100;
74
- }
150
+ return stats.min + ((stats.max - stats.min) * num) / 100;
75
151
  case "PERCENTILE":
76
- // Approximate percentile by linear interpolation of min/max — the
77
- // engine doesn't have access to the sorted value list here. For tight
78
- // OOXML conformance the caller can pass a true percentile separately.
79
152
  if (!Number.isFinite(num))
80
153
  return NaN;
81
- return rangeMin + (rangeMax - rangeMin) * (num / 100);
82
- case "FORMULA":
83
- // FORMULA thresholds aren't evaluated client-side here; the importer
84
- // will have substituted a computed number into `value`. If still a
85
- // string, skip.
86
- return Number.isFinite(num) ? num : NaN;
154
+ return stats.sorted
155
+ ? percentileInc(stats.sorted, num)
156
+ : stats.percentile
157
+ ? stats.percentile(num)
158
+ : stats.min + (stats.max - stats.min) * (num / 100);
159
+ case "FORMULA": {
160
+ if (Number.isFinite(num))
161
+ return num;
162
+ const text = String(threshold.value ?? "").trim();
163
+ if (!text)
164
+ return NaN;
165
+ const literal = Number(text.replace(/^=/, ""));
166
+ if (text.replace(/^=/, "") !== "" && Number.isFinite(literal)) {
167
+ return literal;
168
+ }
169
+ const resolved = options.resolveFormula?.(text);
170
+ return typeof resolved === "number" && Number.isFinite(resolved)
171
+ ? resolved
172
+ : NaN;
173
+ }
87
174
  default:
88
175
  return NaN;
89
176
  }
90
177
  };
91
- // Pick the icon bucket index for a value given ordered thresholds.
92
- // thresholds: length = (iconCount - 1), ascending.
93
- export const pickIconBucket = (value, sortedThresholds) => {
94
- let bucket = 0;
95
- for (const t of sortedThresholds) {
178
+ // Resolve a cfvo threshold value against a range's min/max numeric values.
179
+ // Returns NaN when the threshold can't be resolved (e.g. PERCENT against
180
+ // an empty range), which downstream callers should skip. Pass `sorted`
181
+ // values for an exact PERCENTILE.
182
+ export const resolveThreshold = (threshold, rangeMin, rangeMax, sorted) => resolveCfThreshold(threshold, {
183
+ count: sorted?.length ?? 0,
184
+ min: rangeMin,
185
+ max: rangeMax,
186
+ sorted,
187
+ });
188
+ // Pick the icon bucket index for a value. `thresholds[i]` is the lower bound
189
+ // of bucket `i + 1` (length = iconCount - 1); `strict[i]` makes it a `>`
190
+ // comparison. Checked from the top bucket down, like Excel; non-finite
191
+ // thresholds never match.
192
+ export const pickIconBucket = (value, thresholds, strict) => {
193
+ for (let i = thresholds.length - 1; i >= 0; i--) {
194
+ const t = thresholds[i];
96
195
  if (!Number.isFinite(t))
97
- break;
98
- if (value >= t)
99
- bucket++;
100
- else
101
- break;
196
+ continue;
197
+ if (strict?.[i] ? value > t : value >= t)
198
+ return i + 1;
102
199
  }
103
- return bucket;
200
+ return 0;
104
201
  };
105
202
  // Compute a 0..1 fill fraction for a data-bar cell value.
106
203
  // When `min >= max`, all cells in the range share the same value; return 1
@@ -116,3 +213,62 @@ export const computeDataBarFill = (value, min, max) => {
116
213
  const clamped = Math.max(min, Math.min(max, value));
117
214
  return (clamped - min) / (max - min);
118
215
  };
216
+ /**
217
+ * Whether a cell counts as a number for data bars, colour scales, icon sets
218
+ * and the other numeric rules: its computed value is a number (Excel's rule —
219
+ * a typed `1` or a spilled `=SEQUENCE(3)` value qualifies without any number
220
+ * format), or it carries a number format (the legacy store's signal).
221
+ */
222
+ export const isConditionalFormatNumberCell = (effectiveValue, cellFormat) => getExtendedValueNumber(effectiveValue) != null ||
223
+ !!cellFormat?.numberFormat?.type;
224
+ /**
225
+ * The comparison bound of an above / below average rule: the mean, moved
226
+ * `stdDev` sample standard deviations (Excel's STDEV) above or below it for
227
+ * the "1/2/3 std dev above / below" options. Mirrors the engine's
228
+ * `average_bound` (rnc-store `derive.rs`).
229
+ */
230
+ export const averageBound = (values, above, stdDev) => {
231
+ const mean = values.reduce((sum, v) => sum + v, 0) / values.length;
232
+ if (!stdDev || stdDev <= 0 || values.length < 2)
233
+ return mean;
234
+ const variance = values.reduce((sum, v) => sum + (v - mean) ** 2, 0) / (values.length - 1);
235
+ const offset = stdDev * Math.sqrt(variance);
236
+ return above ? mean + offset : mean - offset;
237
+ };
238
+ /**
239
+ * Whether a CSS colour (`#rrggbb` / `rgb()`) is dark — relative luminance
240
+ * under 0.5. Unparseable colours count as light.
241
+ */
242
+ export const isDarkCssColor = (color) => {
243
+ if (!color)
244
+ return false;
245
+ const hex = /^#?([0-9a-f]{6})/i.exec(color.trim());
246
+ const rgb = /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i.exec(color.trim());
247
+ let channels;
248
+ if (hex) {
249
+ const n = parseInt(hex[1], 16);
250
+ channels = [(n >> 16) & 255, (n >> 8) & 255, n & 255];
251
+ }
252
+ else if (rgb) {
253
+ channels = [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])];
254
+ }
255
+ if (!channels)
256
+ return false;
257
+ const [r, g, b] = channels.map((c) => {
258
+ const v = c / 255;
259
+ return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
260
+ });
261
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b < 0.5;
262
+ };
263
+ /** {@link averageBound} from full-range statistics (sorted values or the engine's mean / std dev). */
264
+ export const averageBoundFromStats = (stats, above, stdDev) => {
265
+ if (stats.sorted) {
266
+ return stats.sorted.length
267
+ ? averageBound(stats.sorted, above, stdDev)
268
+ : undefined;
269
+ }
270
+ if (stats.mean == null || !stats.count)
271
+ return undefined;
272
+ const offset = stdDev && stdDev > 0 ? stdDev * (stats.stdDev ?? 0) : 0;
273
+ return above ? stats.mean + offset : stats.mean - offset;
274
+ };
@@ -0,0 +1,34 @@
1
+ import type { ConditionalFormatRule } from "../types";
2
+ /**
3
+ * Conditional-format precedence, as Excel defines it ("Manage conditional formatting rule
4
+ * precedence", support.microsoft.com): the rule listed FIRST has the highest precedence and wins a
5
+ * conflicting format; new rules go to the top of the list.
6
+ *
7
+ * `priority` carries that order (lower = higher precedence, as in xlsx `<cfRule priority>`). A rule
8
+ * without one ranks below every rule that has one; ties keep the stored order. The grid paints, the
9
+ * Rules Manager lists and the exporters number rules with this one ordering.
10
+ */
11
+ export declare const sortConditionalFormatRules: <T extends Pick<ConditionalFormatRule, "priority">>(rules: ReadonlyArray<T>) => T[];
12
+ /**
13
+ * The `priority` that puts a new rule above every existing one (Excel adds new rules at the top of
14
+ * the list). Priorities may go to zero or below; exporters renumber from 1.
15
+ */
16
+ export declare const getTopConditionalFormatPriority: (rules: ReadonlyArray<Pick<ConditionalFormatRule, "priority">>) => number;
17
+ /** A new rule with its `priority` set to the top of `rules`, unless it already carries one. */
18
+ export declare const withTopConditionalFormatPriority: <T extends Pick<ConditionalFormatRule, "priority">>(rule: T, rules: ReadonlyArray<Pick<ConditionalFormatRule, "priority">>) => T;
19
+ /**
20
+ * Move one rule up (`-1`) or down (`+1`) among `scope` (the rules the Rules Manager is showing —
21
+ * "Show formatting rules for" may hide some), returning the `priority` updates that realise the new
22
+ * order: every rule of the workbook renumbered 1..N in its new precedence order, keeping only the
23
+ * rules whose priority changes. Empty when the move is a no-op (first rule up, last rule down).
24
+ */
25
+ export declare const planConditionalFormatMove: <T extends Pick<ConditionalFormatRule, "id" | "priority">>(rules: ReadonlyArray<T>, ruleId: T["id"], direction: -1 | 1, scope?: ReadonlyArray<Pick<T, "id">>) => Array<{
26
+ rule: T;
27
+ priority: number;
28
+ }>;
29
+ /** `priority` updates that make `ordered` the precedence order (1..N), changed rules only. */
30
+ export declare const planConditionalFormatOrder: <T extends Pick<ConditionalFormatRule, "priority">>(ordered: ReadonlyArray<T>) => Array<{
31
+ rule: T;
32
+ priority: number;
33
+ }>;
34
+ //# sourceMappingURL=cf-priority.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cf-priority.d.ts","sourceRoot":"","sources":["../../../api/cf-priority.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAEtD;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,GACrC,CAAC,SAAS,IAAI,CAAC,qBAAqB,EAAE,UAAU,CAAC,EAEjD,OAAO,aAAa,CAAC,CAAC,CAAC,KACtB,CAAC,EAWwB,CAAC;AAE7B;;;GAGG;AACH,eAAO,MAAM,+BAA+B,GAC1C,OAAO,aAAa,CAAC,IAAI,CAAC,qBAAqB,EAAE,UAAU,CAAC,CAAC,KAC5D,MAQF,CAAC;AAEF,+FAA+F;AAC/F,eAAO,MAAM,gCAAgC,GAC3C,CAAC,SAAS,IAAI,CAAC,qBAAqB,EAAE,UAAU,CAAC,EAEjD,MAAM,CAAC,EACP,OAAO,aAAa,CAAC,IAAI,CAAC,qBAAqB,EAAE,UAAU,CAAC,CAAC,KAC5D,CAGgE,CAAC;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,GACpC,CAAC,SAAS,IAAI,CAAC,qBAAqB,EAAE,IAAI,GAAG,UAAU,CAAC,EAExD,OAAO,aAAa,CAAC,CAAC,CAAC,EACvB,QAAQ,CAAC,CAAC,IAAI,CAAC,EACf,WAAW,CAAC,CAAC,GAAG,CAAC,EACjB,QAAO,aAAa,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAS,KAC1C,KAAK,CAAC;IAAE,IAAI,EAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAerC,CAAC;AAEF,8FAA8F;AAC9F,eAAO,MAAM,0BAA0B,GACrC,CAAC,SAAS,IAAI,CAAC,qBAAqB,EAAE,UAAU,CAAC,EAEjD,SAAS,aAAa,CAAC,CAAC,CAAC,KACxB,KAAK,CAAC;IAAE,IAAI,EAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAGnC,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Conditional-format precedence, as Excel defines it ("Manage conditional formatting rule
3
+ * precedence", support.microsoft.com): the rule listed FIRST has the highest precedence and wins a
4
+ * conflicting format; new rules go to the top of the list.
5
+ *
6
+ * `priority` carries that order (lower = higher precedence, as in xlsx `<cfRule priority>`). A rule
7
+ * without one ranks below every rule that has one; ties keep the stored order. The grid paints, the
8
+ * Rules Manager lists and the exporters number rules with this one ordering.
9
+ */
10
+ export const sortConditionalFormatRules = (rules) => rules
11
+ .map((rule, index) => ({ rule, index }))
12
+ .sort((left, right) => {
13
+ const leftPriority = left.rule.priority ?? Number.POSITIVE_INFINITY;
14
+ const rightPriority = right.rule.priority ?? Number.POSITIVE_INFINITY;
15
+ if (leftPriority === rightPriority) {
16
+ return left.index - right.index;
17
+ }
18
+ return leftPriority - rightPriority;
19
+ })
20
+ .map(({ rule }) => rule);
21
+ /**
22
+ * The `priority` that puts a new rule above every existing one (Excel adds new rules at the top of
23
+ * the list). Priorities may go to zero or below; exporters renumber from 1.
24
+ */
25
+ export const getTopConditionalFormatPriority = (rules) => {
26
+ let top = 1;
27
+ for (const rule of rules) {
28
+ if (typeof rule.priority === "number" && Number.isFinite(rule.priority)) {
29
+ top = Math.min(top, rule.priority);
30
+ }
31
+ }
32
+ return top - 1;
33
+ };
34
+ /** A new rule with its `priority` set to the top of `rules`, unless it already carries one. */
35
+ export const withTopConditionalFormatPriority = (rule, rules) => typeof rule.priority === "number"
36
+ ? rule
37
+ : { ...rule, priority: getTopConditionalFormatPriority(rules) };
38
+ /**
39
+ * Move one rule up (`-1`) or down (`+1`) among `scope` (the rules the Rules Manager is showing —
40
+ * "Show formatting rules for" may hide some), returning the `priority` updates that realise the new
41
+ * order: every rule of the workbook renumbered 1..N in its new precedence order, keeping only the
42
+ * rules whose priority changes. Empty when the move is a no-op (first rule up, last rule down).
43
+ */
44
+ export const planConditionalFormatMove = (rules, ruleId, direction, scope = rules) => {
45
+ const ordered = sortConditionalFormatRules(rules);
46
+ const scopeIds = new Set(scope.map((rule) => rule.id));
47
+ const visible = ordered.filter((rule) => scopeIds.has(rule.id));
48
+ const at = visible.findIndex((rule) => rule.id === ruleId);
49
+ const neighbour = visible[at + direction];
50
+ if (at === -1 || !neighbour) {
51
+ return [];
52
+ }
53
+ const from = ordered.indexOf(visible[at]);
54
+ const to = ordered.indexOf(neighbour);
55
+ const next = [...ordered];
56
+ next[from] = neighbour;
57
+ next[to] = visible[at];
58
+ return planConditionalFormatOrder(next);
59
+ };
60
+ /** `priority` updates that make `ordered` the precedence order (1..N), changed rules only. */
61
+ export const planConditionalFormatOrder = (ordered) => ordered.flatMap((rule, index) => rule.priority === index + 1 ? [] : [{ rule, priority: index + 1 }]);
@@ -1,4 +1,10 @@
1
1
  import type { SpreadsheetTheme } from "../components/canvas-grid/typings";
2
2
  import type { GradientRule } from "../types";
3
- export declare const getGradientColors: (values: (number | undefined)[][][], gradientRule: GradientRule, theme: SpreadsheetTheme) => (string | undefined)[][][];
3
+ import type { CfRangeStats } from "./cf-overlays";
4
+ /**
5
+ * `population` — the rule's full-range statistics when `values` is only the
6
+ * visible slice of a windowed rule; min / mid / max resolve over it instead
7
+ * of the slice.
8
+ */
9
+ export declare const getGradientColors: (values: (number | undefined)[][][], gradientRule: GradientRule, theme: SpreadsheetTheme, population?: CfRangeStats) => (string | undefined)[][][];
4
10
  //# sourceMappingURL=heatmap.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"heatmap.d.ts","sourceRoot":"","sources":["../../../api/heatmap.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,EAAE,EAAE,EAClC,cAAc,YAAY,EAC1B,OAAO,gBAAgB,+BAgExB,CAAC"}
1
+ {"version":3,"file":"heatmap.d.ts","sourceRoot":"","sources":["../../../api/heatmap.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,EAAE,EAAE,EAClC,cAAc,YAAY,EAC1B,OAAO,gBAAgB,EACvB,aAAa,YAAY,+BAoE1B,CAAC"}
@@ -1,9 +1,14 @@
1
1
  import { hexToRgb, isNil, rgbToHex } from "@rowsncolumns/utils";
2
2
  import { getStringifiedColor } from "../themes";
3
- export const getGradientColors = (values, gradientRule, theme) => {
3
+ /**
4
+ * `population` — the rule's full-range statistics when `values` is only the
5
+ * visible slice of a windowed rule; min / mid / max resolve over it instead
6
+ * of the slice.
7
+ */
8
+ export const getGradientColors = (values, gradientRule, theme, population) => {
4
9
  const colors = [];
5
10
  const { minpoint, midpoint, maxpoint } = gradientRule;
6
- const [minValue, midValue, maxValue] = getMinMidMax(values, gradientRule);
11
+ const [minValue, midValue, maxValue] = getMinMidMax(population?.sorted ? [[population.sorted]] : values, gradientRule, population?.sorted ? undefined : population);
7
12
  const range = maxValue - minValue || 1;
8
13
  const minColor = getStringifiedColor(minpoint?.color, theme);
9
14
  const midColor = getStringifiedColor(midpoint?.color, theme);
@@ -67,16 +72,20 @@ const interpolateColor = (color1, color2, ratio) => {
67
72
  const interpolate = (value1, value2, ratio) => {
68
73
  return value1 + (value2 - value1) * ratio;
69
74
  };
70
- const getMinMidMax = (values, { minpoint, midpoint, maxpoint }) => {
71
- const flattenedValues = values
72
- .flat(2)
73
- .filter((value) => typeof value === "number" && Number.isFinite(value));
74
- if (flattenedValues.length === 0) {
75
+ const getMinMidMax = (values, { minpoint, midpoint, maxpoint },
76
+ // Engine statistics without the sorted values (windowed hosts).
77
+ stats) => {
78
+ const flattenedValues = stats
79
+ ? []
80
+ : values
81
+ .flat(2)
82
+ .filter((value) => typeof value === "number" && Number.isFinite(value));
83
+ if (stats ? !stats.count : flattenedValues.length === 0) {
75
84
  return [0, 0, 0];
76
85
  }
77
86
  const sortedValues = [...flattenedValues].sort((a, b) => a - b);
78
- const dataMin = sortedValues[0];
79
- const dataMax = sortedValues[sortedValues.length - 1];
87
+ const dataMin = stats ? stats.min : sortedValues[0];
88
+ const dataMax = stats ? stats.max : sortedValues[sortedValues.length - 1];
80
89
  const parseNumericInput = (value) => {
81
90
  if (typeof value === "number") {
82
91
  return Number.isFinite(value) ? value : undefined;
@@ -93,6 +102,12 @@ const getMinMidMax = (values, { minpoint, midpoint, maxpoint }) => {
93
102
  return undefined;
94
103
  };
95
104
  const getPercentileValue = (percent) => {
105
+ if (stats) {
106
+ const value = stats.percentile?.(percent);
107
+ return value !== undefined && Number.isFinite(value)
108
+ ? value
109
+ : dataMin + ((dataMax - dataMin) * percent) / 100;
110
+ }
96
111
  if (!sortedValues.length) {
97
112
  return dataMin;
98
113
  }
@@ -8,7 +8,7 @@ import type { SheetRange, TableView } from "../types";
8
8
  * @param isActiveCell
9
9
  * @param isSelection
10
10
  */
11
- export declare const getSelectionStrokeStyle: (formulaMode: boolean, isFill: boolean, isActiveCell: boolean, emptySelections: boolean) => "solid" | "dashed";
11
+ export declare const getSelectionStrokeStyle: (formulaMode: boolean, isFill: boolean, isActiveCell: boolean, emptySelections: boolean) => "dashed" | "solid";
12
12
  export type SelectionStrokeOptions = {
13
13
  /** Border width of a range selection outside formula mode. Default `1`. */
14
14
  selectionStrokeWidth?: number;
@@ -37,7 +37,7 @@ export declare const getSelectionStrokeWidth: (formulaMode: boolean, isFill: boo
37
37
  * @param isFill
38
38
  * @returns
39
39
  */
40
- export declare const getSelectionFillOpacity: (formulaMode: boolean, isFill: boolean) => 0 | 1 | 0.2;
40
+ export declare const getSelectionFillOpacity: (formulaMode: boolean, isFill: boolean) => 1 | 0 | 0.2;
41
41
  /**
42
42
  * Get background color of selection
43
43
  * @param formulaMode