cx 26.7.6 → 26.9.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 (73) hide show
  1. package/build/charts/Legend.d.ts +8 -1
  2. package/build/charts/Legend.d.ts.map +1 -1
  3. package/build/charts/Legend.js +8 -4
  4. package/build/charts/LineGraph.d.ts +8 -16
  5. package/build/charts/LineGraph.d.ts.map +1 -1
  6. package/build/charts/LineGraph.js +76 -69
  7. package/build/charts/axis/NumericAxis.d.ts.map +1 -1
  8. package/build/charts/axis/NumericAxis.js +1 -254
  9. package/build/charts/axis/NumericScale.d.ts +59 -0
  10. package/build/charts/axis/NumericScale.d.ts.map +1 -0
  11. package/build/charts/axis/NumericScale.js +254 -0
  12. package/build/jsx-dev-runtime.d.ts +2 -1
  13. package/build/jsx-dev-runtime.d.ts.map +1 -1
  14. package/build/svg/Svg.d.ts +9 -0
  15. package/build/svg/Svg.d.ts.map +1 -1
  16. package/build/svg/Svg.js +9 -1
  17. package/build/ui/index.d.ts +3 -2
  18. package/build/ui/index.d.ts.map +1 -1
  19. package/build/ui/index.js +1 -1
  20. package/build/util/DOM.d.ts.map +1 -1
  21. package/build/util/DOM.js +16 -4
  22. package/build/util/getActiveElement.d.ts +1 -1
  23. package/build/util/getActiveElement.d.ts.map +1 -1
  24. package/build/util/getActiveElement.js +14 -2
  25. package/build/widgets/form/TextArea.d.ts +4 -0
  26. package/build/widgets/form/TextArea.d.ts.map +1 -1
  27. package/build/widgets/form/TextArea.js +7 -1
  28. package/build/widgets/grid/Grid.d.ts +12 -2
  29. package/build/widgets/grid/Grid.d.ts.map +1 -1
  30. package/build/widgets/grid/Grid.js +17 -4
  31. package/build/widgets/nav/MenuItem.js +2 -2
  32. package/build/widgets/overlay/Dropdown.d.ts +4 -4
  33. package/build/widgets/overlay/Dropdown.d.ts.map +1 -1
  34. package/build/widgets/overlay/Dropdown.js +82 -38
  35. package/build/widgets/overlay/Overlay.d.ts.map +1 -1
  36. package/build/widgets/overlay/Overlay.js +7 -3
  37. package/build/widgets/overlay/Window.d.ts.map +1 -1
  38. package/build/widgets/overlay/Window.js +2 -1
  39. package/dist/charts.js +166 -168
  40. package/dist/manifest.js +805 -802
  41. package/dist/svg.js +13 -0
  42. package/dist/util.js +31 -9
  43. package/dist/widgets.js +153 -50
  44. package/package.json +1 -1
  45. package/src/charts/Legend.tsx +22 -2
  46. package/src/charts/LineGraph.tsx +80 -98
  47. package/src/charts/axis/NumericAxis.spec.ts +40 -0
  48. package/src/charts/axis/NumericAxis.tsx +1 -290
  49. package/src/charts/axis/NumericScale.ts +290 -0
  50. package/src/jsx-dev-runtime.ts +2 -1
  51. package/src/svg/Svg.tsx +24 -1
  52. package/src/ui/Cx.tsx +505 -505
  53. package/src/ui/DataProxy.ts +55 -55
  54. package/src/ui/Rescope.ts +50 -50
  55. package/src/ui/exprHelpers.ts +96 -96
  56. package/src/ui/index.ts +3 -6
  57. package/src/util/DOM.ts +15 -4
  58. package/src/util/getActiveElement.ts +16 -2
  59. package/src/widgets/Sandbox.ts +104 -104
  60. package/src/widgets/form/ColorField.scss +112 -112
  61. package/src/widgets/form/DateTimeField.scss +111 -111
  62. package/src/widgets/form/LookupField.maps.scss +26 -26
  63. package/src/widgets/form/MonthField.scss +113 -113
  64. package/src/widgets/form/Select.scss +104 -104
  65. package/src/widgets/form/TextArea.tsx +10 -1
  66. package/src/widgets/form/TextField.scss +66 -66
  67. package/src/widgets/grid/Grid.sorting.spec.tsx +188 -0
  68. package/src/widgets/grid/Grid.tsx +31 -7
  69. package/src/widgets/index.ts +41 -41
  70. package/src/widgets/nav/MenuItem.tsx +2 -2
  71. package/src/widgets/overlay/Dropdown.tsx +87 -27
  72. package/src/widgets/overlay/Overlay.tsx +5 -3
  73. package/src/widgets/overlay/Window.tsx +2 -1
@@ -0,0 +1,290 @@
1
+ import { Stack } from "./Stack";
2
+ import { isNumber } from "../../util/isNumber";
3
+ import { Console } from "../../util/Console";
4
+
5
+ export class NumericScale {
6
+ min: number;
7
+ max: number;
8
+ snapToTicks: number;
9
+ tickDivisions: number[][];
10
+ minLabelDistance: number;
11
+ minLabelTickSize: number;
12
+ minTickDistance: number;
13
+ minTickStep: number;
14
+ tickSizes: number[];
15
+ normalized: boolean;
16
+ inverted: boolean;
17
+ minValue?: number;
18
+ maxValue?: number;
19
+ minValuePadded: number;
20
+ maxValuePadded: number;
21
+ stacks: Record<string, Stack>;
22
+ lowerDeadZone: number;
23
+ upperDeadZone: number;
24
+ origin: number;
25
+ scale: { factor: number; min: number; max: number; minPadding: number; maxPadding: number };
26
+ a: number;
27
+ b: number;
28
+ shouldUpdate: boolean;
29
+
30
+ reset(
31
+ min: number,
32
+ max: number,
33
+ snapToTicks: number,
34
+ tickDivisions: number[][],
35
+ minTickDistance: number,
36
+ minTickStep: number,
37
+ minLabelDistance: number,
38
+ minLabelTickSize: number,
39
+ normalized: boolean,
40
+ inverted: boolean,
41
+ lowerDeadZone: number,
42
+ upperDeadZone: number,
43
+ ): void {
44
+ this.min = min;
45
+ this.max = max;
46
+ this.snapToTicks = snapToTicks;
47
+ this.tickDivisions = tickDivisions;
48
+ this.minLabelDistance = minLabelDistance;
49
+ this.minLabelTickSize = minLabelTickSize;
50
+ this.minTickDistance = minTickDistance;
51
+ this.minTickStep = minTickStep;
52
+ this.tickSizes = [];
53
+ this.normalized = normalized;
54
+ this.inverted = inverted;
55
+ delete this.minValue;
56
+ delete this.maxValue;
57
+ this.stacks = {};
58
+ this.lowerDeadZone = lowerDeadZone || 0;
59
+ this.upperDeadZone = upperDeadZone || 0;
60
+ }
61
+
62
+ map(v: number, offset: number = 0): number {
63
+ return this.origin + (v + offset - this.scale.min + this.scale.minPadding) * this.scale.factor;
64
+ }
65
+
66
+ decodeValue(n: number): number {
67
+ return n;
68
+ }
69
+
70
+ encodeValue(v: number): number {
71
+ return v;
72
+ }
73
+
74
+ constrainValue(v: number): number {
75
+ return Math.max(this.scale.min, Math.min(this.scale.max, v));
76
+ }
77
+
78
+ trackValue(v: number, offset: number = 0, constrain: boolean = false): number {
79
+ let value = (v - this.origin) / this.scale.factor - offset + this.scale.min - this.scale.minPadding;
80
+ if (constrain) value = this.constrainValue(value);
81
+ return value;
82
+ }
83
+
84
+ hash(): any {
85
+ let r: any = {
86
+ origin: this.origin,
87
+ factor: this.scale.factor,
88
+ min: this.scale.min,
89
+ max: this.scale.max,
90
+ minPadding: this.scale.minPadding,
91
+ maxPadding: this.scale.maxPadding,
92
+ };
93
+ r.stacks = Object.keys(this.stacks)
94
+ .map((s) => this.stacks[s].info?.join(","))
95
+ .join(":");
96
+ return r;
97
+ }
98
+
99
+ isSame(x: any): boolean {
100
+ let hash = this.hash();
101
+ let same = x && !Object.keys(hash).some((k) => x[k] !== hash[k]);
102
+ this.shouldUpdate = !same;
103
+ return same;
104
+ }
105
+
106
+ measure(a: number, b: number): void {
107
+ this.a = a;
108
+ this.b = b;
109
+
110
+ if (this.minValue != null && this.min == null) this.min = this.minValue;
111
+ if (this.maxValue != null && this.max == null) this.max = this.maxValue;
112
+
113
+ for (let s in this.stacks) {
114
+ let info = this.stacks[s].measure(this.normalized);
115
+ let [min, max] = info;
116
+ if (this.min == null || min < this.min) this.min = min;
117
+ if (this.max == null || max > this.max) this.max = max;
118
+ this.stacks[s].info = info;
119
+ }
120
+
121
+ if (this.min == null) this.min = 0;
122
+ if (this.max == null) this.max = this.normalized ? 1 : 100;
123
+
124
+ if (this.min == this.max) {
125
+ if (this.min == 0) {
126
+ this.min = -1;
127
+ this.max = 1;
128
+ } else {
129
+ let delta = Math.abs(this.min) * 0.1;
130
+ this.min -= delta;
131
+ this.max += delta;
132
+ }
133
+ }
134
+
135
+ this.origin = this.inverted ? this.b : this.a;
136
+
137
+ this.scale = this.getScale();
138
+
139
+ this.calculateTicks();
140
+ }
141
+
142
+ getScale(tickSizes?: number[]): { factor: number; min: number; max: number; minPadding: number; maxPadding: number } {
143
+ let { min, max } = this;
144
+ let smin = min;
145
+ let smax = max;
146
+
147
+ let tickSize;
148
+ if (tickSizes && isNumber(this.snapToTicks) && tickSizes.length > 0) {
149
+ tickSize = tickSizes[Math.min(tickSizes.length - 1, this.snapToTicks)];
150
+ smin = Math.floor(smin / tickSize) * tickSize;
151
+ smax = Math.ceil(smax / tickSize) * tickSize;
152
+ } else {
153
+ if (this.minValue === min) smin = this.minValuePadded;
154
+ if (this.maxValue === max) smax = this.maxValuePadded;
155
+ }
156
+
157
+ let minPadding = this.minValue === min ? Math.max(0, smin - this.minValuePadded) : 0;
158
+ let maxPadding = this.maxValue === max ? Math.max(0, this.maxValuePadded - smax) : 0;
159
+
160
+ let sign = this.b > this.a ? 1 : -1;
161
+
162
+ let factor =
163
+ smin < smax
164
+ ? (Math.abs(this.b - this.a) - this.lowerDeadZone - this.upperDeadZone) /
165
+ (smax - smin + minPadding + maxPadding)
166
+ : 0;
167
+
168
+ if (factor < 0) factor = 0;
169
+
170
+ if (factor > 0 && (this.lowerDeadZone > 0 || this.upperDeadZone > 0)) {
171
+ while (factor * (min - smin) < this.lowerDeadZone) smin -= this.lowerDeadZone / factor;
172
+
173
+ while (factor * (smax - max) < this.upperDeadZone) smax += this.upperDeadZone / factor;
174
+
175
+ if (tickSize! > 0 && isNumber(this.snapToTicks)) {
176
+ smin = Math.floor(smin / tickSize!) * tickSize!;
177
+ smax = Math.ceil(smax / tickSize!) * tickSize!;
178
+ minPadding = this.minValue === min ? Math.max(0, smin - this.minValuePadded) : 0;
179
+ maxPadding = this.maxValue === max ? Math.max(0, this.maxValuePadded - smax) : 0;
180
+ }
181
+
182
+ factor = smin < smax ? Math.abs(this.b - this.a) / (smax - smin + minPadding + maxPadding) : 0;
183
+ }
184
+
185
+ return {
186
+ factor: sign * (this.inverted ? -factor : factor),
187
+ min: smin,
188
+ max: smax,
189
+ minPadding,
190
+ maxPadding,
191
+ };
192
+ }
193
+
194
+ acknowledge(value: number, width: number = 0, offset: number = 0): void {
195
+ if (value == null) return;
196
+
197
+ if (this.minValue == null || value < this.minValue) {
198
+ this.minValue = value;
199
+ this.minValuePadded = value + offset - width / 2;
200
+ }
201
+ if (this.maxValue == null || value > this.maxValue) {
202
+ this.maxValue = value;
203
+ this.maxValuePadded = value + offset + width / 2;
204
+ }
205
+ }
206
+
207
+ getStack(name: string): Stack {
208
+ let s = this.stacks[name];
209
+ if (!s) s = this.stacks[name] = new Stack();
210
+ return s;
211
+ }
212
+
213
+ stacknowledge(name: string, ordinal: any, value: any): any {
214
+ return this.getStack(name).acknowledge(ordinal, value);
215
+ }
216
+
217
+ stack(name: string, ordinal: any, value: any): number | null {
218
+ let v = this.getStack(name).stack(ordinal, value);
219
+ return v != null ? this.map(v) : null;
220
+ }
221
+
222
+ findTickSize(minPxDist: number): number | undefined {
223
+ return this.tickSizes.find((a) => a >= this.minLabelTickSize && a * Math.abs(this.scale.factor) >= minPxDist);
224
+ }
225
+
226
+ getTickSizes(): number[] {
227
+ return this.tickSizes;
228
+ }
229
+
230
+ calculateTicks(): void {
231
+ let dist = this.minLabelDistance / Math.abs(this.scale.factor);
232
+ let unit = Math.pow(10, Math.floor(Math.log10(dist)));
233
+
234
+ let bestLabelDistance = Infinity;
235
+ let bestTicks: number[] = [];
236
+ let bestScale = this.scale;
237
+
238
+ for (let i = 0; i < this.tickDivisions.length; i++) {
239
+ let divs = this.tickDivisions[i];
240
+ let tickSizes = divs.filter((ts) => ts >= this.minTickStep).map((ts) => ts * unit);
241
+ let scale = this.getScale(tickSizes);
242
+ tickSizes.forEach((size, level) => {
243
+ let labelDistance = size * Math.abs(scale.factor);
244
+ if (labelDistance >= this.minLabelDistance && labelDistance < bestLabelDistance) {
245
+ bestScale = scale;
246
+ bestTicks = tickSizes;
247
+ bestLabelDistance = labelDistance;
248
+ }
249
+ });
250
+ }
251
+ this.scale = bestScale;
252
+ this.tickSizes = bestTicks.filter(
253
+ (ts) => ts >= this.minTickStep && ts * Math.abs(bestScale.factor) >= this.minTickDistance,
254
+ );
255
+ if (this.tickSizes.length > 0) {
256
+ let max = this.tickSizes[this.tickSizes.length - 1];
257
+ this.tickSizes.push(2 * max);
258
+ this.tickSizes.push(5 * max);
259
+ this.tickSizes.push(10 * max);
260
+ let min = this.tickSizes[0];
261
+ let minDist = min * Math.abs(bestScale.factor);
262
+ if (min / 10 >= this.minTickStep && minDist / 10 >= this.minTickDistance) this.tickSizes.splice(0, 0, min / 10);
263
+ else if (min / 5 >= this.minTickStep && minDist / 5 >= this.minTickDistance) this.tickSizes.splice(0, 0, min / 5);
264
+ else if (min / 2 >= this.minTickStep && minDist / 2 >= this.minTickDistance) this.tickSizes.splice(0, 0, min / 2);
265
+ }
266
+ }
267
+
268
+ getTicks(tickSizes: number[]): number[][] {
269
+ return tickSizes.map((size) => {
270
+ let start = Math.ceil((this.scale.min - this.scale.minPadding) / size);
271
+ let end = Math.floor((this.scale.max + this.scale.maxPadding) / size);
272
+ let result: number[] = [];
273
+ for (let i = start; i <= end; i++) result.push(i * size + 0);
274
+ return result;
275
+ });
276
+ }
277
+
278
+ mapGridlines(): number[] {
279
+ let size = this.tickSizes[0];
280
+ let start = Math.ceil((this.scale.min - this.scale.minPadding) / size);
281
+ let end = Math.floor((this.scale.max + this.scale.maxPadding) / size);
282
+ let result: number[] = [];
283
+ for (let i = start; i <= end; i++) result.push(this.map(i * size));
284
+ return result;
285
+ }
286
+
287
+ book(): void {
288
+ Console.warn("NumericAxis does not support the autoSize flag for column and bar graphs.");
289
+ }
290
+ }
@@ -2,4 +2,5 @@ import { jsx, jsxs } from "./jsx-runtime";
2
2
 
3
3
  export const jsxDEV = jsx;
4
4
  export const jsxsDEV = jsxs;
5
- export { Fragment, JSX } from "./jsx-runtime";
5
+ export { Fragment } from "./jsx-runtime";
6
+ export type { JSX } from "./jsx-runtime";
package/src/svg/Svg.tsx CHANGED
@@ -1,4 +1,5 @@
1
1
  /** @jsxImportSource react */
2
+ import { StringProp } from "../ui/Prop";
2
3
  import { RenderingContext } from "../ui/RenderingContext";
3
4
  import { ResizeManager } from "../ui/ResizeManager";
4
5
  import { VDOM, Widget } from "../ui/Widget";
@@ -18,6 +19,15 @@ interface SvgInstance extends BoundedObjectInstance {
18
19
  }
19
20
 
20
21
  export interface SvgConfig extends BoundedObjectConfig {
22
+ /** ARIA role for the `svg` element. Defaults to `img` when `ariaLabel` is set. */
23
+ role?: StringProp;
24
+
25
+ /**
26
+ * Accessible name for the `svg` element. Setting it announces the drawing as a single graphic
27
+ * instead of as its individual shapes, which is also how a tagged PDF export tags it.
28
+ */
29
+ ariaLabel?: StringProp;
30
+
21
31
  /** Set to `true` to automatically calculate width based on the measured height and `aspectRatio`. */
22
32
  autoWidth?: boolean;
23
33
 
@@ -53,6 +63,16 @@ export class Svg extends BoundedObject<SvgConfig, SvgInstance> {
53
63
  super(config);
54
64
  }
55
65
 
66
+ declareData(...args: any[]) {
67
+ return super.declareData(
68
+ {
69
+ role: undefined,
70
+ ariaLabel: undefined,
71
+ },
72
+ ...args,
73
+ );
74
+ }
75
+
56
76
  initState(context: RenderingContext, instance: SvgInstance) {
57
77
  const size = {
58
78
  width: 0,
@@ -147,6 +167,9 @@ class SvgComponent extends VDOM.Component<SvgComponentProps> {
147
167
  const { instance, data, size, children, eventHandlers } = this.props;
148
168
  const { widget } = instance;
149
169
 
170
+ // An unnamed drawing is left alone: `role="img"` would hide its text and put nothing in its place.
171
+ const role = data.role ?? (data.ariaLabel ? "img" : undefined);
172
+
150
173
  const defs: any[] = [];
151
174
  for (const k in (instance as any).clipRects) {
152
175
  let cr = (instance as any).clipRects[k];
@@ -181,7 +204,7 @@ class SvgComponent extends VDOM.Component<SvgComponentProps> {
181
204
  {...eventHandlers}
182
205
  >
183
206
  {size.width > 0 && size.height > 0 && (
184
- <svg>
207
+ <svg role={role} aria-label={data.ariaLabel}>
185
208
  <defs>{defs}</defs>
186
209
  {children}
187
210
  </svg>